When integrating AI into CI/CD, one pain point is parsing free-text responses. Structured outputs solve this by forcing the AI to return valid JSON matching a schema. This tutorial shows you how to use OpenAI's structured outputs feature to get predictable code review comments that your CI pipeline can parse automatically.

This tutorial assumes you have a GitHub Actions or GitLab CI pipeline and an OpenAI API key. You'll need Python 3.8+ installed.

Step 1: Define Your Schema

First, decide what data you need from the AI. For a code review, we want a list of comments with file path, line number, severity, and description. Use Pydantic to define the schema:

from pydantic import BaseModel
from typing import List, Literal

class CodeReviewComment(BaseModel):
    file_path: str
    line_number: int
    severity: Literal["info", "warning", "critical"]
    description: str

class CodeReview(BaseModel):
    comments: List[CodeReviewComment]

Step 2: Call OpenAI with Structured Outputs

Now create a function that sends the diff (or code snippet) to OpenAI and expects a structured response. Use the response_format parameter with your schema:

import openai
from openai import OpenAI

client = OpenAI(api_key="YOUR_API_KEY")

def review_code(diff_text: str) -> CodeReview:
    response = client.beta.chat.completions.parse(
        model="gpt-4o-2024-08-06",
        messages=[
            {
                "role": "system",
                "content": "You are a senior code reviewer. Review the following diff and provide structured comments."
            },
            {
                "role": "user",
                "content": f"Review this diff:\n\n{diff_text}"
            }
        ],
        response_format=CodeReview
    )
    return response.choices[0].message.parsed
Make sure your schema is correct – if the AI can't generate a valid response, it will return an error. Always handle exceptions.

Step 3: Parse and Act on the Output

Now that you have a structured CodeReview object, you can iterate over comments and post them as PR annotations. Here's an example that outputs in GitHub Actions format:

if __name__ == "__main__":
    import sys
    diff_path = sys.argv[1]
    with open(diff_path, "r") as f:
        diff = f.read()
    review = review_code(diff)
    for comment in review.comments:
        print(f"::warning file={comment.file_path},line={comment.line_number}::{comment.description}")

Step 4: Integrate into CI Pipeline (GitHub Actions Example)

Create a workflow that runs on pull requests:

name: AI Code Review
on:
  pull_request:
    types: [opened, synchronize]
jobs:
  review:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - name: Get diff
        run: git diff origin/main -- . > diff.txt
      - name: Run AI review
        env:
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
        run: |
          pip install openai pydantic
          python review.py diff.txt
Test this locally first. Create a small diff file and run the script to verify the annotations appear as expected.

Why This Works

By using structured outputs, you get a guaranteed JSON structure – no regex parsing, no markdown extraction. The CI pipeline can directly read the fields and create precise annotations. This pattern works for any AI task where you need machine-readable responses.

You now have a repeatable pipeline that automatically reviews code and posts structured feedback. Extend it by adding more severity levels or integrating with your project's linting rules.