-
Notifications
You must be signed in to change notification settings - Fork 647
fix(pydantic-ai): Determine if response is streamed based on the method #5957
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
465aab0
f590e32
7971fa3
6ef56fd
d1f6f8d
d7483fa
57268a9
d538ae2
5c3f4eb
051812d
cf39d82
8290186
ba7b2d4
9eb5239
2f6c932
ed9f259
15f2dc8
cae1ed6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,9 @@ | ||
| from contextlib import asynccontextmanager | ||
| from functools import wraps | ||
|
|
||
| from sentry_sdk.consts import SPANDATA | ||
| from sentry_sdk.integrations import DidNotEnable | ||
| from sentry_sdk.traces import StreamedSpan | ||
|
|
||
| from ..spans import ( | ||
| ai_client_span, | ||
|
|
@@ -59,9 +61,19 @@ | |
|
|
||
| @wraps(original_model_request_run) | ||
| async def wrapped_model_request_run(self: "Any", ctx: "Any") -> "Any": | ||
| did_stream = getattr(self, "_did_stream", None) | ||
| cached_result = getattr(self, "_result", None) | ||
| if did_stream or cached_result is not None: | ||
| return await original_model_request_run(self, ctx) | ||
|
|
||
| messages, model, model_settings = _extract_span_data(self, ctx) | ||
|
|
||
| with ai_client_span(messages, None, model, model_settings) as span: | ||
| if isinstance(span, StreamedSpan): | ||
| span.set_attribute(SPANDATA.GEN_AI_RESPONSE_STREAMING, False) | ||
| else: | ||
| span.set_data(SPANDATA.GEN_AI_RESPONSE_STREAMING, False) | ||
|
|
||
| result = await original_model_request_run(self, ctx) | ||
|
|
||
| # Extract response from result if available | ||
|
|
@@ -85,10 +97,20 @@ | |
| @asynccontextmanager | ||
| @wraps(original_stream_method) | ||
| async def wrapped_model_request_stream(self: "Any", ctx: "Any") -> "Any": | ||
| did_stream = getattr(self, "_did_stream", None) | ||
| if did_stream: | ||
| async with original_stream_method(self, ctx) as stream: | ||
|
Check failure on line 102 in sentry_sdk/integrations/pydantic_ai/patches/graph_nodes.py
|
||
| yield stream | ||
|
|
||
| messages, model, model_settings = _extract_span_data(self, ctx) | ||
|
|
||
| # Create chat span for streaming request | ||
| with ai_client_span(messages, None, model, model_settings) as span: | ||
| if isinstance(span, StreamedSpan): | ||
| span.set_attribute(SPANDATA.GEN_AI_RESPONSE_STREAMING, False) | ||
| else: | ||
|
Check warning on line 111 in sentry_sdk/integrations/pydantic_ai/patches/graph_nodes.py
|
||
|
Comment on lines
+109
to
+111
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Stream wrapper sets GEN_AI_RESPONSE_STREAMING to False instead of True The Evidence
Identified by Warden · code-review · 6UA-65U |
||
| span.set_data(SPANDATA.GEN_AI_RESPONSE_STREAMING, False) | ||
|
Check warning on line 112 in sentry_sdk/integrations/pydantic_ai/patches/graph_nodes.py
|
||
|
|
||
| # Call the original stream method | ||
| async with original_stream_method(self, ctx) as stream: | ||
| yield stream | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Async stream wrapper falls through and yields twice when did_stream is true
When
did_streamis true, the early guard yields the stream but then falls through to a secondyield, which@asynccontextmanagerwill reject withRuntimeError: generator didn't stop.Evidence
wrapped_model_request_streamis decorated with@asynccontextmanager.did_streamis truthy, the wrapper yields fromoriginal_stream_methodat line 102 but does not return or use anelseguard.async with original_stream_method(self, ctx)at line 114 andyield streamat line 115.@asynccontextmanagerexpects exactly one yield; a second yield causesRuntimeError: generator didn't stop.Identified by Warden · code-review · EAF-FN4