diff --git a/py/src/braintrust/integrations/google_genai/cassettes/1.30.0/test_async_stream_completion_preserves_creation_parent_when_consumed_later.yaml b/py/src/braintrust/integrations/google_genai/cassettes/1.30.0/test_async_stream_completion_preserves_creation_parent_when_consumed_later.yaml new file mode 100644 index 00000000..3dd5a250 --- /dev/null +++ b/py/src/braintrust/integrations/google_genai/cassettes/1.30.0/test_async_stream_completion_preserves_creation_parent_when_consumed_later.yaml @@ -0,0 +1,68 @@ +interactions: +- request: + body: '{"contents": [{"parts": [{"text": "What is the capital of France?"}], "role": + "user"}], "generationConfig": {"maxOutputTokens": 100}}' + headers: + accept: + - '*/*' + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '133' + content-type: + - application/json + host: + - generativelanguage.googleapis.com + user-agent: + - google-genai-sdk/1.41.0 gl-python/3.13.3 + x-goog-api-client: + - google-genai-sdk/1.41.0 gl-python/3.13.3 + method: POST + uri: https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-001:streamGenerateContent?alt=sse + response: + body: + string: "data: {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \"The\"}],\"role\": + \"model\"}}],\"usageMetadata\": {\"promptTokenCount\": 8,\"totalTokenCount\": + 8,\"promptTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": 8}]},\"modelVersion\": + \"gemini-2.0-flash-001\",\"responseId\": \"_6TiaLjOJ7yBn9kPy5aNyQg\"}\r\n\r\ndata: + {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \" capital of France\"}],\"role\": + \"model\"}}],\"usageMetadata\": {\"promptTokenCount\": 8,\"totalTokenCount\": + 8,\"promptTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": 8}]},\"modelVersion\": + \"gemini-2.0-flash-001\",\"responseId\": \"_6TiaLjOJ7yBn9kPy5aNyQg\"}\r\n\r\ndata: + {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \" is Paris.\\n\"}],\"role\": + \"model\"},\"finishReason\": \"STOP\"}],\"usageMetadata\": {\"promptTokenCount\": + 7,\"candidatesTokenCount\": 8,\"totalTokenCount\": 15,\"promptTokensDetails\": + [{\"modality\": \"TEXT\",\"tokenCount\": 7}],\"candidatesTokensDetails\": + [{\"modality\": \"TEXT\",\"tokenCount\": 8}]},\"modelVersion\": \"gemini-2.0-flash-001\",\"responseId\": + \"_6TiaLjOJ7yBn9kPy5aNyQg\"}\r\n\r\n" + headers: + Alt-Svc: + - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 + Content-Disposition: + - attachment + Content-Type: + - text/event-stream + Date: + - Sun, 05 Oct 2025 17:03:59 GMT + Server: + - scaffolding on HTTPServer2 + Server-Timing: + - gfet4t7; dur=393 + Transfer-Encoding: + - chunked + Vary: + - Origin + - X-Origin + - Referer + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-XSS-Protection: + - '0' + status: + code: 200 + message: OK +version: 1 diff --git a/py/src/braintrust/integrations/google_genai/cassettes/1.30.0/test_async_stream_completion_preserves_no_parent_when_consumed_under_parent.yaml b/py/src/braintrust/integrations/google_genai/cassettes/1.30.0/test_async_stream_completion_preserves_no_parent_when_consumed_under_parent.yaml new file mode 100644 index 00000000..3dd5a250 --- /dev/null +++ b/py/src/braintrust/integrations/google_genai/cassettes/1.30.0/test_async_stream_completion_preserves_no_parent_when_consumed_under_parent.yaml @@ -0,0 +1,68 @@ +interactions: +- request: + body: '{"contents": [{"parts": [{"text": "What is the capital of France?"}], "role": + "user"}], "generationConfig": {"maxOutputTokens": 100}}' + headers: + accept: + - '*/*' + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '133' + content-type: + - application/json + host: + - generativelanguage.googleapis.com + user-agent: + - google-genai-sdk/1.41.0 gl-python/3.13.3 + x-goog-api-client: + - google-genai-sdk/1.41.0 gl-python/3.13.3 + method: POST + uri: https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-001:streamGenerateContent?alt=sse + response: + body: + string: "data: {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \"The\"}],\"role\": + \"model\"}}],\"usageMetadata\": {\"promptTokenCount\": 8,\"totalTokenCount\": + 8,\"promptTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": 8}]},\"modelVersion\": + \"gemini-2.0-flash-001\",\"responseId\": \"_6TiaLjOJ7yBn9kPy5aNyQg\"}\r\n\r\ndata: + {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \" capital of France\"}],\"role\": + \"model\"}}],\"usageMetadata\": {\"promptTokenCount\": 8,\"totalTokenCount\": + 8,\"promptTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": 8}]},\"modelVersion\": + \"gemini-2.0-flash-001\",\"responseId\": \"_6TiaLjOJ7yBn9kPy5aNyQg\"}\r\n\r\ndata: + {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \" is Paris.\\n\"}],\"role\": + \"model\"},\"finishReason\": \"STOP\"}],\"usageMetadata\": {\"promptTokenCount\": + 7,\"candidatesTokenCount\": 8,\"totalTokenCount\": 15,\"promptTokensDetails\": + [{\"modality\": \"TEXT\",\"tokenCount\": 7}],\"candidatesTokensDetails\": + [{\"modality\": \"TEXT\",\"tokenCount\": 8}]},\"modelVersion\": \"gemini-2.0-flash-001\",\"responseId\": + \"_6TiaLjOJ7yBn9kPy5aNyQg\"}\r\n\r\n" + headers: + Alt-Svc: + - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 + Content-Disposition: + - attachment + Content-Type: + - text/event-stream + Date: + - Sun, 05 Oct 2025 17:03:59 GMT + Server: + - scaffolding on HTTPServer2 + Server-Timing: + - gfet4t7; dur=393 + Transfer-Encoding: + - chunked + Vary: + - Origin + - X-Origin + - Referer + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-XSS-Protection: + - '0' + status: + code: 200 + message: OK +version: 1 diff --git a/py/src/braintrust/integrations/google_genai/cassettes/1.30.0/test_stream_completion_preserves_creation_parent_when_consumed_later.yaml b/py/src/braintrust/integrations/google_genai/cassettes/1.30.0/test_stream_completion_preserves_creation_parent_when_consumed_later.yaml new file mode 100644 index 00000000..4e47ddff --- /dev/null +++ b/py/src/braintrust/integrations/google_genai/cassettes/1.30.0/test_stream_completion_preserves_creation_parent_when_consumed_later.yaml @@ -0,0 +1,68 @@ +interactions: +- request: + body: '{"contents": [{"parts": [{"text": "What is the capital of France?"}], "role": + "user"}], "generationConfig": {"maxOutputTokens": 100}}' + headers: + accept: + - '*/*' + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '133' + content-type: + - application/json + host: + - generativelanguage.googleapis.com + user-agent: + - google-genai-sdk/1.41.0 gl-python/3.13.3 + x-goog-api-client: + - google-genai-sdk/1.41.0 gl-python/3.13.3 + method: POST + uri: https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-001:streamGenerateContent?alt=sse + response: + body: + string: "data: {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \"The\"}],\"role\": + \"model\"}}],\"usageMetadata\": {\"promptTokenCount\": 8,\"totalTokenCount\": + 8,\"promptTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": 8}]},\"modelVersion\": + \"gemini-2.0-flash-001\",\"responseId\": \"_qTiaLjwK6vpgbUP2uvA-QU\"}\r\n\r\ndata: + {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \" capital of France\"}],\"role\": + \"model\"}}],\"usageMetadata\": {\"promptTokenCount\": 8,\"totalTokenCount\": + 8,\"promptTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": 8}]},\"modelVersion\": + \"gemini-2.0-flash-001\",\"responseId\": \"_qTiaLjwK6vpgbUP2uvA-QU\"}\r\n\r\ndata: + {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \" is Paris.\\n\"}],\"role\": + \"model\"},\"finishReason\": \"STOP\"}],\"usageMetadata\": {\"promptTokenCount\": + 7,\"candidatesTokenCount\": 8,\"totalTokenCount\": 15,\"promptTokensDetails\": + [{\"modality\": \"TEXT\",\"tokenCount\": 7}],\"candidatesTokensDetails\": + [{\"modality\": \"TEXT\",\"tokenCount\": 8}]},\"modelVersion\": \"gemini-2.0-flash-001\",\"responseId\": + \"_qTiaLjwK6vpgbUP2uvA-QU\"}\r\n\r\n" + headers: + Alt-Svc: + - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 + Content-Disposition: + - attachment + Content-Type: + - text/event-stream + Date: + - Sun, 05 Oct 2025 17:03:58 GMT + Server: + - scaffolding on HTTPServer2 + Server-Timing: + - gfet4t7; dur=391 + Transfer-Encoding: + - chunked + Vary: + - Origin + - X-Origin + - Referer + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-XSS-Protection: + - '0' + status: + code: 200 + message: OK +version: 1 diff --git a/py/src/braintrust/integrations/google_genai/cassettes/1.30.0/test_stream_completion_preserves_no_parent_when_consumed_under_parent.yaml b/py/src/braintrust/integrations/google_genai/cassettes/1.30.0/test_stream_completion_preserves_no_parent_when_consumed_under_parent.yaml new file mode 100644 index 00000000..4e47ddff --- /dev/null +++ b/py/src/braintrust/integrations/google_genai/cassettes/1.30.0/test_stream_completion_preserves_no_parent_when_consumed_under_parent.yaml @@ -0,0 +1,68 @@ +interactions: +- request: + body: '{"contents": [{"parts": [{"text": "What is the capital of France?"}], "role": + "user"}], "generationConfig": {"maxOutputTokens": 100}}' + headers: + accept: + - '*/*' + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '133' + content-type: + - application/json + host: + - generativelanguage.googleapis.com + user-agent: + - google-genai-sdk/1.41.0 gl-python/3.13.3 + x-goog-api-client: + - google-genai-sdk/1.41.0 gl-python/3.13.3 + method: POST + uri: https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-001:streamGenerateContent?alt=sse + response: + body: + string: "data: {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \"The\"}],\"role\": + \"model\"}}],\"usageMetadata\": {\"promptTokenCount\": 8,\"totalTokenCount\": + 8,\"promptTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": 8}]},\"modelVersion\": + \"gemini-2.0-flash-001\",\"responseId\": \"_qTiaLjwK6vpgbUP2uvA-QU\"}\r\n\r\ndata: + {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \" capital of France\"}],\"role\": + \"model\"}}],\"usageMetadata\": {\"promptTokenCount\": 8,\"totalTokenCount\": + 8,\"promptTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": 8}]},\"modelVersion\": + \"gemini-2.0-flash-001\",\"responseId\": \"_qTiaLjwK6vpgbUP2uvA-QU\"}\r\n\r\ndata: + {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \" is Paris.\\n\"}],\"role\": + \"model\"},\"finishReason\": \"STOP\"}],\"usageMetadata\": {\"promptTokenCount\": + 7,\"candidatesTokenCount\": 8,\"totalTokenCount\": 15,\"promptTokensDetails\": + [{\"modality\": \"TEXT\",\"tokenCount\": 7}],\"candidatesTokensDetails\": + [{\"modality\": \"TEXT\",\"tokenCount\": 8}]},\"modelVersion\": \"gemini-2.0-flash-001\",\"responseId\": + \"_qTiaLjwK6vpgbUP2uvA-QU\"}\r\n\r\n" + headers: + Alt-Svc: + - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 + Content-Disposition: + - attachment + Content-Type: + - text/event-stream + Date: + - Sun, 05 Oct 2025 17:03:58 GMT + Server: + - scaffolding on HTTPServer2 + Server-Timing: + - gfet4t7; dur=391 + Transfer-Encoding: + - chunked + Vary: + - Origin + - X-Origin + - Referer + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-XSS-Protection: + - '0' + status: + code: 200 + message: OK +version: 1 diff --git a/py/src/braintrust/integrations/google_genai/cassettes/1.75.0/test_async_stream_completion_preserves_creation_parent_when_consumed_later.yaml b/py/src/braintrust/integrations/google_genai/cassettes/1.75.0/test_async_stream_completion_preserves_creation_parent_when_consumed_later.yaml new file mode 100644 index 00000000..c0189065 --- /dev/null +++ b/py/src/braintrust/integrations/google_genai/cassettes/1.75.0/test_async_stream_completion_preserves_creation_parent_when_consumed_later.yaml @@ -0,0 +1,68 @@ +interactions: +- request: + body: '{"contents": [{"parts": [{"text": "What is the capital of France?"}], "role": + "user"}], "generationConfig": {"maxOutputTokens": 100}}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '133' + Content-Type: + - application/json + Host: + - generativelanguage.googleapis.com + user-agent: + - google-genai-sdk/1.75.0 gl-python/3.12.12 + x-goog-api-client: + - google-genai-sdk/1.75.0 gl-python/3.12.12 + method: POST + uri: https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-001:streamGenerateContent?alt=sse + response: + body: + string: "data: {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \"The\"}],\"role\": + \"model\"}}],\"usageMetadata\": {\"promptTokenCount\": 8,\"totalTokenCount\": + 8,\"promptTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": 8}],\"serviceTier\": + \"standard\"},\"modelVersion\": \"gemini-2.0-flash-001\",\"responseId\": \"q-cBasSYHuqF6MEPk5ra2QM\"}\r\n\r\ndata: + {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \" capital of France\"}],\"role\": + \"model\"}}],\"usageMetadata\": {\"promptTokenCount\": 8,\"totalTokenCount\": + 8,\"promptTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": 8}],\"serviceTier\": + \"standard\"},\"modelVersion\": \"gemini-2.0-flash-001\",\"responseId\": \"q-cBasSYHuqF6MEPk5ra2QM\"}\r\n\r\ndata: + {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \" is **Paris**.\\n\"}],\"role\": + \"model\"},\"finishReason\": \"STOP\"}],\"usageMetadata\": {\"promptTokenCount\": + 7,\"candidatesTokenCount\": 9,\"totalTokenCount\": 16,\"promptTokensDetails\": + [{\"modality\": \"TEXT\",\"tokenCount\": 7}],\"candidatesTokensDetails\": + [{\"modality\": \"TEXT\",\"tokenCount\": 9}],\"serviceTier\": \"standard\"},\"modelVersion\": + \"gemini-2.0-flash-001\",\"responseId\": \"q-cBasSYHuqF6MEPk5ra2QM\"}\r\n\r\n" + headers: + Alt-Svc: + - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 + Content-Disposition: + - attachment + Content-Type: + - text/event-stream + Date: + - Mon, 11 May 2026 14:28:59 GMT + Server: + - scaffolding on HTTPServer2 + Server-Timing: + - gfet4t7; dur=460 + Transfer-Encoding: + - chunked + Vary: + - Origin + - X-Origin + - Referer + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-XSS-Protection: + - '0' + status: + code: 200 + message: OK +version: 1 diff --git a/py/src/braintrust/integrations/google_genai/cassettes/1.75.0/test_async_stream_completion_preserves_no_parent_when_consumed_under_parent.yaml b/py/src/braintrust/integrations/google_genai/cassettes/1.75.0/test_async_stream_completion_preserves_no_parent_when_consumed_under_parent.yaml new file mode 100644 index 00000000..c0189065 --- /dev/null +++ b/py/src/braintrust/integrations/google_genai/cassettes/1.75.0/test_async_stream_completion_preserves_no_parent_when_consumed_under_parent.yaml @@ -0,0 +1,68 @@ +interactions: +- request: + body: '{"contents": [{"parts": [{"text": "What is the capital of France?"}], "role": + "user"}], "generationConfig": {"maxOutputTokens": 100}}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '133' + Content-Type: + - application/json + Host: + - generativelanguage.googleapis.com + user-agent: + - google-genai-sdk/1.75.0 gl-python/3.12.12 + x-goog-api-client: + - google-genai-sdk/1.75.0 gl-python/3.12.12 + method: POST + uri: https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-001:streamGenerateContent?alt=sse + response: + body: + string: "data: {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \"The\"}],\"role\": + \"model\"}}],\"usageMetadata\": {\"promptTokenCount\": 8,\"totalTokenCount\": + 8,\"promptTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": 8}],\"serviceTier\": + \"standard\"},\"modelVersion\": \"gemini-2.0-flash-001\",\"responseId\": \"q-cBasSYHuqF6MEPk5ra2QM\"}\r\n\r\ndata: + {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \" capital of France\"}],\"role\": + \"model\"}}],\"usageMetadata\": {\"promptTokenCount\": 8,\"totalTokenCount\": + 8,\"promptTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": 8}],\"serviceTier\": + \"standard\"},\"modelVersion\": \"gemini-2.0-flash-001\",\"responseId\": \"q-cBasSYHuqF6MEPk5ra2QM\"}\r\n\r\ndata: + {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \" is **Paris**.\\n\"}],\"role\": + \"model\"},\"finishReason\": \"STOP\"}],\"usageMetadata\": {\"promptTokenCount\": + 7,\"candidatesTokenCount\": 9,\"totalTokenCount\": 16,\"promptTokensDetails\": + [{\"modality\": \"TEXT\",\"tokenCount\": 7}],\"candidatesTokensDetails\": + [{\"modality\": \"TEXT\",\"tokenCount\": 9}],\"serviceTier\": \"standard\"},\"modelVersion\": + \"gemini-2.0-flash-001\",\"responseId\": \"q-cBasSYHuqF6MEPk5ra2QM\"}\r\n\r\n" + headers: + Alt-Svc: + - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 + Content-Disposition: + - attachment + Content-Type: + - text/event-stream + Date: + - Mon, 11 May 2026 14:28:59 GMT + Server: + - scaffolding on HTTPServer2 + Server-Timing: + - gfet4t7; dur=460 + Transfer-Encoding: + - chunked + Vary: + - Origin + - X-Origin + - Referer + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-XSS-Protection: + - '0' + status: + code: 200 + message: OK +version: 1 diff --git a/py/src/braintrust/integrations/google_genai/cassettes/1.75.0/test_stream_completion_preserves_creation_parent_when_consumed_later.yaml b/py/src/braintrust/integrations/google_genai/cassettes/1.75.0/test_stream_completion_preserves_creation_parent_when_consumed_later.yaml new file mode 100644 index 00000000..eca53d54 --- /dev/null +++ b/py/src/braintrust/integrations/google_genai/cassettes/1.75.0/test_stream_completion_preserves_creation_parent_when_consumed_later.yaml @@ -0,0 +1,68 @@ +interactions: +- request: + body: '{"contents": [{"parts": [{"text": "What is the capital of France?"}], "role": + "user"}], "generationConfig": {"maxOutputTokens": 100}}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '133' + Content-Type: + - application/json + Host: + - generativelanguage.googleapis.com + user-agent: + - google-genai-sdk/1.75.0 gl-python/3.12.12 + x-goog-api-client: + - google-genai-sdk/1.75.0 gl-python/3.12.12 + method: POST + uri: https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-001:streamGenerateContent?alt=sse + response: + body: + string: "data: {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \"The\"}],\"role\": + \"model\"}}],\"usageMetadata\": {\"promptTokenCount\": 8,\"totalTokenCount\": + 8,\"promptTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": 8}],\"serviceTier\": + \"standard\"},\"modelVersion\": \"gemini-2.0-flash-001\",\"responseId\": \"qucBapXpCNPT6MEP44Tv2Q8\"}\r\n\r\ndata: + {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \" capital of France\"}],\"role\": + \"model\"}}],\"usageMetadata\": {\"promptTokenCount\": 8,\"totalTokenCount\": + 8,\"promptTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": 8}],\"serviceTier\": + \"standard\"},\"modelVersion\": \"gemini-2.0-flash-001\",\"responseId\": \"qucBapXpCNPT6MEP44Tv2Q8\"}\r\n\r\ndata: + {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \" is **Paris**.\\n\"}],\"role\": + \"model\"},\"finishReason\": \"STOP\"}],\"usageMetadata\": {\"promptTokenCount\": + 7,\"candidatesTokenCount\": 9,\"totalTokenCount\": 16,\"promptTokensDetails\": + [{\"modality\": \"TEXT\",\"tokenCount\": 7}],\"candidatesTokensDetails\": + [{\"modality\": \"TEXT\",\"tokenCount\": 9}],\"serviceTier\": \"standard\"},\"modelVersion\": + \"gemini-2.0-flash-001\",\"responseId\": \"qucBapXpCNPT6MEP44Tv2Q8\"}\r\n\r\n" + headers: + Alt-Svc: + - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 + Content-Disposition: + - attachment + Content-Type: + - text/event-stream + Date: + - Mon, 11 May 2026 14:28:58 GMT + Server: + - scaffolding on HTTPServer2 + Server-Timing: + - gfet4t7; dur=466 + Transfer-Encoding: + - chunked + Vary: + - Origin + - X-Origin + - Referer + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-XSS-Protection: + - '0' + status: + code: 200 + message: OK +version: 1 diff --git a/py/src/braintrust/integrations/google_genai/cassettes/1.75.0/test_stream_completion_preserves_no_parent_when_consumed_under_parent.yaml b/py/src/braintrust/integrations/google_genai/cassettes/1.75.0/test_stream_completion_preserves_no_parent_when_consumed_under_parent.yaml new file mode 100644 index 00000000..eca53d54 --- /dev/null +++ b/py/src/braintrust/integrations/google_genai/cassettes/1.75.0/test_stream_completion_preserves_no_parent_when_consumed_under_parent.yaml @@ -0,0 +1,68 @@ +interactions: +- request: + body: '{"contents": [{"parts": [{"text": "What is the capital of France?"}], "role": + "user"}], "generationConfig": {"maxOutputTokens": 100}}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '133' + Content-Type: + - application/json + Host: + - generativelanguage.googleapis.com + user-agent: + - google-genai-sdk/1.75.0 gl-python/3.12.12 + x-goog-api-client: + - google-genai-sdk/1.75.0 gl-python/3.12.12 + method: POST + uri: https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-001:streamGenerateContent?alt=sse + response: + body: + string: "data: {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \"The\"}],\"role\": + \"model\"}}],\"usageMetadata\": {\"promptTokenCount\": 8,\"totalTokenCount\": + 8,\"promptTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": 8}],\"serviceTier\": + \"standard\"},\"modelVersion\": \"gemini-2.0-flash-001\",\"responseId\": \"qucBapXpCNPT6MEP44Tv2Q8\"}\r\n\r\ndata: + {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \" capital of France\"}],\"role\": + \"model\"}}],\"usageMetadata\": {\"promptTokenCount\": 8,\"totalTokenCount\": + 8,\"promptTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": 8}],\"serviceTier\": + \"standard\"},\"modelVersion\": \"gemini-2.0-flash-001\",\"responseId\": \"qucBapXpCNPT6MEP44Tv2Q8\"}\r\n\r\ndata: + {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \" is **Paris**.\\n\"}],\"role\": + \"model\"},\"finishReason\": \"STOP\"}],\"usageMetadata\": {\"promptTokenCount\": + 7,\"candidatesTokenCount\": 9,\"totalTokenCount\": 16,\"promptTokensDetails\": + [{\"modality\": \"TEXT\",\"tokenCount\": 7}],\"candidatesTokensDetails\": + [{\"modality\": \"TEXT\",\"tokenCount\": 9}],\"serviceTier\": \"standard\"},\"modelVersion\": + \"gemini-2.0-flash-001\",\"responseId\": \"qucBapXpCNPT6MEP44Tv2Q8\"}\r\n\r\n" + headers: + Alt-Svc: + - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 + Content-Disposition: + - attachment + Content-Type: + - text/event-stream + Date: + - Mon, 11 May 2026 14:28:58 GMT + Server: + - scaffolding on HTTPServer2 + Server-Timing: + - gfet4t7; dur=466 + Transfer-Encoding: + - chunked + Vary: + - Origin + - X-Origin + - Referer + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-XSS-Protection: + - '0' + status: + code: 200 + message: OK +version: 1 diff --git a/py/src/braintrust/integrations/google_genai/cassettes/latest/test_async_stream_completion_preserves_creation_parent_when_consumed_later.yaml b/py/src/braintrust/integrations/google_genai/cassettes/latest/test_async_stream_completion_preserves_creation_parent_when_consumed_later.yaml new file mode 100644 index 00000000..ad6a0c34 --- /dev/null +++ b/py/src/braintrust/integrations/google_genai/cassettes/latest/test_async_stream_completion_preserves_creation_parent_when_consumed_later.yaml @@ -0,0 +1,65 @@ +interactions: +- request: + body: '{"contents": [{"parts": [{"text": "What is the capital of France?"}], "role": + "user"}], "generationConfig": {"maxOutputTokens": 100}}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '133' + Content-Type: + - application/json + Host: + - generativelanguage.googleapis.com + user-agent: + - google-genai-sdk/2.6.0 gl-python/3.14.3 + x-goog-api-client: + - google-genai-sdk/2.6.0 gl-python/3.14.3 + method: POST + uri: https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-lite:streamGenerateContent?alt=sse + response: + body: + string: "data: {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \"The + capital of France is **\"}],\"role\": \"model\"},\"index\": 0}],\"usageMetadata\": + {\"promptTokenCount\": 8,\"candidatesTokenCount\": 6,\"totalTokenCount\": + 14,\"promptTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": 8}],\"serviceTier\": + \"standard\"},\"modelVersion\": \"gemini-2.5-flash-lite\",\"responseId\": + \"kpodaq33H8Cb_uMPmYDj0QY\"}\r\n\r\ndata: {\"candidates\": [{\"content\": + {\"parts\": [{\"text\": \"Paris**.\"}],\"role\": \"model\"},\"finishReason\": + \"STOP\",\"index\": 0}],\"usageMetadata\": {\"promptTokenCount\": 8,\"candidatesTokenCount\": + 8,\"totalTokenCount\": 16,\"promptTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": + 8}],\"serviceTier\": \"standard\"},\"modelVersion\": \"gemini-2.5-flash-lite\",\"responseId\": + \"kpodaq33H8Cb_uMPmYDj0QY\"}\r\n\r\n" + headers: + Alt-Svc: + - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 + Content-Disposition: + - attachment + Content-Type: + - text/event-stream + Date: + - Mon, 01 Jun 2026 14:43:30 GMT + Server: + - scaffolding on HTTPServer2 + Server-Timing: + - gfet4t7; dur=350 + Transfer-Encoding: + - chunked + Vary: + - Origin + - X-Origin + - Referer + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-XSS-Protection: + - '0' + status: + code: 200 + message: OK +version: 1 diff --git a/py/src/braintrust/integrations/google_genai/cassettes/latest/test_async_stream_completion_preserves_no_parent_when_consumed_under_parent.yaml b/py/src/braintrust/integrations/google_genai/cassettes/latest/test_async_stream_completion_preserves_no_parent_when_consumed_under_parent.yaml new file mode 100644 index 00000000..d31ff31c --- /dev/null +++ b/py/src/braintrust/integrations/google_genai/cassettes/latest/test_async_stream_completion_preserves_no_parent_when_consumed_under_parent.yaml @@ -0,0 +1,65 @@ +interactions: +- request: + body: '{"contents": [{"parts": [{"text": "What is the capital of France?"}], "role": + "user"}], "generationConfig": {"maxOutputTokens": 100}}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '133' + Content-Type: + - application/json + Host: + - generativelanguage.googleapis.com + user-agent: + - google-genai-sdk/2.6.0 gl-python/3.14.3 + x-goog-api-client: + - google-genai-sdk/2.6.0 gl-python/3.14.3 + method: POST + uri: https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-lite:streamGenerateContent?alt=sse + response: + body: + string: "data: {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \"The + capital of France is **\"}],\"role\": \"model\"},\"index\": 0}],\"usageMetadata\": + {\"promptTokenCount\": 8,\"candidatesTokenCount\": 6,\"totalTokenCount\": + 14,\"promptTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": 8}],\"serviceTier\": + \"standard\"},\"modelVersion\": \"gemini-2.5-flash-lite\",\"responseId\": + \"VJsdarWgPJif-8YP89--uQ0\"}\r\n\r\ndata: {\"candidates\": [{\"content\": + {\"parts\": [{\"text\": \"Paris**.\"}],\"role\": \"model\"},\"finishReason\": + \"STOP\",\"index\": 0}],\"usageMetadata\": {\"promptTokenCount\": 8,\"candidatesTokenCount\": + 8,\"totalTokenCount\": 16,\"promptTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": + 8}],\"serviceTier\": \"standard\"},\"modelVersion\": \"gemini-2.5-flash-lite\",\"responseId\": + \"VJsdarWgPJif-8YP89--uQ0\"}\r\n\r\n" + headers: + Alt-Svc: + - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 + Content-Disposition: + - attachment + Content-Type: + - text/event-stream + Date: + - Mon, 01 Jun 2026 14:46:45 GMT + Server: + - scaffolding on HTTPServer2 + Server-Timing: + - gfet4t7; dur=292 + Transfer-Encoding: + - chunked + Vary: + - Origin + - X-Origin + - Referer + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-XSS-Protection: + - '0' + status: + code: 200 + message: OK +version: 1 diff --git a/py/src/braintrust/integrations/google_genai/cassettes/latest/test_stream_completion_preserves_creation_parent_when_consumed_later.yaml b/py/src/braintrust/integrations/google_genai/cassettes/latest/test_stream_completion_preserves_creation_parent_when_consumed_later.yaml new file mode 100644 index 00000000..8586dda8 --- /dev/null +++ b/py/src/braintrust/integrations/google_genai/cassettes/latest/test_stream_completion_preserves_creation_parent_when_consumed_later.yaml @@ -0,0 +1,65 @@ +interactions: +- request: + body: '{"contents": [{"parts": [{"text": "What is the capital of France?"}], "role": + "user"}], "generationConfig": {"maxOutputTokens": 100}}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '133' + Content-Type: + - application/json + Host: + - generativelanguage.googleapis.com + user-agent: + - google-genai-sdk/2.6.0 gl-python/3.14.3 + x-goog-api-client: + - google-genai-sdk/2.6.0 gl-python/3.14.3 + method: POST + uri: https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-lite:streamGenerateContent?alt=sse + response: + body: + string: "data: {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \"The + capital of France is **\"}],\"role\": \"model\"},\"index\": 0}],\"usageMetadata\": + {\"promptTokenCount\": 8,\"candidatesTokenCount\": 6,\"totalTokenCount\": + 14,\"promptTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": 8}],\"serviceTier\": + \"standard\"},\"modelVersion\": \"gemini-2.5-flash-lite\",\"responseId\": + \"kpodatKEAvGN_uMPrYaE0QY\"}\r\n\r\ndata: {\"candidates\": [{\"content\": + {\"parts\": [{\"text\": \"Paris**.\"}],\"role\": \"model\"},\"finishReason\": + \"STOP\",\"index\": 0}],\"usageMetadata\": {\"promptTokenCount\": 8,\"candidatesTokenCount\": + 8,\"totalTokenCount\": 16,\"promptTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": + 8}],\"serviceTier\": \"standard\"},\"modelVersion\": \"gemini-2.5-flash-lite\",\"responseId\": + \"kpodatKEAvGN_uMPrYaE0QY\"}\r\n\r\n" + headers: + Alt-Svc: + - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 + Content-Disposition: + - attachment + Content-Type: + - text/event-stream + Date: + - Mon, 01 Jun 2026 14:43:30 GMT + Server: + - scaffolding on HTTPServer2 + Server-Timing: + - gfet4t7; dur=269 + Transfer-Encoding: + - chunked + Vary: + - Origin + - X-Origin + - Referer + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-XSS-Protection: + - '0' + status: + code: 200 + message: OK +version: 1 diff --git a/py/src/braintrust/integrations/google_genai/cassettes/latest/test_stream_completion_preserves_no_parent_when_consumed_under_parent.yaml b/py/src/braintrust/integrations/google_genai/cassettes/latest/test_stream_completion_preserves_no_parent_when_consumed_under_parent.yaml new file mode 100644 index 00000000..37bd564e --- /dev/null +++ b/py/src/braintrust/integrations/google_genai/cassettes/latest/test_stream_completion_preserves_no_parent_when_consumed_under_parent.yaml @@ -0,0 +1,65 @@ +interactions: +- request: + body: '{"contents": [{"parts": [{"text": "What is the capital of France?"}], "role": + "user"}], "generationConfig": {"maxOutputTokens": 100}}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '133' + Content-Type: + - application/json + Host: + - generativelanguage.googleapis.com + user-agent: + - google-genai-sdk/2.6.0 gl-python/3.14.3 + x-goog-api-client: + - google-genai-sdk/2.6.0 gl-python/3.14.3 + method: POST + uri: https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-lite:streamGenerateContent?alt=sse + response: + body: + string: "data: {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \"The + capital of France is **\"}],\"role\": \"model\"},\"index\": 0}],\"usageMetadata\": + {\"promptTokenCount\": 8,\"candidatesTokenCount\": 6,\"totalTokenCount\": + 14,\"promptTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": 8}],\"serviceTier\": + \"standard\"},\"modelVersion\": \"gemini-2.5-flash-lite\",\"responseId\": + \"VJsdat3wG76s-8YPktuwmA0\"}\r\n\r\ndata: {\"candidates\": [{\"content\": + {\"parts\": [{\"text\": \"Paris**.\"}],\"role\": \"model\"},\"finishReason\": + \"STOP\",\"index\": 0}],\"usageMetadata\": {\"promptTokenCount\": 8,\"candidatesTokenCount\": + 8,\"totalTokenCount\": 16,\"promptTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": + 8}],\"serviceTier\": \"standard\"},\"modelVersion\": \"gemini-2.5-flash-lite\",\"responseId\": + \"VJsdat3wG76s-8YPktuwmA0\"}\r\n\r\n" + headers: + Alt-Svc: + - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 + Content-Disposition: + - attachment + Content-Type: + - text/event-stream + Date: + - Mon, 01 Jun 2026 14:46:44 GMT + Server: + - scaffolding on HTTPServer2 + Server-Timing: + - gfet4t7; dur=293 + Transfer-Encoding: + - chunked + Vary: + - Origin + - X-Origin + - Referer + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-XSS-Protection: + - '0' + status: + code: 200 + message: OK +version: 1 diff --git a/py/src/braintrust/integrations/google_genai/test_google_genai.py b/py/src/braintrust/integrations/google_genai/test_google_genai.py index b710abdd..51a7c60f 100644 --- a/py/src/braintrust/integrations/google_genai/test_google_genai.py +++ b/py/src/braintrust/integrations/google_genai/test_google_genai.py @@ -222,6 +222,102 @@ def test_basic_completion(memory_logger, mode): _assert_metrics_are_valid(span["metrics"], start, end) +@pytest.mark.vcr +def test_stream_completion_preserves_creation_parent_when_consumed_later(memory_logger): + assert not memory_logger.pop() + + client = Client() + with logger.start_span(name="stream_parent"): + stream = client.models.generate_content_stream( + model=MODEL, + contents="What is the capital of France?", + config=types.GenerateContentConfig(max_output_tokens=100), + ) + + text = "" + for chunk in stream: + if chunk.text: + text += chunk.text + + assert "Paris" in text + spans = memory_logger.pop() + parent_span = find_span_by_name(spans, "stream_parent") + stream_span = find_span_by_name(spans, "generate_content_stream") + assert stream_span["span_parents"] == [parent_span["span_id"]] + + +@pytest.mark.vcr +def test_stream_completion_preserves_no_parent_when_consumed_under_parent(memory_logger): + assert not memory_logger.pop() + + client = Client() + stream = client.models.generate_content_stream( + model=MODEL, + contents="What is the capital of France?", + config=types.GenerateContentConfig(max_output_tokens=100), + ) + + text = "" + with logger.start_span(name="consumer_parent"): + for chunk in stream: + if chunk.text: + text += chunk.text + + assert "Paris" in text + spans = memory_logger.pop() + stream_span = find_span_by_name(spans, "generate_content_stream") + assert stream_span.get("span_parents") is None + + +@pytest.mark.vcr +@pytest.mark.asyncio +async def test_async_stream_completion_preserves_creation_parent_when_consumed_later(memory_logger): + assert not memory_logger.pop() + + client = Client() + with logger.start_span(name="stream_parent"): + stream = await client.aio.models.generate_content_stream( + model=MODEL, + contents="What is the capital of France?", + config=types.GenerateContentConfig(max_output_tokens=100), + ) + + text = "" + async for chunk in stream: + if chunk.text: + text += chunk.text + + assert "Paris" in text + spans = memory_logger.pop() + parent_span = find_span_by_name(spans, "stream_parent") + stream_span = find_span_by_name(spans, "generate_content_stream") + assert stream_span["span_parents"] == [parent_span["span_id"]] + + +@pytest.mark.vcr +@pytest.mark.asyncio +async def test_async_stream_completion_preserves_no_parent_when_consumed_under_parent(memory_logger): + assert not memory_logger.pop() + + client = Client() + stream = await client.aio.models.generate_content_stream( + model=MODEL, + contents="What is the capital of France?", + config=types.GenerateContentConfig(max_output_tokens=100), + ) + + text = "" + with logger.start_span(name="consumer_parent"): + async for chunk in stream: + if chunk.text: + text += chunk.text + + assert "Paris" in text + spans = memory_logger.pop() + stream_span = find_span_by_name(spans, "generate_content_stream") + assert stream_span.get("span_parents") is None + + # Test 1b: Basic Completion (Async) @pytest.mark.vcr @pytest.mark.asyncio diff --git a/py/src/braintrust/integrations/google_genai/tracing.py b/py/src/braintrust/integrations/google_genai/tracing.py index adf7ec29..3f231f73 100644 --- a/py/src/braintrust/integrations/google_genai/tracing.py +++ b/py/src/braintrust/integrations/google_genai/tracing.py @@ -1,5 +1,6 @@ """Google GenAI-specific span creation, metadata extraction, stream handling, and output normalization.""" +import contextlib import contextvars import dataclasses import logging @@ -9,7 +10,7 @@ from braintrust.bt_json import bt_safe_deep_copy from braintrust.integrations.utils import _materialize_attachment -from braintrust.logger import start_span +from braintrust.logger import NOOP_SPAN, _state, current_logger, current_span, parent_context, start_span from braintrust.span_types import SpanTypeAttribute from braintrust.util import clean_nones @@ -1132,6 +1133,45 @@ async def _run_async_traced_call( return result +def _current_parent_export() -> str | None: + span = current_span() + if span != NOOP_SPAN: + return span.export() + return _state.current_parent.get() + + +@contextlib.contextmanager +def _stream_span_context( + *, + parent: str | None, + name: str, + span_type: SpanTypeAttribute, + input: dict[str, Any], # pylint: disable=redefined-builtin + metadata: dict[str, Any] | None, +): + if parent is None: + logger = current_logger() + if logger is not None: + with logger._start_span_impl( # pylint: disable=protected-access + name=name, + type=span_type, + input=input, + metadata=metadata, + lookup_span_parent=False, + ) as span: + yield span + return + + token = _state.context_manager.set_current_span(None) + legacy_token = _state.current_span.set(NOOP_SPAN) + try: + with parent_context(parent), start_span(name=name, type=span_type, input=input, metadata=metadata) as span: + yield span + finally: + _state.current_span.reset(legacy_token) + _state.context_manager.unset_current_span(token) + + def _run_stream_traced_call( api_client: Any, args: list[Any], @@ -1151,34 +1191,44 @@ def _run_stream_traced_call( finalize_logged_output: Callable[[Any, dict[str, Any], dict[str, Any] | None, str], None] | None = None, ) -> Any: input, clean_kwargs = prepare_call(api_client, args, kwargs) + captured_parent = _current_parent_export() - if before_invoke is not None: - before_invoke() + def stream_generator(): + if before_invoke is not None: + before_invoke() - start = time.time() - first_token_time = None - output = None - metrics = None - metadata = None - parent_export = None - with start_span(name=name, type=span_type, input=input, metadata=clean_kwargs or None) as span: - chunks = [] - for chunk in invoke(): - if first_token_time is None and ( - first_token_predicate(chunk) if first_token_predicate is not None else True - ): - first_token_time = time.time() - chunks.append(chunk) - yield chunk - - output, metrics, metadata = _normalize_logged_result(aggregate(chunks, start, first_token_time)) - span.log(output=output, metrics=metrics, metadata=metadata) - parent_export = span.export() + start = time.time() + first_token_time = None + output = None + metrics = None + metadata = None + parent_export = None + with _stream_span_context( + parent=captured_parent, + name=name, + span_type=span_type, + input=input, + metadata=clean_kwargs or None, + ) as span: + chunks = [] + for chunk in invoke(): + if first_token_time is None and ( + first_token_predicate(chunk) if first_token_predicate is not None else True + ): + first_token_time = time.time() + chunks.append(chunk) + yield chunk - if finalize_logged_output is not None and parent_export is not None and metrics is not None: - finalize_logged_output(output, metrics, metadata, parent_export) + output, metrics, metadata = _normalize_logged_result(aggregate(chunks, start, first_token_time)) + span.log(output=output, metrics=metrics, metadata=metadata) + parent_export = span.export() - return output + if finalize_logged_output is not None and parent_export is not None and metrics is not None: + finalize_logged_output(output, metrics, metadata, parent_export) + + return output + + return stream_generator() def _run_async_stream_traced_call( @@ -1200,6 +1250,7 @@ def _run_async_stream_traced_call( finalize_logged_output: Callable[[Any, dict[str, Any], dict[str, Any] | None, str], None] | None = None, ) -> Any: input, clean_kwargs = prepare_call(api_client, args, kwargs) + captured_parent = _current_parent_export() async def stream_generator(): if before_invoke is not None: @@ -1211,7 +1262,13 @@ async def stream_generator(): metrics = None metadata = None parent_export = None - with start_span(name=name, type=span_type, input=input, metadata=clean_kwargs or None) as span: + with _stream_span_context( + parent=captured_parent, + name=name, + span_type=span_type, + input=input, + metadata=clean_kwargs or None, + ) as span: chunks = [] async for chunk in await invoke(): if first_token_time is None and (