fix(deps): upgrade OpenAI stack and resolve dependency vulnerabilities
This commit is contained in:
1 parent
bb873ec0e0
commit
6b2c0f199d
14 files changed
+2738
-2732
No files matched your search
@@ -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"),
|
||||
|
||||
Reference in new issue
Block a user