fix(otelMiddleware): open iteration span for structured-output finalization - #1055
Conversation
…zation No-tools + outputSchema skips the agent loop, so only phase=structuredOutput ran and otelMiddleware never opened a generation span — captureContent was a silent no-op and backends like PostHog saw bare traces. Treat structuredOutput as a model call for span lifecycle; keep native-combined single-span behavior. Closes #1054
📝 WalkthroughWalkthroughotelMiddleware now creates iteration spans for structured-output finalization and numbers spans by model-call count. Unit and end-to-end tests validate content, usage, phase filtering, and numbering. Documentation and the changeset describe the behavior. ChangesStructured-output observability
Sequence Diagram(s)sequenceDiagram
participant StructuredOutputFinalization
participant otelMiddleware
participant ProviderModel
participant IterationSpan
StructuredOutputFinalization->>otelMiddleware: onConfig phase structuredOutput
otelMiddleware->>IterationSpan: create numbered iteration span
StructuredOutputFinalization->>ProviderModel: send structured-output request
ProviderModel-->>IterationSpan: return content and usage
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
View your CI Pipeline Execution ↗ for commit be444a8
☁️ Nx Cloud last updated this comment at |
@tanstack/ai
@tanstack/ai-acp
@tanstack/ai-angular
@tanstack/ai-anthropic
@tanstack/ai-bedrock
@tanstack/ai-byteplus
@tanstack/ai-claude-code
@tanstack/ai-client
@tanstack/ai-code-mode
@tanstack/ai-code-mode-skills
@tanstack/ai-codex
@tanstack/ai-devtools-core
@tanstack/ai-durable-stream
@tanstack/ai-elevenlabs
@tanstack/ai-event-client
@tanstack/ai-fal
@tanstack/ai-gemini
@tanstack/ai-grok
@tanstack/ai-grok-build
@tanstack/ai-groq
@tanstack/ai-isolate-cloudflare
@tanstack/ai-isolate-node
@tanstack/ai-isolate-quickjs
@tanstack/ai-mcp
@tanstack/ai-memory
@tanstack/ai-mistral
@tanstack/ai-ollama
@tanstack/ai-openai
@tanstack/ai-opencode
@tanstack/ai-openrouter
@tanstack/ai-persistence
@tanstack/ai-preact
@tanstack/ai-react
@tanstack/ai-react-ui
@tanstack/ai-sandbox
@tanstack/ai-sandbox-cloudflare
@tanstack/ai-sandbox-daytona
@tanstack/ai-sandbox-docker
@tanstack/ai-sandbox-local-process
@tanstack/ai-sandbox-sprites
@tanstack/ai-sandbox-vercel
@tanstack/ai-solid
@tanstack/ai-solid-ui
@tanstack/ai-svelte
@tanstack/ai-utils
@tanstack/ai-vue
@tanstack/ai-vue-ui
@tanstack/openai-base
@tanstack/preact-ai-devtools
@tanstack/react-ai-devtools
@tanstack/solid-ai-devtools
commit: |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@testing/e2e/tests/middleware.spec.ts`:
- Around line 244-246: Update the assertions around inputMessages in the
middleware test to parse the serialized gen_ai.input.messages value as JSON and
verify that the resulting collection contains at least one message with a
content field. Replace the length-only check while preserving the existing
string-type validation.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 03837be8-a4f0-481b-b0dd-4b261da21780
📒 Files selected for processing (6)
.changeset/otel-structured-output-iteration-span.mddocs/advanced/otel.mddocs/config.jsonpackages/ai/src/middlewares/otel.tspackages/ai/tests/middlewares/otel.test.tstesting/e2e/tests/middleware.spec.ts
| const inputMessages = iter.attributes['gen_ai.input.messages'] | ||
| expect(typeof inputMessages).toBe('string') | ||
| expect(inputMessages.length).toBeGreaterThan(0) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Assert a parsed input message.
inputMessages.length passes for '[]'. The test can pass when no prompt content was captured. Parse the value and assert that it contains a message with content.
Proposed test assertion
const inputMessages = iter.attributes['gen_ai.input.messages']
expect(typeof inputMessages).toBe('string')
-expect(inputMessages.length).toBeGreaterThan(0)
+expect(JSON.parse(inputMessages)).toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({ content: expect.any(String) }),
+ ]),
+)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const inputMessages = iter.attributes['gen_ai.input.messages'] | |
| expect(typeof inputMessages).toBe('string') | |
| expect(inputMessages.length).toBeGreaterThan(0) | |
| const inputMessages = iter.attributes['gen_ai.input.messages'] | |
| expect(typeof inputMessages).toBe('string') | |
| expect(JSON.parse(inputMessages)).toEqual( | |
| expect.arrayContaining([ | |
| expect.objectContaining({ content: expect.any(String) }), | |
| ]), | |
| ) |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@testing/e2e/tests/middleware.spec.ts` around lines 244 - 246, Update the
assertions around inputMessages in the middleware test to parse the serialized
gen_ai.input.messages value as JSON and verify that the resulting collection
contains at least one message with a content field. Replace the length-only
check while preserving the existing string-type validation.
Summary
otelMiddleware.onConfignow opens an iteration (CLIENT) span forphase === 'structuredOutput', not onlybeforeModel.outputSchemaruns that skip the agent loop: they previously emitted only a rootchatspan, socaptureContentwas a silent no-op and backends keyed on generation spans (e.g. PostHog$ai_generation) saw empty traces.#N+1, not a duplicate#0.structuredOutput).Closes #1054
Test plan
packages/aitests/middlewares/otel.test.ts(52 tests) — structuredOutput opens span + content; non-model phases do not; finalization after beforeModel is#1middleware.spec.ts— Claude 3.7 legacy finalization path asserts 1 CLIENT span +gen_ai.input.messagesdocs/advanced/otel.mdnotes structured-output finalization spansSummary by CodeRabbit
New Features
Documentation