|
1 | | -from collections.abc import AsyncGenerator |
| 1 | +import json |
| 2 | +from collections.abc import AsyncGenerator |
2 | 3 | from unittest.mock import AsyncMock, patch |
3 | 4 |
|
4 | 5 | import pytest |
5 | 6 |
|
6 | 7 | pytestmark = pytest.mark.filterwarnings( |
7 | 8 | "ignore:unclosed event loop <ProactorEventLoop.*:ResourceWarning" |
8 | 9 | ) |
9 | | -from fastapi.testclient import TestClient |
10 | | -from src.core.app.test_builder import build_test_app as build_app |
11 | | -from src.core.config.app_config import ( |
12 | | - AppConfig, |
13 | | - AuthConfig, |
14 | | - BackendConfig, |
15 | | - BackendSettings, |
16 | | - LoggingConfig, |
17 | | - SessionConfig, |
18 | | -) |
19 | | -from src.core.domain.chat import ( |
20 | | - ChatCompletionChoice, |
21 | | - ChatCompletionChoiceMessage, |
22 | | -) |
23 | | -from src.core.domain.chat import ( |
24 | | - ChatResponse as ChatCompletionResponse, |
25 | | -) |
26 | | - |
27 | | - |
| 10 | +from fastapi.testclient import TestClient |
| 11 | +from src.anthropic_converters import openai_stream_to_anthropic_stream |
| 12 | +from src.anthropic_models import AnthropicMessagesRequest |
| 13 | +from src.core.app.test_builder import build_test_app as build_app |
| 14 | +from src.core.config.app_config import ( |
| 15 | + AppConfig, |
| 16 | + AuthConfig, |
| 17 | + BackendConfig, |
| 18 | + BackendSettings, |
| 19 | + LoggingConfig, |
| 20 | + SessionConfig, |
| 21 | +) |
| 22 | +from src.core.domain.chat import ( |
| 23 | + ChatCompletionChoice, |
| 24 | + ChatCompletionChoiceMessage, |
| 25 | +) |
| 26 | +from src.core.domain.chat import ( |
| 27 | + ChatResponse as ChatCompletionResponse, |
| 28 | +) |
| 29 | + |
| 30 | + |
28 | 31 | @pytest.fixture() |
29 | 32 | def anthropic_client(): |
30 | 33 | """Create TestClient with config patched for Anthropic.""" |
@@ -203,7 +206,7 @@ async def generator() -> AsyncGenerator[bytes, None]: |
203 | 206 | return generator() |
204 | 207 |
|
205 | 208 |
|
206 | | -def test_anthropic_messages_streaming_frontend(anthropic_client): |
| 209 | +def test_anthropic_messages_streaming_frontend(anthropic_client): |
207 | 210 | with patch( |
208 | 211 | "src.core.services.request_processor_service.RequestProcessor.process_request", |
209 | 212 | new_callable=AsyncMock, |
@@ -251,13 +254,52 @@ async def mock_streaming_generator(): |
251 | 254 | text += chunk |
252 | 255 | # Check that we get Anthropic streaming format |
253 | 256 | assert "content_block_delta" in text or "delta" in text |
254 | | - assert "event: message_stop" in text |
255 | | - mock_process.assert_awaited_once() |
256 | | - |
257 | | - |
258 | | -# ------------------------------------------------------------ |
259 | | -# Auth error |
260 | | -# ------------------------------------------------------------ |
| 257 | + assert "event: message_stop" in text |
| 258 | + mock_process.assert_awaited_once() |
| 259 | + |
| 260 | + |
| 261 | +@pytest.mark.asyncio |
| 262 | +async def test_anthropic_stream_converts_openai_terminal_error_to_error_event(): |
| 263 | + async def source() -> AsyncGenerator[bytes, None]: |
| 264 | + payload = { |
| 265 | + "id": "chatcmpl-context-length", |
| 266 | + "object": "chat.completion.chunk", |
| 267 | + "created": 123, |
| 268 | + "model": "gpt-5.5", |
| 269 | + "choices": [{"index": 0, "delta": {}, "finish_reason": "error"}], |
| 270 | + "error": { |
| 271 | + "type": "invalid_request_error", |
| 272 | + "code": "context_length_exceeded", |
| 273 | + "message": "Your input exceeds the context window.", |
| 274 | + "param": "input", |
| 275 | + }, |
| 276 | + } |
| 277 | + yield f"data: {json.dumps(payload)}\n\n".encode() |
| 278 | + |
| 279 | + request = AnthropicMessagesRequest( |
| 280 | + model="claude-3-haiku-20240229", |
| 281 | + max_tokens=128, |
| 282 | + messages=[{"role": "user", "content": "Hello"}], |
| 283 | + stream=True, |
| 284 | + ) |
| 285 | + |
| 286 | + events = [ |
| 287 | + event |
| 288 | + async for event in openai_stream_to_anthropic_stream( |
| 289 | + source(), request, request.model, "session-error" |
| 290 | + ) |
| 291 | + ] |
| 292 | + body = "".join(events) |
| 293 | + |
| 294 | + assert "event: error" in body |
| 295 | + assert "context_length_exceeded" not in body |
| 296 | + assert "Your input exceeds the context window." in body |
| 297 | + assert "event: message_delta" not in body |
| 298 | + |
| 299 | + |
| 300 | +# ------------------------------------------------------------ |
| 301 | +# Auth error |
| 302 | +# ------------------------------------------------------------ |
261 | 303 |
|
262 | 304 |
|
263 | 305 | def test_anthropic_messages_auth_failure(anthropic_client): |
|
0 commit comments