Files
ZNJJ-api-server/test/api/test_stream_error_codes.py
T

77 lines
3.1 KiB
Python
Raw Normal View History

import json
import httpx
import pytest
from fastapi import FastAPI
from src.agent.collect import validated_reply
from src.agent.llm import LLMAPIError, LLMAuthError, LLMRateLimitError
from src.api.endpoints import router
from src.backends.langgraph import LangGraphBackend
from src.core.config import Config
from src.core.fastgpt_client import get_chat_backend
class FailingLLM:
def __init__(self, error_type, partial):
self.error_type = error_type
self.partial = partial
async def complete(self, *args, **kwargs):
raise self.error_type('private provider detail')
async def stream_tokens(self, *args, **kwargs):
if self.partial:
yield '<state>1002</state>'
yield 'partial reply'
raise self.error_type('private provider detail')
class Graph:
async def ainvoke(self, data, *, config, context):
await validated_reply(context.llm, 'system', 'user', '1002')
raise AssertionError('failed turn must not complete')
def sse_events(body):
events = []
for block in body.strip().split('\n\n'):
lines = block.splitlines()
kind = next(line[7:] for line in lines if line.startswith('event: '))
data = json.loads(next(line[6:] for line in lines if line.startswith('data: ')))
events.append((kind, data))
return events
@pytest.mark.asyncio
@pytest.mark.parametrize('partial', [False, True], ids=['before-text', 'after-text'])
@pytest.mark.parametrize('error_type,code,message', [
(LLMAuthError, '401', '认证失败'),
(LLMRateLimitError, '429', '请求过于频繁,请稍后重试'),
(LLMAPIError, '500', '大模型服务器无响应'),
])
async def test_http_sse_errors_match_non_stream_codes_and_terminate(
monkeypatch, partial, error_type, code, message,
):
# 独立协议测试 app:实际 LangGraphBackend/LLM 错误映射,不启动真实 provider。
monkeypatch.setattr(Config, 'AGENT_BACKEND', 'fastgpt')
app = FastAPI()
app.include_router(router)
backend = LangGraphBackend(graph=Graph(), llm=FailingLLM(error_type, partial))
app.dependency_overrides[get_chat_backend] = lambda: backend
payload = {'sessionId': 'error-audit', 'timeStamp': '20261022120000', 'text': 'hello'}
async with httpx.AsyncClient(transport=httpx.ASGITransport(app=app), base_url='http://test') as client:
response = await client.post('/chat?stream=true', json=payload)
assert response.status_code == 200 # 错误分类在 SSE data.code,不改已发送的 HTTP 头
assert response.headers['content-type'].startswith('text/event-stream')
events = sse_events(response.text)
assert events[-1] == ('error', {'code': code, 'msg': message})
assert sum(kind == 'error' for kind, _ in events) == 1
assert all(kind != 'done' for kind, _ in events)
assert any(kind == 'text_delta' for kind, _ in events) is partial
assert 'private provider detail' not in response.text
result = await client.post('/chat', json=payload)
assert result.json()['code'] == code
assert result.json()['msg'] == message