Skip to content

fix(ai): capture Gemini tool history in generation input - #5088

Merged
marandaneto merged 4 commits into
PostHog:mainfrom
gouveags:fix/ai-gemini-tool-history
Sep 30, 2026
Merged

marandaneto merged 4 commits into
PostHog:mainfrom
gouveags:fix/ai-gemini-tool-history

Conversation

@gouveags

@gouveags gouveags commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Problem

On a Gemini follow-up request, the Google SDK sends the model's functionCall and the matching functionResponse, but @posthog/ai captures both messages in $ai_input with empty content. This is the input-history half of #5085; #5087 addresses the output IDs.

Changes

  • Capture Gemini function calls as the existing normalized function block, including the ID when available.
  • Capture function responses as tool_result blocks linked by tool_use_id, following the Claude Agent SDK convention already in this package.
  • Preserve JSON responses, media-only parts, and responses containing both.
  • Bound each captured tool result to 5,000 UTF-8 bytes by default. Small results stay structured; oversized results become marked truncated text. Explicit full AI capture bypasses this limit. No public SDK types, provider requests, or provider responses change.
  • Add an @posthog/ai patch changeset.

Tests were committed first and failed on the previous formatter: six focused unit cases and both real-SDK local recorder/replay cases reproduced empty captured content; two parts-only unit cases reproduced missing result content. They pass after the fix. A review follow-up also reproduced oversized results in both modes before adding the cap. Final validation: 1,040 package unit tests, 279 offline cassette tests, build, type check, package lint, and formatting. The recorder/replay tests use the real Google SDK with a local server, not a live-provider second turn.

Tool-result JSON and media-part metadata will now be captured when privacy mode is off. The existing binary redactor still redacts inline base64, and posthogPrivacyMode still suppresses captured input/output entirely. It does not redact arbitrary URLs in tool results. The size limit is per result, not a total conversation-history limit.

The automated draft #5086 overlaps #5085. This PR keeps the input change separate from #5087 and uses a private formatter type rather than adding a public exported type.

Release info Sub-libraries affected

Libraries affected

  • @posthog/ai

Checklist

  • Tests for new code
  • Accounted for the impact of any changes across different platforms
  • Accounted for backwards compatibility of any changes (no breaking changes!)
  • Took care not to unnecessarily increase the bundle size
  • Added an @posthog/ai patch changeset

🤖 Agent context

Autonomy: Human-driven (agent-assisted)

I directed the scope and reviewed the implementation with Codex. We used Vitest, the existing Gemini recorder/replay harness, and GitHub CLI. Each change was committed as a failing test followed by its fix; all four commits are signed. No agent session link is shared because this private work session also includes unrelated workspace context. GitHub would not allow assigning my external account to this PR.

@gouveags
gouveags requested a review from a team as a code owner September 24, 2026 05:18
@gouveags

Copy link
Copy Markdown
Contributor Author

@greptileai review

@greptile-apps

greptile-apps Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Retrigger

The PR appears safe to merge; the previous unbounded tool-result capture finding is addressed.

Reviews (2) · Last reviewed commit: "fix(ai): bound captured Gemini tool resu..."

Comment thread packages/ai/src/gemini/index.ts Outdated
@gouveags

Copy link
Copy Markdown
Contributor Author

@greptileai review

@marandaneto
marandaneto requested a review from a team September 24, 2026 09:50

@marandaneto marandaneto left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code looks good; no qualifying findings. Verdict: correct.

@marandaneto

Copy link
Copy Markdown
Member

Comparison with the other AI integrations:

  • This brings Gemini tool history capture into line functionally: calls, arguments, and results supplied in the request are included in $ai_input.
  • The normalized function and tool_result blocks match the Claude Agent integration, with linking IDs when Gemini supplies them. OpenAI and direct Anthropic retain more provider-native input shapes.
  • Privacy behavior uses the shared generation-capture pipeline: posthogPrivacyMode: true suppresses input/output, including tool history, even when full AI capture is enabled.
  • Binary redaction uses the shared redactor. Arbitrary JSON values and URLs are not automatically scrubbed. Full AI capture bypasses binary redaction and truncation, but not privacy mode.
  • The main policy difference is truncation: Gemini bounds each whole serialized tool result to 5,000 UTF-8 bytes plus the truncation marker, converting oversized results to text. Claude Agent recursively caps each individual string at 5,000 bytes while preserving objects/arrays, so many small fields can still produce a large result.

The whole-result bound here is stronger against oversized tool results; identical truncation behavior across providers is not required for this fix.

@marandaneto

Copy link
Copy Markdown
Member

suggestion: Non-blocking follow-up — standardize tool-result capture policy across the AI adapters.

Keep the stronger whole-result bound in this PR. Separately, agree on a shared policy for per-result byte limits, whether the truncation marker counts toward the limit, preserving structured content versus falling back to truncated text, and how any conversation-wide budget should work. OpenAI, direct Anthropic, Claude Agent, and Gemini do not currently have one uniform formatting/truncation contract.

Once agreed, extract shared internal formatting utilities with a consistent JSON-safety → binary-redaction → size-bounding order, while retaining provider-specific message shapes and IDs. Add cross-adapter contract tests for nested results, many small fields, multibyte text, binary content, full-capture bypass, and privacy-mode suppression. Full capture should continue to bypass redaction/size limits without overriding privacy mode.

This is a separate consistency/resource-safety improvement, not a blocker for merging this PR.

@marandaneto

Copy link
Copy Markdown
Member

@PostHog/team-ai-observability looks good to me but worth checking since i never used gemini

@gouveags

Copy link
Copy Markdown
Contributor Author

Hey @marandaneto, thanks for the review! Is there anything else you need from me to get this PR merged?

@marandaneto
marandaneto merged commit 882606d into PostHog:main Sep 30, 2026
64 of 67 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants