fix: camelize JSON-schema required values for OpenAI strict structured outputs - #38
Merged
Merged
Conversation
…red outputs `deep_camelize_keys` camelized hash keys but not the string values inside `required` arrays, leaving `properties` in camelCase while `required` stayed snake_case. OpenAI strict structured outputs (enforced since the Mastra SDK upgrade) reject that mismatch. Recursively align every `required` array with the camelized property keys (handles nested / anyOf schemas too). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Versioning is handled in dedicated release PRs (e.g. #35) and the monorepo pins this gem by git SHA, not version — so a patch bump is unnecessary here and broke frozen-mode `bundle install` in CI (gemspec 0.6.1 vs locked 0.6.0). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
GuillenSanti
marked this pull request as ready for review
July 1, 2026 12:33
enriclluelles
approved these changes
Jul 1, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Since the Mastra SDK upgrade (
ai5→6,@ai-sdk/azure2→3,@mastra/core1.4→1.45), agents using structured outputs (generate_object) started failing in production with:This is firing across many structured-output agents at once — banking transaction categorization, the finance OCR document scanner, the interactive GraphQL path, performance-review summaries, and more (1,000+ events / 300+ users).
Triggered by: factorialco/factorial-agent#1826 ("MASTRA UPGRADE", merged 2026-07-01) — https://github.com/factorialco/factorial-agent/pull/1826 — which bumped
ai5→6 /@ai-sdk/azure2→3 /@mastra/core1.4→1.45, enabling OpenAI strict structured outputs. The largest wave (banking + OCR Sidekiq jobs and more) escalated right after its deploy today; the same schema signature also appears via the interactive GraphQL path going back ~6 days. Either way the upgrade did not introduce the bug — it exposed this latent one, and the root cause + fix are identical for all of them.Affected Sentry issues (same root cause)
Dashboard — all
Invalid schema for response_formaterrors. Every one has the same signature:requiredmust include every key inproperties;Missing '<camelCaseField>'.Missing 'transactionId'·Banking::Jobs::Transactions::CategorizeMissing 'accountNumber'·Finance::EventHandlers::Invoices::ProcessProcessing— surfaced as BACKEND-55W1 "OCR agent is temporarily unavailable"Missing 'accountNumber'·ApiPrivate::GraphqlController#executeMissing 'performanceReviewSummary', BACKEND-5605Missing 'lastMeetingContext'Root cause (a latent bug, not the upgrade)
Ai::StructToJsonSchema.convertbuilds the schema from aT::Structwith snake_case keys in bothpropertiesandrequired— internally consistent.Ai::Clients::Mastra#deep_camelize_keysthen runsdeep_transform_keys, which camelizes hash KEYS but not array string VALUES:propertieskeys → camelCase (transaction_id→transactionId) ✅requiredvalues → stay snake_case (transaction_id) ❌ (they are array elements, not keys)So the schema sent to OpenAI has always been internally inconsistent (
propertiescamelCase,requiredsnake_case).Why it only breaks now: the pre-upgrade path did not enforce OpenAI strict structured outputs, so the mismatch was tolerated. The upgraded SDK enables strict mode, where OpenAI requires
requiredto list every property key by exact name. The camel/snake mismatch now fails validation. The upgrade did not introduce the bug — it exposed a latent one.Fix
After
deep_transform_keys, recursively align everyrequiredarray with the camelized property keys (camelize_schema_required!). The walk is recursive so it also handles nested and nilable (anyOf) structs — e.g. the OCRpayment_details.account_numbercase (context=('properties','paymentDetails','anyOf','0'), Missing 'accountNumber'), not just flat top-level schemas.Verification
StructToJsonSchema.convert→deep_camelize_keys):properties=[transactionId, categoryId],required=[transaction_id, category_id]→ strict REJECTSrequired=[transactionId, categoryId]→ strict ACCEPTSanyOf). Note the pre-existing "converts struct fields to camelCase" spec never assertedrequired— which is how this slipped through.bundle exec rspec→ 85 examples, 0 failures ·srb tc→ no errors ·rubocop→ no offenses.Impact
Fixes structured-output failures across all agents whose struct has multi-word (snake_case) fields — banking categorization, finance OCR, etc. This is a cross-domain production incident; after merge + release, bump the
aigem pin in the monorepoGemfile.🤖 Generated with Claude Code