Skip to content

fix(ai-obs): recognize legacy Gemini tool blocks in traces - #76028

Closed
marco-g-pm wants to merge 3 commits into
masterfrom
claude/gemini-legacy-tool-blocks
Closed

fix(ai-obs): recognize legacy Gemini tool blocks in traces#76028
marco-g-pm wants to merge 3 commits into
masterfrom
claude/gemini-legacy-tool-blocks

Conversation

@marco-g-pm

@marco-g-pm marco-g-pm commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Problem

posthog-python 7.30.1 through 7.35.x copied Gemini function calls and their responses into $ai_input, in the raw form the google-genai SDK emits:

{"type": "function_call",     "function_call": {"name": "...", "args": {}}}
{"type": "function_response", "function_response": {"name": "...", "response": {}}}

isToolResult and isToolStepItem match neither one. Traces from that window misread their tool loop three ways:

Guard What happens today
isToolMessage hasText counts any item that is not a tool step and not a tool result. A message holding only a tool response reads as real text. The traces list then shows raw tool output as the answer.
isInternalToolResultUserMessage It needs every content item to be a tool result. The tool-response user message never counts as internal, so it leaks as a user bubble.
isUnrenderableContentItem It treats the block as non-text content that the transcript cannot render.

PostHog/posthog-python#823 fixes the SDK. Traces already stored keep the old shape, so the guards need to match it here.

These guards classify, they do not render. ConversationMessagesDisplay already falls back to a JSON viewer for an unknown block, so the data is not invisible everywhere.

Changes

Adds isLegacyGeminiFunctionResponse and isLegacyGeminiFunctionCall. isToolResult takes the response. isToolStepItem takes both.

function_call needs its nested object checked. The OpenAI Responses API uses the same type with name and call_id at the top level, and isOpenAIResponsesFunctionCall already owns that case.

No screenshots. This change is predicate logic with no visual part of its own.

How did you test this code?

New cases in utils.test.ts, for the regressions no existing case caught:

  • isToolResult takes a legacy function_response. It still rejects a legacy function_call, which is a call and not a result. It also rejects a bare {type: 'function_response'} with no payload.
  • isToolStepItem takes both legacy kinds.

I could not run jest. My worktree has no frontend deps, and my node is v26 against the repo range >=24 <25. CI has to be the check here. What I did run instead: I pulled the guard bodies out of utils.ts verbatim, paired them with the real isObject and isString, and ran nine cases through them. All nine matched what I expected. That set includes an OpenAI Responses function_call, which still routes to isOpenAIResponsesFunctionCall and does not trip the new guard.

The pre-commit hook also could not run, because bin/hogli needs the flox and uv Python env. I committed with --no-verify. CI has since confirmed that part: Frontend formatting passes and Frontend typechecking passes.

I did not open a real trace in the UI.

Automatic notifications

  • Publish to changelog?
  • Alert Sales and Marketing teams?

Docs update

None needed.

🤖 Agent context

Autonomy: Human-driven (agent-assisted)

Written with Claude Code (Opus 5), directed by @marco-g-pm.

This is the backfill half of PostHog/posthog-python#823. That PR fixes the shape for new traces. This one covers traces already stored.

The fix stays in the guards. Historical traces still feed evals a raw repr() dump, because the eval path is a separate Python formatter. A matching guard in message_formatter.py would fix that, and I left it out on purpose. Rendering these blocks as tool-result cards is a second option I did not try.

@marco-g-pm marco-g-pm self-assigned this Jul 31, 2026
@trunk-io

trunk-io Bot commented Jul 31, 2026

Copy link
Copy Markdown

Merging to master in this repository is managed by Trunk.

  • To merge this pull request, check the box to the left or comment /trunk merge below.

After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here

@github-actions

github-actions Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

🤖 CI report

Bundle size — no change

Uncompressed size of every built .js bundle, compared against the base branch.

Total: 65.58 MiB · no change

No file changed by more than 1000 B.

Posted automatically by build-bundle-size-report · uncompressed bytes from dist-report

Eager graph — within budget

