A Hands-On Guide to Anthropic’s Recent Structured Output Capabilities

-

announced Structured Outputs for its top models in its API, a brand new feature designed to be sure that model-generated outputs exactly match the JSON Schemas provided by developers. 

This solves an issue many developers face when a system or process consumes an LLM’s output for further processing. It’s essential for that system to “know” what to anticipate as its input so it may possibly process it accordingly. 

Similarly, when displaying model output to a user, you wish this to be in the identical format every time. 

To date, it’s been a pain to make sure consistent output formats from Anthropic models. Nonetheless, it seems that Anthropic has now solved this problem for its top models anyway. From their announcement (linked at the tip of the article), they are saying, 

The Claude Developer Platform now supports structured outputs for Claude Sonnet 4.5 and Opus 4.1. Available in public beta, this feature ensures API responses at all times match your specified JSON schemas or tool definitions. 

Now, one thing to recollect before we take a look at some example code, is that Anthropic guarantees that the model’s output will adhere to a specified format, that any output will probably be 100% accurate. The models can and should hallucinate occasionally. 

So you may get perfectly formatted incorrect answers! 

Establishing our dev environment 

Before we take a look at some sample Python code, it’s best practice to create a separate development environment where you’ll be able to install any mandatory software and experiment with coding. Now, anything you do on this environment will probably be siloed and won’t impact any of your other projects. 

I’ll be using Miniconda for this, but you should use whatever method you’re most aware of. 

If you wish to go down the Miniconda route and don’t have already got it, you need to install it first. Get it using this link:

https://docs.anaconda.com/miniconda/ 

To follow together with my examples, you’ll also need an Anthropic API key and a few credit in your account. For reference, I used 12 cents to run the code in this text. If you happen to have already got an Anthropic account, you’ll be able to get an API key using the Anthropic console at https://console.anthropic.com/settings/keys.  

1/ Create our recent dev environment and install the required libraries 

this on WSL2 Ubuntu for Windows.

(base) $ conda create -n anth_test python=3.13 -y 
(base) $ conda activate anth_test 
(anth_test) $ pip install anthropic beautifulsoup4 requests 
(anth_test) $ pip install httpx jupyter 

2/ Start Jupyter 

Now type in ‘jupyter notebook’ into your command prompt. You need to see a jupyter notebook open in your browser. If that doesn’t occur robotically, you’ll likely see a screenful of data after the command. Near the underside, you’ll discover a URL to repeat and paste into your browser. It’ll look much like this:

http://127.0.0.1:8888/tree?token=3b9f7bd07b6966b41b68e2350721b2d0b6f388d248cc69

Code Examples

In our two coding examples, we’ll use the brand new output_format parameter available within the beta API. When specifying the structured output, we will use two different styles.

1. Raw JSON Schema.

Because the name suggests, the structure is defined by a JSON schema block passed on to the output format definition.

2. A Pydantic model class.

It is a regular Python class using Pydantic’s BaseModel that specifies the info we wish the model to output. It’s a far more compact solution to define a structure than a JSON schema.

Example code 1 — Text summarisation 

This is beneficial if you’ve a bunch of various texts you wish to summarise, but want the summaries to have the identical structure. In this instance, we’ll process the Wikipedia entries for some famous scientists and retrieve specific key facts about them in a highly organised way.

In our summary, we wish to output the next structure for every scientist,

  • The name of the Scientist
  • When and where they were born
  • Their most important claim to fame
  • The 12 months they won the Nobel Prize
  • When and where they died

Note: Most text in Wikipedia, excluding quotations, has been released under the Creative Commons Attribution-Sharealike 4.0 International License (CC-BY-SA) and the GNU Free Documentation License (GFDL) In brief which means you might be free:

to Share — copy and redistribute the fabric in any medium or format

to Adapt — remix, transform, and construct upon the fabric

for any purpose, even commercially.

Let’s break the code into manageable sections, each with an evidence.

First, we import the required third-party libraries and arrange connections to Anthropic using our API Key.

import anthropic
import httpx
import requests
import json
import os
from bs4 import BeautifulSoup

