Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Python Structured Output

Structured output uses the Rust runtime’s output loop and Python ergonomic helpers for schema construction, validators, and final-output functions.

Output Schemas

Build OutputSchema from a Pydantic model:

from pydantic import BaseModel

from starweaver import OutputPolicy, OutputSchema, create_agent
from starweaver.testing import TestModel


class Answer(BaseModel):
    answer: str


async def run_structured() -> None:
    agent = create_agent(
        model=TestModel.text('{"answer":"Paris"}'),
        output_policy=OutputPolicy.structured(OutputSchema.from_pydantic(Answer)),
    )
    result = await agent.run("answer")
    assert result.structured_output == {"answer": "Paris"}

Use OutputSchema(name, schema, description=..., strict=...) for hand-written JSON Schema.

Output Modes

OutputPolicy selects the model-facing output mode:

  • OutputPolicy.text() for plain text.
  • OutputPolicy.structured(schema) for structured output.
  • OutputPolicy.auto(schema) for provider-chosen structured output.
  • OutputPolicy.native_json_schema(schema) for native JSON schema mode.
  • OutputPolicy.native_json_object(schema) for native JSON object mode.
  • OutputPolicy.tool(schema) for tool-based output.
  • OutputPolicy.tool_or_text(schema) when text fallback is allowed.
  • OutputPolicy.prompted(schema) for prompt-only structured output.
  • OutputPolicy.image() for image output flows.

Validators

Use validators when the model output parses but fails application rules. Raise OutputRetry to request another model turn within the policy retry budget.

from pydantic import BaseModel

from starweaver import OutputPolicy, OutputRetry, OutputSchema, create_agent
from starweaver.testing import TestModel


class Answer(BaseModel):
    answer: str


def require_paris(output: dict[str, object]) -> None:
    if output["answer"] != "Paris":
        raise OutputRetry("answer must be Paris")


async def validate_answer() -> None:
    policy = (
        OutputPolicy.structured(OutputSchema.from_pydantic(Answer))
        .with_validator(require_paris)
        .with_retries(1)
    )
    agent = create_agent(
        model=TestModel.responses(
            [{"text": '{"answer":"London"}'}, {"text": '{"answer":"Paris"}'}]
        ),
        output_policy=policy,
    )
    result = await agent.run("answer")
    assert result.structured_output == {"answer": "Paris"}

Validators can accept an OutputContext first parameter when they need runtime metadata.

Output Functions

OutputFunction exposes a final-output function to the model. The callback can return a string for text output, a JSON-serializable value for structured output, or OutputValue.text() / OutputValue.json() when the boundary should be explicit.

from starweaver import OutputFunction, OutputPolicy, create_agent
from starweaver.testing import TestModel


def final_answer(args: dict[str, object]) -> dict[str, object]:
    return {"answer": args["answer"]}


async def run_output_function() -> None:
    final = OutputFunction(
        "final_answer",
        {
            "type": "object",
            "properties": {"answer": {"type": "string"}},
            "required": ["answer"],
        },
        final_answer,
    )
    agent = create_agent(
        model=TestModel.responses(
            [
                TestModel.tool_call_response(
                    [
                        {
                            "id": "call_final",
                            "name": "final_answer",
                            "arguments": {"answer": "Paris"},
                        }
                    ]
                )
            ]
        ),
        output_policy=OutputPolicy().with_function(final),
    )
    result = await agent.run("answer")
    assert result.structured_output == {"answer": "Paris"}

Per-Run Output

Output policy can be attached at agent construction or per run:

result = await agent.run(
    "return JSON",
    output_policy=OutputPolicy.structured(OutputSchema.from_pydantic(Answer)),
)

Use per-run output when one agent serves multiple endpoint shapes.