Agent Class Pattern
In shortBuild a weather assistant using the Agent class with tools and conditional routing.
- 6 min read
- 9 sections
- Updated
- v0.10.0
- Markdown
What you will build
A conversational weather assistant that demonstrates the core building blocks of a 10xGraph agent. The example shows how an Agent orchestrates LLM calls, how ToolNode exposes Python functions as callable tools, and how conditional routing decides whether to execute a tool or return a final response to the user. The graph loops between the agent and a tool node until the LLM produces a plain text reply, then stops.
This is a foundation pattern you extend for search agents, customer support, data extraction, and multi-agent systems.
How to run it
Clone the 10xGraph repository and run the example from the agentflow/examples/agent-class/ directory:
cd agentflow/examples/agent-class
pip install "10xgraph[google-genai]"
export GEMINI_API_KEY="<your_key>"
python graph.pyFor other providers, install the corresponding extra: 10xgraph[openai], 10xgraph[anthropic].
Environment variables:
GEMINI_API_KEY: your Google API key (required if using Gemini)OPENAI_API_KEY: your OpenAI key (required if using GPT)ANTHROPIC_API_KEY: your Anthropic key (required if using Claude)
How the graph works
The agent runs in a loop: it receives a user message, decides whether to call a tool, executes the tool if needed, and then responds. A routing function (should_use_tools) controls the flow.
flowchart TD
A([User Message]) --> B["MAIN<br/>Agent Node"]
B -->|has tool calls| C["TOOL<br/>ToolNode"]
B -->|no more tools| D([END])
C -->|tool result| B
style A fill:#4A90D9,color:#fff
style B fill:#7B68EE,color:#fff
style C fill:#50C878,color:#fff
style D fill:#FF6B6B,color:#fff
The sequence of execution:
- User sends a message to the graph.
- The MAIN node (an
Agent) forwards messages and any available tools to the LLM. - The LLM responds: either with a tool call or a plain text reply.
- The routing function checks the response. If it contains a tool call, it sends the state to the TOOL node. Otherwise, it sends the state to END.
- The TOOL node executes the requested tool (e.g.,
get_weather), captures the result as a message, and returns to MAIN. - MAIN sends the tool result to the LLM again, and the loop repeats.
- When the LLM produces a plain text reply with no tool calls, the routing function routes to END and the graph stops.
This loop handles multi-turn conversations where the agent calls tools multiple times before answering.
The example code, step by step
Step 1: Define your tool
A tool is a Python function with type hints. The type hints and docstring become the JSON schema the LLM sees.
def get_weather(location: str) -> str:
"""Get the current weather for a specific location."""
return f"The weather in {location} is sunny"In production, this function would call a real weather API. For this example, it returns a simple mock response.
Step 2: Wrap the tool in a ToolNode
A ToolNode takes a list of callables and exposes them as tools the LLM can invoke. The node executes tools in parallel if the LLM requests multiple tools at once.
from tenxgraph.core.graph import ToolNode
tool_node = ToolNode([get_weather])Step 3: Create the graph and add the Agent node
Create a StateGraph and add an Agent node. The Agent handles LLM calls, message formatting, and tool integration. The tool_node parameter tells the Agent which node name to route to when tools are called.
from tenxgraph.core.graph import Agent, StateGraph
graph = StateGraph()
graph.add_node(
"MAIN",
Agent(
model="google/gemini-2.5-flash",
system_prompt=[
{
"role": "system",
"content": "You are a helpful assistant. Help user queries effectively.",
}
],
tool_node="TOOL",
),
)The model parameter accepts provider-qualified strings: google/gemini-2.5-flash, openai/gpt-4o, anthropic/claude-opus-5. The system prompt is a list of message dicts; the Agent will include these in every LLM call.
Step 4: Add the tool node
graph.add_node("TOOL", tool_node)Step 5: Write the routing function
After each node runs, the graph calls a routing function to decide the next node. This function inspects the state and returns the name of the next node (or a special constant like END).
from tenxgraph.core.state.agent_state import AgentState
from tenxgraph.utils.constants import END
def should_use_tools(state: AgentState) -> str:
"""Route to TOOL if the last message has tool calls, else END."""
if not state.context or len(state.context) == 0:
return "TOOL"
last_message = state.context[-1]
# If assistant message has tool calls, go to TOOL
if (
hasattr(last_message, "tools_calls")
and last_message.tools_calls
and len(last_message.tools_calls) > 0
and last_message.role == "assistant"
):
return "TOOL"
# If tool message, go back to MAIN for a response
if last_message.role == "tool":
return "MAIN"
# Otherwise end
return ENDAgentState is the state object passed through the graph. state.context is a list of messages. The role field tells you who sent the message: "user", "assistant", or "tool".
Step 6: Wire the edges and compile
Connect the nodes with edges and a conditional routing rule. The conditional edge calls should_use_tools after the MAIN node runs, and routes based on the returned node name.
graph.add_conditional_edges(
"MAIN",
should_use_tools,
{"TOOL": "TOOL", END: END},
)
graph.add_edge("TOOL", "MAIN") # Always return to MAIN after tools run
graph.set_entry_point("MAIN")
app = graph.compile()Step 7: Invoke the graph
Create an input dict with messages, set a config (thread_id for persistence, recursion_limit to prevent infinite loops), and invoke.
from tenxgraph.core.state.message import Message
inp = {"messages": [Message.text_message("How is weather in London?")]}
config = {"thread_id": "12345", "recursion_limit": 10}
res = app.invoke(inp, config=config)
for msg in res["messages"]:
print(f"[{msg.role}] {msg.text()}")The messages come back in order: the user message, an assistant message carrying the get_weather tool call (empty text), the tool result, and the final assistant reply. The final wording depends on the model.
Complete source
import os
from dotenv import load_dotenv
from tenxgraph.core.graph import Agent, StateGraph, ToolNode
from tenxgraph.core.state.agent_state import AgentState
from tenxgraph.core.state.message import Message
from tenxgraph.utils.constants import END
load_dotenv()
def get_weather(
location: str,
) -> str:
"""
Get the current weather for a specific location.
This demo shows injectable parameters: tool_call_id and state are automatically injected.
"""
return f"The weather in {location} is sunny"
tool_node = ToolNode([get_weather])
graph = StateGraph()
graph.add_node(
"MAIN",
Agent(
model="google/gemini-2.5-flash",
system_prompt=[
{
"role": "system",
"content": "You are a helpful assistant, Help user queries effectively.",
}
],
tool_node="TOOL",
),
)
graph.add_node("TOOL", tool_node)
def should_use_tools(state: AgentState) -> str:
"""Determine if we should use tools or end the conversation."""
if not state.context or len(state.context) == 0:
return "TOOL" # No context, might need tools
last_message = state.context[-1]
# If the last message is from assistant and has tool calls, go to TOOL
if (
hasattr(last_message, "tools_calls")
and last_message.tools_calls
and len(last_message.tools_calls) > 0
and last_message.role == "assistant"
):
return "TOOL"
# If last message is a tool result, we should be done (AI will make final response)
if last_message.role == "tool":
return "MAIN"
# Default to END for other cases
return END
# Add conditional edges from MAIN
graph.add_conditional_edges(
"MAIN",
should_use_tools,
{"TOOL": "TOOL", END: END},
)
# Always go back to MAIN after TOOL execution
graph.add_edge("TOOL", "MAIN")
graph.set_entry_point("MAIN")
app = graph.compile()
if __name__ == "__main__":
inp = {"messages": [Message.text_message("How is weather in London?")]}
config = {"thread_id": "12345", "recursion_limit": 10}
res = app.invoke(inp, config=config)
for i in res["messages"]:
print("**********************")
print("Message Type: ", i.role)
print(i)
print("**********************")
print("\n\n")Key concepts
| Concept | Purpose |
|---|---|
Agent |
Wraps LLM calls and message conversion. Set tool_node to the name of the node that runs its tool calls. |
ToolNode |
Wraps a list of callables, executes them in parallel, and returns tool-result messages. Injectable params like state and tool_call_id are filled automatically. |
should_use_tools |
The routing function. Runs after every node and returns the next node name. |
| Conditional edge | Connects a node to a routing function and maps return values to next nodes. |
AgentState |
The graph state. Contains context (list of messages) and any custom fields you add by subclassing. |
| Recursion limit | Prevents infinite loops. Default is 25 hops (LLM calls + tool calls). |
What to try next
- Add more tools: pass additional functions to
ToolNode([get_weather, get_news, search]). - Add custom state: subclass
AgentStateto track user context, session data, or conversation metadata. - Enable memory: use a
Checkpointer(SQLite or Postgres+Redis) to persist state across runs on the same thread. - Serve over HTTP: create a
10xgraph.jsonconfig and run10xgraph apito expose the graph as a REST API.
See also
- Agent class reference: all constructor options and methods.
- ToolNode and tool decorator: write custom tools, injectable parameters, error handling.
- Conditional edges and routing: routing patterns and control flow.
- Checkpoint and persist state: keep conversation memory across runs.
Frequently asked questions
- What is the difference between the Agent class and plain nodes?
- The Agent class wraps LLM calls, message conversion, and tool integration. Plain nodes are lower-level functions you write directly. Use Agent for LLM orchestration, plain nodes for deterministic logic.
- Can I use a different model provider?
- Yes. Change the model string like google/gemini-2.5-flash, openai/gpt-4o, or anthropic/claude-opus-5. Install the corresponding provider extra.
- How do I add more tools?
- Pass additional functions to ToolNode. The LLM sees all tool schemas and chooses which to call based on the user query.