Skip to main content

Why Structured Outputs?

Unstructured text is hard to process programmatically:
# Unstructured (hard to parse)
"I found 3 issues. The first one is a memory leak in
the user service, which is critical. The second..."

# Structured (easy to parse)
{
  "issues": [
    {"type": "memory_leak", "location": "user-service", "severity": "critical"},
    ...
  ]
}

Techniques

1. Explicit Format Instructions

Tell the agent exactly what structure you want:
## Output Format

Return a JSON object with this exact structure:
```json
{
  "status": "success" | "error",
  "findings": [
    {
      "file": "path/to/file.ts",
      "line": 42,
      "issue": "description",
      "severity": "low" | "medium" | "high" | "critical"
    }
  ],
  "summary": "one sentence summary"
}
Return ONLY the JSON. No prose, no markdown code blocks.

### 2. Schema Enforcement

Provide a JSON Schema for validation:

```markdown
## Output Schema

Your response must conform to this JSON Schema:

```json
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "required": ["status", "data"],
  "properties": {
    "status": {
      "type": "string",
      "enum": ["success", "error", "partial"]
    },
    "data": {
      "type": "array",
      "items": {
        "type": "object",
        "required": ["id", "value"],
        "properties": {
          "id": {"type": "string"},
          "value": {"type": "number"}
        }
      }
    },
    "error": {
      "type": "string"
    }
  }
}

### 3. Few-Shot Examples

Show examples of correct output:

```markdown
## Examples

Input: "Analyze user.ts for security issues"
Output:
{
  "file": "user.ts",
  "issues": [
    {"line": 23, "type": "sql_injection", "severity": "critical"}
  ]
}

Input: "Analyze config.ts for security issues"
Output:
{
  "file": "config.ts",
  "issues": []
}

Now analyze: auth.ts

4. Template-Based Output

Provide a fill-in-the-blank template:
## Response Template

Fill in this template exactly:

**Status**: [SUCCESS/FAILURE]
**Changes Made**:
- [file1]: [description of change]
- [file2]: [description of change]
**Tests**: [PASSED/FAILED/SKIPPED]
**Next Steps**: [action item or "None"]

Common Formats

Action Results

{
  "action": "create_file",
  "success": true,
  "path": "/src/components/Button.tsx",
  "changes": {
    "linesAdded": 45,
    "linesRemoved": 0
  },
  "nextAction": "run_tests"
}

Analysis Reports

{
  "analysis": {
    "scope": "src/api/**/*.ts",
    "filesAnalyzed": 23,
    "timeMs": 1250
  },
  "findings": [
    {
      "id": "SEC-001",
      "category": "security",
      "severity": "high",
      "file": "src/api/auth.ts",
      "line": 45,
      "message": "Hardcoded secret detected",
      "suggestion": "Use environment variable"
    }
  ],
  "summary": {
    "critical": 0,
    "high": 1,
    "medium": 3,
    "low": 5
  }
}

Task Status

{
  "task": "Implement user authentication",
  "status": "in_progress",
  "progress": {
    "completed": ["database schema", "user model"],
    "current": "JWT middleware",
    "remaining": ["login endpoint", "tests"]
  },
  "blockers": [],
  "estimatedCompletion": "2 more steps"
}

Decision Records

{
  "decision": "Use PostgreSQL over MongoDB",
  "context": "Need relational data with ACID compliance",
  "options": [
    {"name": "PostgreSQL", "score": 9, "pros": ["ACID", "mature"], "cons": ["schema migrations"]},
    {"name": "MongoDB", "score": 6, "pros": ["flexible schema"], "cons": ["no joins"]}
  ],
  "rationale": "Relational integrity is critical for financial data",
  "reversibility": "low"
}

Validation

Post-Processing

// Parse and validate agent output
function parseAgentOutput(raw) {
  try {
    const parsed = JSON.parse(raw);

    // Validate required fields
    if (!parsed.status) throw new Error("Missing status");
    if (!Array.isArray(parsed.findings)) throw new Error("Invalid findings");

    return { success: true, data: parsed };
  } catch (e) {
    return { success: false, error: e.message, raw };
  }
}

Retry on Invalid Output

async function getStructuredOutput(prompt, maxRetries = 2) {
  for (let i = 0; i <= maxRetries; i++) {
    const response = await agent.query(prompt);
    const result = parseAgentOutput(response);

    if (result.success) return result.data;

    // Add feedback for retry
    prompt += `\n\nYour previous response was invalid: ${result.error}.
    Please return valid JSON only.`;
  }

  throw new Error("Failed to get valid structured output");
}

Schema Validation with Zod

import { z } from 'zod';

const FindingSchema = z.object({
  file: z.string(),
  line: z.number(),
  issue: z.string(),
  severity: z.enum(['low', 'medium', 'high', 'critical'])
});

const AnalysisSchema = z.object({
  status: z.enum(['success', 'error']),
  findings: z.array(FindingSchema),
  summary: z.string()
});

// Validate agent output
const result = AnalysisSchema.safeParse(JSON.parse(agentOutput));
if (!result.success) {
  console.error("Invalid output:", result.error);
}

Prompt Patterns

The “Only JSON” Pattern

You are a JSON-only responder.
- Return ONLY valid JSON
- No markdown code blocks
- No explanatory text
- No "Here is the JSON:" preambles

Task: [description]

Schema: [schema]

The “Strict Format” Pattern

CRITICAL: Your entire response must be valid JSON matching this schema.
Any deviation will cause system failure.

Schema:
{...}

Task: [description]

The “Transform” Pattern

Transform the following input into the specified output format.

Input:
[raw data]

Output format:
{
  "field1": "extracted from input",
  "field2": "computed value"
}

Anti-Patterns

Common mistakes:
  • Asking for “JSON-like” output (be specific)
  • Not providing a complete schema
  • Allowing optional explanations (they become mandatory)
  • Not validating output in code
  • Assuming the first response is valid

Bad Prompt

Can you give me the results in JSON format?
Something like {name, value} for each item.

Good Prompt

Return a JSON array. Each element must have:
- "name": string (required)
- "value": number (required)
- "unit": string (optional)

Example: [{"name": "count", "value": 42}]

Return ONLY the JSON array, no other text.

Best Practices

  • Always provide complete schema or examples
  • Validate output before using
  • Implement retry logic for parsing failures
  • Use “ONLY JSON” instructions explicitly
  • Test with edge cases (empty results, errors)
  • Include error format in schema