Structured output

Generate structured data with Zod schemas using withStructuredOutput. Parse and validate model responses, stream structured partials, and process multiple inputs with batch.

Structured output lets you define the shape of the data you expect and receive a parsed, validated JavaScript value instead of manually extracting JSON from text. Orcus uses your Zod schema to generate JSON Schema instructions, asks Gemini Web to follow those instructions, and validates the returned data with Zod. The provider does not enforce the supplied schema through native JSON Schema configuration in this interface, so invalid output can still occur.

Install Zod if it is not already available in your project. Orcus uses Zod for schema conversion and runtime validation.

bash
npm install @orcushq/gemini zod

Initialize the client

Configure the Gemini Web session cookies and XSRF token before making requests. The SDK does not read environment variables automatically.

.env
GEMINI_COOKIES="your_complete_cookie_header"GEMINI_XSRF_TOKEN="your_at_value"
src/structured.ts
import 'dotenv/config'import { z } from 'zod'import {  GeminiWeb,  GeminiStructuredOutputError,} from '@orcushq/gemini' const cookies = process.env.GEMINI_COOKIESconst xsrfToken = process.env.GEMINI_XSRF_TOKEN if (!cookies?.trim() || !xsrfToken?.trim()) {  throw new Error(    'GEMINI_COOKIES and GEMINI_XSRF_TOKEN are required',  )} const model = new GeminiWeb({  model: 'gemini-3.6-flash',  cookies,  xsrfToken,}) const personSchema = z.object({  name: z.string(),  age: z.number(),})

Basic usage

Use withStructuredOutput() to create a runnable from your Zod schema. Call invoke() with your prompt to receive the parsed value, original response text, underlying response, and metadata.

typescript
const structured = model.withStructuredOutput(personSchema) const result = await structured.invoke(  'My name is Vikas and I am 21.',) console.log(result.value)console.log(result.raw)console.log(result.response)console.log(result.metadata)

Example result

json
{  "name": "Vikas",  "age": 21}

result.value contains the parsed and Zod-validated object, with its TypeScript type inferred from your schema. result.raw contains the original response text, result.response contains the underlying GeminiResponse, and result.metadata contains response metadata.

Inspect the served model

The requested model does not guarantee which model answers. In the verified test run, the request asked for `gemini-3.6-flash`, while the metadata reported `gemini-flash-lite` with `modelFellBack: true` and `fallbackReason: "MODEL_UNAVAILABLE"`. Inspect the returned metadata when model selection matters.

Streaming

Use structured.stream() to receive text chunks and inspect best-effort structured partials. Each chunk exposes content, accumulated, done, and partial. The partial field contains a parsed and validated value when the accumulated output matches your schema.

src/structured-stream.ts
const structured = model.withStructuredOutput(personSchema) for await (const chunk of structured.stream(  'Return JSON with name "Vikas" and age 21.',)) {  if (chunk.content) {    process.stdout.write(chunk.content)  }   if (chunk.partial !== undefined) {    console.log('\nPartial result:', chunk.partial)  }   if (chunk.done) {    console.log('\nFinal result:', chunk.partial)  }}

Example output

text
Partial result: { name: 'Vikas', age: 21 }Final result: { name: 'Vikas', age: 21 }

Streaming partials are best effort

Actual chunks and partial values depend on when the model emits parseable JSON. A partial value may remain undefined while the output is incomplete or invalid. If the final output cannot be parsed or validated, the stream raises `GeminiStructuredOutputError`.

Batch processing

Use structured.batch() to process multiple inputs and receive one structured result per input. Results preserve their input order, and you can configure concurrency through the batch options.

src/structured-batch.ts
const structured = model.withStructuredOutput(personSchema) const results = await structured.batch(  [    'Return a JSON person object with name "Alice" and age 30.',    'Return a JSON person object with name "Bob" and age 25.',  ],  {    concurrency: 2,  },) for (const result of results) {  console.log(result.value)}

Example output

json
[  {    "name": "Alice",    "age": 30  },  {    "name": "Bob",    "age": 25  }]

Options

Pass options as the second argument to withStructuredOutput() to customize the instructions, provide a JSON Schema override, or replace the default JSON extraction step.

typescript
const customJsonSchema = {  type: 'object',  properties: {    name: { type: 'string' },    age: { type: 'number' },  },  required: ['name', 'age'],  additionalProperties: false,} const structured = model.withStructuredOutput(  personSchema,  {    name: 'person',    instructions:      'Return a person object with the exact requested name and age.',    jsonSchema: customJsonSchema,    parse: (text) => {      const match = text.match(/\{[\s\S]*\}/)       if (!match) {        throw new Error('No JSON object found in the response')      }       return match[0]    },  },) const result = await structured.invoke(  'Return a person object with name "Maya" and age 28.',) console.log(result.value)console.log(structured.instructions)
  • name identifies the structured output in metadata.
  • instructions adds custom instructions after the generated schema instructions.
  • jsonSchema overrides the JSON Schema generated from the Zod schema.
  • parse replaces the default JSON extraction step. It must return a string containing the JSON payload; Orcus then parses and validates that payload against the Zod schema.
  • structured.instructions exposes the instruction fragment appended to each request.

Schema validation

Orcus parses extracted JSON and validates the resulting value using Zod. If the JSON cannot be extracted or parsed, or the resulting value does not match the schema, the SDK raises GeminiStructuredOutputError. Validation failures can include schema issues in the error details.

typescript
try {  const structured = model.withStructuredOutput(personSchema)   const result = await structured.invoke(    'Extract the person information from this text.',  )   console.log(result.value)} catch (error) {  if (error instanceof GeminiStructuredOutputError) {    console.error('Structured output failed:', error.message)    console.error('Error details:', error.details)  } else {    throw error  }}

Handle invalid structured data

You can use a custom parser to test how the SDK handles data that does not match the schema. The following example deliberately returns an invalid person object to demonstrate validation error handling.

typescript
const invalidStructured = model.withStructuredOutput(  personSchema,  {    name: 'invalid-person',    parse: () =>      JSON.stringify({        name: 123,        age: 'not-a-number',      }),  },) try {  await invalidStructured.invoke('Return a person object.')} catch (error) {  if (error instanceof GeminiStructuredOutputError) {    console.error('Validation error:', error.message)    console.error('Error details:', error.details)  } else {    throw error  }}

Important limitations

  • The schema is sent as prompt instructions, not as native provider-enforced JSON Schema configuration.
  • The model can return invalid JSON or data that does not match the schema. Runtime validation detects these failures but does not prevent them during generation.
  • Zod validates the parsed result; it does not guarantee that the model follows the schema.
  • Handle GeminiStructuredOutputError when valid structured data is required for application logic.
  • Validate data again at security-sensitive or other trust boundaries where your application requires it.