mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-12 03:48:56 +00:00
* fix(responses): honor caller stream flag when provider forces SSE The Responses handlers decided whether to hand back a streaming iterator from the provider payload's `stream` field, which chatgpt sets unconditionally because the Codex backend only serves SSE. A caller that sent `stream: false` therefore received a raw SSE stream on /v1/responses, and the chat-completions bridge failed with "Unknown items in responses API response: []" once its recovery path lost the raw SSE it reads from Transport streaming still follows the provider payload; only the caller's own `stream` value now decides the response shape. When the provider forces SSE for a non-streaming caller the body is read and aggregated through the existing path * test(chatgpt): inject the authenticator into the responses config so handler tests never log in * fix(responses): treat an extra_body stream flag as the caller's own and drop a redundant comment * test(integration): audit the chatgpt caller stream flag across responses, chat and messages --------- Co-authored-by: SeongWoon Cho <coffee@soylatte.kr> Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
50 lines
1.7 KiB
Python
50 lines
1.7 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
from collections.abc import Mapping, Sequence
|
|
from pathlib import Path
|
|
from typing import Final
|
|
|
|
from integration._support import responses_vendor as rv
|
|
from pydantic import JsonValue
|
|
|
|
from litellm.integrations.custom_logger import CustomLogger
|
|
|
|
OUT_ENVIRONMENT: Final = "AGENTIC_PROBE_OUT"
|
|
|
|
|
|
class AgenticProbe(CustomLogger):
|
|
"""Records every agentic-loop hook call the proxy makes, one JSON line per call, and never runs a loop."""
|
|
|
|
async def async_should_run_agentic_loop(
|
|
self,
|
|
response: object,
|
|
model: str,
|
|
messages: Sequence[Mapping[str, object]],
|
|
tools: Sequence[Mapping[str, object]] | None,
|
|
stream: bool,
|
|
custom_llm_provider: str,
|
|
kwargs: Mapping[str, object],
|
|
) -> tuple[bool, dict[str, object]]: # mutable-ok: the CustomLogger hook contract returns a dict
|
|
out: Final = os.environ.get(OUT_ENVIRONMENT)
|
|
if out:
|
|
line: Final[Mapping[str, JsonValue]] = {
|
|
"marker": rv.newest_marker(f"{messages!s} {response!s}"),
|
|
"surface": str(kwargs.get("_agentic_loop_api_surface")),
|
|
"response_type": type(response).__name__,
|
|
"stream": stream,
|
|
"model": model,
|
|
"provider": custom_llm_provider,
|
|
}
|
|
with open(out, "a", encoding="utf-8") as sink:
|
|
sink.write(json.dumps(line) + "\n")
|
|
return False, {}
|
|
|
|
|
|
probe: Final = AgenticProbe()
|
|
|
|
|
|
def lines(path: Path, marker: str) -> tuple[Mapping[str, JsonValue], ...]:
|
|
recorded: Final = tuple(rv.JSON_OBJECT.validate_json(line) for line in path.read_text().splitlines() if line)
|
|
return tuple(line for line in recorded if line["marker"] == marker)
|