|
39 | 39 | from posthog.ai.openai import OpenAI |
40 | 40 | from posthog.ai.openai.openai_async import AsyncOpenAI |
41 | 41 | from posthog.ai.openai.wrapper_utils import reset_fallback_warnings |
| 42 | + from posthog.test.ai.utils import RecordingAsyncStream |
42 | 43 |
|
43 | 44 | OPENAI_AVAILABLE = True |
44 | 45 | except ImportError: |
@@ -2352,3 +2353,100 @@ def test_integration_stop_reason(mock_client): |
2352 | 2353 | assert props["$ai_stop_reason"] in ("stop", "length") |
2353 | 2354 | assert props["$ai_provider"] == "openai" |
2354 | 2355 | assert props["$ai_input_tokens"] > 0 |
| 2356 | + |
| 2357 | + |
| 2358 | +@pytest.mark.asyncio |
| 2359 | +async def test_async_chat_streaming_supports_async_with( |
| 2360 | + mock_client, streaming_tool_call_chunks |
| 2361 | +): |
| 2362 | + """Regression test for #393: chat completions stream=True must support |
| 2363 | + `async with` (the protocol pydantic-ai relies on).""" |
| 2364 | + |
| 2365 | + async def mock_create(self, **kwargs): |
| 2366 | + return RecordingAsyncStream(streaming_tool_call_chunks) |
| 2367 | + |
| 2368 | + with patch( |
| 2369 | + "openai.resources.chat.completions.AsyncCompletions.create", new=mock_create |
| 2370 | + ): |
| 2371 | + client = AsyncOpenAI(api_key="test-key", posthog_client=mock_client) |
| 2372 | + |
| 2373 | + response = await client.chat.completions.create( |
| 2374 | + model="gpt-4", |
| 2375 | + messages=[{"role": "user", "content": "Hi"}], |
| 2376 | + stream=True, |
| 2377 | + posthog_distinct_id="test-id", |
| 2378 | + ) |
| 2379 | + |
| 2380 | + chunks = [] |
| 2381 | + async with response as stream: |
| 2382 | + async for chunk in stream: |
| 2383 | + chunks.append(chunk) |
| 2384 | + |
| 2385 | + assert chunks == streaming_tool_call_chunks |
| 2386 | + assert mock_client.capture.call_count == 1 |
| 2387 | + call_args = mock_client.capture.call_args[1] |
| 2388 | + props = call_args["properties"] |
| 2389 | + assert call_args["event"] == "$ai_generation" |
| 2390 | + assert props["$ai_provider"] == "openai" |
| 2391 | + assert props["$ai_model"] == "gpt-4" |
| 2392 | + |
| 2393 | + |
| 2394 | +@pytest.mark.asyncio |
| 2395 | +async def test_async_responses_streaming_supports_async_with(mock_client): |
| 2396 | + """Regression test for #393: responses stream=True must support |
| 2397 | + `async with`.""" |
| 2398 | + from unittest.mock import MagicMock |
| 2399 | + |
| 2400 | + chunk = MagicMock() |
| 2401 | + chunk.type = "response.text.delta" |
| 2402 | + chunk.text = "hello" |
| 2403 | + |
| 2404 | + async def mock_create(self, **kwargs): |
| 2405 | + return RecordingAsyncStream([chunk]) |
| 2406 | + |
| 2407 | + with patch("openai.resources.responses.AsyncResponses.create", new=mock_create): |
| 2408 | + client = AsyncOpenAI(api_key="test-key", posthog_client=mock_client) |
| 2409 | + |
| 2410 | + response = await client.responses.create( |
| 2411 | + model="gpt-4o-mini", |
| 2412 | + input=[{"role": "user", "content": "Hi"}], |
| 2413 | + stream=True, |
| 2414 | + posthog_distinct_id="test-id", |
| 2415 | + ) |
| 2416 | + |
| 2417 | + async with response as stream: |
| 2418 | + received = [c async for c in stream] |
| 2419 | + |
| 2420 | + assert received == [chunk] |
| 2421 | + assert mock_client.capture.call_count == 1 |
| 2422 | + |
| 2423 | + |
| 2424 | +@pytest.mark.asyncio |
| 2425 | +async def test_async_chat_streaming_early_exit_closes_provider_stream( |
| 2426 | + mock_client, streaming_tool_call_chunks |
| 2427 | +): |
| 2428 | + """Breaking out of the stream early must close the underlying provider |
| 2429 | + stream (release the HTTP connection) and still capture the event.""" |
| 2430 | + source = RecordingAsyncStream(streaming_tool_call_chunks) |
| 2431 | + |
| 2432 | + async def mock_create(self, **kwargs): |
| 2433 | + return source |
| 2434 | + |
| 2435 | + with patch( |
| 2436 | + "openai.resources.chat.completions.AsyncCompletions.create", new=mock_create |
| 2437 | + ): |
| 2438 | + client = AsyncOpenAI(api_key="test-key", posthog_client=mock_client) |
| 2439 | + |
| 2440 | + response = await client.chat.completions.create( |
| 2441 | + model="gpt-4", |
| 2442 | + messages=[{"role": "user", "content": "Hi"}], |
| 2443 | + stream=True, |
| 2444 | + posthog_distinct_id="test-id", |
| 2445 | + ) |
| 2446 | + |
| 2447 | + async with response as stream: |
| 2448 | + async for _ in stream: |
| 2449 | + break |
| 2450 | + |
| 2451 | + assert source.closed is True |
| 2452 | + assert mock_client.capture.call_count == 1 |
0 commit comments