Structured output
Build on DeepSeek model invocation and streaming to extract structured JSON data, validate it with Zod, and consume typed results using @orcushq/deepseek.
In the previous sections, you learned how to invoke a DeepSeek model, pass conversation history, and consume generated text through streaming. The next step is to work with responses as structured data instead of handling the generated text manually. Orcus provides withStructuredOutput() to request JSON output, parse the response, and validate it against a Zod schema.
Structured output uses prompt instructions rather than native provider-enforced JSON Schema configuration. The model can still return invalid JSON or data that does not match your schema, so the SDK validates the result before returning it.
Installation
Install the DeepSeek SDK and Zod if they are not already available in your project.
npm install @orcushq/deepseek zodBasic usage
Start with the same DeepSeekWeb client configuration used in the previous examples. Set your session cookie and authorization value in .env, define a Zod schema, and pass it to withStructuredOutput().
import 'dotenv/config'import { z } from 'zod'import { DeepSeekWeb } from '@orcushq/deepseek' const model = new DeepSeekWeb({ model: 'deepseek-v3', dsSessionId: process.env.DEEPSEEK_SESSION_ID ?? '', authorization: process.env.DEEPSEEK_AUTHORIZATION ?? '',}) const personSchema = z.object({ name: z.string(), age: z.number(),}) const structured = model.withStructuredOutput(personSchema) const result = await structured.invoke( 'My name is Vikas and I am 21 years old.',) console.log(result.value)console.log(result.raw)Set DEEPSEEK_SESSION_ID and DEEPSEEK_AUTHORIZATION in your .env file. Both credentials are required. dotenv/config loads them for this example; the SDK does not load environment variables automatically.
Understanding the result
structured.invoke() returns an object containing the validated value, the original response text, the underlying response, and its metadata.
- `result.value` - The parsed, Zod-validated value, with its TypeScript type inferred from the schema.
- `result.raw` - The original text returned by the model.
- `result.response` - The underlying
DeepSeekResponseobject. - `result.metadata` - Metadata from the underlying response.
{ "name": "Vikas", "age": 21}The output above is illustrative. The actual value depends on the model response, and the SDK raises DeepSeekStructuredOutputError if extraction, parsing, or schema validation fails.
Streaming structured output
Just as you streamed text in the previous section, you can stream structured output using structured.stream(). Each chunk contains the newly generated text, accumulated output, completion state, and a best-effort partial value.
const structured = model.withStructuredOutput(personSchema) for await (const chunk of structured.stream( 'My name is Vikas and I am 21 years old.',)) { if (chunk.content) { process.stdout.write(chunk.content) } if (chunk.partial !== undefined) { console.log('\nParsed value:', chunk.partial) } if (chunk.done) { console.log('\nFinal value:', chunk.partial) }}Structured stream fields
- `content` - Newly generated text in the current chunk.
- `accumulated` - The complete output text accumulated so far.
- `done` - Indicates the final chunk of a successful stream.
- `partial` - The parsed and validated value, or
undefinedif the accumulated output is incomplete or invalid.
Partials are best effort
`partial` is not a field-by-field JSON parser. The SDK attempts to extract, parse, and validate the accumulated output against the complete schema. It can remain undefined until the entire object is valid. If the final output fails parsing or validation, the stream raises `DeepSeekStructuredOutputError`.
Batch processing
When you need structured results for multiple prompts, use structured.batch(). It applies the same schema to each input and returns results in input order. This uses the SDK client’s batch scheduling, not a native DeepSeek batch endpoint.
const structured = model.withStructuredOutput(personSchema) const results = await structured.batch( [ 'My name is Vikas and I am 21 years old.', 'My name is Priya and I am 34 years old.', ], { concurrency: 2, },) for (const result of results) { console.log(result.value)}Example output
[ { "name": "Vikas", "age": 21 }, { "name": "Priya", "age": 34 }]The example output is illustrative. Each result contains the same fields as the result returned by structured.invoke(). If a response cannot be validated, the batch operation can fail rather than returning a valid structured value for that input.
Customizing structured output
Pass a second argument to withStructuredOutput() to customize the instructions, override the generated JSON Schema, or provide a custom JSON extraction function.
const structured = model.withStructuredOutput(personSchema, { name: 'person', instructions: 'Use the information provided in the prompt.', jsonSchema: { type: 'object', properties: { name: { type: 'string' }, age: { type: 'number' }, }, required: ['name', 'age'], },}) console.log(structured.instructions)- `name` - Adds a schema name to the generated prompt instructions. It does not create a separate provider-side schema registration.
- `instructions` - Adds custom guidance to the structured-output prompt.
- `jsonSchema` - Overrides the JSON Schema generated from the Zod schema.
- `parse` - Replaces the default JSON extraction step. It must return a JSON string, which the SDK parses and validates against the Zod schema.
- `structured.instructions` - Exposes the instruction text appended to requests, which is useful for debugging.
const structured = model.withStructuredOutput(personSchema, { parse: (text) => { const start = text.indexOf('{') const end = text.lastIndexOf('}') if (start === -1 || end === -1 || end < start) { throw new Error('No JSON object found') } return text.slice(start, end + 1) },})The custom parser above is an example of an extraction strategy, not a complete JSON parser. It assumes the response contains one JSON object and may not work for nested or multiple JSON payloads. The default parser already supports JSON in fenced code blocks and JSON embedded in surrounding text.
Supported invocation options
Structured-output runnables expose a narrower per-call option surface than DeepSeekWeb.invoke(). The supported host options are signal, model, chatSessionId, and parentMessageId; structured batch operations additionally support concurrency. Do not assume every DeepSeekInvokeOptions field is forwarded through a structured-output runnable.
Handling validation errors
If the response cannot be extracted as JSON, cannot be parsed, or does not satisfy the Zod schema, the SDK raises DeepSeekStructuredOutputError. Catch this error when your application depends on validated structured data.
import { DeepSeekStructuredOutputError } from '@orcushq/deepseek' try { const result = await structured.invoke( 'Extract the person information from this text.', ) console.log(result.value)} catch (error) { if (error instanceof DeepSeekStructuredOutputError) { console.error('Structured output failed:', error.message) console.error('Error details:', error.details) } else { throw error }}Important limitations
- JSON output is requested through prompt instructions, not native provider-enforced JSON Schema configuration.
- Zod validates the returned data; it does not guarantee that the model generates valid JSON.
- A structured stream can emit text before a complete valid object is available.
- A final structured-stream parsing or validation failure raises
DeepSeekStructuredOutputError. - Validate data again at application trust boundaries when required by your security or business logic.