http_client = httpx.Client()
api_key = 'YOUR_API_KEY'
client = anthropic.Anthropic(
    api_key=api_key,
    http_client=http_client
)

That is the function that may scrape Wikipedia for us.

def get_article_content(url):
    try:
        headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'}
        response = requests.get(url, headers=headers)
        soup = BeautifulSoup(response.content, "html.parser")
        article = soup.find("div", class_="mw-body-content")
        if article:
            content = "n".join(p.text for p in article.find_all("p"))
            return content[:15000] 
        else:
            return ""
    except Exception as e:
        print(f"Error scraping {url}: {e}")
        return ""

Next, we define our JSON schema, which specifies the precise format for the model’s output.

summary_schema = {
    "type": "object",
    "properties": {
        "name": {"type": "string", "description": "The name of the Scientist"},
        "born": {"type": "string", "description": "When and where the scientist was born"},
        "fame": {"type": "string", "description": "A summary of what their most important claim to fame is"},
        "prize": {"type": "integer", "description": "The 12 months they won the Nobel Prize. 0 if none."},
        "death": {"type": "string", "description": "When and where they died. 'Still alive' if living."}
    },
    "required": ["name", "born", "fame", "prize", "death"],
    "additionalProperties": False
}

This function serves because the interface between our Python script and the Anthropic API. Its primary goal is to take unstructured text (an article) and force the AI to return a structured data object (JSON) containing specific fields, akin to the scientist’s name, birth date, and Nobel Prize details.

The function calls client.messages.create to send a request to the model. It sets the temperature to 0.2, which lowers the model’s creativity to make sure the extracted data is factual and precise. The extra_headers parameter enables a selected beta feature that just isn’t yet standard. By passing the anthropic-beta header with the worth structured-outputs-2025-11-13, the code tells the API to activate the Structured Outputs logic for this specific request, forcing it to supply valid JSON that matches your defined structure.

Since the output_format parameter is used, the model returns a raw string that’s guaranteed to be valid JSON. The road json.loads(response.content[0].text) parses this string right into a native Python dictionary, making the info immediately ready for programmatic use.

def get_article_summary(text: str):
    if not text: return None
    
    try:
        response = client.messages.create(
            model="claude-sonnet-4-5", # Use the newest available model
            max_tokens=1024,
            temperature=0.2,
            messages=[
                {"role": "user", "content": f"Summarize this article:nn{text}"}
            ],
            # Enable the beta feature
            extra_headers={
                "anthropic-beta": "structured-outputs-2025-11-13"
            },
            # Pass the brand new parameter here
            extra_body={
                "output_format": {
                    "type": "json_schema",
                    "schema": summary_schema
                }
            }
        )

        # The API returns the JSON directly within the text content
        return json.loads(response.content[0].text)

    except anthropic.BadRequestError as e:
        print(f"API Error: {e}")
        return None
    except Exception as e:
        print(f"Error: {e}")
        return None

That is where we pull the whole lot together. The varied URLs we wish to scrape are defined. Their contents are passed to the model for processing, before the tip results are displayed.

urls = [
    "https://en.wikipedia.org/wiki/Albert_Einstein",
    "https://en.wikipedia.org/wiki/Richard_Feynman",
    "https://en.wikipedia.org/wiki/James_Clerk_Maxwell",
    "https://en.wikipedia.org/wiki/Alan_Guth"
]

print("Scraping and analyzing articles...")

for i, url in enumerate(urls):
    print(f"n--- Processing Article {i+1} ---")
    content = get_article_content(url)
    
    if content:
        summary = get_article_summary(content)
        if summary:
            print(f"Scientist: {summary.get('name')}")
            print(f"Born:      {summary.get('born')}")
            print(f"Fame:      {summary.get('fame')}")
            print(f"Nobel:     {summary.get('prize')}")
            print(f"Died:      {summary.get('death')}")
        else:
            print("Did not generate summary.")
    else:
        print("Skipping (No content)")

print("nDone.")

After I ran the above code, I got this output.

Scraping and analyzing articles...

