AI Structured Output
Use LlmGenerateObjectTool from @loopstack/llm-provider-module to generate structured data conforming to a JSON Schema. Provider-agnostic — works with Claude, OpenAI, and other providers.
Define a Document
import { z } from 'zod';
import { Document } from '@loopstack/common';
export const FileDocumentSchema = z
.object({
filename: z.string(),
description: z.string(),
code: z.string(),
})
.strict();
export type FileDocumentType = z.infer<typeof FileDocumentSchema>;
@Document({
schema: FileDocumentSchema,
widget: __dirname + '/file-document.yaml',
})
export class FileDocument {
filename: string;
description: string;
code: string;
}Workflow Example
import { toJSONSchema, z } from 'zod';
import { BaseWorkflow, DocumentEntity, Transition, Workflow } from '@loopstack/common';
import type { LoopstackContext } from '@loopstack/common';
import type { LlmGenerateObjectResult } from '@loopstack/llm-provider-module';
import { LlmGenerateObjectTool, LlmMessageDocument } from '@loopstack/llm-provider-module';
import { FileDocument, FileDocumentSchema, FileDocumentType } from './documents/file-document';
interface StructuredOutputState {
language?: string;
llmResult?: DocumentEntity<FileDocumentType>;
}
@Workflow({
schema: z.object({
language: z.enum(['python', 'javascript', 'java', 'cpp', 'ruby', 'go', 'php']).default('python'),
}),
})
export class PromptStructuredOutputWorkflow extends BaseWorkflow<{ language: string }, StructuredOutputState> {
constructor(private readonly llmGenerateObject: LlmGenerateObjectTool) {
super();
}
@Transition({ to: 'ready' })
async greeting(state: StructuredOutputState, ctx: LoopstackContext): Promise<StructuredOutputState> {
const args = ctx.args as { language: string };
await this.documentStore.save(
LlmMessageDocument,
{
role: 'assistant',
content: [{ type: 'text', text: `Creating a Hello World script in ${args.language}...` }],
},
{ id: 'status' },
);
return { ...state, language: args.language };
}
@Transition({ from: 'ready', to: 'prompt_executed' })
async prompt(state: StructuredOutputState): Promise<StructuredOutputState> {
const result = await this.llmGenerateObject.call(
{
outputSchema: toJSONSchema(FileDocumentSchema) as Record<string, unknown>,
prompt: this.render(__dirname + '/templates/prompt.md', { language: state.language }),
},
{ config: { provider: 'claude', model: 'claude-sonnet-4-6' } },
);
const objectResult = result.data as LlmGenerateObjectResult;
const llmResult = await this.documentStore.save(FileDocument, objectResult.data as FileDocumentType, {
validate: 'skip',
});
return { ...state, llmResult };
}
@Transition({ from: 'prompt_executed', to: 'end' })
async respond(state: StructuredOutputState): Promise<unknown> {
await this.documentStore.save(
LlmMessageDocument,
{
role: 'assistant',
content: [{ type: 'text', text: `Generated: ${state.llmResult?.content?.description ?? ''}` }],
},
{ id: 'status' },
);
return {};
}
}How It Works
- Convert your Zod schema to JSON Schema using
toJSONSchema() - Pass the JSON Schema as
outputSchematollmGenerateObject.call() - The provider forces the LLM to return data matching the schema
- Save the result as a typed document using
this.documentStore.save()
Key Parameters
await this.llmGenerateObject.call(
{
outputSchema: toJSONSchema(MyDocumentSchema) as Record<string, unknown>,
prompt: 'Generate structured data.',
},
{ config: { provider: 'claude', model: 'claude-sonnet-4-6' } },
);Registry References
- prompt-structured-output-example-workflow — Generates structured code files using the LLM provider
Last updated on