Type-safe JSON responses with Pydantic, Zod, or Effect schemas
LLMs generate text. Applications need data structures. Structured outputs bridge this gap—define a schema (Pydantic in Python, Zod or Effect Schema in TypeScript), and the Dedalus SDK ensures responses conform with full type safety.This is essential for building reliable applications. Instead of parsing free-form text and hoping for the best, you get validated objects that your code can trust.
import Dedalus from 'dedalus-labs';import { effectResponseFormat } from 'dedalus-labs/helpers/effect';import * as Schema from 'effect/Schema';const client = new Dedalus();const Event = Schema.Struct({name: Schema.String,city: Schema.String,date: Schema.String,});async function main() {const completion = await client.chat.completions.parse({model: 'openai/gpt-5.2',messages: [{role: 'user',content:'Return 3 upcoming basketball events near San Francisco as JSON. Use ISO dates (YYYY-MM-DD).',},],response_format: effectResponseFormat(Schema.Struct({ query: Schema.String, events: Schema.Array(Event) }),'events_response',),mcpServers: ['windsor/ticketmaster-mcp'],});console.log(completion.choices[0]?.message.parsed);}main();
Define type-safe tools with automatic argument parsing:
Report incorrect code
Copy
Ask AI
import asynciofrom dedalus_labs import AsyncDedalusfrom dotenv import load_dotenvfrom pydantic import BaseModelload_dotenv()class SearchEventsArgs(BaseModel):city: strmonth: strmax_results: int = 5async def main():client = AsyncDedalus() tools = [ { "type": "function", "function": { "name": "search_events", "description": "Search for events in a city during a month.", "parameters": { "type": "object", "properties": { "city": {"type": "string"}, "month": {"type": "string", "description": "YYYY-MM"}, "max_results": {"type": "integer", "default": 5}, }, "required": ["city", "month"], "additionalProperties": False, }, "strict": True, } } ] completion = await client.chat.completions.parse( model="openai/gpt-5.2", messages=[{ "role": "user", "content": "Call search_events for San Francisco in 2026-01.", }], tools=tools, tool_choice={"type": "tool", "name": "search_events"}, ) message = completion.choices[0].message if message.tool_calls: tool_call = message.tool_calls[0] print(f"Tool called: {tool_call.function.name}") print(f"Parsed args: {tool_call.function.parsed_arguments}")if **name** == "**main**":asyncio.run(main())
If you need deterministic tool calling, set tool_choice to one of the object variants:
{ type: 'auto' } (model decides), { type: 'any' } (require a tool call), { type: 'tool', name: 'search_events' } (require a specific tool), { type: 'none' } (disable tools). Passing the OpenAI string form (e.g. tool_choice: 'required') will fail schema validation with a 422.
TypeScript: tool parameters with Effect Schema
Report incorrect code
Copy
Ask AI
import { effectFunction } from 'dedalus-labs/helpers/effect';import * as Schema from 'effect/Schema';const SearchEventsTool = effectFunction({name: 'search_events',parameters: Schema.Struct({city: Schema.String,month: Schema.String, // YYYY-MMmax_results: Schema.NullOr(Schema.Number),}),description: 'Search for events in a city during a month.',});
Tool parameters must be an object schema (use Schema.Struct({ ... })).
The Runner supports response_format with automatic schema conversion:
Report incorrect code
Copy
Ask AI
import asynciofrom dedalus_labs import AsyncDedalus, DedalusRunnerfrom dotenv import load_dotenvfrom pydantic import BaseModelload_dotenv()class Event(BaseModel):name: strcity: strdate: strclass EventsResponse(BaseModel):query: strevents: list[Event]def as_bullets(items: list[str]) -> str:"""Format items as a bulleted list."""return "\n".join(f"• {item}" for item in items)async def main():client = AsyncDedalus()runner = DedalusRunner(client) result = await runner.run( input=( "Find me the nearest basketball games in January in San Francisco using Ticketmaster. " "Then call as_bullets with a list of items (one per event: name, city, date)." ), model="anthropic/claude-opus-4-5", mcp_servers=["windsor/ticketmaster-mcp"], # Discover events via Ticketmaster tools=[as_bullets], response_format=EventsResponse, max_steps=5, ) print(result.final_output)if **name** == "**main**":asyncio.run(main())
.create() expects a plain JSON Schema object. Don’t pass a Pydantic model, Zod schema, or Effect
schema directly.
“Streaming + typed output” is language-dependent: - Python: .stream(...) yields typed events
and a typed final snapshot. - TypeScript: stream tokens and validate the final JSON with
Zod/Effect.
The Dedalus SDK’s .parse() and .stream() methods work across all providers. Schema enforcement varies:Strict Enforcement (CFG-based, schema guarantees):
You can use .parse() and .stream() with models from any provider. In practice, you only change model—everything else stays the same.For a full list of model IDs, see the providers guide.
import { effectResponseFormat, effectFunction } from 'dedalus-labs/helpers/effect';// For response schemaseffectResponseFormat(MyEffectSchema, 'schema_name')// For tool definitionseffectFunction({ name: 'tool_name', description: 'What the tool does', parameters: MyEffectParametersSchema, function: (args) => { ... },})
Using @effect/schema (deprecated upstream)
If you still use @effect/schema, schemas from @effect/schema/Schema also work with helpers/effect.You still need to install effect (the Dedalus SDK uses effect/JSONSchema and effect/Schema for conversion + validation).Prefer effect/Schema for new code.