Using AI to automate pull request reviews, generate release notes, or triage issues is powerful โ but plain text output is fragile in CI. One malformed response and your pipeline breaks. The fix? Structured outputs. By forcing the AI to return JSON that matches a schema, you can parse, validate, and act on it with confidence.
This tutorial shows you a practical pattern: send a well-crafted prompt, request a strict JSON response, validate it against a schema, and use the data in your CI workflow. You'll build this with Python and GitHub Actions, but the same approach applies to any language or CI platform.
Step 1: Define your output schema
First, create a schema that describes exactly what you want from the AI. Python's Pydantic makes this easy and gives you built-in validation.
from pydantic import BaseModel, ValidationError
class PRSummary(BaseModel):
title: str
description: str
changed_files: list[str]
risk_level: Literal["low", "medium", "high"]
review_comments: list[dict] # each dict has 'file' and 'comment'
Step 2: Prompt for strict JSON output
Now tell the AI to answer in JSON only. Be explicit about the fields and format. Include the schema in the prompt.
prompt = f"""You are a code reviewer. Analyze the following diff.
Return ONLY valid JSON matching this exact structure:
{{
"title": "short summary",
"description": "2-3 sentences",
"changed_files": ["file1.py", ...],
"risk_level": "low|medium|high",
"review_comments": [{{"file": "path", "comment": "note"}}]
}}
Diff:
{diff}
"""
Step 3: Extract and parse the JSON
In your CI script, strip any false formatting and parse the response. Use the schema to validate immediately.
import json, re
def parse_ai_response(raw: str) -> PRSummary:
# Remove code fences if present
raw = re.sub(r"^```(?:json)?|```$", "", raw.strip())
try:
data = json.loads(raw)
return PRSummary(**data)
except (json.JSONDecodeError, ValidationError) as e:
raise SystemExit(f"AI output failed validation: {e}")
Step 4: Wire it into CI
Create a GitHub Actions job that runs the AI call, parses the result, and posts a PR comment.
name: ai-review
on: pull_request
jobs:
review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with: python-version: '3.12'
- run: pip install pydantic requests
- name: Run AI script
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
run: |
python scripts/generate_pr_summary.py \
--diff-file changed_files.txt \
--output summary.json
- name: Parse and validate output
run: python scripts/parse_ai_output.py summary.json
If the AI fails to produce valid JSON, the script exits with a non-zero code and the job fails. That's a feature โ you want to catch it early rather than ship a bad automation step.
Why this works
Structured outputs move the risk from 'garbage text' to 'valid JSON.' You can always fall back, retry, or log a clean error. Combined with a schema, the AI becomes a reliable API โ perfect for autonomous workflows.
Start with one simple use case today. Define a schema, prompt for JSON, validate, and only then take action. Your CI will thank you.
Curious how you handle schema drift when the model returns valid JSON but with unexpected fields. We've been using Pydantic validators but still see edge cases.