Skip to main content

Structured Output

Structured output lets you enforce a JSON schema on the LLM’s response. Instead of free-form text, the agent returns validated, typed data—ideal for sentiment analysis, entity extraction, form filling, and API integrations.

What is Structured Output?

With structured output, you define a Zod schema that describes the expected shape of the response. RadarOS:
  1. Converts the schema to a provider-specific format (e.g., json_schema for OpenAI)
  2. Instructs the LLM to respond in that format
  3. Parses and validates the response against the schema
  4. Exposes the result in RunOutput.structured

Using Zod Schemas

Set structuredOutput in AgentConfig:
import { Agent, openai } from "@radaros/core";
import { z } from "zod";

const SentimentSchema = z.object({
  sentiment: z.enum(["positive", "negative", "neutral"]),
  confidence: z.number().min(0).max(1),
  keywords: z.array(z.string()),
});

const agent = new Agent({
  name: "SentimentAnalyzer",
  model: openai("gpt-4o"),
  instructions: "Analyze the sentiment of the given text.",
  structuredOutput: SentimentSchema,
});

const result = await agent.run("This product is amazing! Best purchase ever.");
console.log(result.structured);
// { sentiment: "positive", confidence: 0.95, keywords: ["amazing", "best"] }

How It Works

1

Schema

Define a Zod schema (object, array, nested, etc.).
2

responseFormat

RadarOS converts the schema to the provider’s responseFormat (e.g., { type: "json_schema", schema: {...} }).
3

LLM

The model is instructed to return valid JSON matching the schema.
4

Parse & Validate

The raw response is parsed and validated with schema.safeParse(). Invalid output may throw or be handled by the provider.

RunOutput.structured

When structuredOutput is set, the parsed and validated result is available on RunOutput.structured:
const result = await agent.run("Tell me about Paris.");

// result.text — raw text from the model (may be JSON string)
console.log(result.text);

// result.structured — parsed, validated object
console.log(result.structured);
// { name: "Paris", country: "France", population: 2161000, ... }

Example: Sentiment Analysis Agent

import { Agent, openai } from "@radaros/core";
import { z } from "zod";

const SentimentSchema = z.object({
  sentiment: z.enum(["positive", "negative", "neutral"]),
  confidence: z.number().min(0).max(1).describe("Confidence score 0-1"),
  keywords: z.array(z.string()).describe("Key phrases that influenced the sentiment"),
});

const agent = new Agent({
  name: "SentimentAnalyzer",
  model: openai("gpt-4o"),
  instructions: `Analyze the sentiment of the given text.
Return valid JSON matching the schema.`,
  structuredOutput: SentimentSchema,
  logLevel: "info",
});

const result = await agent.run(
  "The service was slow and the staff was rude. Very disappointed."
);

console.log(result.structured);
// {
//   sentiment: "negative",
//   confidence: 0.92,
//   keywords: ["slow", "rude", "disappointed"]
// }

Example: City Info with Rich Schema

const CityInfoSchema = z.object({
  name: z.string(),
  country: z.string(),
  population: z.number().describe("Approximate population"),
  landmarks: z.array(z.string()).describe("Famous landmarks"),
  bestTimeToVisit: z.string(),
});

const agent = new Agent({
  name: "CityInfo",
  model: openai("gpt-4o"),
  instructions: "Provide city information. Always respond with valid JSON.",
  structuredOutput: CityInfoSchema,
});

const result = await agent.run("Tell me about Paris, France.");
console.log(JSON.stringify(result.structured, null, 2));

Structured Output in Express / Swagger

When you expose an agent with structuredOutput via Express, RadarOS automatically generates a typed response schema in the OpenAPI spec. Swagger UI will display the exact shape of the structured field — consumers of your API see the full contract without extra documentation.
import express from "express";
import { Agent, openai } from "@radaros/core";
import { createAgentRouter } from "@radaros/transport";
import { z } from "zod";

const analyst = new Agent({
  name: "analyst",
  model: openai("gpt-4o-mini"),
  instructions: "Analyze data and provide insights.",
  structuredOutput: z.object({
    summary: z.string(),
    keyPoints: z.array(z.string()),
    confidence: z.number().min(0).max(1),
  }),
});

const app = express();
app.use(express.json());

const router = createAgentRouter({
  agents: { analyst },
  swagger: { enabled: true },
});

app.use("/api", router);
app.listen(3000);
The generated OpenAPI spec for POST /api/agents/analyst/run will include:
"structured": {
  "type": "object",
  "properties": {
    "summary": { "type": "string" },
    "keyPoints": {
      "type": "array",
      "items": { "type": "string" }
    },
    "confidence": {
      "type": "number",
      "minimum": 0,
      "maximum": 1
    }
  },
  "required": ["summary", "keyPoints", "confidence"]
}
Agents without structuredOutput use the generic RunOutput schema where structured is untyped.

Provider Support

ProviderSupport
OpenAIresponseFormat: { type: "json_schema", schema: {...} }
Google GeminiresponseFormat: "json" with json_schema
AnthropicStructured output via tool use or response format where supported
Vertex AIresponseFormat: "json" with json_schema
RadarOS abstracts provider differences. Use a Zod schema and the framework handles the rest.