Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,7 @@
"pages": [
"sdk/guides/hello-world",
"sdk/guides/custom-tools",
"sdk/guides/structured-output",
"sdk/guides/mcp",
"sdk/guides/skill",
"sdk/guides/plugins",
Expand Down
50 changes: 50 additions & 0 deletions sdk/guides/structured-output.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
---

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Look in the other examples. You can add a runnable example (and you had one n your PR). Follow what the other pages did :-)

title: Structured Output
description: Attach a schema to any tool so the LLM returns typed, validated fields alongside the tool's own arguments.
---

import RunExampleCode from "/sdk/shared-snippets/how-to-run-example.mdx";

Pass a Pydantic model (or a JSON Schema dict) as a tool's `response_schema`. Its fields are merged into the schema the LLM sees, so the model must populate them when it calls that tool, and the reply is validated on receipt — no prompting for a format, no output parsing.

```python
class ProjectFacts(BaseModel):
description: str = Field(description="One-paragraph description of the project.")
facts: list[str] = Field(description="Three concise, distinct facts.")


agent = Agent(
llm=llm,
tools=[Tool(name="FinishTool", params={"response_schema": ProjectFacts})],
)
```

The tool keeps its own arguments — `FinishTool` still takes `message`, now alongside `description` and `facts`. This works on any tool, including [custom](/sdk/guides/custom-tools) and [MCP](/sdk/guides/mcp) tools.

## Reading results

Resolved tools live on `agent.tools_map`. Use `parse_last_response()` for the most recent call, or `parse_response(action)` for a specific one:

```python
finish_tool = agent.tools_map["finish"]
facts = cast(ProjectFacts | None, finish_tool.parse_last_response(conversation.state.events))
```

`parse_last_response()` returns `None` if the tool has not been called. With a JSON Schema dict instead of a model, both methods return a validated `dict`.

<Note>
`parse_last_response()` re-reads the tool call, so it works after a conversation is persisted and reloaded. `action.structured_output` is in-memory only — it is not serialized with the event and comes back `None` after a round-trip, so prefer the parse methods.
</Note>

## Constraints

- **Reserved names.** A schema may not declare `kind`, `security_risk`, `structured_output`, or `summary`, nor reuse one of the tool's own field names (e.g. `message` on `FinishTool`). Both raise a `ValueError` when the tool is resolved.
- **One tool per spec.** A spec that resolves to a tool set is rejected; attach the schema to the individual tool instead.

## Ready-to-run Example

```python icon="python" expandable examples/01_standalone_sdk/56_structured_output.py
# content is auto-synced
```

<RunExampleCode path_to_script="examples/01_standalone_sdk/56_structured_output.py"/>
Loading