mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-05 02:41:56 +00:00
* test(integration): optional Anthropic tool properties stay optional on the OpenAI Responses wire (Pylon #6619) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): Bedrock InvokeModel count-suffixed cache usage fields are reported and charged (Pylon #6708) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): anthropic messages honors the deployment request timeout (Pylon #6505) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): drop client_metadata before the Bedrock Converse body reaches the provider (Pylon #6645) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): repeat Bedrock requests under one session name assume the role once (Pylon #6681) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): messages stream keeps include_usage off the Responses wire with always_include_stream_usage (Pylon #6466) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): clamp sub-16 max_tokens to the Responses API floor instead of 400 (Pylon #6539) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): forwarded client x- headers reach the provider on /v1/responses (Pylon #6565) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): Anthropic messages stop_sequences reach OpenAI-compatible providers as stop (Pylon #6536) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): Codex namespace tools reach a chat upstream flattened and round-trip through /v1/responses (Pylon #6409) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): keep Claude 4.6 legacy thinking budget_tokens on /v1/messages (Pylon #6727) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): nvidia nim ranking keeps image passages and applies top_n without sending top_k (Pylon #6401) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): tpm-only model rejects priority traffic once recorded tokens reach the model tpm (Pylon #6344) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): reasoning-only chunks open an Anthropic thinking block at index zero on /v1/messages streams (Pylon #6337) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): file content streams to the client before the upstream finishes sending (Pylon #6315) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): agent whose card lives only at agentCard/v1.0 is reached with bearer auth (Pylon #6249) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): vertex batch create returns a batch when outputInfo is null (Pylon #6374) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): fireworks session id is sent as x-session-affinity and cached tokens land in spend log metadata (Pylon #6220) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): advisor sub-call failure does not cool down the executor deployment (Pylon #6212) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): Gemini /v1/messages cache_control creates cachedContent with Anthropic ttl (Pylon #6221) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): bedrock_mantle max_output_tokens below 16 is clamped before reaching Mantle (Pylon #6262) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): missing thinking signature 400 on /v1/messages retries without thinking blocks (Pylon #6222) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): format Gemini messages cache_control wire test (Pylon #6221) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): rebuilt shared aiohttp session keeps the configured keepalive timeout (Pylon #6387) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): sagemaker_chat signs the inference component header and sends hf_model_name as the body model (Pylon #6187) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): Bedrock Converse DeepSeek drops Anthropic thinking and sends V3 reasoning_effort raw (Pylon #6149) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): concurrent team model TPM requests are reserved before the provider call (Pylon #6075) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): /v1/messages honors the configured timeout against a stalled upstream (Pylon #6025) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): Codex additional_tools input items reach Bedrock Mantle as top-level tools (Pylon #6012) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): advisor api_base without api_key never sends the proxy Anthropic key to the caller host (Pylon #6226) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): rerank responses carry call id, latency and cost headers (Pylon #5981) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): parse the outbound Anthropic body with the typed JSON adapter (Pylon #6025) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): Bedrock Knowledge Base search forwards userContext to the Retrieve body (Pylon #5991) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): Marengo 3.0 text embeddings reach Bedrock nested under inputType (Pylon #5949) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): sub-16 max_tokens over a responses deployment reaches OpenAI as 16 (Pylon #6008) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): midturn system correction reaches the OpenAI Responses wire via /v1/messages (Pylon #6449) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): concurrent requests over a key tpm limit are rejected before reaching the provider (Pylon #5737) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): chat to responses bridge keeps deployment AWS credentials for Bedrock Mantle SigV4 (Pylon #5870) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): vertex gemini stream split across many fragments completes without stalling the proxy (Pylon #5838) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): bedrock mantle /v1/messages stream keeps stream true and relays SSE events (Pylon #5596) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): large chat payloads are released from worker memory after the request ends (Pylon #5920) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): streaming success logs v3 rate limit remaining values for callbacks (Pylon #5767) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): a database created search tool backs Anthropic web search interception (Pylon #5669) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): register july provider regression contracts Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): make july provider regression tests deterministic under cache and worker sharing Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): drop order-fragile worker memory probe pending a real retention regression check Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): apply ruff import sorting and formatting Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): drop stale contract entry and pass question to advisor executor Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(integration): use tiktoken-backed executor model in advisor tests 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>
201 lines
11 KiB
Python
201 lines
11 KiB
Python
import json
|
|
import os
|
|
import uuid
|
|
from pathlib import Path
|
|
from typing import Final
|
|
from urllib.parse import parse_qs
|
|
|
|
import pytest
|
|
import yaml
|
|
from integration._support.client import Gateway
|
|
from integration._support.process import owned_proxy
|
|
from integration._support.wire import Reply, Request, wire_server
|
|
from integration.providers.test_bedrock_auth_wire import MODEL, RESPONSE
|
|
|
|
|
|
@pytest.mark.covers("other.provider_wire.bedrock.db_yaml_role_reference_reaches_sts_and_signed_request")
|
|
def test_role_reference_from_db_and_yaml_reaches_real_sts_http_and_bedrock(gateway: Gateway, tmp_path: Path) -> None:
|
|
role: Final = "arn:aws:iam::123456789012:role/integration-" + uuid.uuid4().hex
|
|
assumed_key: Final = "ASIAINTEGRATION000001"
|
|
assumed_token: Final = "synthetic-assumed-session-token"
|
|
|
|
def sts(request: Request) -> Reply:
|
|
parameters: Final = parse_qs(request.body.decode())
|
|
action: Final = parameters["Action"][0]
|
|
assert request.method == "POST" and action in {"GetCallerIdentity", "AssumeRole"}
|
|
if action == "GetCallerIdentity":
|
|
result = "<GetCallerIdentityResult><Arn>arn:aws:iam::123456789012:user/integration-source</Arn><UserId>integration-source</UserId><Account>123456789012</Account></GetCallerIdentityResult>"
|
|
else:
|
|
assert parameters["RoleArn"] == [role]
|
|
assert parameters["RoleSessionName"][0] in {"integration-yaml-session", "integration-db-session"}
|
|
result = f"<AssumeRoleResult><Credentials><AccessKeyId>{assumed_key}</AccessKeyId><SecretAccessKey>synthetic-assumed-secret-key-for-testing</SecretAccessKey><SessionToken>{assumed_token}</SessionToken><Expiration>2035-01-01T00:00:00Z</Expiration></Credentials><AssumedRoleUser><Arn>arn:aws:sts::123456789012:assumed-role/integration/session</Arn><AssumedRoleId>integration:session</AssumedRoleId></AssumedRoleUser><PackedPolicySize>0</PackedPolicySize></AssumeRoleResult>"
|
|
return Reply(
|
|
content_type="text/xml",
|
|
body=f'<{action}Response xmlns="https://sts.amazonaws.com/doc/2011-06-15/">{result}<ResponseMetadata><RequestId>synthetic-sts-request</RequestId></ResponseMetadata></{action}Response>'.encode(),
|
|
)
|
|
|
|
def bedrock(request: Request) -> Reply:
|
|
assert request.method == "POST" and request.target == "/model/anthropic.claude-3-haiku-20240307-v1%3A0/converse"
|
|
assert f"Credential={assumed_key}/" in request.headers["authorization"]
|
|
assert request.headers["x-amz-security-token"] == assumed_token
|
|
assert json.loads(request.body)["messages"][0]["content"][0]["text"] == "synthetic role request"
|
|
return Reply(body=RESPONSE)
|
|
|
|
with wire_server(sts) as authority, wire_server(bedrock) as provider:
|
|
parameters: Final = {
|
|
"model": MODEL,
|
|
"aws_region_name": "us-east-1",
|
|
"aws_role_name": "os.environ/INTEGRATION_ROLE_ARN",
|
|
"aws_session_name": "integration-yaml-session",
|
|
"aws_bedrock_runtime_endpoint": provider.url,
|
|
"aws_sts_endpoint": authority.url,
|
|
}
|
|
alias: Final = "integration-role-yaml-" + uuid.uuid4().hex
|
|
configuration: Final = yaml.safe_load(Path("tests/integration/proxy_config.yaml").read_text())
|
|
configuration["model_list"] = [{"model_name": alias, "litellm_params": parameters, "model_info": {"id": alias}}]
|
|
path: Final = tmp_path / "roles.yaml"
|
|
path.write_text(yaml.safe_dump(configuration))
|
|
empty: Final = tmp_path / "empty-aws-config"
|
|
empty.write_text("")
|
|
overrides: Final = {
|
|
"INTEGRATION_ROLE_ARN": role,
|
|
"AWS_ACCESS_KEY_ID": "AKIAINTEGRATION000001",
|
|
"AWS_SECRET_ACCESS_KEY": "synthetic-source-secret-key-for-testing",
|
|
"AWS_CONFIG_FILE": str(empty),
|
|
"AWS_SHARED_CREDENTIALS_FILE": str(empty),
|
|
"AWS_EC2_METADATA_DISABLED": "true",
|
|
"AWS_ENDPOINT_URL_STS": authority.url,
|
|
"AWS_DEFAULT_REGION": "us-east-1",
|
|
"LITELLM_RUST": "false",
|
|
}
|
|
with (
|
|
owned_proxy(
|
|
gateway,
|
|
tmp_path,
|
|
overrides,
|
|
config=path,
|
|
remove_environment=tuple(name for name in os.environ if name.startswith("AWS_")),
|
|
) as candidate,
|
|
candidate.scenario() as scenario,
|
|
):
|
|
database_model: Final = scenario.model(
|
|
**{**parameters, "api_key": None, "aws_session_name": "integration-db-session"}
|
|
)
|
|
for generation in range(2):
|
|
for model in (alias, database_model):
|
|
response: Final = candidate.request(
|
|
"POST",
|
|
"/v1/chat/completions",
|
|
{
|
|
"model": model,
|
|
"messages": [{"role": "user", "content": "synthetic role request"}],
|
|
"cache": {"no-cache": True},
|
|
},
|
|
)
|
|
assert response.status_code == 200, response.text
|
|
assert response.json()["choices"][0]["message"]["content"] == "bedrock wire control"
|
|
assert response.json()["usage"]["total_tokens"] == 15
|
|
assert len(provider.drain()) == 1
|
|
if generation == 0:
|
|
target: Final = next(
|
|
entry for entry in candidate.get("/model/info")["data"] if entry["model_name"] == database_model
|
|
)
|
|
response: Final = candidate.request(
|
|
"PATCH",
|
|
f"/model/{target['model_info']['id']}/update",
|
|
{"model_info": {"description": "role reload"}},
|
|
)
|
|
assert response.status_code == 200, response.text
|
|
assumed: Final = tuple(
|
|
parse_qs(request.body.decode())
|
|
for request in authority.drain()
|
|
if parse_qs(request.body.decode())["Action"] == ["AssumeRole"]
|
|
)
|
|
assert {entry["RoleSessionName"][0] for entry in assumed} == {
|
|
"integration-yaml-session",
|
|
"integration-db-session",
|
|
}
|
|
assert all(entry["RoleArn"] == [role] for entry in assumed)
|
|
|
|
|
|
@pytest.mark.covers("providers.bedrock_assume_role.repeat_requests_reuse_cached_sts_session_per_session_name")
|
|
def test_repeat_requests_under_one_session_name_assume_role_once_per_session_name(
|
|
gateway: Gateway, tmp_path: Path
|
|
) -> None:
|
|
role: Final = "arn:aws:iam::123456789012:role/integration-" + uuid.uuid4().hex
|
|
assumed_key: Final = "ASIAINTEGRATION000002"
|
|
assumed_token: Final = "synthetic-cached-session-token"
|
|
first_session: Final = "integration-attributed-user-a-" + uuid.uuid4().hex[:8]
|
|
second_session: Final = "integration-attributed-user-b-" + uuid.uuid4().hex[:8]
|
|
|
|
def sts(request: Request) -> Reply:
|
|
parameters: Final = parse_qs(request.body.decode())
|
|
action: Final = parameters["Action"][0]
|
|
assert request.method == "POST" and action in {"GetCallerIdentity", "AssumeRole"}
|
|
if action == "GetCallerIdentity":
|
|
result = "<GetCallerIdentityResult><Arn>arn:aws:iam::123456789012:user/integration-source</Arn><UserId>integration-source</UserId><Account>123456789012</Account></GetCallerIdentityResult>"
|
|
else:
|
|
assert parameters["RoleArn"] == [role]
|
|
result = f"<AssumeRoleResult><Credentials><AccessKeyId>{assumed_key}</AccessKeyId><SecretAccessKey>synthetic-assumed-secret-key-for-testing</SecretAccessKey><SessionToken>{assumed_token}</SessionToken><Expiration>2035-01-01T00:00:00Z</Expiration></Credentials><AssumedRoleUser><Arn>arn:aws:sts::123456789012:assumed-role/integration/session</Arn><AssumedRoleId>integration:session</AssumedRoleId></AssumedRoleUser><PackedPolicySize>0</PackedPolicySize></AssumeRoleResult>"
|
|
return Reply(
|
|
content_type="text/xml",
|
|
body=f'<{action}Response xmlns="https://sts.amazonaws.com/doc/2011-06-15/">{result}<ResponseMetadata><RequestId>synthetic-sts-request</RequestId></ResponseMetadata></{action}Response>'.encode(),
|
|
)
|
|
|
|
def bedrock(request: Request) -> Reply:
|
|
assert request.method == "POST" and request.target == "/model/anthropic.claude-3-haiku-20240307-v1%3A0/converse"
|
|
assert f"Credential={assumed_key}/" in request.headers["authorization"]
|
|
assert request.headers["x-amz-security-token"] == assumed_token
|
|
return Reply(body=RESPONSE)
|
|
|
|
with wire_server(sts) as authority, wire_server(bedrock) as provider:
|
|
empty: Final = tmp_path / "empty-aws-config"
|
|
empty.write_text("")
|
|
overrides: Final = {
|
|
"AWS_ACCESS_KEY_ID": "AKIAINTEGRATION000002",
|
|
"AWS_SECRET_ACCESS_KEY": "synthetic-source-secret-key-for-testing",
|
|
"AWS_CONFIG_FILE": str(empty),
|
|
"AWS_SHARED_CREDENTIALS_FILE": str(empty),
|
|
"AWS_EC2_METADATA_DISABLED": "true",
|
|
"AWS_ENDPOINT_URL_STS": authority.url,
|
|
"AWS_DEFAULT_REGION": "us-east-1",
|
|
"LITELLM_RUST": "false",
|
|
}
|
|
with (
|
|
owned_proxy(
|
|
gateway,
|
|
tmp_path,
|
|
overrides,
|
|
remove_environment=tuple(name for name in os.environ if name.startswith("AWS_")),
|
|
) as candidate,
|
|
candidate.scenario() as scenario,
|
|
):
|
|
parameters: Final = {
|
|
"model": MODEL,
|
|
"api_key": None,
|
|
"aws_region_name": "us-east-1",
|
|
"aws_role_name": role,
|
|
"aws_bedrock_runtime_endpoint": provider.url,
|
|
"aws_sts_endpoint": authority.url,
|
|
}
|
|
first_model: Final = scenario.model(**{**parameters, "aws_session_name": first_session})
|
|
second_model: Final = scenario.model(**{**parameters, "aws_session_name": second_session})
|
|
for model in (first_model, first_model, second_model, second_model):
|
|
response: Final = candidate.request(
|
|
"POST",
|
|
"/v1/chat/completions",
|
|
{
|
|
"model": model,
|
|
"messages": [{"role": "user", "content": "synthetic cached role request"}],
|
|
"cache": {"no-cache": True},
|
|
},
|
|
)
|
|
assert response.status_code == 200, response.text
|
|
assert response.json()["choices"][0]["message"]["content"] == "bedrock wire control"
|
|
assert len(provider.drain()) == 1
|
|
assumed: Final = tuple(
|
|
parse_qs(request.body.decode())
|
|
for request in authority.drain()
|
|
if parse_qs(request.body.decode())["Action"] == ["AssumeRole"]
|
|
)
|
|
assert tuple(entry["RoleSessionName"][0] for entry in assumed) == (first_session, second_session), assumed
|