fa45d8aa5f
- health_checklist.json: 192.168.1.122→node122
- ocr_client.py: docstring IP→node122
- docs/market-data-requirements.md: IP→node122
- 所有API调用通过ProxyHandler({})绕过系统代理
Privoxy对node122:18003返回500,直连正常
137 lines
4.3 KiB
Python
137 lines
4.3 KiB
Python
"""
|
|
Shared helpers for recovering Responses API output items from raw SSE chunks.
|
|
|
|
The same recovery logic is needed in multiple places (e.g. the ChatGPT
|
|
Responses transformation and the LiteLLM Responses-to-Chat-Completions
|
|
bridge). Keep the implementation in a single module so a fix in one
|
|
caller automatically applies to all of them.
|
|
"""
|
|
|
|
import json
|
|
from typing import Any, Dict, Optional
|
|
|
|
from litellm.constants import STREAM_SSE_DONE_STRING
|
|
|
|
_MAX_CONTENT_INDEX = 1024
|
|
|
|
|
|
def parse_sse_json_chunk(chunk: str) -> Optional[Dict[str, Any]]:
|
|
"""Parse a single raw SSE line into a JSON object dict.
|
|
|
|
Returns ``None`` for empty lines, ``event:`` lines, ``[DONE]`` markers,
|
|
invalid JSON, or non-dict payloads. Centralizes the parsing step that
|
|
feeds into the recovery helpers in this module so behavior stays
|
|
consistent across all callers.
|
|
"""
|
|
# Import locally to avoid a circular import with the streaming handler.
|
|
from litellm.litellm_core_utils.streaming_handler import CustomStreamWrapper
|
|
|
|
stripped_chunk = (
|
|
CustomStreamWrapper._strip_sse_data_from_chunk(chunk.strip()) or ""
|
|
).strip()
|
|
if (
|
|
not stripped_chunk
|
|
or stripped_chunk == STREAM_SSE_DONE_STRING
|
|
or stripped_chunk.startswith("event:")
|
|
):
|
|
return None
|
|
try:
|
|
parsed_chunk = json.loads(stripped_chunk)
|
|
except json.JSONDecodeError:
|
|
return None
|
|
if not isinstance(parsed_chunk, dict):
|
|
return None
|
|
return parsed_chunk
|
|
|
|
|
|
def record_output_item_chunk(
|
|
parsed_chunk: Dict[str, Any],
|
|
output_items: Dict[int, Dict[str, Any]],
|
|
) -> None:
|
|
"""Record an OUTPUT_ITEM_DONE chunk into ``output_items`` keyed by
|
|
``output_index`` (falling back to the next free slot when missing).
|
|
"""
|
|
item = parsed_chunk.get("item")
|
|
if not isinstance(item, dict):
|
|
return
|
|
try:
|
|
output_index_raw = parsed_chunk.get("output_index")
|
|
if output_index_raw is None:
|
|
raise ValueError("missing output_index")
|
|
output_index = int(output_index_raw)
|
|
except (TypeError, ValueError):
|
|
output_index = len(output_items)
|
|
output_items[output_index] = item
|
|
|
|
|
|
def record_output_text_chunk(
|
|
parsed_chunk: Dict[str, Any],
|
|
output_items: Dict[int, Dict[str, Any]],
|
|
text_only_items: Dict[int, Dict[str, Any]],
|
|
) -> None:
|
|
"""Record an OUTPUT_TEXT_DONE chunk as a synthetic message item in
|
|
``text_only_items``. Real OUTPUT_ITEM_DONE events already captured in
|
|
``output_items`` take precedence at the same ``output_index``.
|
|
"""
|
|
text = parsed_chunk.get("text")
|
|
if not isinstance(text, str):
|
|
return
|
|
|
|
try:
|
|
output_index_raw = parsed_chunk.get("output_index")
|
|
if output_index_raw is None:
|
|
raise ValueError("missing output_index")
|
|
output_index = int(output_index_raw)
|
|
except (TypeError, ValueError):
|
|
output_index = len(text_only_items)
|
|
|
|
if output_index in output_items:
|
|
return
|
|
|
|
item = text_only_items.get(output_index)
|
|
if item is None:
|
|
item = {
|
|
"type": "message",
|
|
"id": parsed_chunk.get("item_id") or f"msg_{output_index}",
|
|
"role": "assistant",
|
|
"status": "completed",
|
|
"content": [],
|
|
}
|
|
text_only_items[output_index] = item
|
|
|
|
content = item.setdefault("content", [])
|
|
if not isinstance(content, list):
|
|
return
|
|
|
|
try:
|
|
content_index_raw = parsed_chunk.get("content_index")
|
|
if content_index_raw is None:
|
|
raise ValueError("missing content_index")
|
|
content_index = int(content_index_raw)
|
|
except (TypeError, ValueError):
|
|
content_index = len(content)
|
|
|
|
if content_index < 0 or content_index > _MAX_CONTENT_INDEX:
|
|
return
|
|
|
|
while len(content) <= content_index:
|
|
content.append(
|
|
{
|
|
"type": "output_text",
|
|
"text": "",
|
|
"annotations": [],
|
|
}
|
|
)
|
|
|
|
content_item = content[content_index]
|
|
if not isinstance(content_item, dict):
|
|
content_item = {}
|
|
content[content_index] = content_item
|
|
|
|
content_item["type"] = "output_text"
|
|
content_item["text"] = text
|
|
if parsed_chunk.get("annotations") is not None:
|
|
content_item["annotations"] = parsed_chunk["annotations"]
|
|
else:
|
|
content_item.setdefault("annotations", [])
|