Skip to content

Commit b3f292a

Browse files
authored
fix(google_genai): Make sure streaming spans attach to correct parent (#484)
Implemented a red/green fix for the Google GenAI streaming parent-span issue. This was noticed when examining a trace w/ livekit agents + vertex ai. What was happening: - In trace.json, generate_content_stream LLM spans were parented to the outer/root span instead of the correct parent. - Root cause is in `py/src/braintrust/integrations/google_genai/tracing.py`: streaming wrappers created the Braintrust span only when the returned stream was consumed, not when the stream was created. - Vertex creates the stream while the session parent is active, but consumes it later in a different context, so the parent was lost. Fix: - Capture the active Braintrust parent when generate_content_stream / async stream is created. - Re-apply that captured parent when the stream is later consumed.
1 parent e76f474 commit b3f292a

14 files changed

Lines changed: 983 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
interactions:
2+
- request:
3+
body: '{"contents": [{"parts": [{"text": "What is the capital of France?"}], "role":
4+
"user"}], "generationConfig": {"maxOutputTokens": 100}}'
5+
headers:
6+
accept:
7+
- '*/*'
8+
accept-encoding:
9+
- gzip, deflate
10+
connection:
11+
- keep-alive
12+
content-length:
13+
- '133'
14+
content-type:
15+
- application/json
16+
host:
17+
- generativelanguage.googleapis.com
18+
user-agent:
19+
- google-genai-sdk/1.41.0 gl-python/3.13.3
20+
x-goog-api-client:
21+
- google-genai-sdk/1.41.0 gl-python/3.13.3
22+
method: POST
23+
uri: https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-001:streamGenerateContent?alt=sse
24+
response:
25+
body:
26+
string: "data: {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \"The\"}],\"role\":
27+
\"model\"}}],\"usageMetadata\": {\"promptTokenCount\": 8,\"totalTokenCount\":
28+
8,\"promptTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": 8}]},\"modelVersion\":
29+
\"gemini-2.0-flash-001\",\"responseId\": \"_6TiaLjOJ7yBn9kPy5aNyQg\"}\r\n\r\ndata:
30+
{\"candidates\": [{\"content\": {\"parts\": [{\"text\": \" capital of France\"}],\"role\":
31+
\"model\"}}],\"usageMetadata\": {\"promptTokenCount\": 8,\"totalTokenCount\":
32+
8,\"promptTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": 8}]},\"modelVersion\":
33+
\"gemini-2.0-flash-001\",\"responseId\": \"_6TiaLjOJ7yBn9kPy5aNyQg\"}\r\n\r\ndata:
34+
{\"candidates\": [{\"content\": {\"parts\": [{\"text\": \" is Paris.\\n\"}],\"role\":
35+
\"model\"},\"finishReason\": \"STOP\"}],\"usageMetadata\": {\"promptTokenCount\":
36+
7,\"candidatesTokenCount\": 8,\"totalTokenCount\": 15,\"promptTokensDetails\":
37+
[{\"modality\": \"TEXT\",\"tokenCount\": 7}],\"candidatesTokensDetails\":
38+
[{\"modality\": \"TEXT\",\"tokenCount\": 8}]},\"modelVersion\": \"gemini-2.0-flash-001\",\"responseId\":
39+
\"_6TiaLjOJ7yBn9kPy5aNyQg\"}\r\n\r\n"
40+
headers:
41+
Alt-Svc:
42+
- h3=":443"; ma=2592000,h3-29=":443"; ma=2592000
43+
Content-Disposition:
44+
- attachment
45+
Content-Type:
46+
- text/event-stream
47+
Date:
48+
- Sun, 05 Oct 2025 17:03:59 GMT
49+
Server:
50+
- scaffolding on HTTPServer2
51+
Server-Timing:
52+
- gfet4t7; dur=393
53+
Transfer-Encoding:
54+
- chunked
55+
Vary:
56+
- Origin
57+
- X-Origin
58+
- Referer
59+
X-Content-Type-Options:
60+
- nosniff
61+
X-Frame-Options:
62+
- SAMEORIGIN
63+
X-XSS-Protection:
64+
- '0'
65+
status:
66+
code: 200
67+
message: OK
68+
version: 1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
interactions:
2+
- request:
3+
body: '{"contents": [{"parts": [{"text": "What is the capital of France?"}], "role":
4+
"user"}], "generationConfig": {"maxOutputTokens": 100}}'
5+
headers:
6+
accept:
7+
- '*/*'
8+
accept-encoding:
9+
- gzip, deflate
10+
connection:
11+
- keep-alive
12+
content-length:
13+
- '133'
14+
content-type:
15+
- application/json
16+
host:
17+
- generativelanguage.googleapis.com
18+
user-agent:
19+
- google-genai-sdk/1.41.0 gl-python/3.13.3
20+
x-goog-api-client:
21+
- google-genai-sdk/1.41.0 gl-python/3.13.3
22+
method: POST
23+
uri: https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-001:streamGenerateContent?alt=sse
24+
response:
25+
body:
26+
string: "data: {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \"The\"}],\"role\":
27+
\"model\"}}],\"usageMetadata\": {\"promptTokenCount\": 8,\"totalTokenCount\":
28+
8,\"promptTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": 8}]},\"modelVersion\":
29+
\"gemini-2.0-flash-001\",\"responseId\": \"_6TiaLjOJ7yBn9kPy5aNyQg\"}\r\n\r\ndata:
30+
{\"candidates\": [{\"content\": {\"parts\": [{\"text\": \" capital of France\"}],\"role\":
31+
\"model\"}}],\"usageMetadata\": {\"promptTokenCount\": 8,\"totalTokenCount\":
32+
8,\"promptTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": 8}]},\"modelVersion\":
33+
\"gemini-2.0-flash-001\",\"responseId\": \"_6TiaLjOJ7yBn9kPy5aNyQg\"}\r\n\r\ndata:
34+
{\"candidates\": [{\"content\": {\"parts\": [{\"text\": \" is Paris.\\n\"}],\"role\":
35+
\"model\"},\"finishReason\": \"STOP\"}],\"usageMetadata\": {\"promptTokenCount\":
36+
7,\"candidatesTokenCount\": 8,\"totalTokenCount\": 15,\"promptTokensDetails\":
37+
[{\"modality\": \"TEXT\",\"tokenCount\": 7}],\"candidatesTokensDetails\":
38+
[{\"modality\": \"TEXT\",\"tokenCount\": 8}]},\"modelVersion\": \"gemini-2.0-flash-001\",\"responseId\":
39+
\"_6TiaLjOJ7yBn9kPy5aNyQg\"}\r\n\r\n"
40+
headers:
41+
Alt-Svc:
42+
- h3=":443"; ma=2592000,h3-29=":443"; ma=2592000
43+
Content-Disposition:
44+
- attachment
45+
Content-Type:
46+
- text/event-stream
47+
Date:
48+
- Sun, 05 Oct 2025 17:03:59 GMT
49+
Server:
50+
- scaffolding on HTTPServer2
51+
Server-Timing:
52+
- gfet4t7; dur=393
53+
Transfer-Encoding:
54+
- chunked
55+
Vary:
56+
- Origin
57+
- X-Origin
58+
- Referer
59+
X-Content-Type-Options:
60+
- nosniff
61+
X-Frame-Options:
62+
- SAMEORIGIN
63+
X-XSS-Protection:
64+
- '0'
65+
status:
66+
code: 200
67+
message: OK
68+
version: 1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
interactions:
2+
- request:
3+
body: '{"contents": [{"parts": [{"text": "What is the capital of France?"}], "role":
4+
"user"}], "generationConfig": {"maxOutputTokens": 100}}'
5+
headers:
6+
accept:
7+
- '*/*'
8+
accept-encoding:
9+
- gzip, deflate
10+
connection:
11+
- keep-alive
12+
content-length:
13+
- '133'
14+
content-type:
15+
- application/json
16+
host:
17+
- generativelanguage.googleapis.com
18+
user-agent:
19+
- google-genai-sdk/1.41.0 gl-python/3.13.3
20+
x-goog-api-client:
21+
- google-genai-sdk/1.41.0 gl-python/3.13.3
22+
method: POST
23+
uri: https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-001:streamGenerateContent?alt=sse
24+
response:
25+
body:
26+
string: "data: {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \"The\"}],\"role\":
27+
\"model\"}}],\"usageMetadata\": {\"promptTokenCount\": 8,\"totalTokenCount\":
28+
8,\"promptTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": 8}]},\"modelVersion\":
29+
\"gemini-2.0-flash-001\",\"responseId\": \"_qTiaLjwK6vpgbUP2uvA-QU\"}\r\n\r\ndata:
30+
{\"candidates\": [{\"content\": {\"parts\": [{\"text\": \" capital of France\"}],\"role\":
31+
\"model\"}}],\"usageMetadata\": {\"promptTokenCount\": 8,\"totalTokenCount\":
32+
8,\"promptTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": 8}]},\"modelVersion\":
33+
\"gemini-2.0-flash-001\",\"responseId\": \"_qTiaLjwK6vpgbUP2uvA-QU\"}\r\n\r\ndata:
34+
{\"candidates\": [{\"content\": {\"parts\": [{\"text\": \" is Paris.\\n\"}],\"role\":
35+
\"model\"},\"finishReason\": \"STOP\"}],\"usageMetadata\": {\"promptTokenCount\":
36+
7,\"candidatesTokenCount\": 8,\"totalTokenCount\": 15,\"promptTokensDetails\":
37+
[{\"modality\": \"TEXT\",\"tokenCount\": 7}],\"candidatesTokensDetails\":
38+
[{\"modality\": \"TEXT\",\"tokenCount\": 8}]},\"modelVersion\": \"gemini-2.0-flash-001\",\"responseId\":
39+
\"_qTiaLjwK6vpgbUP2uvA-QU\"}\r\n\r\n"
40+
headers:
41+
Alt-Svc:
42+
- h3=":443"; ma=2592000,h3-29=":443"; ma=2592000
43+
Content-Disposition:
44+
- attachment
45+
Content-Type:
46+
- text/event-stream
47+
Date:
48+
- Sun, 05 Oct 2025 17:03:58 GMT
49+
Server:
50+
- scaffolding on HTTPServer2
51+
Server-Timing:
52+
- gfet4t7; dur=391
53+
Transfer-Encoding:
54+
- chunked
55+
Vary:
56+
- Origin
57+
- X-Origin
58+
- Referer
59+
X-Content-Type-Options:
60+
- nosniff
61+
X-Frame-Options:
62+
- SAMEORIGIN
63+
X-XSS-Protection:
64+
- '0'
65+
status:
66+
code: 200
67+
message: OK
68+
version: 1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
interactions:
2+
- request:
3+
body: '{"contents": [{"parts": [{"text": "What is the capital of France?"}], "role":
4+
"user"}], "generationConfig": {"maxOutputTokens": 100}}'
5+
headers:
6+
accept:
7+
- '*/*'
8+
accept-encoding:
9+
- gzip, deflate
10+
connection:
11+
- keep-alive
12+
content-length:
13+
- '133'
14+
content-type:
15+
- application/json
16+
host:
17+
- generativelanguage.googleapis.com
18+
user-agent:
19+
- google-genai-sdk/1.41.0 gl-python/3.13.3
20+
x-goog-api-client:
21+
- google-genai-sdk/1.41.0 gl-python/3.13.3
22+
method: POST
23+
uri: https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-001:streamGenerateContent?alt=sse
24+
response:
25+
body:
26+
string: "data: {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \"The\"}],\"role\":
27+
\"model\"}}],\"usageMetadata\": {\"promptTokenCount\": 8,\"totalTokenCount\":
28+
8,\"promptTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": 8}]},\"modelVersion\":
29+
\"gemini-2.0-flash-001\",\"responseId\": \"_qTiaLjwK6vpgbUP2uvA-QU\"}\r\n\r\ndata:
30+
{\"candidates\": [{\"content\": {\"parts\": [{\"text\": \" capital of France\"}],\"role\":
31+
\"model\"}}],\"usageMetadata\": {\"promptTokenCount\": 8,\"totalTokenCount\":
32+
8,\"promptTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": 8}]},\"modelVersion\":
33+
\"gemini-2.0-flash-001\",\"responseId\": \"_qTiaLjwK6vpgbUP2uvA-QU\"}\r\n\r\ndata:
34+
{\"candidates\": [{\"content\": {\"parts\": [{\"text\": \" is Paris.\\n\"}],\"role\":
35+
\"model\"},\"finishReason\": \"STOP\"}],\"usageMetadata\": {\"promptTokenCount\":
36+
7,\"candidatesTokenCount\": 8,\"totalTokenCount\": 15,\"promptTokensDetails\":
37+
[{\"modality\": \"TEXT\",\"tokenCount\": 7}],\"candidatesTokensDetails\":
38+
[{\"modality\": \"TEXT\",\"tokenCount\": 8}]},\"modelVersion\": \"gemini-2.0-flash-001\",\"responseId\":
39+
\"_qTiaLjwK6vpgbUP2uvA-QU\"}\r\n\r\n"
40+
headers:
41+
Alt-Svc:
42+
- h3=":443"; ma=2592000,h3-29=":443"; ma=2592000
43+
Content-Disposition:
44+
- attachment
45+
Content-Type:
46+
- text/event-stream
47+
Date:
48+
- Sun, 05 Oct 2025 17:03:58 GMT
49+
Server:
50+
- scaffolding on HTTPServer2
51+
Server-Timing:
52+
- gfet4t7; dur=391
53+
Transfer-Encoding:
54+
- chunked
55+
Vary:
56+
- Origin
57+
- X-Origin
58+
- Referer
59+
X-Content-Type-Options:
60+
- nosniff
61+
X-Frame-Options:
62+
- SAMEORIGIN
63+
X-XSS-Protection:
64+
- '0'
65+
status:
66+
code: 200
67+
message: OK
68+
version: 1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
interactions:
2+
- request:
3+
body: '{"contents": [{"parts": [{"text": "What is the capital of France?"}], "role":
4+
"user"}], "generationConfig": {"maxOutputTokens": 100}}'
5+
headers:
6+
Accept:
7+
- '*/*'
8+
Accept-Encoding:
9+
- gzip, deflate
10+
Connection:
11+
- keep-alive
12+
Content-Length:
13+
- '133'
14+
Content-Type:
15+
- application/json
16+
Host:
17+
- generativelanguage.googleapis.com
18+
user-agent:
19+
- google-genai-sdk/1.75.0 gl-python/3.12.12
20+
x-goog-api-client:
21+
- google-genai-sdk/1.75.0 gl-python/3.12.12
22+
method: POST
23+
uri: https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-001:streamGenerateContent?alt=sse
24+
response:
25+
body:
26+
string: "data: {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \"The\"}],\"role\":
27+
\"model\"}}],\"usageMetadata\": {\"promptTokenCount\": 8,\"totalTokenCount\":
28+
8,\"promptTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": 8}],\"serviceTier\":
29+
\"standard\"},\"modelVersion\": \"gemini-2.0-flash-001\",\"responseId\": \"q-cBasSYHuqF6MEPk5ra2QM\"}\r\n\r\ndata:
30+
{\"candidates\": [{\"content\": {\"parts\": [{\"text\": \" capital of France\"}],\"role\":
31+
\"model\"}}],\"usageMetadata\": {\"promptTokenCount\": 8,\"totalTokenCount\":
32+
8,\"promptTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": 8}],\"serviceTier\":
33+
\"standard\"},\"modelVersion\": \"gemini-2.0-flash-001\",\"responseId\": \"q-cBasSYHuqF6MEPk5ra2QM\"}\r\n\r\ndata:
34+
{\"candidates\": [{\"content\": {\"parts\": [{\"text\": \" is **Paris**.\\n\"}],\"role\":
35+
\"model\"},\"finishReason\": \"STOP\"}],\"usageMetadata\": {\"promptTokenCount\":
36+
7,\"candidatesTokenCount\": 9,\"totalTokenCount\": 16,\"promptTokensDetails\":
37+
[{\"modality\": \"TEXT\",\"tokenCount\": 7}],\"candidatesTokensDetails\":
38+
[{\"modality\": \"TEXT\",\"tokenCount\": 9}],\"serviceTier\": \"standard\"},\"modelVersion\":
39+
\"gemini-2.0-flash-001\",\"responseId\": \"q-cBasSYHuqF6MEPk5ra2QM\"}\r\n\r\n"
40+
headers:
41+
Alt-Svc:
42+
- h3=":443"; ma=2592000,h3-29=":443"; ma=2592000
43+
Content-Disposition:
44+
- attachment
45+
Content-Type:
46+
- text/event-stream
47+
Date:
48+
- Mon, 11 May 2026 14:28:59 GMT
49+
Server:
50+
- scaffolding on HTTPServer2
51+
Server-Timing:
52+
- gfet4t7; dur=460
53+
Transfer-Encoding:
54+
- chunked
55+
Vary:
56+
- Origin
57+
- X-Origin
58+
- Referer
59+
X-Content-Type-Options:
60+
- nosniff
61+
X-Frame-Options:
62+
- SAMEORIGIN
63+
X-XSS-Protection:
64+
- '0'
65+
status:
66+
code: 200
67+
message: OK
68+
version: 1

0 commit comments

Comments
 (0)