feat(llm): migrate model calls to LangChain ChatOpenAI

This commit is contained in:
Eric Wang
2026-10-24 17:08:46 +08:00
parent 97de142e6d
commit d8d48c26a3
13 changed files with 489 additions and 96 deletions
+26 -12
View File
@@ -2,11 +2,13 @@ import asyncio
import json
from contextlib import aclosing
from types import SimpleNamespace
from unittest.mock import AsyncMock
from unittest.mock import AsyncMock, Mock
import httpx
import pytest
from openai import AsyncOpenAI
from langchain_core.messages import AIMessageChunk
from src.agent.chat_model import ManagedChatOpenAI
from src.agent.llm import LLMAPIError, LLMRateLimitError, OpenAILLMService
from src.agent.prompts import SINGLE_CONFIRM, render_prompt
@@ -72,7 +74,10 @@ async def test_commit_is_not_lost_when_consumer_closes_after_graph_completes():
async def test_factory_disables_sdk_retries():
llm = OpenAILLMService(model='m', api_key='audit')
try:
assert llm._get_client().max_retries == 0
model = llm._get_client()
assert model.max_retries == 0
assert model.root_async_client.max_retries == 0
assert model.root_client.max_retries == 0
finally:
await llm.aclose()
@@ -101,8 +106,13 @@ async def test_actual_http_attempt_count_is_not_multiplied_by_sdk(stream, status
return completion(stream)
llm = OpenAILLMService(model='m', api_key='audit', max_retries=3,
extra_body={'reasoning_effort': 'none'})
llm._client = AsyncOpenAI(api_key='audit', base_url='http://audit/v1', max_retries=0,
http_client=httpx.AsyncClient(transport=httpx.MockTransport(handler)))
llm._client = ManagedChatOpenAI(
model='m', api_key='audit', base_url='http://audit/v1', max_retries=0,
use_responses_api=False, stream_usage=False, temperature=None,
extra_body={'reasoning_effort': 'none'},
http_client=httpx.Client(trust_env=False),
http_async_client=httpx.AsyncClient(transport=httpx.MockTransport(handler)),
)
llm._retry_delay = AsyncMock()
try:
result = ''.join([t async for t in llm.stream_tokens('system', 'user')]) if stream else await llm.complete('system', 'user')
@@ -120,19 +130,19 @@ async def test_stream_failure_after_first_token_closes_response_without_retry():
def __init__(self): self.closed = False
def __aiter__(self): return self.generate()
async def generate(self):
yield SimpleNamespace(choices=[SimpleNamespace(delta=SimpleNamespace(content='partial'))])
yield AIMessageChunk(content='partial')
raise OSError('broken socket')
async def close(self): self.closed = True
async def aclose(self): self.closed = True
response = Response()
create = AsyncMock(return_value=response)
create = Mock(return_value=response)
llm = OpenAILLMService(model='m', api_key='audit')
llm._client = SimpleNamespace(chat=SimpleNamespace(completions=SimpleNamespace(create=create)))
llm._client = SimpleNamespace(astream=create)
tokens = []
with pytest.raises(LLMAPIError):
async for token in llm.stream_tokens('system', 'user'):
tokens.append(token)
assert tokens == ['partial']
assert create.await_count == 1
assert create.call_count == 1
assert response.closed
@@ -145,8 +155,12 @@ async def test_retry_budget_exhaustion_is_exact(stream):
return httpx.Response(429, json={'error': {'message': 'retry'}})
llm = OpenAILLMService(model='m', api_key='audit', max_retries=2)
llm._retry_delay = AsyncMock()
llm._client = AsyncOpenAI(api_key='audit', base_url='http://audit/v1', max_retries=0,
http_client=httpx.AsyncClient(transport=httpx.MockTransport(handler)))
llm._client = ManagedChatOpenAI(
model='m', api_key='audit', base_url='http://audit/v1', max_retries=0,
use_responses_api=False, stream_usage=False, temperature=None,
http_client=httpx.Client(trust_env=False),
http_async_client=httpx.AsyncClient(transport=httpx.MockTransport(handler)),
)
try:
with pytest.raises(LLMRateLimitError):
if stream: