mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-05 02:41:56 +00:00
* test(integration): streamed Bedrock Messages usage cost equals the recorded spend (Pylon #6667) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): echoed cost-map model info is not persisted as deployment overrides (Pylon #6844) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): reset sweep runs on one pod per tick while replicas share the lease (Pylon #6521) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): bedrock post-call guardrail scans streamed Anthropic Messages tool use without 500 (Pylon #6503) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): realtime cached audio tokens bill at the audio cache-read rate (Pylon #6704) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): legacy GET /spend/logs returns at most the 10000 most recent rows and flags truncation (Pylon #6752) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): itemize Responses API cache write tokens as cache creation cost (Pylon #6454) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): migration entrypoint deploys pending migrations before proxy startup (Pylon #6649) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): opted-in team keys stop at the owner's personal budget (Pylon #6641) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): guardrail information stays in the spend log when the caller sends metadata on /v1/messages (Pylon #6614) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): key model allowlist is enforced on Bedrock passthrough routes (Pylon #6419) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): JWT mapped key backfills a null user email from token claims (Pylon #6266) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): scheduled budget reset recovers from a transient DB transport failure (Pylon #6582) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): team key lists models granted through a team access group (Pylon #6044) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): JWT subject without team claim lands in the configured default team (Pylon #5895) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): end-user spend lands for a key without user_id when the auth cache is Redis (Pylon #6021) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): stale-low redis counter still blocks team member over budget (Pylon #5824) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): prompt-carrying spend rows are written in byte-bounded statements (Pylon #6083) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): logs UI session_total_spend sums every round of a multi-round session (Pylon #5928) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): config.yaml guardrails are served by the guardrail usage detail and overview (Pylon #5813) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): plain chat request skips the object permission lookup (Pylon #5965) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): register july accounting regression contracts Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): isolate cost map override clear on owned proxy Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): make reset lease claim and db relay refusal deterministic Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): poll pg_stat settle, bound unbanned relay refusals, clear reset lease on teardown Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): bound relay refusals so the budget sweep can reconnect Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: kerry <kerry@berri.ai> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
77 lines
3.6 KiB
Python
77 lines
3.6 KiB
Python
import uuid
|
|
from pathlib import Path
|
|
from typing import Final
|
|
|
|
import pytest
|
|
import yaml
|
|
from pydantic import JsonValue
|
|
|
|
from tests.integration._support.client import Gateway, object_value, string_value
|
|
from tests.integration._support.process import owned_proxy
|
|
|
|
WINDOW: Final = {"start_date": "2026-01-01", "end_date": "2026-01-07"}
|
|
|
|
|
|
def listed_guardrail(gateway: Gateway, guardrail_name: str) -> dict[str, JsonValue]:
|
|
listed: Final = gateway.get("/v2/guardrails/list")["guardrails"]
|
|
assert isinstance(listed, list), listed
|
|
matches: Final = tuple(object_value(row) for row in listed if object_value(row)["guardrail_name"] == guardrail_name)
|
|
assert len(matches) == 1, f"{guardrail_name} appears {len(matches)} times in {listed}"
|
|
return matches[0]
|
|
|
|
|
|
@pytest.mark.covers("mgmt.guardrails.usage.config_yaml_guardrail_has_detail_and_overview_row")
|
|
def test_config_yaml_guardrail_is_served_by_usage_detail_and_overview(gateway: Gateway, tmp_path: Path) -> None:
|
|
guardrail_name: Final = "tool-permission-" + uuid.uuid4().hex
|
|
config: Final = yaml.safe_load(Path("tests/integration/proxy_config.yaml").read_text())
|
|
config["guardrails"] = [
|
|
{
|
|
"guardrail_name": guardrail_name,
|
|
"litellm_params": {
|
|
"guardrail": "tool_permission",
|
|
"mode": "post_call",
|
|
"default_on": False,
|
|
"rules": [{"id": "deny_delete", "tool_name": "(?i)^.*(delete|drop).*", "decision": "deny"}],
|
|
"default_action": "allow",
|
|
"on_disallowed_action": "block",
|
|
},
|
|
"guardrail_info": {"type": "Tool Permission", "description": "declared in config.yaml"},
|
|
}
|
|
]
|
|
path: Final = tmp_path / "guardrail.yaml"
|
|
path.write_text(yaml.safe_dump(config))
|
|
with owned_proxy(gateway, tmp_path, {}, config=path) as candidate:
|
|
guardrail_id: Final = string_value(listed_guardrail(candidate, guardrail_name)["guardrail_id"])
|
|
detail: Final = candidate.request("GET", f"/guardrails/usage/detail/{guardrail_id}", params=WINDOW)
|
|
assert detail.status_code == 200, detail.text
|
|
body: Final = object_value(detail.json())
|
|
assert {
|
|
"guardrail_id": body["guardrail_id"],
|
|
"guardrail_name": body["guardrail_name"],
|
|
"provider": body["provider"],
|
|
"type": body["type"],
|
|
"description": body["description"],
|
|
"requestsEvaluated": body["requestsEvaluated"],
|
|
"failRate": body["failRate"],
|
|
} == {
|
|
"guardrail_id": guardrail_id,
|
|
"guardrail_name": guardrail_name,
|
|
"provider": "tool_permission",
|
|
"type": "Tool Permission",
|
|
"description": "declared in config.yaml",
|
|
"requestsEvaluated": 0,
|
|
"failRate": 0.0,
|
|
}, detail.text
|
|
overview: Final = candidate.request("GET", "/guardrails/usage/overview", params=WINDOW)
|
|
assert overview.status_code == 200, overview.text
|
|
rows: Final = object_value(overview.json())["rows"]
|
|
assert isinstance(rows, list), overview.text
|
|
config_rows: Final = tuple(object_value(row) for row in rows if object_value(row)["id"] == guardrail_id)
|
|
assert len(config_rows) == 1, overview.text
|
|
assert (config_rows[0]["name"], config_rows[0]["provider"], config_rows[0]["requestsEvaluated"]) == (
|
|
guardrail_name,
|
|
"tool_permission",
|
|
0,
|
|
), overview.text
|
|
missing: Final = candidate.request("GET", f"/guardrails/usage/detail/{uuid.uuid4()}", params=WINDOW)
|
|
assert missing.status_code == 404, missing.text
|