--- Processing Article 1 ---
Scientist: Albert Einstein
Born:      14 March 1879 in Ulm, Kingdom of Württemberg, German Empire
Fame:      Developing the speculation of relativity and the mass-energy equivalence formula E = mc2, plus contributions to quantum theory including the photoelectric effect
Nobel:     1921
Died:      18 April 1955

--- Processing Article 2 ---
Scientist: Richard Phillips Feynman
Born:      May 11, 1918, in Recent York City
Fame:      Path integral formulation of quantum mechanics, quantum electrodynamics, Feynman diagrams, and contributions to particle physics including the parton model
Nobel:     1965
Died:      February 15, 1988

--- Processing Article 3 ---
Scientist: James Clerk Maxwell
Born:      13 June 1831 in Edinburgh, Scotland
Fame:      Developed the classical theory of electromagnetic radiation, unifying electricity, magnetism, and lightweight through Maxwell's equations. Also key contributions to statistical mechanics, color theory, and diverse other fields of physics and arithmetic.
Nobel:     0
Died:      5 November 1879

--- Processing Article 4 ---
Scientist: Alan Harvey Guth
Born:      February 27, 1947 in Recent Brunswick, Recent Jersey
Fame:      Pioneering the speculation of cosmic inflation, which proposes that the early universe underwent a phase of exponential expansion driven by positive vacuum energy density
Nobel:     0
Died:      Still alive

Done.

Not too shabby! Alan Guth will probably be delighted that he’s still alive, but alas, he hasn’t yet won a Nobel Prize. Also, note that James Clerk Maxwell had died before the Nobel Prize was in operation.

Example code 2 — Automated Code Security & Refactoring Agent.

Here is a totally different use case and a really practical example for software engineering. Normally, while you ask an LLM to “fix code,” it gives you a conversational response mixed with code blocks. This makes it hard to integrate right into a CI/CD pipeline or an IDE plugin.

Through the use of Structured Outputs, we will force the model to return the clean code, a list of specific bugs found, and a security risk assessment in a single, machine-readable JSON object.

The Scenario

We’ll feed the model a Python function containing a dangerous SQL Injection vulnerability and poor coding practices. The model must discover the precise flaws and rewrite the code securely.

import anthropic
import httpx
import os
import json
from pydantic import BaseModel, Field, ConfigDict
from typing import List, Literal

# --- SETUP ---
http_client = httpx.Client()
api_key = 'YOUR_API_KEY'
client = anthropic.Anthropic(api_key=api_key, http_client=http_client)

# Intentionally bad code
bad_code_snippet = """
import sqlite3

def get_user(u):
    conn = sqlite3.connect('app.db')
    c = conn.cursor()
    # DANGER: Direct string concatenation
    query = "SELECT * FROM users WHERE username = '" + u + "'"
    c.execute(query)
    return c.fetchall()
"""

# --- DEFINE SCHEMA WITH STRICT CONFIG ---
# We add model_config = ConfigDict(extra="forbid") to make sure 
# "additionalProperties": false is generated within the schema.

class BugReport(BaseModel):
    model_config = ConfigDict(extra="forbid") 
    
    severity: Literal["Low", "Medium", "High", "Critical"]
    line_number_approx: int = Field(description="The approximate line number where the difficulty exists.")
    issue_type: str = Field(description="e.g., 'Security', 'Performance', 'Style'")
    description: str = Field(description="Short explanation of the bug.")

class CodeReviewResult(BaseModel):
    model_config = ConfigDict(extra="forbid") 
    
    is_safe_to_run: bool = Field(description="True provided that no Critical/High security risks exist.")
    detected_bugs: List[BugReport]
    refactored_code: str = Field(description="The whole, fixed Python code string.")
    explanation: str = Field(description="A temporary summary of changes made.")

