litellm/tests/integration/compatibility/test_openai_consumer.py
Yuneng Jiang c00f1b4a5c
Some checks failed
Terraform Provider / gofmt, vet, build, test (push) Has been cancelled
Terraform Provider / Provider endpoints vs proxy OpenAPI schema (push) Has been cancelled
LiteLLM Rust / rust-lint (push) Has been cancelled
LiteLLM Rust / rust-test (push) Has been cancelled
test: add extension and browser integration contracts
Adds integration contracts for MCP lifecycle, protocol errors and OAuth
configuration, A2A wire versions, the OpenAI consumer path, persisted
toolsets, callback delivery, guardrail effects, configured prices, the
filtered spend ledger, and a CircleCI-owned browser flow for project
detachment, with the ASGI, browser-state, client and MCP helpers they use.
Consolidates the eleven commits previously stacked on
litellm_integration_providers onto its rebased tip
2026-09-16 13:15:53 -07:00

109 lines
4.6 KiB
Python

import json
import uuid
from importlib.metadata import version
from typing import Final
import httpx
import pytest
from openai import AsyncOpenAI, OpenAI
from integration._support.client import Gateway
from integration._support.wire import Reply, Request, wire_server
@pytest.mark.covers("other.compatibility.openai.retained_client_parses_tools_and_usage")
async def test_retained_openai_clients_parse_real_proxy_tool_and_usage_responses(gateway: Gateway) -> None:
assert version("openai") == "2.33.0", (
"Retain this consumer version independently before upgrading the candidate lock"
)
def provider(request: Request) -> Reply:
assert request.method == "POST" and request.target == "/v1/chat/completions"
body: Final = json.loads(request.body)
tools: Final = body.get("tools")
if tools:
assert tools[0]["function"]["name"] == "add"
message: Final = (
{
"role": "assistant",
"content": None,
"tool_calls": [
{
"id": "synthetic-call",
"type": "function",
"function": {"name": "add", "arguments": '{"a":3,"b":5}'},
}
],
}
if tools
else {"role": "assistant", "content": "Synthetic answer: 8"}
)
return Reply(
body=json.dumps(
{
"id": "chatcmpl-" + uuid.uuid4().hex,
"object": "chat.completion",
"created": 1,
"model": "gpt-4o-mini",
"choices": [{"index": 0, "message": message, "finish_reason": "tool_calls" if tools else "stop"}],
"usage": {"prompt_tokens": 11, "completion_tokens": 4, "total_tokens": 15},
}
).encode()
)
with wire_server(provider) as wire, gateway.scenario() as scenario:
model: Final = scenario.model(api_base=wire.url + "/v1")
key: Final = scenario.key(models=[model])
parameters: Final = {
"model": model,
"messages": [{"role": "user", "content": "synthetic tool request"}],
"tools": [
{
"type": "function",
"function": {
"name": "add",
"parameters": {
"type": "object",
"properties": {"a": {"type": "integer"}, "b": {"type": "integer"}},
"required": ["a", "b"],
},
},
}
],
"extra_body": {"cache": {"no-cache": True}},
}
plain: Final = {name: value for name, value in parameters.items() if name != "tools"}
with OpenAI(
api_key=key,
base_url=str(gateway.client.base_url).rstrip("/") + "/v1",
max_retries=0,
http_client=httpx.Client(timeout=10, trust_env=False),
) as sync:
first: Final = sync.chat.completions.create(**parameters)
first_text: Final = sync.chat.completions.create(**plain)
async with AsyncOpenAI(
api_key=key,
base_url=str(gateway.client.base_url).rstrip("/") + "/v1",
max_retries=0,
http_client=httpx.AsyncClient(timeout=10, trust_env=False),
) as asynchronous:
second: Final = await asynchronous.chat.completions.create(**parameters)
second_text: Final = await asynchronous.chat.completions.create(**plain)
assert len({response.id for response in (first, second, first_text, second_text)}) == 4
for response in (first, second, first_text, second_text):
assert response.object == "chat.completion"
assert (
response.usage.prompt_tokens == 11
and response.usage.completion_tokens == 4
and response.usage.total_tokens == 15
)
for response in (first, second):
assert response.choices[0].finish_reason == "tool_calls"
call: Final = response.choices[0].message.tool_calls[0]
assert call.id == "synthetic-call" and call.function.name == "add"
assert json.loads(call.function.arguments) == {"a": 3, "b": 5}
for response in (first_text, second_text):
assert response.choices[0].finish_reason == "stop"
assert response.choices[0].message.content == "Synthetic answer: 8"
assert not response.choices[0].message.tool_calls
assert len(wire.drain()) == 4