Skip to content

Structured Output - Groq -- All object properties must be required #2202

@ZachHandley

Description

@ZachHandley

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workinggood first issueGood for newcomershelp wantedExtra attention is needed

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions