Repository navigation
fix(ai): preserve Gemini tool call IDs in captured generations - #1150
Merged
Merged
Conversation
Gemini returns IDs on function calls and function responses, but the captured $ai_output_choices and $ai_input dropped them, so tool results could not be matched to their calls. Keep the call ID on output (streaming and non-streaming) and on input history calls, and the result's ID as tool_use_id, matching PostHog/posthog-js#5087.
marandaneto
approved these changes
Oct 30, 2026
marandaneto
left a comment
Member
There was a problem hiding this comment.
The code looks good; no qualifying findings. Verdict: correct.
marandaneto
enabled auto-merge (squash)
October 30, 2026 13:11
Member
|
thanks @mosafariuk |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
💡 Motivation and Context
Gemini returns an
idon function calls (FunctionCall.id) and on function responses (FunctionResponse.id). The Python SDK drops both from captured generations:$ai_output_choices: the non-streaming formatter (format_gemini_response), the per-chunk extractor and the streaming assembler (format_gemini_streaming_output) all build the function block without the ID.$ai_input:_format_partnormalizesfunction_callandfunction_responseparts without their IDs.So a Gemini tool result can't be matched to the call that produced it, unlike OpenAI and Anthropic captures, which keep tool-call IDs.
This is the Python counterpart of PostHog/posthog-js#5087 (output IDs) and the ID handling in its Gemini input normalization. It uses the same shape:
idon the function block, andtool_use_idon a function result. The ID is added only when Gemini returned one as a string, so calls without an ID keep their current shape.💚 How did you test it?
posthog/test/ai/gemini/test_gemini.py, built from realgoogle.genai.typesobjects:test_function_call_ids_in_output_choices: parametrized over streaming and non-streaming, and over an ID versus no ID. The cases with an ID fail onmainand pass with this change. The no-ID cases pass either way, which shows the existing shape is unchanged.test_function_call_ids_in_input_history: afunction_calland afunction_responsewith the same ID incontents. The test assertsidandtool_use_idin$ai_input. It fails onmainand passes with this change.MagicMockparts pass unchanged. A mocked part answers any attribute, so the ID is only kept when it's a realstr.test_metrics_autocapture.py, which also fail for me on unmodifiedmainon macOS and are unrelated.ruff format --check,ruff check,mypy | mypy-baseline filterandmake public_api_checkare clean (no public API change).Models.generate_contentandgenerate_content_streamto return a realGenerateContentResponsecontainingFunctionCall(id="call_649034", ...). Onmain, both paths capture the call without its ID. With this change, both include"id": "call_649034". The async client uses the same converter functions.📝 Checklist
If releasing new changes
sampo addto generate a changeset file🤖 Agent context
Autonomy: Human-driven (agent-assisted)
@posthog/aifixes with the Python SDK, then drafted the fix and tests. I reviewed and ran all of it.idon calls,tool_use_idon results). I typed the new blocks asDict[str, Any]instead of importingFormattedFunctionCallinto the converter, so the public API snapshot doesn't change.