Skip to content

Commit cff77db

Browse files
fix(google-genai): make streaming responses work (again) (#3421)
Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
1 parent 97cbcd9 commit cff77db

File tree

2 files changed

+40
-8
lines changed

2 files changed

+40
-8
lines changed

packages/opentelemetry-instrumentation-google-generativeai/opentelemetry/instrumentation/google_generativeai/__init__.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,18 @@
4848
"method": "generate_content",
4949
"span_name": "gemini.generate_content",
5050
},
51+
{
52+
"package": "google.genai.models",
53+
"object": "Models",
54+
"method": "generate_content_stream",
55+
"span_name": "gemini.generate_content",
56+
},
57+
{
58+
"package": "google.genai.models",
59+
"object": "AsyncModels",
60+
"method": "generate_content_stream",
61+
"span_name": "gemini.generate_content",
62+
},
5163
]
5264

5365

@@ -66,8 +78,10 @@ def _build_from_streaming_response(
6678
event_logger,
6779
):
6880
complete_response = ""
81+
last_chunk = None
6982
for item in response:
7083
item_to_yield = item
84+
last_chunk = item
7185
complete_response += str(item.text)
7286

7387
yield item_to_yield
@@ -76,16 +90,18 @@ def _build_from_streaming_response(
7690
emit_choice_events(response, event_logger)
7791
else:
7892
set_response_attributes(span, complete_response, llm_model)
79-
set_model_response_attributes(span, response, llm_model)
93+
set_model_response_attributes(span, last_chunk or response, llm_model)
8094
span.end()
8195

8296

8397
async def _abuild_from_streaming_response(
8498
span, response: GenerateContentResponse, llm_model, event_logger
8599
):
86100
complete_response = ""
101+
last_chunk = None
87102
async for item in response:
88103
item_to_yield = item
104+
last_chunk = item
89105
complete_response += str(item.text)
90106

91107
yield item_to_yield
@@ -94,7 +110,7 @@ async def _abuild_from_streaming_response(
94110
emit_choice_events(response, event_logger)
95111
else:
96112
set_response_attributes(span, complete_response, llm_model)
97-
set_model_response_attributes(span, response, llm_model)
113+
set_model_response_attributes(span, last_chunk if last_chunk else response, llm_model)
98114
span.end()
99115

100116

packages/opentelemetry-instrumentation-google-generativeai/tests/test_new_library_instrumentation.py

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,31 @@ def test_library_instrumentation():
99
"""Test that the google-genai library gets properly instrumented."""
1010
# Import the library
1111
from google import genai
12-
from google.genai.models import Models
12+
from google.genai.models import AsyncModels, Models
1313

1414
# Set up instrumentor
1515
instrumentor = GoogleGenerativeAiInstrumentor()
1616

1717
# Verify methods are not wrapped initially
1818
assert not hasattr(Models.generate_content, '__wrapped__')
19+
assert not hasattr(Models.generate_content_stream, '__wrapped__')
20+
assert not hasattr(AsyncModels.generate_content, '__wrapped__')
21+
assert not hasattr(AsyncModels.generate_content_stream, '__wrapped__')
1922

2023
try:
2124
instrumentor.instrument()
2225

23-
# Verify methods are now wrapped
26+
# Verify all methods are now wrapped
2427
assert hasattr(Models.generate_content, '__wrapped__')
28+
assert hasattr(Models.generate_content_stream, '__wrapped__')
29+
assert hasattr(AsyncModels.generate_content, '__wrapped__')
30+
assert hasattr(AsyncModels.generate_content_stream, '__wrapped__')
2531

26-
# Verify it's our wrapper
27-
wrapped_method = Models.generate_content
28-
assert callable(wrapped_method)
32+
# Verify they're callable
33+
assert callable(Models.generate_content)
34+
assert callable(Models.generate_content_stream)
35+
assert callable(AsyncModels.generate_content)
36+
assert callable(AsyncModels.generate_content_stream)
2937

3038
# Test that we can create a client
3139
client = genai.Client(api_key="test_key")
@@ -38,6 +46,9 @@ def test_library_instrumentation():
3846

3947
# Verify methods are unwrapped
4048
assert not hasattr(Models.generate_content, '__wrapped__')
49+
assert not hasattr(Models.generate_content_stream, '__wrapped__')
50+
assert not hasattr(AsyncModels.generate_content, '__wrapped__')
51+
assert not hasattr(AsyncModels.generate_content_stream, '__wrapped__')
4152

4253

4354
def test_instrumentation_dependencies():
@@ -54,7 +65,7 @@ def test_wrapped_methods():
5465
instrumentor = GoogleGenerativeAiInstrumentor()
5566
methods = instrumentor._wrapped_methods()
5667

57-
assert len(methods) == 2
68+
assert len(methods) == 4
5869

5970
# Should be using new library methods
6071
packages = [method.get("package", "") for method in methods]
@@ -64,3 +75,8 @@ def test_wrapped_methods():
6475
objects = [method.get("object", "") for method in methods]
6576
assert "Models" in objects
6677
assert "AsyncModels" in objects
78+
79+
# Should wrap both generate_content and generate_content_stream
80+
wrapped_methods = [method.get("method", "") for method in methods]
81+
assert wrapped_methods.count("generate_content") == 2
82+
assert wrapped_methods.count("generate_content_stream") == 2

0 commit comments

Comments
 (0)