From Chat to Agent¶
Ask a chatbot to summarize README.md and it can produce a plausible answer.
It cannot read the project unless the application gives it that capability.
An Axio Agent with tools=[] can only exchange messages with its transport.
You do not need a new loop. You need to add one controlled action to Axio’s
existing loop.
Outcome¶
You will build a project-document assistant that handles one read_document
tool call and then returns a final answer.
Fast Track¶
Construct
Agent(..., tools=[])and observe the chat-only boundary.Write one plain async
read_documenthandler.Wrap the handler in
Tooland pass it to the same Agent.Use
StubTransportto verify the tool round trip exactly.
Hands-on delta¶
1. Start without tools¶
The transport below returns one text response. The Agent has no action it can offer before that response ends.
import asyncio
from axio import (
Agent,
CompletionTransport,
MemoryContextStore,
Tool,
ToolResultBlock,
)
from axio.testing import StubTransport, make_text_response, make_tool_use_response
async def try_chat() -> None:
transport = StubTransport(
[
make_text_response("I cannot read README.md without a tool."),
]
)
agent = Agent(
system="Answer questions about project documents. Use facts from tools.",
transport=transport,
tools=[],
)
answer = await agent.run("Summarize README.md.", MemoryContextStore())
assert answer == "I cannot read README.md without a tool."
assert agent.tools == []
print(f"chat-only: {answer}")
With a live transport, the model might refuse, guess, or explain what it would do. None of those responses can contain newly retrieved document data.
2. Add one async handler¶
A handler is ordinary application code. It does not know about messages, provider formats, or the agent loop.
Keep the small DOCUMENTS mapping from the downloaded snapshot. Add the
handler, wrap it as a tool, and register that tool on the Agent:
async def read_document(path: str) -> str:
"""Return one project document summary from its exact path."""
document = DOCUMENTS.get(path)
if document is None:
return f"Document {path} was not found."
return f"{path}: {document['summary']}"
read_document_tool = Tool(name="read_document", handler=read_document)
def build_agent(transport: CompletionTransport) -> Agent:
return Agent(
system="Answer questions about project documents. Use tools for facts.",
transport=transport,
tools=[read_document_tool],
)
Tool gives the handler a stable model-facing name. It also derives a schema
from the Python signature and uses the docstring as the default description.
Read Tool System for the complete Tool contract.
3. Notice the missing loop code¶
Your harness did not implement while, parse tool-call JSON, or append a tool
result manually.
sequenceDiagram
participant H as Harness
participant A as Axio Agent
participant T as Transport
participant G as read_document Tool
H->>A: run(prompt, context)
A->>T: stream(messages, tools, system)
T-->>A: read_document call
A->>G: validated arguments
G-->>A: document result
A->>T: stream(history with result, tools, system)
T-->>A: final text
A-->>H: answer
Axio owns this sequence. A transport performs one model request; the Agent decides whether the returned stop reason requires another iteration.
See Agent & the Agentic Loop for exact iteration and completion behavior.
Try It¶
Run uv run python examples/tutorial/from_chat_to_agent.py from the repository
root. The program first proves the chat-only failure, then proves the tool
round trip:
chat-only: I cannot read README.md without a tool.
with-tool: README.md: Project overview.
Replace the stub with a provider transport when you want to observe live model selection. The Agent, tool, and context interfaces do not change.
Done when¶
[ ] The chat-only Agent has
tools=[].[ ]
read_documentis a plain async function.[ ]
read_document_toolwraps that handler with a stable name.[ ] The deterministic run records one successful
ToolResultBlock.[ ] The Agent returns the final text from the second transport call.
Next failure¶
The assistant can read a document only when the user already knows its exact
path. If the user asks for “architecture documentation,” read_document is
the wrong shape. The next lesson adds search and gives the model enough
information to choose between two tools.
Continue with Tools That Route.