docs(sdk): add structured output guide - #695
Open
luciobaiocchi wants to merge 3 commits into
Open
Conversation
Documents the response_schema mechanism landed in OpenHands/software-agent-sdk#4207: attaching a Pydantic model or JSON Schema to any tool spec, reading typed results via parse_response / parse_last_response, the raw JSON Schema form, and the constraints (reserved field names, one tool per spec, round-trip to dict).
VascoSch92
reviewed
Aug 7, 2026
| result = cast(ProjectFacts, finish_tool.parse_response(event.action)) | ||
| ``` | ||
|
|
||
| The values also live on the action itself as `action.structured_output` (a plain dict), which is what gets persisted with the event. |
Member
There was a problem hiding this comment.
The claim that action.structured_output "is what gets persisted with the event" is incorrect. It's a PrivateAttr/property excluded from event serialization, so it comes back None after any persist/reload round-trip
Worth correcting so readers don't rely on this field surviving persistence.
| ## Constraints | ||
|
|
||
| <Warning> | ||
| **Reserved field names.** A response schema may not declare `kind`, `security_risk`, `structured_output`, or `summary`. The SDK injects those onto every action, so a schema using them is rejected with a `ValueError` when the tool is resolved — at configuration time, not mid-run. |
Member
There was a problem hiding this comment.
a response_schema field that shares a name with the underlying tool's own action field also raises a ValueError at resolution time.
| @@ -0,0 +1,124 @@ | |||
| --- | |||
Member
There was a problem hiding this comment.
Look in the other examples. You can add a runnable example (and you had one n your PR). Follow what the other pages did :-)
- fix the persistence claim: structured_output is a PrivateAttr excluded from event serialization, so it is None after a reload; parse_last_response re-reads the tool call and does survive - note that a schema field clashing with the tool's own field also raises at resolution time, not just the reserved meta names - follow the other guides: add a Ready-to-run Example block backed by examples/01_standalone_sdk/56_structured_output.py - trim the prose throughout (124 -> 50 lines)
5 tasks
VascoSch92
approved these changes
Aug 10, 2026
Observed while running the example: the model may attempt to send the schema fields when calling other tools, which are rejected as unexpected arguments before the agent retries.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Documents the
response_schemastructured-output mechanism landed inOpenHands/software-agent-sdk#4207 (merged as
da03ee40). The feature shippedwithout a docs page — the example that would have carried one was deferred out
of that PR, so there is currently nothing in the guides describing it.
Summary
Adds
sdk/guides/structured-output.mdxand registers it indocs.jsonnext toCustom Tools (both are tool-system guides).
The guide covers:
Tool(name=..., params={"response_schema": ...})parse_last_response()/parse_response()offagent.tools_map, plusaction.structured_outputFinishTool) — e.g. forcing a rationale onevery terminal command
dictinstead of a modelinstance
kind,security_risk,structured_output,summary), one tool per spec (tool sets are rejected),strict validation
<Note>on the persistence/remote round-trip, where a Pydantic schema isserialized to JSON Schema and
parse_response()then returns a dictNotes
Every code claim in the guide was executed against the merged SDK before
writing, rather than inferred from the diff: schema fields merge while the tool
keeps its own arguments,
action.kindstaysFinishAction(no dynamic subclassleaks into the event log),
structured_outputcarries the values,parse_response()returns the typed model for a Pydantic schema and a validateddict for a JSON Schema, and all four reserved field names are rejected at
resolve time.
No
expandable examples/...code block is used, since there is nostructured-output example file in the SDK for
sync_code_blocks.pyto syncfrom. If an example lands later, the blocks can be converted to the synced form.
Branched off current
main(9a171cf), so the diff is just the new guide plusthe one navigation line.