Skip to content

Commit 5c9b6fb

Browse files
committed
fix(llma): fix Gemini
1 parent 73ab99a commit 5c9b6fb

2 files changed

Lines changed: 144 additions & 2 deletions

File tree

‎posthog/ai/gemini/gemini_converter.py‎

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -343,7 +343,7 @@ def extract_gemini_web_search_count(response: Any) -> int:
343343
Extract web search count from Gemini response.
344344
345345
Gemini bills per request that uses grounding, not per query.
346-
Returns 1 if grounding_metadata is present, 0 otherwise.
346+
Returns 1 if grounding_metadata is present with actual search data, 0 otherwise.
347347
348348
Args:
349349
response: The response from Gemini API
@@ -359,7 +359,21 @@ def extract_gemini_web_search_count(response: Any) -> int:
359359
hasattr(candidate, "grounding_metadata")
360360
and candidate.grounding_metadata
361361
):
362-
return 1
362+
grounding_metadata = candidate.grounding_metadata
363+
364+
# Check if web_search_queries exists and is non-empty
365+
if hasattr(grounding_metadata, "web_search_queries"):
366+
queries = grounding_metadata.web_search_queries
367+
368+
if queries is not None and len(queries) > 0:
369+
return 1
370+
371+
# Check if grounding_chunks exists and is non-empty
372+
if hasattr(grounding_metadata, "grounding_chunks"):
373+
chunks = grounding_metadata.grounding_chunks
374+
375+
if chunks is not None and len(chunks) > 0:
376+
return 1
363377

364378
# Also check for google_search or grounding in function call names
365379
if hasattr(candidate, "content") and candidate.content:
@@ -369,6 +383,7 @@ def extract_gemini_web_search_count(response: Any) -> int:
369383
function_name = getattr(
370384
part.function_call, "name", ""
371385
).lower()
386+
372387
if (
373388
"google_search" in function_name
374389
or "grounding" in function_name

‎posthog/test/ai/gemini/test_gemini.py‎

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1114,3 +1114,130 @@ async def mock_async_streaming_response():
11141114
assert props["$ai_web_search_count"] == 1
11151115
assert props["$ai_input_tokens"] == 30
11161116
assert props["$ai_output_tokens"] == 15
1117+
1118+
1119+
def test_empty_grounding_metadata_no_web_search(mock_client, mock_google_genai_client):
1120+
"""Test that empty grounding_metadata (all null fields) does not count as web search."""
1121+
1122+
# Create mock response with empty grounding metadata (all null fields)
1123+
mock_response = MagicMock()
1124+
1125+
# Mock usage metadata
1126+
mock_usage = MagicMock()
1127+
mock_usage.prompt_token_count = 10
1128+
mock_usage.candidates_token_count = 10
1129+
mock_usage.cached_content_token_count = 0
1130+
mock_usage.thoughts_token_count = 0
1131+
mock_response.usage_metadata = mock_usage
1132+
1133+
# Mock empty grounding metadata (all fields are None)
1134+
mock_grounding_metadata = MagicMock()
1135+
mock_grounding_metadata.web_search_queries = None
1136+
mock_grounding_metadata.grounding_chunks = None
1137+
mock_grounding_metadata.grounding_supports = None
1138+
mock_grounding_metadata.retrieval_metadata = None
1139+
mock_grounding_metadata.retrieval_queries = None
1140+
mock_grounding_metadata.search_entry_point = None
1141+
1142+
# Mock text part
1143+
mock_text_part = MagicMock()
1144+
mock_text_part.text = "Hey there! How can I help you today?"
1145+
type(mock_text_part).text = mock_text_part.text
1146+
1147+
# Mock content with parts
1148+
mock_content = MagicMock()
1149+
mock_content.parts = [mock_text_part]
1150+
1151+
# Mock candidate with empty grounding metadata
1152+
mock_candidate = MagicMock()
1153+
mock_candidate.content = mock_content
1154+
mock_candidate.grounding_metadata = mock_grounding_metadata
1155+
type(mock_candidate).grounding_metadata = mock_candidate.grounding_metadata
1156+
1157+
mock_response.candidates = [mock_candidate]
1158+
mock_response.text = "Hey there! How can I help you today?"
1159+
1160+
# Mock the generate_content method
1161+
mock_google_genai_client.models.generate_content.return_value = mock_response
1162+
1163+
client = Client(api_key="test-key", posthog_client=mock_client)
1164+
1165+
response = client.models.generate_content(
1166+
model="gemini-2.5-flash",
1167+
contents="Hello",
1168+
posthog_distinct_id="test-id",
1169+
)
1170+
1171+
assert response == mock_response
1172+
assert mock_client.capture.call_count == 1
1173+
1174+
call_args = mock_client.capture.call_args[1]
1175+
props = call_args["properties"]
1176+
1177+
# Verify web search count is 0 (not present in properties when 0)
1178+
assert "$ai_web_search_count" not in props
1179+
assert props["$ai_input_tokens"] == 10
1180+
assert props["$ai_output_tokens"] == 10
1181+
1182+
1183+
def test_empty_array_grounding_metadata_no_web_search(
1184+
mock_client, mock_google_genai_client
1185+
):
1186+
"""Test that grounding_metadata with empty arrays does not count as web search."""
1187+
1188+
# Create mock response with grounding metadata having empty arrays
1189+
mock_response = MagicMock()
1190+
1191+
# Mock usage metadata
1192+
mock_usage = MagicMock()
1193+
mock_usage.prompt_token_count = 15
1194+
mock_usage.candidates_token_count = 12
1195+
mock_usage.cached_content_token_count = 0
1196+
mock_usage.thoughts_token_count = 0
1197+
mock_response.usage_metadata = mock_usage
1198+
1199+
# Mock grounding metadata with empty arrays
1200+
mock_grounding_metadata = MagicMock()
1201+
mock_grounding_metadata.web_search_queries = []
1202+
mock_grounding_metadata.grounding_chunks = []
1203+
mock_grounding_metadata.grounding_supports = []
1204+
1205+
# Mock text part
1206+
mock_text_part = MagicMock()
1207+
mock_text_part.text = "I can help with that."
1208+
type(mock_text_part).text = mock_text_part.text
1209+
1210+
# Mock content with parts
1211+
mock_content = MagicMock()
1212+
mock_content.parts = [mock_text_part]
1213+
1214+
# Mock candidate with grounding metadata containing empty arrays
1215+
mock_candidate = MagicMock()
1216+
mock_candidate.content = mock_content
1217+
mock_candidate.grounding_metadata = mock_grounding_metadata
1218+
type(mock_candidate).grounding_metadata = mock_candidate.grounding_metadata
1219+
1220+
mock_response.candidates = [mock_candidate]
1221+
mock_response.text = "I can help with that."
1222+
1223+
# Mock the generate_content method
1224+
mock_google_genai_client.models.generate_content.return_value = mock_response
1225+
1226+
client = Client(api_key="test-key", posthog_client=mock_client)
1227+
1228+
response = client.models.generate_content(
1229+
model="gemini-2.5-flash",
1230+
contents="What can you do?",
1231+
posthog_distinct_id="test-id",
1232+
)
1233+
1234+
assert response == mock_response
1235+
assert mock_client.capture.call_count == 1
1236+
1237+
call_args = mock_client.capture.call_args[1]
1238+
props = call_args["properties"]
1239+
1240+
# Verify web search count is 0 (not present in properties when 0)
1241+
assert "$ai_web_search_count" not in props
1242+
assert props["$ai_input_tokens"] == 15
1243+
assert props["$ai_output_tokens"] == 12

0 commit comments

Comments
 (0)