ToolResult
Python · package toolnexus · SPEC §1 · python/src/toolnexus/types.py
@dataclassclass ToolResult: output: str is_error: bool metadata: Optional[dict[str, Any]] = NoneWhat every execute returns. Three fields, and the whole tool-calling loop is built on them:
output is the text handed back to the model, is_error says whether the call failed, and
metadata is free-form — except for one reserved key that turns a result into a suspension.
When to use it
Section titled “When to use it”Every time you write a tool. It is the return type of Tool.execute, so
you construct one on every code path — success, failure, and everything in between.
Why an error flag and not an exception
Section titled “Why an error flag and not an exception”output is always a str — it is what the model reads. Serialize structured data yourself
(json.dumps) rather than expecting the loop to do it, so you control exactly what the model sees.
Examples
Section titled “Examples”1. Success and failure on the same tool
Section titled “1. Success and failure on the same tool”from toolnexus import ToolResult
CONFIG = {"region": "eu-west-1"}
def read_config(key: str) -> ToolResult: if key not in CONFIG: # Recoverable: the model can read this and try another key. return ToolResult(output=f"No such config key: {key}", is_error=True) return ToolResult(output=CONFIG[key], is_error=False)
found = read_config("region")assert found.output == "eu-west-1"assert found.is_error is False
missing = read_config("nope")assert missing.is_error is True
print("ok:", found.output, "|", missing.output)2. Structured output and metadata
Section titled “2. Structured output and metadata”output must be a string, so serialize deliberately. metadata rides alongside for your code —
the model never sees it, which makes it the right place for bookkeeping.
from toolnexus import ToolResult
def search(q: str) -> ToolResult: hits = [ {"id": 1, "title": "Getting started"}, {"id": 2, "title": "Advanced usage"}, ] return ToolResult( # The model reads this. Make it legible, not just valid. output="\n".join(f"#{h['id']} {h['title']}" for h in hits), is_error=False, # Your code reads this. The model never sees it. metadata={"title": f"search: {q}", "count": len(hits), "ids": [h["id"] for h in hits]}, )
res = search("usage")assert res.metadata["count"] == 2assert res.metadata["ids"] == [1, 2]assert "Advanced usage" in res.output
print("ok:", res.metadata["title"])3. The reserved key — metadata["pending"] is a suspension
Section titled “3. The reserved key — metadata["pending"] is a suspension”metadata is free-form with one exception. A pending key holding a Request means “this tool
cannot finish until something out-of-band happens” — the loop parks the run instead of returning.
You rarely write this by hand; pending builds it for you.
from toolnexus import pending, auth_required, pending_of
# pending() returns a ToolResult carrying metadata["pending"] = Request.res = pending(kind="input", prompt="Which environment?")
assert res.is_error is True # a parked call is not a successreq = pending_of(res)assert req is not None, "pending_of reads the suspension back off the result"assert req.kind == "input"assert req.prompt == "Which environment?"assert req.id, "an id is generated as the correlation key"
# auth_required is sugar for the login case.auth = auth_required("https://example.com/login")assert pending_of(auth).kind == "authorization"assert pending_of(auth).url == "https://example.com/login"
# An ordinary result has no suspension.from toolnexus import ToolResultassert pending_of(ToolResult(output="done", is_error=False)) is None
print("ok:", req.kind, "|", pending_of(auth).kind)Fields
Section titled “Fields”| Field | Type | What it is |
|---|---|---|
output |
str |
The text handed to the model. Always a string — serialize structured data yourself. |
is_error |
bool |
Whether the call failed. Fed back to the model, not raised. |
metadata |
dict[str, Any] | None |
Free-form, for your code. Reserved: pending holds a §10 Request. |
See also
Section titled “See also”Tool— what returns thisToolContext— whatexecutereceivespending— build a suspending resultpending_of— read a suspension back off a result