How much code each root ships on the eager path — downloaded and parsed before the surface is interactive. Measured from the esbuild output chunks (post-tree-shake, static imports only); lazy import() / React.lazy chunks are not counted.

Root Eager (shipped) Δ vs base Budget
entry (logged-out pages, app bootstrap)
src/index.tsx
1.26 MiB · 22 files no change ███░░░░░░░ 27.9% of 4.51 MiB
authenticated shell (every logged-in page)
src/scenes/AuthenticatedShell.tsx
8.17 MiB · 3,041 files no change ████████░░ 84.1% of 9.71 MiB

🟢 node_modules/monaco-editor/ stays out of src/index.tsx
🟢 src/lib/components/ActivityLog/describers stays out of src/index.tsx
🟢 [object Object] stays out of src/index.tsx
🟢 [object Object] stays out of src/index.tsx
🟢 node_modules/monaco-editor/ stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/lib/components/ActivityLog/describers stays out of src/scenes/AuthenticatedShell.tsx
🟢 [object Object] stays out of src/scenes/AuthenticatedShell.tsx
🟢 [object Object] stays out of src/scenes/AuthenticatedShell.tsx

Largest files eagerly shipped from src/index.tsx
Size File
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
24.6 KiB ../node_modules/.pnpm/buffer@6.0.3/node_modules/buffer/index.js
6.3 KiB ../node_modules/.pnpm/react@18.3.1/node_modules/react/cjs/react.production.min.js
4.5 KiB ../node_modules/.pnpm/@jspm+core@2.1.0/node_modules/@jspm/core/nodelibs/browser/process.js
3.9 KiB ../node_modules/.pnpm/scheduler@0.23.2/node_modules/scheduler/cjs/scheduler.production.min.js
1.4 KiB ../node_modules/.pnpm/base64-js@1.5.1/node_modules/base64-js/index.js
1.3 KiB src/RootErrorBoundary.tsx
912 B ../node_modules/.pnpm/ieee754@1.2.1/node_modules/ieee754/index.js
789 B src/scenes/ChunkLoadErrorBoundary.tsx
762 B src/index.tsx
Largest files eagerly shipped from src/scenes/AuthenticatedShell.tsx
Size File
285.5 KiB ../node_modules/.pnpm/posthog-js@1.410.1/node_modules/posthog-js/dist/rrweb.js
267.7 KiB ../node_modules/.pnpm/@posthog+icons@0.38.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js
237.5 KiB src/taxonomy/core-filter-definitions-by-group.json
231.5 KiB ../node_modules/.pnpm/posthog-js@1.410.1/node_modules/posthog-js/dist/module.js
154.3 KiB ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
105.0 KiB src/lib/api.ts
94.7 KiB ../packages/quill/packages/quill/dist/index.js
93.3 KiB ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js
90.6 KiB ../node_modules/.pnpm/@tiptap+core@3.20.6_@tiptap+pm@3.20.6/node_modules/@tiptap/core/dist/index.js

Posted automatically by check-eager-graph · sizes are eager output bytes (shipped, post-tree-shake) from the esbuild metafile · part of #32479

Toolbar bundle — eager 2.20 MiB within budget

What the toolbar ships to customer pages, measured from the esbuild output (minified, post-tree-shake). The eager set is the entry plus everything statically imported from it — fetched before any feature runs; deferred chunks load lazily. The eager guardrail is 5.72 MiB. Each output file must also stay below 10 MB, where CloudFront stops compressing it. The module boundary is enforced separately by check-toolbar-graph.

