Skip to content

Commit 8dbcfa9

Browse files
fenilfalduDwij1704dot-agi
authored
Agno instrumentation (#1050)
* base instrumentation * Refactored Agno instrumentation * Code refactored * added v2 docs * ruff checks * added docs * enhanced docs :) * v2 docs updated * ruff checks * some work for @fenilfaldu to follow * docs final update * Delete examples/agno/agno_comprehensive_tutorial.py code cleanup * Delete examples/agno/agno_comprehensive_tutorial.ipynb code cleanup * import agentops before library import --------- Co-authored-by: Dwij <[email protected]> Co-authored-by: Pratyush Shukla <[email protected]>
1 parent d6c0537 commit 8dbcfa9

26 files changed

+4403
-0
lines changed

agentops/instrumentation/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,11 @@ class InstrumentorConfig(TypedDict):
384384
"min_version": "0.1.0",
385385
"package_name": "google-adk", # Actual pip package name
386386
},
387+
"agno": {
388+
"module_name": "agentops.instrumentation.agno",
389+
"class_name": "AgnoInstrumentor",
390+
"min_version": "0.1.0",
391+
},
387392
}
388393

389394
# Combine all target packages for monitoring
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
"""Agno Agent instrumentation package."""
2+
3+
import logging
4+
5+
from .instrumentor import AgnoInstrumentor
6+
7+
logger = logging.getLogger(__name__)
8+
9+
__version__ = "1.0.0"
10+
11+
LIBRARY_NAME = "agno"
12+
LIBRARY_VERSION = __version__
13+
14+
__all__ = [
15+
"AgnoInstrumentor",
16+
"LIBRARY_NAME",
17+
"LIBRARY_VERSION",
18+
]
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
"""Agno Agent attributes package for span instrumentation."""
2+
3+
from .agent import get_agent_run_attributes
4+
from .team import get_team_run_attributes
5+
from .tool import get_tool_execution_attributes
6+
from .workflow import get_workflow_run_attributes, get_workflow_session_attributes
7+
8+
__all__ = [
9+
"get_agent_run_attributes",
10+
"get_team_run_attributes",
11+
"get_tool_execution_attributes",
12+
"get_workflow_run_attributes",
13+
"get_workflow_session_attributes",
14+
]
Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
1+
"""Agno Agent run attributes handler."""
2+
3+
from typing import Optional, Tuple, Dict, Any
4+
5+
from agentops.instrumentation.common.attributes import AttributeMap
6+
from agentops.semconv import SpanAttributes, WorkflowAttributes, AgentAttributes, ToolAttributes
7+
from agentops.semconv.span_kinds import SpanKind as AgentOpsSpanKind
8+
9+
10+
def get_agent_run_attributes(
11+
args: Optional[Tuple] = None,
12+
kwargs: Optional[Dict] = None,
13+
return_value: Optional[Any] = None,
14+
) -> AttributeMap:
15+
"""Extract span attributes for Agent.run/arun calls.
16+
17+
Args:
18+
args: Positional arguments passed to the run method (self, message, ...)
19+
kwargs: Keyword arguments passed to the run method
20+
return_value: The return value from the run method (RunResponse)
21+
22+
Returns:
23+
A dictionary of span attributes to be set on the agent span
24+
"""
25+
attributes: AttributeMap = {}
26+
27+
# Initialize variables to avoid UnboundLocalError
28+
agent_name = None
29+
30+
# Base attributes
31+
attributes[SpanAttributes.AGENTOPS_SPAN_KIND] = AgentOpsSpanKind.WORKFLOW
32+
attributes[SpanAttributes.LLM_SYSTEM] = "agno"
33+
attributes[SpanAttributes.LLM_REQUEST_STREAMING] = "False"
34+
35+
# AgentOps entity attributes (matching CrewAI pattern)
36+
attributes[SpanAttributes.AGENTOPS_ENTITY_NAME] = "Agent"
37+
38+
# Extract agent information from args[0] (self)
39+
if args and len(args) >= 1:
40+
agent = args[0]
41+
42+
# Core agent identification using AgentAttributes
43+
if hasattr(agent, "agent_id") and agent.agent_id:
44+
agent_id = str(agent.agent_id)
45+
attributes[AgentAttributes.AGENT_ID] = agent_id
46+
attributes["agno.agent.id"] = agent_id
47+
48+
if hasattr(agent, "name") and agent.name:
49+
agent_name = str(agent.name)
50+
attributes[AgentAttributes.AGENT_NAME] = agent_name
51+
attributes["agno.agent.name"] = agent_name
52+
53+
if hasattr(agent, "role") and agent.role:
54+
agent_role = str(agent.role)
55+
attributes[AgentAttributes.AGENT_ROLE] = agent_role
56+
attributes["agno.agent.role"] = agent_role
57+
58+
# Check if agent is part of a team
59+
if hasattr(agent, "_team") and agent._team:
60+
team = agent._team
61+
if hasattr(team, "name") and team.name:
62+
attributes["agno.agent.parent_team"] = str(team.name)
63+
attributes["agno.agent.parent_team_display"] = f"Under {team.name}"
64+
if hasattr(team, "team_id") and team.team_id:
65+
attributes["agno.agent.parent_team_id"] = str(team.team_id)
66+
67+
# Model information using AgentAttributes
68+
if hasattr(agent, "model") and agent.model:
69+
model = agent.model
70+
if hasattr(model, "id"):
71+
model_id = str(model.id)
72+
attributes[AgentAttributes.AGENT_MODELS] = model_id
73+
attributes["agno.agent.model_id"] = model_id
74+
attributes[SpanAttributes.LLM_RESPONSE_MODEL] = model_id
75+
76+
if hasattr(model, "provider"):
77+
model_provider = str(model.provider)
78+
attributes["agno.agent.model_provider"] = model_provider
79+
attributes[SpanAttributes.LLM_REQUEST_MODEL] = model_id if hasattr(model, "id") else "unknown"
80+
81+
# Agent configuration details
82+
agent_config = {}
83+
84+
if hasattr(agent, "description") and agent.description:
85+
agent_config["description"] = str(agent.description)[:500] # Limit length
86+
87+
if hasattr(agent, "goal") and agent.goal:
88+
agent_config["goal"] = str(agent.goal)[:500] # Limit length
89+
90+
if hasattr(agent, "instructions") and agent.instructions:
91+
if isinstance(agent.instructions, list):
92+
agent_config["instructions"] = " | ".join(str(i) for i in agent.instructions[:3]) # First 3
93+
else:
94+
agent_config["instructions"] = str(agent.instructions)[:500]
95+
96+
if hasattr(agent, "expected_output") and agent.expected_output:
97+
agent_config["expected_output"] = str(agent.expected_output)[:300]
98+
99+
if hasattr(agent, "markdown"):
100+
agent_config["markdown"] = str(agent.markdown)
101+
102+
if hasattr(agent, "reasoning"):
103+
agent_config["reasoning"] = str(agent.reasoning)
104+
105+
if hasattr(agent, "stream"):
106+
agent_config["stream"] = str(agent.stream)
107+
108+
if hasattr(agent, "retries"):
109+
agent_config["max_retry_limit"] = str(agent.retries)
110+
111+
if hasattr(agent, "response_model") and agent.response_model:
112+
agent_config[SpanAttributes.LLM_RESPONSE_MODEL] = str(agent.response_model.__name__)
113+
114+
if hasattr(agent, "show_tool_calls"):
115+
agent_config["show_tool_calls"] = str(agent.show_tool_calls)
116+
117+
if hasattr(agent, "tool_call_limit") and agent.tool_call_limit:
118+
agent_config["tool_call_limit"] = str(agent.tool_call_limit)
119+
120+
# Add agent config to attributes
121+
for key, value in agent_config.items():
122+
attributes[f"agno.agent.{key}"] = value
123+
124+
# Tools information
125+
if hasattr(agent, "tools") and agent.tools:
126+
tools_info = []
127+
tool_names = []
128+
129+
for tool in agent.tools:
130+
tool_dict = {}
131+
132+
if hasattr(tool, "name"):
133+
tool_name = str(tool.name)
134+
tool_dict["name"] = tool_name
135+
tool_names.append(tool_name)
136+
elif hasattr(tool, "__name__"):
137+
tool_name = str(tool.__name__)
138+
tool_dict["name"] = tool_name
139+
tool_names.append(tool_name)
140+
elif callable(tool):
141+
tool_name = getattr(tool, "__name__", "unknown_tool")
142+
tool_dict["name"] = tool_name
143+
tool_names.append(tool_name)
144+
145+
if hasattr(tool, "description"):
146+
description = str(tool.description)
147+
if len(description) > 200:
148+
description = description[:197] + "..."
149+
tool_dict["description"] = description
150+
151+
if tool_dict: # Only add if we have some info
152+
tools_info.append(tool_dict)
153+
154+
# Set tool attributes
155+
if tool_names:
156+
attributes["agno.agent.tools_count"] = str(len(tool_names))
157+
158+
if tools_info:
159+
# Instead of storing as JSON blob, set individual tool attributes
160+
for i, tool in enumerate(tools_info):
161+
prefix = f"agent.tool.{i}"
162+
if "name" in tool:
163+
attributes[f"{prefix}.{ToolAttributes.TOOL_NAME}"] = tool["name"]
164+
if "description" in tool:
165+
attributes[f"{prefix}.{ToolAttributes.TOOL_DESCRIPTION}"] = tool["description"]
166+
167+
# Memory and knowledge information
168+
if hasattr(agent, "memory") and agent.memory:
169+
memory_type = type(agent.memory).__name__
170+
attributes["agno.agent.memory_type"] = memory_type
171+
172+
if hasattr(agent, "knowledge") and agent.knowledge:
173+
knowledge_type = type(agent.knowledge).__name__
174+
attributes["agno.agent.knowledge_type"] = knowledge_type
175+
176+
if hasattr(agent, "storage") and agent.storage:
177+
storage_type = type(agent.storage).__name__
178+
attributes["agno.agent.storage_type"] = storage_type
179+
180+
# Session information
181+
if hasattr(agent, "session_id") and agent.session_id:
182+
session_id = str(agent.session_id)
183+
attributes["agno.agent.session_id"] = session_id
184+
185+
if hasattr(agent, "user_id") and agent.user_id:
186+
user_id = str(agent.user_id)
187+
attributes["agno.agent.user_id"] = user_id
188+
189+
# Extract run input information
190+
if args and len(args) >= 2:
191+
message = args[1] # The message argument
192+
if message:
193+
message_str = str(message)
194+
if len(message_str) > 500:
195+
message_str = message_str[:497] + "..."
196+
attributes[WorkflowAttributes.WORKFLOW_INPUT] = message_str
197+
attributes["agno.agent.input"] = message_str
198+
# AgentOps entity input (matching CrewAI pattern)
199+
attributes[SpanAttributes.AGENTOPS_ENTITY_INPUT] = message_str
200+
201+
# Extract kwargs information
202+
if kwargs:
203+
if kwargs.get("stream") is not None:
204+
attributes[SpanAttributes.LLM_REQUEST_STREAMING] = str(kwargs["stream"])
205+
206+
if kwargs.get("session_id"):
207+
attributes["agno.agent.run_session_id"] = str(kwargs["session_id"])
208+
209+
if kwargs.get("user_id"):
210+
attributes["agno.agent.run_user_id"] = str(kwargs["user_id"])
211+
212+
# Extract return value information
213+
if return_value:
214+
if hasattr(return_value, "run_id") and return_value.run_id:
215+
run_id = str(return_value.run_id)
216+
attributes["agno.agent.run_id"] = run_id
217+
218+
if hasattr(return_value, "session_id") and return_value.session_id:
219+
session_id = str(return_value.session_id)
220+
attributes["agno.agent.response_session_id"] = session_id
221+
222+
if hasattr(return_value, "agent_id") and return_value.agent_id:
223+
agent_id = str(return_value.agent_id)
224+
attributes["agno.agent.response_agent_id"] = agent_id
225+
226+
if hasattr(return_value, "content") and return_value.content:
227+
content = str(return_value.content)
228+
if len(content) > 500:
229+
content = content[:497] + "..."
230+
attributes[WorkflowAttributes.WORKFLOW_OUTPUT] = content
231+
attributes["agno.agent.output"] = content
232+
233+
if hasattr(return_value, "event") and return_value.event:
234+
event = str(return_value.event)
235+
attributes["agno.agent.event"] = event
236+
237+
# Tool executions from the response
238+
if hasattr(return_value, "tools") and return_value.tools:
239+
tool_executions = []
240+
for tool_exec in return_value.tools:
241+
tool_exec_dict = {}
242+
243+
if hasattr(tool_exec, "tool_name") and tool_exec.tool_name:
244+
tool_exec_dict["name"] = str(tool_exec.tool_name)
245+
246+
if hasattr(tool_exec, "tool_args") and tool_exec.tool_args:
247+
try:
248+
import json
249+
250+
args_str = json.dumps(tool_exec.tool_args)
251+
if len(args_str) > 200:
252+
args_str = args_str[:197] + "..."
253+
tool_exec_dict["parameters"] = args_str
254+
except:
255+
tool_exec_dict["parameters"] = str(tool_exec.tool_args)
256+
257+
if hasattr(tool_exec, "result") and tool_exec.result:
258+
result_str = str(tool_exec.result)
259+
if len(result_str) > 200:
260+
result_str = result_str[:197] + "..."
261+
tool_exec_dict["result"] = result_str
262+
263+
if hasattr(tool_exec, "tool_call_error") and tool_exec.tool_call_error:
264+
tool_exec_dict["error"] = str(tool_exec.tool_call_error)
265+
266+
tool_exec_dict["status"] = "success" # Default to success
267+
268+
if tool_exec_dict:
269+
tool_executions.append(tool_exec_dict)
270+
271+
if tool_executions:
272+
# Add tool executions (limit to first 3)
273+
limited_executions = tool_executions[:3]
274+
for i, tool_exec in enumerate(limited_executions):
275+
for key, value in tool_exec.items():
276+
attributes[f"agno.agent.tool_execution.{i}.{key}"] = value
277+
278+
# Workflow type
279+
attributes[WorkflowAttributes.WORKFLOW_TYPE] = "agent_run"
280+
281+
# Add display name for better UI visualization
282+
if agent_name:
283+
# Check if we have parent team info
284+
parent_team = attributes.get("agno.agent.parent_team")
285+
if parent_team:
286+
attributes["agno.agent.display_name"] = f"{agent_name} (Agent under {parent_team})"
287+
else:
288+
attributes["agno.agent.display_name"] = f"{agent_name} (Agent)"
289+
290+
return attributes

0 commit comments

Comments
 (0)