-
Notifications
You must be signed in to change notification settings - Fork 472
Description
Describe the bug
I'm not 100% sure it's a bug, but whenever I try to create a new structured output response with a Zod schema, if any of the values are not required, I get an error with Groq's API
I saw something about it in a comment somewhere, about json_schema vs json_object responses and stuff, but I am confused about why there's little documentation around this either way
To Reproduce
Code to reproduce the behavior:
// Purpose: showcase that when using Zod schemas with optional fields as
// Groq responseFormat, optional keys tend to disappear because providers
// enforce "required-only, closed objects" under the hood. This is a
// self-contained, provider-agnostic repro (no project-specific imports).
import { Groq } from '@llamaindex/groq'
import { z } from 'zod'
// Minimal step schema used in a plan
const StepSchema = z.object({
id: z.string(),
type: z.string(),
description: z.string(),
})
// 1) Plan schema with an OPTIONAL field `note`
const PlanWithOptionalNote = z.object({
steps: z.array(StepSchema).min(1),
summary: z.string(),
note: z.string().optional(), // <-- optional on purpose
})
// 2) Plan schema with the SAME field but REQUIRED
const PlanWithRequiredNote = z.object({
steps: z.array(StepSchema).min(1),
summary: z.string(),
note: z.string(), // <-- required
})
function prompt(includeNoteValue: string) {
return [
'Create a step-by-step plan as JSON. Keep it minimal.',
`Explicitly include a field note: "${includeNoteValue}" if your schema allows it.`,
'Use a single short step with id "s1" and type "demo".',
].join('\n')
}
async function runOnce(groq: Groq, schema: z.ZodTypeAny, label: string, noteValue: string) {
const res: any = await groq.chat({
messages: [
{ role: 'system', content: 'Return only JSON that matches the provided schema.' },
{ role: 'user', content: prompt(noteValue) },
],
responseFormat: schema,
})
const raw = res?.message?.content ?? '{}'
let obj: any
try { obj = JSON.parse(raw) } catch { obj = {} }
const parsed = schema.safeParse(obj)
const ok = parsed.success
const hasNote = typeof obj?.note === 'string'
console.log(`\n=== ${label} ===`)
console.log('raw:', raw)
console.log('validated:', ok)
console.log('note present:', hasNote, 'value:', obj?.note)
if (!ok) {
console.log('issues:', JSON.stringify((parsed as any).error?.issues ?? [], null, 2))
}
}
async function main() {
const apiKey = process.env.GROQ_API_KEY || ''
if (!apiKey) {
console.error('Set GROQ_API_KEY before running this test.')
process.exit(1)
}
const groq = new Groq({ apiKey, model: 'openai/gpt-oss-120b', temperature: 0.1 })
// Case A: Optional field allowed by Zod. Expectation from a dev:
// Model may include note if instructed. Reality with strict providers:
// Optional keys are often DROPPED from the effective json_schema,
// so the model omits them despite the prompt.
await runOnce(groq, PlanWithOptionalNote, 'Optional note schema', 'OPTIONAL_OK')
// Case B: Same schema but note is REQUIRED. Since it’s in the required set,
// the provider must include it and we observe it appears in output.
await runOnce(groq, PlanWithRequiredNote, 'Required note schema', 'REQUIRED_OK')
}
main().catch((e) => {
console.error(e)
process.exit(1)
})
Expected behavior
I would expect it to generate the schema, and if the key makes sense to include in the output, then include it?
Screenshots
If applicable, add screenshots to help explain your problem.
Desktop (please complete the following information):
- OS: [e.g. macOS, Linux]
- JS Runtime / Framework / Bundler (select all applicable)
- Node.js
- Deno
- Bun
- Next.js
- ESBuild
- Rollup
- Webpack
- Turbopack
- Vite
- Waku
- Edge Runtime
- AWS Lambda
- Cloudflare Worker
- Others (please elaborate on this)
- Version [e.g. 22]
Additional context
Again, not sure if this is actually a bug or not, but, it really feels like one. In Python I can ask for a Pydantic object with non-required fields, and I saw some comment about it somewhere in either Groq's or the LlamaIndex TS package, but I can't find it