fix(deps): upgrade OpenAI stack and resolve dependency vulnerabilities

This commit is contained in:
chenmingxuan committed 2026-10-28 11:22:04 +08:00
1 parent bb873ec0e0
commit 6b2c0f199d
14 files changed
+2738 -2732

No files matched your search

+53 -8
View File
@@ -5,7 +5,7 @@ import json
from types import SimpleNamespace
from unittest.mock import AsyncMock
import httpx
import httpx2 as httpx
from langchain_core.messages import AIMessage, AIMessageChunk, HumanMessage, ToolMessage
from langchain_core.runnables import RunnableLambda
from pydantic import BaseModel, SecretStr
@@ -59,26 +59,46 @@ def response_stream(status, arguments, name):
]
if status:
events.append({"type": "response." + status, "response": body})
return "".join("data: " + json.dumps(event) + "\n\n" for event in events) + "data: [DONE]\n\n"
return (
"".join("data: " + json.dumps(event, ensure_ascii=False) + "\n\n" for event in events)
+ "data: [DONE]\n\n"
)
class FragmentedEventStream(httpx.AsyncByteStream):
"""Split SSE frames and multibyte UTF-8 characters at transport boundaries."""
def __init__(self, text):
self.data = text.encode("utf-8")
async def __aiter__(self):
for offset in range(0, len(self.data), 4093):
yield self.data[offset : offset + 4093]
@pytest_asyncio.fixture
async def transport_case(monkeypatch, tmp_path):
async def transport_case(monkeypatch, tmp_path, request):
from langchain_openai import ChatOpenAI
import openai
requests, outputs, clients, events = [], [], [], []
def handle(request):
payload = json.loads(request.content)
def handle(http_request):
payload = json.loads(http_request.content)
requests.append(payload)
status, arguments = outputs.pop(0)
name = payload.get("tools", [{"name": "finish"}])[0]["name"]
if payload.get("stream"):
stream = response_stream(status, arguments, name)
body = (
{"stream": FragmentedEventStream(stream)}
if getattr(request, "param", None) == "fragmented"
else {"text": stream}
)
return httpx.Response(
200,
text=response_stream(status, arguments, name),
headers={"content-type": "text/event-stream"},
**body,
)
return httpx.Response(200, json=response_body(status, arguments, name))
@@ -175,15 +195,40 @@ async def test_completed_stream_returns_original_valid_arguments(transport_case)
assert result.tool_calls[0]["args"] == {"passed": True}
@pytest.mark.asyncio
@pytest.mark.parametrize("transport_case", ["fragmented"], indirect=True)
@pytest.mark.parametrize("status", ["completed", None])
async def test_large_unicode_tool_stream_requires_completion(transport_case, status):
case = transport_case
arguments = {"passed": True, "evidence": "截图已核验。\n" * 65536}
encoded = json.dumps(arguments, ensure_ascii=False)
assert len(encoded.encode("utf-8")) > 1024 * 1024
case.outputs.append((status, encoded))
if status == "completed":
result = await wrapped(case).complete([])
assert result.response_metadata["status"] == "completed"
assert result.tool_calls[0]["args"] == arguments
else:
with pytest.raises(LLMExhaustedError) as failure:
await wrapped(case).complete([])
assert failure.value.failure.category is FailureCategory.INVALID_OUTPUT
assert len(case.requests) == 1
@pytest.mark.asyncio
@pytest.mark.parametrize("recovers", [False, True])
@pytest.mark.parametrize("initial_status", [None, "failed"])
async def test_integrity_retry_is_bounded_and_keeps_existing_tool_evidence(
transport_case, recovers
transport_case, recovers, initial_status
):
case = transport_case
case.endpoint.max_retries = 5
case.outputs.extend(
[(None, '{"passed":false}'), ("completed" if recovers else None, '{"passed":true}')]
[
(initial_status, '{"passed":false}'),
("completed" if recovers else initial_status, '{"passed":true}'),
]
)
messages = [
HumanMessage(content="Inspect then verify"),