litellm/tests/integration/streaming/test_openai_stream_passthrough_fields.py
devin-ai-integration[bot] 1dbde0845e
test: migrate SDK and proxy-booting legacy CircleCI tests to their tiers (#45758)
* test: move guardrails, image, audio, batches, search legacy tests to unit

* test: restore the custom guardrail and remaining chunk 1 unit rows

* test: move the ocr matrix to e2e

* test: drop comments from moved tests and fix vertex ocr credentials

* test: move litellm utils and unified google legacy tests to their tiers

* test: move logging callback and pass-through unit legacy tests to their tiers

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test: move store_model_in_db legacy tests and start the admin ui migration

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test: finish the proxy job rows and drop their CircleCI jobs

Moves the remaining multi-instance, openai endpoint, otel, pass-through and
top-level proxy tests into tests/integration with scripted upstreams, rewrites
the weak replacements to assert the outbound wire and exact responses, and
removes the build_and_test job group this PR owns from CircleCI

* test: tighten the key generation and error log assertions, rename migrated unit files to their mapped names

* test: shard the pagerduty unit tests and record the rust harness file on the coverage allowlist

* test: drive the SSO sign-in tests through a scripted IdP and isolate the cost center stand-in

* test: type the e2e beta header, prompt cache and secret manager values

* test: assert the missing model error message on the OpenAI routes

* test: drop the deleted otel base class import and restore mcp redaction and exact langfuse usage checks

* test: make the langfuse fixture directory a package for the layout check

* test: restore collected no-match cases, real langfuse and gcs payload checks, and gate the aws secret lane

* test: keep the ocr e2e pdf fixture next to the test

* test: put meta below covers on the streaming tool search e2e

* test: run the OCR matrix against a scripted upstream and fix two runner-only e2e failures

* test: count only OCR calls in the OCR matrix recorder

* test: pin OCR parse bodies and billed cost, group new e2e cases into classes

* ci: restore the python image anchor dropped in the main sync

---------

Co-authored-by: yuneng <yuneng@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-11 00:17:21 -07:00

150 lines
5.2 KiB
Python

import json
from collections.abc import Iterator
from contextlib import contextmanager
from typing import Final
import httpx
import openai
from tests.integration._support.client import Gateway
from tests.integration._support.wire import Reply, Request, wire_server
_CHAT_CHUNKS: Final = (
{
"id": "chatcmpl-logprobs",
"object": "chat.completion.chunk",
"created": 1,
"model": "gpt-4o-mini",
"choices": [
{
"index": 0,
"delta": {"role": "assistant", "content": "hello"},
"logprobs": {
"content": [
{
"token": "hello",
"logprob": -0.1,
"bytes": [104, 101, 108, 108, 111],
"top_logprobs": [
{"token": "hello", "logprob": -0.1, "bytes": [104, 101, 108, 108, 111]},
{"token": "hi", "logprob": -2.3, "bytes": [104, 105]},
],
}
]
},
"finish_reason": None,
}
],
},
{
"id": "chatcmpl-logprobs",
"object": "chat.completion.chunk",
"created": 1,
"model": "gpt-4o-mini",
"choices": [{"index": 0, "delta": {}, "logprobs": None, "finish_reason": "stop"}],
},
)
_COMPLETION_CHUNKS: Final = (
{
"id": "cmpl-usage",
"object": "text_completion",
"created": 1,
"model": "gpt-3.5-turbo-instruct",
"choices": [{"text": "done", "index": 0, "logprobs": None, "finish_reason": None}],
},
{
"id": "cmpl-usage",
"object": "text_completion",
"created": 1,
"model": "gpt-3.5-turbo-instruct",
"choices": [{"text": "", "index": 0, "logprobs": None, "finish_reason": "stop"}],
},
{
"id": "cmpl-usage",
"object": "text_completion",
"created": 1,
"model": "gpt-3.5-turbo-instruct",
"choices": [],
"usage": {"prompt_tokens": 7, "completion_tokens": 3, "total_tokens": 10},
},
)
def _event_stream(chunks: tuple[dict[str, object], ...]) -> Reply:
return Reply(
chunks=(*(f"data: {json.dumps(chunk)}\n\n".encode() for chunk in chunks), b"data: [DONE]\n\n"),
content_type="text/event-stream",
)
@contextmanager
def _client(gateway: Gateway) -> Iterator[openai.OpenAI]:
with (
httpx.Client(timeout=15, trust_env=False) as transport,
openai.OpenAI(
api_key=gateway.key, base_url=str(gateway.client.base_url.join("/v1")), max_retries=0, http_client=transport
) as client,
):
yield client
def test_chat_stream_relays_the_provider_logprobs(gateway: Gateway) -> None:
def upstream(request: Request) -> Reply:
return _event_stream(_CHAT_CHUNKS)
with wire_server(upstream) as provider, gateway.scenario() as scenario, _client(gateway) as client:
model: Final = scenario.model(model="openai/gpt-4o-mini", api_base=f"{provider.url}/v1")
chunks: Final = tuple(
client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": "Hello!"}],
logprobs=True,
top_logprobs=2,
stream=True,
)
)
sent: Final = provider.drain()
assert len(sent) == 1
outbound: Final = json.loads(sent[0].body)
assert (outbound["logprobs"], outbound["top_logprobs"], outbound["stream"]) == (True, 2, True)
assert "".join(chunk.choices[0].delta.content or "" for chunk in chunks if chunk.choices) == "hello"
relayed: Final = tuple(
chunk.choices[0].logprobs.model_dump(exclude_none=True)
for chunk in chunks
if chunk.choices and chunk.choices[0].logprobs is not None
)
assert relayed == (_CHAT_CHUNKS[0]["choices"][0]["logprobs"],)
def test_completion_stream_reports_the_provider_usage_when_include_usage_is_set(gateway: Gateway) -> None:
def upstream(request: Request) -> Reply:
return _event_stream(_COMPLETION_CHUNKS)
with wire_server(upstream) as provider, gateway.scenario() as scenario, _client(gateway) as client:
model: Final = scenario.model(model="openai/gpt-3.5-turbo-instruct", api_base=f"{provider.url}/v1")
chunks: Final = tuple(
client.completions.create(
model=model,
prompt="hey",
stream=True,
stream_options={"include_usage": True},
max_tokens=4,
)
)
sent: Final = provider.drain()
assert len(sent) == 1
outbound: Final = json.loads(sent[0].body)
assert (outbound["prompt"], outbound["stream_options"], outbound["max_tokens"]) == (
"hey",
{"include_usage": True},
4,
)
assert "".join(choice.text or "" for chunk in chunks for choice in chunk.choices) == "done"
usages: Final = tuple(
(chunk.usage.prompt_tokens, chunk.usage.completion_tokens, chunk.usage.total_tokens)
for chunk in chunks
if chunk.usage is not None
)
assert usages == ((7, 3, 10),)