Building AI Agents from Scratch

Structured Outputs

Force a JSON object your application can consume instead of parsing prose.

A UI, a ticket, or a test cannot reliably parse “Sure! I found three files…” Ask the model for an object. Validate it before you trust it.

Schema on the last call

When the user wants a machine-readable result, either (a) add a submit_result tool whose arguments are the schema, or (b) use the host’s structured-output / response_format JSON schema if the compatible API supports it. The tool trick works everywhere Chat Completions tools work.

RESULT_TOOL = {
    "type": "function",
    "function": {
        "name": "submit_result",
        "description": "Submit the final structured answer. Call this once when you are done.",
        "parameters": {
            "type": "object",
            "properties": {
                "summary": {"type": "string"},
                "files": {"type": "array", "items": {"type": "string"}},
                "ok": {"type": "boolean"},
            },
            "required": ["summary", "files", "ok"],
        },
    },
}


def submit_result(summary: str, files: list, ok: bool) -> dict:
    return {"ok": True, "accepted": True, "summary": summary, "files": files[:20]}

Stop the loop when submit_result runs successfully. Return that dict to your application, not the last assistant paragraph. If the model rambles instead of calling it, nudge once in a follow-up user message, then fail.

Validate again

The schema is a hint. Check types yourself. Drop extra keys. If files contains paths your tools never saw, mark ok false — the model invented them.

Check it

Run a scout question and print the dict from submit_result. Parse it with json.loads in a two-line test. If you have to use a regex on the assistant text, the lesson is not done. Next: block writes until a human says yes.