Agno Integration
Agno (formerly phidata) is a Python framework for building agentic systems — single agents, multi-agent teams, and autonomous workflows. This guide shows how to add full distributed tracing with Nexus: per-run traces, tool call spans, and Team routing visibility.
Installation
pip install keylightdigital-nexus agno openai
Get your API key from Dashboard → API Keys and set it as an environment variable:
export NEXUS_API_KEY="nxs_your_key_here"
Basic agent trace
Wrap each agent.run() call in a Nexus trace. Every run appears in the dashboard with its status, duration, and metadata:
import os
from agno.agent import Agent
from agno.models.openai import OpenAIChat
from nexus_sdk import NexusClient
nexus = NexusClient(api_key=os.environ["NEXUS_API_KEY"])
agent = Agent(
name="research-agent",
model=OpenAIChat(id="gpt-4o"),
instructions=["You are a concise research assistant."],
)
def run_agent(task: str) -> str:
trace = nexus.start_trace({
"agent_id": "agno-research-agent",
"name": f"agent: {task[:60]}",
"status": "running",
"started_at": nexus.now(),
"metadata": {
"task": task[:200],
"model": "gpt-4o",
},
})
try:
result = agent.run(task)
nexus.end_trace(trace["trace_id"], {"status": "success"})
return result.content
except Exception as e:
nexus.end_trace(trace["trace_id"], {
"status": "error",
"metadata": {"error": str(e)},
})
raise
Tool call spans
Agno tools are plain Python functions passed to the Agent. Wrap them in a Nexus span to capture inputs, outputs, and errors per tool invocation:
import os
from agno.agent import Agent
from agno.models.openai import OpenAIChat
from nexus_sdk import NexusClient
nexus = NexusClient(api_key=os.environ["NEXUS_API_KEY"])
def make_traced_tool(tool_fn, tool_name: str, trace_id: str):
"""Returns a version of tool_fn that records a Nexus span per call."""
def wrapper(*args, **kwargs):
span = nexus.start_span(trace_id, {
"name": f"tool:{tool_name}",
"type": "tool",
"metadata": {
"tool": tool_name,
"args": str(args)[:200],
"kwargs": str(kwargs)[:200],
},
})
try:
result = tool_fn(*args, **kwargs)
nexus.end_span(span["id"], {"output": str(result)[:500]})
return result
except Exception as e:
nexus.end_span(span["id"], {"error": str(e)})
raise
wrapper.__name__ = tool_fn.__name__
wrapper.__doc__ = tool_fn.__doc__
return wrapper
# Define your tools as plain functions
def web_search(query: str) -> str:
"""Search the web for up-to-date information."""
# ... your search implementation ...
return f"Results for: {query}"
def get_weather(location: str) -> str:
"""Get current weather for a location."""
# ... your weather API call ...
return f"Sunny, 22C in {location}"
def run_agent_with_tool_spans(task: str) -> str:
trace = nexus.start_trace({
"agent_id": "agno-tool-agent",
"name": f"agent: {task[:60]}",
"status": "running",
"started_at": nexus.now(),
"metadata": {"task": task[:200]},
})
try:
# Wrap tools with the current trace_id before each run
traced_tools = [
make_traced_tool(web_search, "web_search", trace["trace_id"]),
make_traced_tool(get_weather, "get_weather", trace["trace_id"]),
]
agent = Agent(
name="tool-agent",
model=OpenAIChat(id="gpt-4o"),
tools=traced_tools,
)
result = agent.run(task)
nexus.end_trace(trace["trace_id"], {"status": "success"})
return result.content
except Exception as e:
nexus.end_trace(trace["trace_id"], {
"status": "error",
"metadata": {"error": str(e)},
})
raise
Team routing
An Agno Team routes tasks to member agents. Add a span per member run to capture which agent handled the task and what it returned:
import os
from agno.agent import Agent
from agno.team import Team
from agno.models.openai import OpenAIChat
from nexus_sdk import NexusClient
nexus = NexusClient(api_key=os.environ["NEXUS_API_KEY"])
# Define member agents
researcher = Agent(
name="researcher",
role="Research specialist — find facts and cite sources.",
model=OpenAIChat(id="gpt-4o"),
)
writer = Agent(
name="writer",
role="Content writer — turn facts into clear prose.",
model=OpenAIChat(id="gpt-4o"),
)
team = Team(
members=[researcher, writer],
model=OpenAIChat(id="gpt-4o"),
instructions=["Route tasks to the most appropriate member agent."],
)
def run_team_traced(task: str) -> str:
trace = nexus.start_trace({
"agent_id": "agno-team",
"name": f"team: {task[:60]}",
"status": "running",
"started_at": nexus.now(),
"metadata": {
"task": task[:200],
"members": ["researcher", "writer"],
},
})
try:
# Add a routing span to capture which member was selected
routing_span = nexus.start_span(trace["trace_id"], {
"name": "team:route",
"type": "tool",
"metadata": {"task": task[:200]},
})
result = team.run(task)
# Capture the responding member from the run result
responding_member = getattr(result, "member_name", "unknown")
nexus.end_span(routing_span["id"], {
"output": f"routed to: {responding_member}",
})
nexus.end_trace(trace["trace_id"], {
"status": "success",
"metadata": {"responding_member": responding_member},
})
return result.content
except Exception as e:
nexus.end_trace(trace["trace_id"], {
"status": "error",
"metadata": {"error": str(e)},
})
raise
The team:route span in the Nexus dashboard waterfall shows exactly which member agent handled each task and the full output — critical for debugging misconfigured routing prompts.
Full example
A complete script combining agent tracing, tool spans, and Team routing in a single runnable file:
import os
from agno.agent import Agent
from agno.team import Team
from agno.models.openai import OpenAIChat
from nexus_sdk import NexusClient
nexus = NexusClient(api_key=os.environ["NEXUS_API_KEY"])
# ----- Tools -----
def make_traced_tool(tool_fn, tool_name: str, trace_id: str):
def wrapper(*args, **kwargs):
span = nexus.start_span(trace_id, {
"name": f"tool:{tool_name}",
"type": "tool",
"metadata": {"tool": tool_name, "args": str(args)[:200]},
})
try:
result = tool_fn(*args, **kwargs)
nexus.end_span(span["id"], {"output": str(result)[:500]})
return result
except Exception as e:
nexus.end_span(span["id"], {"error": str(e)})
raise
wrapper.__name__ = tool_fn.__name__
wrapper.__doc__ = tool_fn.__doc__
return wrapper
def web_search(query: str) -> str:
"""Search the web for information."""
return f"[search results for '{query}']"
def calculator(expression: str) -> str:
"""Evaluate a mathematical expression."""
try:
return str(eval(expression)) # noqa: S307
except Exception as e:
return f"error: {e}"
# ----- Agents -----
def build_agents(trace_id: str):
traced_search = make_traced_tool(web_search, "web_search", trace_id)
traced_calc = make_traced_tool(calculator, "calculator", trace_id)
researcher = Agent(
name="researcher",
role="Research specialist. Use web_search to find facts.",
model=OpenAIChat(id="gpt-4o"),
tools=[traced_search],
)
analyst = Agent(
name="analyst",
role="Data analyst. Use calculator for numeric tasks.",
model=OpenAIChat(id="gpt-4o"),
tools=[traced_calc],
)
return researcher, analyst
# ----- Runner -----
def run(task: str) -> str:
trace = nexus.start_trace({
"agent_id": "agno-demo-team",
"name": f"team: {task[:60]}",
"status": "running",
"started_at": nexus.now(),
"metadata": {"task": task[:200], "model": "gpt-4o"},
})
try:
researcher, analyst = build_agents(trace["trace_id"])
team = Team(
members=[researcher, analyst],
model=OpenAIChat(id="gpt-4o"),
instructions=["Route research tasks to researcher, math tasks to analyst."],
)
routing_span = nexus.start_span(trace["trace_id"], {
"name": "team:route",
"type": "tool",
"metadata": {"task": task[:200]},
})
result = team.run(task)
member = getattr(result, "member_name", "unknown")
nexus.end_span(routing_span["id"], {"output": f"routed to: {member}"})
nexus.end_trace(trace["trace_id"], {
"status": "success",
"metadata": {"responding_member": member},
})
return result.content
except Exception as e:
nexus.end_trace(trace["trace_id"], {
"status": "error",
"metadata": {"error": str(e)},
})
raise
if __name__ == "__main__":
print(run("What is the GDP of Germany?"))
print(run("What is 17 * 89 + 42?"))
Debugging patterns
Team always routes to the same member
Check the team:route span in the trace waterfall. If the same agent handles every task, the team’s routing instructions are too vague. Add explicit role boundaries in each agent’s role field and the team’s instructions.
Tool calls not appearing in spans
The make_traced_tool wrapper must be applied before the Agent is constructed, with the correct trace_id in the closure. If you cache agents across requests, rebuild them per trace so each tool wrapper captures the right trace_id.
Traces stuck in “running” state
A trace stays running if nexus.end_trace() is never called. Always wrap agent.run() and team.run() in a try/except so the trace closes even when an exception propagates.
Ready to instrument your Agno agents?
Start for free — no credit card required. See traces in under 5 minutes.
Start free →