# --- API CALL ---
try:
    print("Analyzing code for security vulnerabilities...n")
    
    response = client.messages.create(
        model="claude-sonnet-4-5", 
        max_tokens=2048,
        temperature=0.0,
        messages=[
            {
                "role": "user", 
                "content": f"Review and refactor this Python code:nn{bad_code_snippet}"
            }
        ],
        extra_headers={
            "anthropic-beta": "structured-outputs-2025-11-13"
        },
        extra_body={
            "output_format": {
                "type": "json_schema",
                "schema": CodeReviewResult.model_json_schema()
            }
        }
    )

    # Parse Result
    result = json.loads(response.content[0].text)

    # --- DISPLAY OUTPUT ---
    print(f"Secure to Run: {result['is_safe_to_run']}")
    print("-" * 40)
    
    print("BUGS DETECTED:")
    for bug in result['detected_bugs']:
        # Color code the severity (Red for Critical)
        prefix = "🔴" if bug['severity'] in ["Critical", "High"] else "🟡"
        print(f"{prefix} [{bug['severity']}] Line {bug['line_number_approx']}: {bug['description']}")

    print("-" * 40)
    print("REFACTORED CODE:")
    print(result['refactored_code'])

except anthropic.BadRequestError as e:
    print(f"API Schema Error: {e}")
except Exception as e:
    print(f"Error: {e}")

This code acts as an automatic security auditor. As an alternative of asking the AI to “chat” about code, it forces the AI to fill out a strict, digital form containing specific details about bugs and security risks.

Here is how it really works in three easy steps.

  1. First, the code defines exactly what the reply must appear like using Python classes at the side of Pydantic. It tells the AI: “Give me a JSON object containing an inventory of bugs, a severity rating (like ‘Critical’ or ‘Low’) for every, and the fixed code string.”
  2. When sending the vulnerable code to the API, it passes the Pydantic blueprint using the output_format parameter. This strictly constrains the model, stopping it from hallucinating or adding conversational filler. It return valid data matching your blueprint.
  3. The script receives the AI’s response, which is guaranteed to be machine-readable JSON. It then robotically parses this data to display a clean report, flagging the SQL injection as a “Critical” issue for instance and printing the secure, refactored version of the code.

Here is the output I received after running the code.

Analyzing code for security vulnerabilities...

Secure to Run: False
----------------------------------------
BUGS DETECTED:
🔴 [Critical] Line 7: SQL injection vulnerability resulting from direct string concatenation in query construction. Attacker can inject malicious SQL code through the username parameter.
🟡 [Medium] Line 4: Database connection and cursor usually are not properly closed, resulting in potential resource leaks.
🟡 [Low] Line 1: Function parameter name 'u' just isn't descriptive. Should use meaningful variable names.
----------------------------------------
REFACTORED CODE:
import sqlite3
from contextlib import closing

def get_user(username):
    """
    Retrieve user information from the database by username.
    
    Args:
        username (str): The username to go looking for
        
    Returns:
        list: List of tuples containing user data, or empty list if not found
    """
    with sqlite3.connect('app.db') as conn:
        with closing(conn.cursor()) as cursor:
            # Use parameterized query to stop SQL injection
            query = "SELECT * FROM users WHERE username = ?"
            cursor.execute(query, (username,))
            return cursor.fetchall()

Why is that this powerful?

Integration-ready. You may run this script in a GitHub Motion. If is_safe_to_run is False, you’ll be able to robotically block a Pull Request.

Separation of concerns. You get the metadata (bugs, severity) separate from the content (the code). You don’t should use to strip out “Here is your fixed code” text from the response.

Strict typing. The severity field is constrained to specific Enum values (Critical, High, etc.), ensuring your downstream logic doesn’t break when the model returns “Severe” as an alternative of “Critical” for instance.

Summary

Anthropic’s release of native Structured Outputs is a game-changer for developers who need reliability, not only conversation. By enforcing strict JSON schemas, we will now treat Large Language Models less like chatbots and more like deterministic software components.

In this text, I demonstrated how one can use this recent beta feature to streamline data extraction and output, and construct automated workflows that integrate seamlessly with Python code. If you happen to’re a user of Anthropic’s API, the times of writing fragile to parse AI responses are finally over.

For more details about this recent beta feature, click the link below to go to Anthropics’ official documentation page.

https://platform.claude.com/docs/en/build-with-claude/structured-outputs

ASK ANA

What are your thoughts on this topic?
Let us know in the comments below.

0 0 votes
Article Rating
guest
0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments

Share this article

Recent posts

0
Would love your thoughts, please comment.x
()
x