Structured Output For Automation
Hook
You ask an AI to generate an Ansible playbook. It returns a block of YAML code, formatted as prose in a markdown block. You copy it into a file. The YAML has an indentation error (spaces instead of tabs on line 47). The playbook fails to parse. You fix it manually. You ask the AI to generate a Terraform configuration. It returns text, not a structured file. You extract it, fix formatting errors, and only then can you run terraform plan. Hours of manual formatting and debugging.
But what if the AI generated structured output directly? Not text describing YAML, but YAML itself. Not a markdown code block, but a structured file. What if the output included metadata: "This is a Terraform v1.2 configuration for AWS. Required variables: vpc_id, subnet_ids. Tested on Terraform 1.2+." What if there was validation: "Your output matches the expected schema for an Ansible playbook (tasks, hosts, handlers). No syntax errors detected."
That's structured output: AI generates not just human-readable text, but machine-readable, machine-actionable output that can be directly integrated into automation pipelines.
Purpose
Structured output means the AI generates:
- Structured data (JSON, YAML, TOML) not prose.
- Validated output that matches expected schema.
- Metadata about the output (version, dependencies, test status).
- Integration-ready output that can be directly consumed by tools.
For IT operations, this enables:
- Automated infrastructure generation: AI generates Terraform/Ansible/Pulumi code → tool validates it → tool deploys it. No manual intervention.
- Documentation generation: AI generates API specs, runbooks, SLAs as structured documents that can be auto-rendered, auto-indexed, auto-validated.
- Compliance reporting: AI generates compliance reports as structured data → reports are validated against standards → dashboards are auto-updated.
This lesson teaches you to design prompts that produce structured output, validate output against schema, and integrate into CI/CD and automation pipelines.
Why This Matters
Manual formatting is error-prone and time-consuming. If 80% of the time you spend with AI-generated code is fixing formatting/syntax errors, structured output can save significant time.
Additionally, structured output enables automation. If an Ansible playbook is generated and validated automatically, it can be directly deployed (with approval). No manual copy-paste, no syntax checking, no debugging. The AI is part of an automation pipeline, not a separate tool.
Core Concepts
Key insight: Structured Output Requires Schema Definition
To get structured output, you must:
- Define the expected output schema.
- Tell the AI the schema.
- Ask AI to generate output matching the schema.
- Validate output against schema.
Example (Ansible playbook schema):
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"name": {"type": "string"},
"hosts": {"type": "string"},
"tasks": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {"type": "string"},
"module": {"type": "string"},
"args": {"type": "object"}
},
"required": ["name", "module"]
}
},
"handlers": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {"type": "string"},
"module": {"type": "string"}
}
}
}
},
"required": ["name", "hosts", "tasks"]
}
Prompt to AI:
Generate an Ansible playbook that installs Nginx.
Output as YAML (not prose). Validate against this schema:
- name (string): Playbook name
- hosts (string): Target hosts
- tasks (array of objects): Each task has name (string), module (string), args (object)
- handlers (optional, array): Handler tasks
Example valid output:
---
name: Install Nginx
hosts: webservers
tasks:
- name: Update package manager
apt:
update_cache: yes
- name: Install Nginx
apt:
name: nginx
state: present
handlers:
- name: Restart Nginx
service:
name: nginx
state: restarted
By providing schema and example, AI knows exactly what to output.
Key insight: Validation Catches Errors Before Use
Structured output can be validated before it's used. Example (JSON schema validation):
Input: AI-generated Terraform configuration
Validator: Check against Terraform schema
Validation:
├─ Required fields present? (resource type, name, required args)
├─ Syntax valid? (valid HCL)
├─ Variables defined? (referenced variables exist)
├─ Type-correct? (string args are strings, not numbers)
└─ Result: 95 validation errors
Common errors caught:
├─ Missing required arguments (e.g., "instance_type" for EC2 instance)
├─ Wrong types (string where number expected)
├─ Syntax errors (missing quotes, wrong brackets)
├─ Undefined variables (referenced variable not defined)
Without validation, you'd deploy the config and get runtime errors in Terraform.
With validation, errors are caught immediately and reported to the AI.
Key insight: Metadata Guides Integration
Structured output includes metadata:
---
metadata:
version: "1.0"
tool: "Ansible"
tool_version: "2.10+"
author: "Claude"
created: "2024-03-20T10:15:00Z"
validated: true
validation_errors: 0
estimated_run_time: "2 minutes"
dependencies:
packages: ["nginx", "openssl"]
services: ["systemd"]
content:
name: "Install and configure Nginx"
hosts: "webservers"
tasks: [...]
Metadata tells consuming tools:
- Is this output validated?
- What version of the tool does it require?
- Are there dependencies I need to check?
- How long will this take to run?
- Who/what generated it?
Key insight: Integration Into CI/CD
Structured output can be integrated into automation pipelines:
CI/CD Pipeline:
Step 1: Generate
├─ AI generates infrastructure code (Terraform)
└─ Output: terraform_config.json (structured, with metadata)
Step 2: Validate
├─ Validator checks schema
├─ Check: Are all required fields present?
├─ Check: Is syntax valid?
├─ Check: Do all variables exist?
└─ Result: 0 errors → Proceed. N>0 errors → Stop, report to AI
Step 3: Test (optional)
├─ Run terraform plan on a test environment
├─ Check: Does the plan look reasonable?
├─ Check: Does it match what was requested?
└─ Result: Plan looks good → Proceed. Issues → Stop
Step 4: Approve (manual gate)
├─ Human reviews the generated code
├─ Human reviews terraform plan
├─ Human clicks "Approve"
└─ Or human rejects and asks AI to modify
Step 5: Deploy
├─ terraform apply on production
├─ Monitor for errors during deployment
└─ Report results
Result: Infrastructure generated by AI, tested, approved by human, deployed automatically. No manual copy-paste.
Practical Use Cases
Use Case 1: Auto-Generating Monitoring Rules
Scenario: You need to add monitoring for a new service. Normally, you'd manually write Prometheus rules.
Without structured output:
Q: Write a Prometheus rule for monitoring a new API service.
A: Here's a rule. You'll need to modify it based on your thresholds...
[User manually edits the rule, tests it, deploys it.]
With structured output:
Q: Generate a Prometheus rule for a new API service (upstream_api).
Output as YAML. Schema:
- name (string): Rule name
- expr (string): Prometheus query
- for (string): How long to wait before alerting
- labels (object): Tags
- annotations (object): Alert message template
Service details:
- Endpoint: /api/v1/data
- Expected latency: <200ms (p99)
- Error rate: <0.1%
A: (Structured output)
name: upstream_api_latency_high
expr: histogram_quantile(0.99, rate(http_request_duration_seconds_bucket{job="upstream_api"}[5m])) > 0.2
for: 5m
labels:
severity: warning
service: upstream_api
annotations:
summary: "API latency is high ({{ $value }}s)"
description: "{{ $labels.instance }} has p99 latency above 200ms"
name: upstream_api_error_rate_high
expr: rate(http_request_errors_total{job="upstream_api"}[5m]) > 0.001
for: 5m
labels:
severity: warning
service: upstream_api
annotations:
summary: "API error rate is high ({{ $value | humanizePercentage }})"
[System validates rules]
[System deploys rules directly to Prometheus]
[No manual editing needed]
Use Case 2: Auto-Generating Compliance Reports
Scenario: Quarterly compliance audit requires documentation of controls. Normally manual.
Without structured output:
Q: Generate a compliance report for SOC 2 Type II.
A: [Long prose document with paragraphs and bullet points]
[You manually extract data and format for audit tool]
With structured output:
Q: Generate a SOC 2 Type II compliance report. Output as JSON with this schema:
{
"report_id": string,
"report_date": ISO8601,
"scope": string,
"controls": [
{
"id": string,
"name": string,
"description": string,
"status": enum["compliant", "non_compliant", "in_progress"],
"evidence": [
{
"document": string,
"date": ISO8601,
"description": string
}
]
}
]
}
A: (Structured JSON output)
{
"report_id": "SOC2-2024-Q1",
"report_date": "2024-03-31T00:00:00Z",
"scope": "Cloud infrastructure and API services",
"controls": [
{
"id": "CC6.1",
"name": "Logical access controls",
"description": "The organization implements logical access controls...",
"status": "compliant",
"evidence": [
{
"document": "IAM_Policy_2024.pdf",
"date": "2024-01-15T00:00:00Z",
"description": "Multi-factor authentication enabled for all users"
},
{
"document": "Access_Audit_Log_Q1_2024.csv",
"date": "2024-03-31T00:00:00Z",
"description": "No unauthorized access attempts detected in Q1"
}
]
},
...
]
}
[System validates JSON against schema]
[System imports into audit tracking tool]
[Compliance dashboard is auto-updated]
[No manual data entry needed]
Examples
Example 1: Prompt for Structured Terraform Output
Prompt:
"Generate a Terraform configuration that creates an AWS VPC with the following:
- VPC with CIDR 10.0.0.0/16
- 2 subnets (public: 10.0.1.0/24, private: 10.0.2.0/24)
- Internet gateway for public subnet
- NAT gateway for private subnet access to internet
Output as valid Terraform HCL (not prose). Include:
1. Complete, ready-to-deploy code
2. Variable definitions (variables.tf)
3. Outputs (outputs.tf)
4. Comments explaining key decisions
Format: Three separate code blocks labeled:
- [FILE: main.tf]
- [FILE: variables.tf]
- [FILE: outputs.tf]
Include metadata comment in each file:
// Generated by Claude
// Version: Terraform 1.2+
// Date: 2024-03-20
// Requires: AWS provider v4.0+"
Output:
[FILE: main.tf]
provider "aws" {
region = var.aws_region
}
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = var.vpc_name
}
}
Public subnet
resource "aws_subnet" "public" {
vpc_id = aws_vpc.main.id
cidr_block = var.public_subnet_cidr
availability_zone = "${var.aws_region}a"
map_public_ip_on_launch = true
tags = {
Name = "${var.vpc_name}-public"
}
}
Private subnet
resource "aws_subnet" "private" {
vpc_id = aws_vpc.main.id
cidr_block = var.private_subnet_cidr
availability_zone = "${var.aws_region}a"
tags = {
Name = "${var.vpc_name}-private"
}
}
Internet Gateway
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = {
Name = "${var.vpc_name}-igw"
}
}
Route table for public subnet
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.main.id
}
tags = {
Name = "${var.vpc_name}-public-rt"
}
}
resource "aws_route_table_association" "public" {
subnet_id = aws_subnet.public.id
route_table_id = aws_route_table.public.id
}
Elastic IP for NAT Gateway
resource "aws_eip" "nat" {
domain = "vpc"
tags = {
Name = "${var.vpc_name}-nat-eip"
}
depends_on = [aws_internet_gateway.main]
}
NAT Gateway
resource "aws_nat_gateway" "main" {
subnet_id = aws_subnet.public.id
allocation_id = aws_eip.nat.id
tags = {
Name = "${var.vpc_name}-nat"
}
depends_on = [aws_internet_gateway.main]
}
Route table for private subnet
resource "aws_route_table" "private" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.main.id
}
tags = {
Name = "${var.vpc_name}-private-rt"
}
}
resource "aws_route_table_association" "private" {
subnet_id = aws_subnet.private.id
route_table_id = aws_route_table.private.id
}
[FILE: variables.tf]
variable "aws_region" {
type = string
default = "us-east-1"
description = "AWS region"
}
variable "vpc_name" {
type = string
default = "main"
description = "Name of the VPC"
}
variable "vpc_cidr" {
type = string
default = "10.0.0.0/16"
description = "VPC CIDR block"
}
variable "public_subnet_cidr" {
type = string
default = "10.0.1.0/24"
description = "Public subnet CIDR"
}
variable "private_subnet_cidr" {
type = string
default = "10.0.2.0/24"
description = "Private subnet CIDR"
}
[FILE: outputs.tf]
output "vpc_id" {
value = aws_vpc.main.id
description = "VPC ID"
}
output "public_subnet_id" {
value = aws_subnet.public.id
description = "Public subnet ID"
}
output "private_subnet_id" {
value = aws_subnet.private.id
description = "Private subnet ID"
}
output "nat_gateway_ip" {
value = aws_eip.nat.public_ip
description = "NAT Gateway Elastic IP"
}
Example 2: Validation Pipeline for Structured Output
Input: AI-generated Ansible playbook (YAML)
Validation Step 1: Schema Validation
├─ Load expected schema (JSON schema for Ansible playbook)
├─ Validate YAML against schema
├─ Checks:
├─ Required fields present (name, hosts, tasks)
├─ Task format correct (each task has name, module)
├─ Handler format correct
└─ No unknown fields
├─ Result: PASS or FAIL (with specific errors)
Validation Step 2: Syntax Validation
├─ Check YAML is well-formed (parseable)
├─ Check indentation is correct
├─ Check quoting is correct
└─ Result: PASS or FAIL
Validation Step 3: Ansible-Specific Validation
├─ Check module names are valid (apt, service, copy, etc.)
├─ Check required module arguments are present
├─ Check variable references are valid
├─ Check handlers are referenced by tasks
└─ Result: PASS or FAIL
Validation Step 4: Content Validation
├─ Check playbook does what was requested
├─ Check it follows best practices (idempotent, clear names)
├─ Check it handles edge cases (error handling, restart on change)
└─ Result: PASS or FAIL (manual review)
Summary Report:
├─ Validation results: 4/4 PASSED
├─ Errors: 0
├─ Warnings: 1 (consider adding error handling)
├─ Ready to deploy: YES
If any validation fails:
├─ Report specific errors to AI
├─ AI generates revised playbook
├─ Repeat validation until all checks pass
Example 3: Structured Output in CI/CD Pipeline
GitHub Actions workflow:
name: AI-Generated Infrastructure Deployment
on: [push to main]
jobs:
generate:
runs-on: ubuntu-latest
steps:
- name: Generate Terraform config with Claude
run: |
curl https://api.anthropic.com/v1/messages \
-H "x-api-key: ${{ secrets.CLAUDE_API_KEY }}" \
-H "content-type: application/json" \
-d @prompt.json > generated_config.json
- name: Validate output schema
run: |
python validate_schema.py generated_config.json - name: Extract Terraform code
run: |
python extract_terraform.py generated_config.json > main.tf - name: Terraform format check
run: |
terraform fmt -check main.tf - name: Terraform validate
run: |
terraform init
terraform validate - name: Terraform plan
run: |
terraform plan -out=tfplan - name: Upload plan artifact
uses: actions/upload-artifact@v2
with:
name: terraform-plan
path: tfplan
review:
runs-on: ubuntu-latest
needs: generate
steps:
- name: Approve/Reject (manual gate)
uses: trstringer/manual-approval@v1
with:
secret: ${{ secrets.GITHUB_TOKEN }}
approvers: 'devops-team'
additional-token-permissions: 'contents:read'
deploy:
runs-on: ubuntu-latest
needs: review
steps:
- name: Download plan artifact
uses: actions/download-artifact@v2
with:
name: terraform-plan
- name: Terraform apply
run: |
terraform apply tfplan - name: Report success
run: |
echo "Infrastructure deployed successfully"
Result: Infrastructure generated by AI → Validated → Tested → Approved by human → Deployed automatically. Zero manual formatting/debugging.
Anti-Patterns
Anti-Pattern 1: Assuming Valid Output
You ask AI to generate code. It returns some output. You assume it's valid and deploy it.
But the code has syntax errors, missing required fields, or logic bugs.
Fix: Always validate before using. Even structured output needs validation.
Anti-Pattern 2: Not Providing Schema to AI
You ask AI to generate structured output, but don't define the schema.
AI guesses at the format. Output is inconsistent.
Fix: Provide schema/example to the AI. Show expected format.
Anti-Pattern 3: Treating Structured Output as Gold
Structured output is generated by AI. It's not inherently correct, just well-formatted.
Verify the content, not just the format.
Fix: Validate schema (format). But also validate content (does it match what was requested?).
Human Judgment Checkpoints
Have you defined the schema for structured output? Without schema, output is inconsistent.
Are you validating before use? Structure is good. Validation is better.
Is the AI generating the right content, not just right format? Validation catches syntax errors, not logic errors.
Can you integrate structured output into your pipeline? If not, what's blocking integration?
Key Takeaways
Structured output is machine-readable: JSON, YAML, code, not prose.
Schema definition is crucial: Define expected output format before asking AI to generate.
Validation catches errors early: Check schema, syntax, and content before using generated output.
Metadata is part of structured output: Include version, dependencies, validation status.
Integration into CI/CD enables automation: Generate → Validate → Test → Approve → Deploy. No manual steps.
Validation can be multi-layered: Schema validation, syntax validation, tool-specific validation, content validation.
Structured output reduces manual work: No copy-paste, no formatting fixes, no debugging syntax errors.
AI is part of the automation pipeline, not separate: AI generates code that's validated and deployed automatically.
Verify content even if format is correct: Well-formatted wrong code is still wrong.
Skill.re