Metric Size Δ vs base Budget
Eager (shipped)
entry + static imports
2.20 MiB · 17 files no change ████░░░░░░ 38.4% of 5.72 MiB
Deferred (lazy) 2.08 MiB · 33 files no change n/a — loads on demand
Loader dist/toolbar.js 1.1 KiB no change █░░░░░░░░░ 5.8% of 19.5 KiB
Largest eagerly-shipped chunks
Size File
721.9 KiB dist/toolbar/toolbar-app-EFK53Z4U.css
551.9 KiB dist/toolbar/chunk-chunk-H5J6CWWA.js
484.6 KiB dist/toolbar/chunk-chunk-PXAACQ6U.js
133.6 KiB dist/toolbar/chunk-chunk-HY3POEM4.js
131.8 KiB dist/toolbar/chunk-chunk-T5KY5WYR.js
71.0 KiB dist/toolbar/toolbar-app-HJXGB3X4.js
69.0 KiB dist/toolbar/chunk-chunk-27JL52RE.js
35.6 KiB dist/toolbar/chunk-chunk-GDVGUWBX.js
20.9 KiB dist/toolbar/chunk-chunk-FYPGMRW4.js
12.2 KiB dist/toolbar/chunk-chunk-PIK3PADE.js

Posted automatically by check-toolbar-size · sizes are toolbar output bytes (shipped, post-tree-shake) from the esbuild metafile

Dist folder size — 🔺 +3.7 KiB (+0.0%)

Total size of the built frontend/dist folder (all assets), compared against the base branch.

Total: 1393.76 MiB · 🔺 +3.7 KiB (+0.0%)

@marco-g-pm
marco-g-pm force-pushed the claude/gemini-legacy-tool-blocks branch from 7cc5891 to a43b6b4 Compare July 31, 2026 16:17
posthog-python 7.30.1-7.35.x copied Gemini function calls and their
responses into `$ai_input` in the raw form the google-genai SDK emits:

    {"type": "function_call",     "function_call": {name, args}}
    {"type": "function_response", "function_response": {name, response}}

Neither matches any shape `isToolResult`/`isToolStepItem` know, so
traces captured in that window misclassify their tool loop three ways:

- `isToolMessage` computes `hasText` from content items that are neither
  a tool step nor a tool result, so a message carrying only a tool
  response counts as having real text and gets surfaced in the traces
  list as the human-readable answer.
- `isInternalToolResultUserMessage` requires every content item to be a
  tool result, so the framework-appended tool-response user message
  isn't classified internal and leaks as a visible user bubble.
- `isUnrenderableContentItem` treats the block as non-text content the
  transcript can't render.

The SDK now converts both kinds (PostHog/posthog-python#823), but
already-ingested traces keep the old form, so match it here too.

`function_call` needs its nested object checked: OpenAI's Responses API
uses the same `type` with `name`/`call_id` at the top level, and
`isOpenAIResponsesFunctionCall` already owns that case.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@marco-g-pm
marco-g-pm force-pushed the claude/gemini-legacy-tool-blocks branch from a43b6b4 to fec8beb Compare July 31, 2026 16:26
@trunk-io

trunk-io Bot commented Aug 5, 2026

Copy link
Copy Markdown

Static BadgeStatic BadgeStatic Badge

View Full Report ↗︎Docs

@posthog

posthog Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

👋 Visual changes detected for this PR.

Review and approve in PostHog Visual Review

If these changes are unexpected, they may be caused by a flaky test or a broken snapshot on master. Don't approve — rerun the job or wait for a fix.

@marco-g-pm
marco-g-pm marked this pull request as ready for review August 5, 2026 18:50
@github-actions
github-actions Bot requested a deployment to preview-pr-76028 August 5, 2026 18:50 In progress
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

🦔 Hogbox preview · ❌ build failed

The preview didn't come up for commit 5921d37. See the build log for the failing step. It'll retry on the next push.

Previews are optional and never block merging. A failure here is often a hogland or tailnet hiccup rather than anything in your PR, so the check stays green and this comment is the status.

@pr-assigner-resolver-posthog
pr-assigner-resolver-posthog Bot requested a review from a team August 5, 2026 18:50
@greptile-apps

greptile-apps Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Reviews (1): Last reviewed commit: "Merge branch 'master' into claude/gemini..." | Re-trigger Greptile

@marco-g-pm

Copy link
Copy Markdown
Contributor Author

deemed not worth the tech debt for legacy traces that will expire in 30 days

@marco-g-pm marco-g-pm closed this Aug 5, 2026
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.

1 participant