fix(server-utils): Restore request config attributes on google-genai chat spans - #23316
Conversation
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 0f1c434. Configure here.
…ssage spans Removing the chats.create() span dropped the config it captured (temperature, top_p, top_k, max_tokens, frequency_penalty, presence_penalty, available_tools and system_instructions). That config is set once on the chat instance and reused for every chat.sendMessage() and chat.sendMessageStream() call, so those spans lost it and the trace no longer showed the chat configuration. Capture the params at chats.create() time and weld model plus config onto each message span. The create-time config is the default and a per-message config overrides it key by key. The create history is left off the message spans. Fixes getsentry#20086 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…on chat spans @google/genai resolves each request's config as `params.config ?? chat.config`, so a per-message config given to chat.sendMessage() or chat.sendMessageStream() replaces the create-time config wholesale rather than merging with it. The previous shallow merge welded create-time fields such as systemInstruction, tools and sampling settings onto message spans that did not actually send them, over-reporting what the request carried. Fall back to the create-time config only when the message carries no config of its own. Lock the corrected attributes in the per-message config test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
35019b0 to
ab1b14d
Compare
|
👋 @logaretm, @nicohrubec — Please review this PR when you get a chance! |
3 similar comments
|
👋 @logaretm, @nicohrubec — Please review this PR when you get a chance! |
|
👋 @logaretm, @nicohrubec — Please review this PR when you get a chance! |
|
👋 @logaretm, @nicohrubec — Please review this PR when you get a chance! |
Two conflicts, both in the google-genai instrumentation that develop moved under us. packages/server-utils/src/ai/google-genai/index.ts: develop added low-cardinality span names when span streaming is on (getsentry#23573) and dropped the captureException calls from the instrumentation (getsentry#23024), touching the same lines where this branch threads the chat create-time params through. Kept both: the merged apply() computes attributeParams via mergeChatCreateParams, feeds those to extractRequestAttributes plus addPrivateRequestAttributes, then derives model with develop's || 'unknown' and its spanName sentinel logic. packages/server-utils/test/ai/lib/tracing/google-genai.test.ts: add/add, develop landed a span-names suite at the same path this branch used for the config suite. Took develop's file whole and re-added the config suite beside it, hoisting the shared setupClient to module scope and switching the config assertions from spanToJSON to spanToStaticSpanJSON, since spanToJSON now returns the streamed shape (attributes, no data). Also updated the Cloudflare integration test. Its chat span assertion matches attributes exhaustively with toEqual, while that scenario passes a config to chats.create, so propagating it adds temperature, top_p and max_tokens to the chat span. Verified the assertion fails without those three keys and passes with them. The node suites assert per key, so they were unaffected. Verified: @sentry/server-utils 432 tests passing (44 files), node google-genai and google-genai-v2 integration 24 passing, cloudflare google-genai integration 1 passing, oxfmt --check, oxlint --type-aware, oxlint src --type-aware --type-check. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Sorry for the delay, @zkasuran! Taking a look at this now. |
|
Thanks for sending this fix! There were a few issues, but fairly easy to address in the fixup. Will squash and land as soon as CI approves. Basically, the fix here was missing the default instrumentation path for some platforms. As a consequence, a Cloudflare Worker gets the restored attributes and a Node app on the default integration would not. Fixed in the fixup commit that I pushed to your branch. I also updated the PR description to match. |

chats.create()takes aconfig(temperature, topP, topK, maxOutputTokens, frequencyPenalty, presencePenalty, tools, systemInstruction) that@google/genaireuses for everychat.sendMessage()andchat.sendMessageStream()call on that chat. #19990 removed thechats.create()span, which was the only span reporting those values, and nothing took over. Chat spans have carried model and token counts but no request config since then.Both google-genai instrumentation paths are fixed:
instrumentGoogleGenAIClient, the client proxy, used by@sentry/cloudflareand@sentry/vercel-edgeand available for manual wrapping.googleGenAIIntegration, the diagnostics-channel integration, which is the default for Node, Bun, Deno, Astro, AWS Lambda and Google Cloud Functions.Only the first path was in the original report, but both build their attributes from the per-message arguments alone, so both lost the same data.
Decisions
The config is read from the chat instance, not captured at create time.
@google/genaistoresconfigas a plain property on theChatobject, and both paths already hold that object: the proxy passes it as the instrumented method'scontext, and the channel path receives it asdata.self.extractModel()already recovers the model the same way. Carrying thechats.create()arguments forward instead would have meant threading state throughcreateDeepProxy, which fixes only the proxy path and moves that function away from itsopenaiandanthropic-aicounterparts that #19990 deliberately converged. The cost is a dependency on an internal field name, which this file already accepts formodel/modelVersion.A per-message config replaces the chat config, it does not merge into it. The SDK resolves the request as
params.config ?? chat.config, so a message that carries its own config sends only that config. The span mirrors that. Merging key by key would report a create-timemaxOutputTokensalongside a per-messagetemperatureand describe a request that was never sent.The chat
historystays off the message spans. The SDK does send it, folded intocontents, and the instance carries the whole transcript, but repeating every past turn on every message span duplicates what earlier spans already reported and grows without bound.gen_ai.request.messageskeeps just the message being sent.Non-chat calls are unaffected.
models.generateContentandmodels.embedContenthave no chat instance, so they resolve their config from their own arguments exactly as before.Fixes #20086
AI assistance (Claude, Anthropic) was used in developing this change. The design, review and verification were done by the author.