fix(server-utils): Derive the Vercel AI conversation id from the OpenAI conversation option - #24979
RulaKhaled wants to merge 4 commits into
Conversation
…AI conversation option `gen_ai.conversation.id` was filled with `providerMetadata.openai.responseId`, the id of the response that just came back, so every call got its own value under a grouping attribute. The id now comes from `providerOptions.openai.conversation` (or `azure`), the Conversations API id that is the same on every turn. Child spans inherit it from the active operation span, and `Sentry.setConversationId()` still wins. Part of #24832 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
size-limit report 📦
|
isaacs
left a comment
There was a problem hiding this comment.
There are some choices here that we should probably call out and I'd maybe add a follow-up for the openai inconsistency, but this is good overall.
Deferring reading the id from runtimeContext / experimental_telemetry.metadata until #24721 lands is sensible. That should be ready to go once the convention is approved.
| }, | ||
| }); | ||
|
|
||
| // Chaining on the previous response names a response, not a thread, so no conversation id. |
There was a problem hiding this comment.
Definitely out of scope for this PR, but I noticed that the direct OpenAI integration does the reverse of this comment: extractConversationId in packages/server-utils/src/ai/openai/utils.ts line 161 maps previous_response_id to gen_ai.conversation.id. That has the same problem you're fixing in this PR, because the value differs on each turn of a chain. So the same OpenAI conversation now gets different gen_ai.conversation.id values depending on whether the user calls OpenAI directly or through the AI SDK.
I'd suggest naming it in the description here as a todo, and adding a follow-up task to #24832 so we can converge on a single rule.
There was a problem hiding this comment.
yah noticed this, adding it to the description as a todo and will put a task on #24830
…d spans A root operation took the conversation id from whatever span was active, so a generateText inside a tool's execute joined the outer conversation. Roots now read only their own providerOptions. The azure conversation is also read when the openai options have none. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # packages/server-utils/src/integrations/vercel-ai/vercel-ai-dc-subscriber.ts # packages/server-utils/src/integrations/vercel-ai/vercel-ai-orchestrion-subscriber.ts
The Vercel AI integration filled
gen_ai.conversation.idwithproviderMetadata.openai.responseId. That is the id of the response that just came back, so every call got a different value under a grouping attribute and showed up as its own one-span conversation. The same value is already ongen_ai.response.id. The mapping came in with #16992 and #19903 later had to guard it from overwriting user-set ids.The id now comes from
providerOptions.openai.conversation(orazure), the Conversations APIconv_id, which is the same on every turn. Child model-call and tool spans inherit it from the active operation span.Sentry.setConversationId()still wins, sinceconversationIdIntegrationwrites the scope value onspanStartafter these start attributes. The v6 adapter forwardsproviderOptionssoai4 to 6 behave the same.Not in this PR: reading the id from
runtimeContext/experimental_telemetry.metadatagoes with #24706 once #24721 lands.Part of #24832
🤖 Generated with Claude Code