@@ -1191,3 +1191,229 @@ def test_embed_content_span_origin(sentry_init, capture_events, mock_genai_clien
11911191 assert event ["contexts" ]["trace" ]["origin" ] == "manual"
11921192 for span in event ["spans" ]:
11931193 assert span ["origin" ] == "auto.ai.google_genai"
1194+
1195+
1196+ @pytest .mark .asyncio
1197+ @pytest .mark .parametrize (
1198+ "send_default_pii, include_prompts" ,
1199+ [
1200+ (True , True ),
1201+ (True , False ),
1202+ (False , True ),
1203+ (False , False ),
1204+ ],
1205+ )
1206+ async def test_async_embed_content (
1207+ sentry_init , capture_events , send_default_pii , include_prompts , mock_genai_client
1208+ ):
1209+ """Test async embed_content method."""
1210+ sentry_init (
1211+ integrations = [GoogleGenAIIntegration (include_prompts = include_prompts )],
1212+ traces_sample_rate = 1.0 ,
1213+ send_default_pii = send_default_pii ,
1214+ )
1215+ events = capture_events ()
1216+
1217+ # Mock the async HTTP response
1218+ mock_http_response = create_mock_http_response (EXAMPLE_EMBED_RESPONSE_JSON )
1219+
1220+ with mock .patch .object (
1221+ mock_genai_client ._api_client ,
1222+ "async_request" ,
1223+ return_value = mock_http_response ,
1224+ ):
1225+ with start_transaction (name = "google_genai_embeddings_async" ):
1226+ await mock_genai_client .aio .models .embed_content (
1227+ model = "text-embedding-004" ,
1228+ contents = [
1229+ "What is your name?" ,
1230+ "What is your favorite color?" ,
1231+ ],
1232+ )
1233+
1234+ assert len (events ) == 1
1235+ (event ,) = events
1236+
1237+ assert event ["type" ] == "transaction"
1238+ assert event ["transaction" ] == "google_genai_embeddings_async"
1239+
1240+ # Should have 1 span for embeddings
1241+ assert len (event ["spans" ]) == 1
1242+ (embed_span ,) = event ["spans" ]
1243+
1244+ # Check embeddings span
1245+ assert embed_span ["op" ] == OP .GEN_AI_EMBEDDINGS
1246+ assert embed_span ["description" ] == "embeddings text-embedding-004"
1247+ assert embed_span ["data" ][SPANDATA .GEN_AI_OPERATION_NAME ] == "embeddings"
1248+ assert embed_span ["data" ][SPANDATA .GEN_AI_SYSTEM ] == "gcp.gemini"
1249+ assert embed_span ["data" ][SPANDATA .GEN_AI_REQUEST_MODEL ] == "text-embedding-004"
1250+
1251+ # Check input texts if PII is allowed
1252+ if send_default_pii and include_prompts :
1253+ input_texts = json .loads (embed_span ["data" ][SPANDATA .GEN_AI_EMBEDDINGS_INPUT ])
1254+ assert input_texts == [
1255+ "What is your name?" ,
1256+ "What is your favorite color?" ,
1257+ ]
1258+ else :
1259+ assert SPANDATA .GEN_AI_EMBEDDINGS_INPUT not in embed_span ["data" ]
1260+
1261+ # Check usage data (sum of token counts from statistics: 10 + 15 = 25)
1262+ # Note: Only available in newer versions with ContentEmbeddingStatistics
1263+ if SPANDATA .GEN_AI_USAGE_INPUT_TOKENS in embed_span ["data" ]:
1264+ assert embed_span ["data" ][SPANDATA .GEN_AI_USAGE_INPUT_TOKENS ] == 25
1265+
1266+
1267+ @pytest .mark .asyncio
1268+ async def test_async_embed_content_string_input (
1269+ sentry_init , capture_events , mock_genai_client
1270+ ):
1271+ """Test async embed_content with a single string instead of list."""
1272+ sentry_init (
1273+ integrations = [GoogleGenAIIntegration (include_prompts = True )],
1274+ traces_sample_rate = 1.0 ,
1275+ send_default_pii = True ,
1276+ )
1277+ events = capture_events ()
1278+
1279+ # Mock response with single embedding
1280+ single_embed_response = {
1281+ "embeddings" : [
1282+ {
1283+ "values" : [0.1 , 0.2 , 0.3 ],
1284+ "statistics" : {
1285+ "tokenCount" : 5 ,
1286+ "truncated" : False ,
1287+ },
1288+ },
1289+ ],
1290+ "metadata" : {
1291+ "billableCharacterCount" : 10 ,
1292+ },
1293+ }
1294+ mock_http_response = create_mock_http_response (single_embed_response )
1295+
1296+ with mock .patch .object (
1297+ mock_genai_client ._api_client , "async_request" , return_value = mock_http_response
1298+ ):
1299+ with start_transaction (name = "google_genai_embeddings_async" ):
1300+ await mock_genai_client .aio .models .embed_content (
1301+ model = "text-embedding-004" ,
1302+ contents = "Single text input" ,
1303+ )
1304+
1305+ (event ,) = events
1306+ (embed_span ,) = event ["spans" ]
1307+
1308+ # Check that single string is handled correctly
1309+ input_texts = json .loads (embed_span ["data" ][SPANDATA .GEN_AI_EMBEDDINGS_INPUT ])
1310+ assert input_texts == ["Single text input" ]
1311+ # Should use token_count from statistics (5), not billable_character_count (10)
1312+ # Note: Only available in newer versions with ContentEmbeddingStatistics
1313+ if SPANDATA .GEN_AI_USAGE_INPUT_TOKENS in embed_span ["data" ]:
1314+ assert embed_span ["data" ][SPANDATA .GEN_AI_USAGE_INPUT_TOKENS ] == 5
1315+
1316+
1317+ @pytest .mark .asyncio
1318+ async def test_async_embed_content_error_handling (
1319+ sentry_init , capture_events , mock_genai_client
1320+ ):
1321+ """Test error handling in async embed_content."""
1322+ sentry_init (
1323+ integrations = [GoogleGenAIIntegration ()],
1324+ traces_sample_rate = 1.0 ,
1325+ )
1326+ events = capture_events ()
1327+
1328+ # Mock an error at the HTTP level
1329+ with mock .patch .object (
1330+ mock_genai_client ._api_client ,
1331+ "async_request" ,
1332+ side_effect = Exception ("Async Embedding API Error" ),
1333+ ):
1334+ with start_transaction (name = "google_genai_embeddings_async" ):
1335+ with pytest .raises (Exception , match = "Async Embedding API Error" ):
1336+ await mock_genai_client .aio .models .embed_content (
1337+ model = "text-embedding-004" ,
1338+ contents = ["This will fail" ],
1339+ )
1340+
1341+ # Should have both transaction and error events
1342+ assert len (events ) == 2
1343+ error_event , _ = events
1344+
1345+ assert error_event ["level" ] == "error"
1346+ assert error_event ["exception" ]["values" ][0 ]["type" ] == "Exception"
1347+ assert error_event ["exception" ]["values" ][0 ]["value" ] == "Async Embedding API Error"
1348+ assert error_event ["exception" ]["values" ][0 ]["mechanism" ]["type" ] == "google_genai"
1349+
1350+
1351+ @pytest .mark .asyncio
1352+ async def test_async_embed_content_without_statistics (
1353+ sentry_init , capture_events , mock_genai_client
1354+ ):
1355+ """Test async embed_content response without statistics (older package versions)."""
1356+ sentry_init (
1357+ integrations = [GoogleGenAIIntegration ()],
1358+ traces_sample_rate = 1.0 ,
1359+ )
1360+ events = capture_events ()
1361+
1362+ # Response without statistics (typical for older google-genai versions)
1363+ # Embeddings exist but don't have the statistics field
1364+ old_version_response = {
1365+ "embeddings" : [
1366+ {
1367+ "values" : [0.1 , 0.2 , 0.3 ],
1368+ },
1369+ {
1370+ "values" : [0.2 , 0.3 , 0.4 ],
1371+ },
1372+ ],
1373+ }
1374+ mock_http_response = create_mock_http_response (old_version_response )
1375+
1376+ with mock .patch .object (
1377+ mock_genai_client ._api_client , "async_request" , return_value = mock_http_response
1378+ ):
1379+ with start_transaction (name = "google_genai_embeddings_async" ):
1380+ await mock_genai_client .aio .models .embed_content (
1381+ model = "text-embedding-004" ,
1382+ contents = ["Test without statistics" , "Another test" ],
1383+ )
1384+
1385+ (event ,) = events
1386+ (embed_span ,) = event ["spans" ]
1387+
1388+ # No usage tokens since there are no statistics in older versions
1389+ # This is expected and the integration should handle it gracefully
1390+ assert SPANDATA .GEN_AI_USAGE_INPUT_TOKENS not in embed_span ["data" ]
1391+
1392+
1393+ @pytest .mark .asyncio
1394+ async def test_async_embed_content_span_origin (
1395+ sentry_init , capture_events , mock_genai_client
1396+ ):
1397+ """Test that async embed_content spans have correct origin."""
1398+ sentry_init (
1399+ integrations = [GoogleGenAIIntegration ()],
1400+ traces_sample_rate = 1.0 ,
1401+ )
1402+ events = capture_events ()
1403+
1404+ mock_http_response = create_mock_http_response (EXAMPLE_EMBED_RESPONSE_JSON )
1405+
1406+ with mock .patch .object (
1407+ mock_genai_client ._api_client , "async_request" , return_value = mock_http_response
1408+ ):
1409+ with start_transaction (name = "google_genai_embeddings_async" ):
1410+ await mock_genai_client .aio .models .embed_content (
1411+ model = "text-embedding-004" ,
1412+ contents = ["Test origin" ],
1413+ )
1414+
1415+ (event ,) = events
1416+
1417+ assert event ["contexts" ]["trace" ]["origin" ] == "manual"
1418+ for span in event ["spans" ]:
1419+ assert span ["origin" ] == "auto.ai.google_genai"
0 commit comments