answer_output
Python · package toolnexus · SPEC §10 · python/src/toolnexus/types.py
def answer_output(id: str, output: str) -> AnswerWraps a human’s typed string reply into the Answer a suspended run resumes with — the success
counterpart to answer_declined. Builds
Answer(id=id, ok=True, data={"output": output}) — the one recognised key under Answer.data
for a plain-text resolution, so a wait_for implementation stops guessing whether the payload
lives at value, answers, or output. output must be a str; a non-string raises
TypeError immediately rather than silently degrading to "" and handing the model a
fabricated result under a done status.
When to use it
Section titled “When to use it”- Your
wait_forresolves apending(kind="input", ...)orpending(kind="approval", ...)suspension with a human’s typed answer — “which environment?”, “what’s the order number?” — and you want the recognised, cross-portAnswer.data["output"]shape rather than inventing your own key. - You are wiring a durable queue or a chat UI’s reply back into a suspended run: the operator’s
reply text is exactly what
answer_outputwraps. - You are unit-testing a tool’s
ctx.answerhandling without a real human in the loop — passwait_for=lambda req: answer_output(req.id, "staging")and assert the tool reads it back.
Why this and not the alternative
Section titled “Why this and not the alternative”Examples
Section titled “Examples”1. The smallest useful call — build the Answer directly
Section titled “1. The smallest useful call — build the Answer directly”from toolnexus import answer_output
a = answer_output("pnd-1", "staging")
assert a.ok is Trueassert a.id == "pnd-1"assert a.data == {"output": "staging"} # the one recognised key — no guessing
print("ok:", a.id, "|", a.data)2. The realistic case — a non-string output errors loudly, not silently
Section titled “2. The realistic case — a non-string output errors loudly, not silently”from toolnexus import answer_output
try: answer_output("pnd-1", {"value": "staging"}) # type: ignore[arg-type] raise AssertionError("expected TypeError")except TypeError: pass
# The correct call always carries a plain str:a = answer_output("pnd-1", "staging")assert isinstance(a.data["output"], str)
print("ok: non-string output rejected before it ever reaches the tool")3. The full surface — a suspended run resumes through wait_for
Section titled “3. The full surface — a suspended run resumes through wait_for”import asyncio
from toolnexus import ToolResult, answer_output, create_client, create_toolkit, define_tool, pending
class RecordingTransport: def __init__(self, responses): self._responses = list(responses)
def post(self, url, headers, payload, timeout): return self._responses.pop(0)
def open(self, url, headers, payload, timeout): # noqa: A003 raise NotImplementedError
seen = {}
async def ask_human(args=None, ctx=None): if ctx is not None and ctx.answer is not None: seen["answer"] = ctx.answer return ToolResult(output=ctx.answer.data["output"], is_error=False) return pending(kind="input", prompt="which environment?")
async def main(): transport = RecordingTransport([ { "choices": [{ "message": { "role": "assistant", "content": None, "tool_calls": [{"id": "c1", "type": "function", "function": {"name": "ask_human", "arguments": "{}"}}], }, }], "usage": {"total_tokens": 5}, }, {"choices": [{"message": {"role": "assistant", "content": "ok"}}], "usage": {"total_tokens": 5}}, ]) tk = await create_toolkit(builtins=False, extra_tools=[ define_tool(ask_human, name="ask_human", description="asks") ]) try: client = create_client( base_url="http://mock.local/v1", style="openai", model="mock", api_key="unused", http_transport=transport, wait_for=lambda req: answer_output(req.id, "staging"), ) result = await client.run("go", tk)
assert result.status == "done" assert seen["answer"].data["output"] == "staging"
print("ok:", seen["answer"].data) finally: await tk.close()
asyncio.run(main())See also
Section titled “See also”pending— Return a Pending from a tool to park the run until someone answers.answer_declined— The refusal counterpart: wraps a decline/cancel/expiry into the sameAnswershape.auth_required— The auth-shaped suspension: hand back a URL, resume once the user has granted access.WaitFor— The single hook where the host resolves a suspension — in-process prompt or durable queue, same contract.pending_of— Detect that a RunResult is parked rather than finished, and get the Request that parked it.