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
7.9 KiB
Python
201 lines
7.9 KiB
Python
import json
|
|
import uuid
|
|
from pathlib import Path
|
|
from typing import Final
|
|
|
|
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
|
|
|
|
_ADVISOR_KEY: Final = "synthetic-advisor-key"
|
|
_PROXY_ANTHROPIC_KEY: Final = "sk-proxy-owned-anthropic-secret"
|
|
_QUESTION: Final = "which index should this query use"
|
|
_ADVICE: Final = "use the composite index on (tenant_id, created_at)"
|
|
_FINAL_ANSWER: Final = "done, the composite index is the right one"
|
|
|
|
|
|
def _advisor_call_message(question: str) -> dict[str, object]:
|
|
return {
|
|
"role": "assistant",
|
|
"content": None,
|
|
"tool_calls": [
|
|
{
|
|
"id": "advisor-call",
|
|
"type": "function",
|
|
"function": {"name": "advisor", "arguments": json.dumps({"question": question})},
|
|
}
|
|
],
|
|
}
|
|
|
|
|
|
_FINAL_MESSAGE: Final = {"role": "assistant", "content": _FINAL_ANSWER}
|
|
|
|
|
|
def _chat_completion(identity: str, message: dict[str, object], finish_reason: str) -> Reply:
|
|
return Reply(
|
|
body=json.dumps(
|
|
{
|
|
"id": f"chatcmpl-{identity}",
|
|
"object": "chat.completion",
|
|
"created": 1,
|
|
"model": "llama-3.3-70b-versatile",
|
|
"choices": [{"index": 0, "message": message, "finish_reason": finish_reason}],
|
|
"usage": {"prompt_tokens": 10, "completion_tokens": 4, "total_tokens": 14},
|
|
}
|
|
).encode()
|
|
)
|
|
|
|
|
|
def _executor_reply(body: dict[str, object], identity: str, question: str) -> Reply:
|
|
messages: Final = body["messages"]
|
|
assert isinstance(messages, list)
|
|
if any(message.get("role") == "tool" for message in messages):
|
|
assert messages[-1]["content"] == _ADVICE
|
|
return _chat_completion(identity, _FINAL_MESSAGE, "stop")
|
|
tools: Final = body["tools"]
|
|
assert isinstance(tools, list)
|
|
assert tools[0]["function"]["name"] == "advisor"
|
|
return _chat_completion(identity, _advisor_call_message(question), "tool_calls")
|
|
|
|
|
|
@pytest.mark.covers("providers.anthropic_messages_advisor.sub_call_uses_the_configured_advisor_deployment")
|
|
def test_advisor_sub_call_reaches_the_router_deployment_with_its_key_instead_of_anthropic_unauthenticated(
|
|
gateway: Gateway,
|
|
) -> None:
|
|
identity: Final = "advisor-wire-" + uuid.uuid4().hex
|
|
migration: Final = "please plan the migration " + identity
|
|
question: Final = _QUESTION + " " + identity
|
|
|
|
def respond(request: Request) -> Reply:
|
|
body: Final = json.loads(request.body)
|
|
if request.target == "/v1/chat/completions":
|
|
assert request.headers["authorization"] == "Bearer integration-provider-key"
|
|
return _executor_reply(body, identity, question)
|
|
assert request.target == "/v1/messages"
|
|
assert request.headers["x-api-key"] == _ADVISOR_KEY
|
|
assert body["model"] == "claude-opus-4-1-20250805"
|
|
assert body["messages"] == [
|
|
{"role": "user", "content": migration},
|
|
{"role": "user", "content": question},
|
|
]
|
|
assert "tools" not in body
|
|
return Reply(
|
|
body=json.dumps(
|
|
{
|
|
"id": f"msg-{identity}",
|
|
"type": "message",
|
|
"role": "assistant",
|
|
"model": "claude-opus-4-1-20250805",
|
|
"content": [{"type": "text", "text": _ADVICE}],
|
|
"stop_reason": "end_turn",
|
|
"stop_sequence": None,
|
|
"usage": {"input_tokens": 12, "output_tokens": 6},
|
|
}
|
|
).encode()
|
|
)
|
|
|
|
with wire_server(respond) as wire, gateway.scenario() as scenario:
|
|
executor: Final = scenario.model(model="hosted_vllm/gpt-4o-mini", api_base=wire.url + "/v1")
|
|
advisor: Final = scenario.model(
|
|
model="anthropic/claude-opus-4-1-20250805", api_base=wire.url, api_key=_ADVISOR_KEY
|
|
)
|
|
response: Final = gateway.request(
|
|
"POST",
|
|
"/v1/messages",
|
|
{
|
|
"model": executor,
|
|
"max_tokens": 64,
|
|
"messages": [{"role": "user", "content": migration}],
|
|
"tools": [{"type": "advisor_20260301", "name": "advisor", "model": advisor}],
|
|
},
|
|
)
|
|
assert response.status_code == 200, response.text
|
|
body: Final = response.json()
|
|
assert body["content"] == [{"type": "text", "text": _FINAL_ANSWER}], response.text
|
|
assert body["stop_reason"] == "end_turn", response.text
|
|
assert [request.target for request in wire.drain()] == [
|
|
"/v1/chat/completions",
|
|
"/v1/messages",
|
|
"/v1/chat/completions",
|
|
]
|
|
|
|
|
|
def _advice_reply(identity: str) -> Reply:
|
|
return Reply(
|
|
body=json.dumps(
|
|
{
|
|
"id": f"msg-{identity}",
|
|
"type": "message",
|
|
"role": "assistant",
|
|
"model": "claude-opus-4-1-20250805",
|
|
"content": [{"type": "text", "text": _ADVICE}],
|
|
"stop_reason": "end_turn",
|
|
"stop_sequence": None,
|
|
"usage": {"input_tokens": 12, "output_tokens": 6},
|
|
}
|
|
).encode()
|
|
)
|
|
|
|
|
|
@pytest.mark.covers("providers.anthropic_messages_advisor.caller_api_base_without_api_key_never_receives_the_proxy_key")
|
|
def test_advisor_api_base_without_api_key_is_rejected_before_the_proxy_anthropic_key_reaches_the_caller_host(
|
|
gateway: Gateway, tmp_path: Path
|
|
) -> None:
|
|
identity: Final = "advisor-leak-" + uuid.uuid4().hex
|
|
question: Final = _QUESTION + " " + identity
|
|
|
|
def executor(request: Request) -> Reply:
|
|
assert request.target == "/v1/chat/completions", request.target
|
|
return _executor_reply(json.loads(request.body), identity, question)
|
|
|
|
def caller_host(request: Request) -> Reply:
|
|
return _advice_reply(identity)
|
|
|
|
config: Final = yaml.safe_load(Path("tests/integration/proxy_config.yaml").read_text())
|
|
config["general_settings"]["allow_client_side_credentials"] = True
|
|
path: Final = tmp_path / "client-side-credentials.yaml"
|
|
path.write_text(yaml.safe_dump(config))
|
|
with (
|
|
wire_server(executor) as executor_wire,
|
|
wire_server(caller_host) as caller_wire,
|
|
owned_proxy(gateway, tmp_path, {"ANTHROPIC_API_KEY": _PROXY_ANTHROPIC_KEY}, config=path) as candidate,
|
|
candidate.scenario() as scenario,
|
|
):
|
|
model: Final = scenario.model(model="hosted_vllm/gpt-4o-mini", api_base=executor_wire.url + "/v1")
|
|
response: Final = candidate.request(
|
|
"POST",
|
|
"/v1/messages",
|
|
{
|
|
"model": model,
|
|
"max_tokens": 64,
|
|
"messages": [{"role": "user", "content": "please plan the migration"}],
|
|
"tools": [
|
|
{
|
|
"type": "advisor_20260301",
|
|
"name": "advisor",
|
|
"model": "anthropic/claude-opus-4-1-20250805",
|
|
"api_base": caller_wire.url,
|
|
}
|
|
],
|
|
},
|
|
)
|
|
received: Final = caller_wire.drain()
|
|
assert [
|
|
(request.target, request.headers.get("x-api-key"), json.loads(request.body)["messages"])
|
|
for request in received
|
|
] == [], response.text
|
|
assert response.is_error, response.text
|
|
assert response.json() == {
|
|
"type": "error",
|
|
"error": {
|
|
"type": "api_error",
|
|
"message": (
|
|
"advisor tool definition sets 'api_base' without 'api_key'. A caller-supplied api_base is only "
|
|
"honored alongside a caller-supplied api_key, so the proxy's own credentials are never sent to a "
|
|
"caller-chosen destination."
|
|
),
|
|
},
|
|
}, response.text
|
|
assert executor_wire.drain() == (), response.text
|