Skip to main content
Typed output allows constraining LLM responses to specific JSON schemas.

Basic JSON

You can pass a JSON schema to the outputSchema parameter of the Agent.send method. The schema is used to constrain the LLM’s response to a specific JSON object shape.
final result = await agent.send(
  'The windy city in the US of A',
  outputSchema: JsonSchema.create({
    'type': 'object',
    'properties': {
      'city': {'type': 'string'},
    },
    'required': ['city'],
  }),
);

print(result.output); // {"city":"Chicago"}

Parsed Map

You can also pass a type to the sendFor method. By default, dartantic already knows how to parse the LLM’s response to a Map<String, dynamic>.
final result = await agent.sendFor<Map<String, dynamic>>(
  'Name 3 colors that make you happy',
  outputSchema: JsonSchema.create({
    'type': 'object',
    'properties': {
      'colors': {
        'type': 'array',
        'items': {'type': 'string'},
      },
    },
    'required': ['colors'],
  }),
);

print(result.output['colors']); // ['sunshine yellow', 'ocean blue', 'grass green']

Custom Types

If you’d like to parse the JSON output to a custom type, you can pass a type to the sendFor method along with the outputFromJson parameter.
class Weather {
  final String city;
  final int temp;
  
  Weather.fromJson(Map<String, dynamic> json)
    : city = json['city'],
      temp = json['temp'];
}

final result = await agent.sendFor<Weather>(
  'How hot is it in the Big Apple right now?',
  outputSchema: JsonSchema.create({
    'type': 'object',
    'properties': {
      'city': {'type': 'string'},
      'temp': {'type': 'integer'},
    },
    'required': ['city', 'temp'],
  }),
  outputFromJson: Weather.fromJson,
);

print('${result.output.city}: ${result.output.temp}°F');

Schema Generation

Hand-writing schemas and JSON serialization is a lot of boilerplate. Consider using the json_serializable and soti_schema_plus packages to automate this:
// let's use the `soti_schema_plus` package to generate the schema and JSON serialization
@SotiSchema()
@JsonSerializable()
class TownAndCountry {
  final String town;
  final String country;
  
  TownAndCountry({required this.town, required this.country});
  
  factory TownAndCountry.fromJson(Map<String, dynamic> json) =>
      _$TownAndCountryFromJson(json);
  
  @jsonSchema
  static Map<String, dynamic> get schemaMap => 
      _$TownAndCountrySchemaMap;
}

// now we pass the schema and JSON serialization to the agent and magic happens
final result = await agent.sendFor<TownAndCountry>(
  'What is the capital of France?',
  outputSchema: TownAndCountry.schema,
  outputFromJson: TownAndCountry.fromJson,
);

print('${result.output.town}, ${result.output.country}');

Streaming

You can stream structured JSON from the provider by passing the outputSchema parameter to the sendStream method.
// Stream structured JSON
await for (final chunk in agent.sendStream(
  'List 3 facts',
  outputSchema: factsSchema,
)) {
  stdout.write(chunk.output); // Streams JSON chunks
}
This is useful if you’d like to render the JSON progressively as it’s streamed.

With Tools

Most providers support both tools and typed output in the same request, allowing you to gather information via tools and return structured results:
final weatherTool = Tool(
  name: 'get_weather',
  description: 'Get current weather',
  inputSchema: weatherInputSchema,
  onCall: (input) => getWeather(input['city']),
);

final agent = Agent('openai', tools: [weatherTool]);

final result = await agent.sendFor<WeatherReport>(
  'Get weather for Seattle and format as a report',
  outputSchema: WeatherReport.schema,
  outputFromJson: WeatherReport.fromJson,
);

print(result.output); // WeatherReport with structured data

Provider Support

ProviderTools + Typed OutputMethod
OpenAINative response_format
Anthropicreturn_result tool
GoogleDouble agent orchestrator
TogetherOpenAI-compatible
OpenRouterOpenAI-compatible
OllamaComing soon
CohereAPI limitation
Google’s Double Agent Pattern: Google’s API doesn’t support tools and typed output in a single call, but dartantic handles this transparently with a two-phase approach: first executing tools, then requesting structured output with the tool results. See the typed output example for working code.

Examples

Next Steps