Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/examples-integration-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ jobs:

# Google ADK examples
- { path: 'examples/google_adk/human_approval.py', name: 'Google ADK Human Approval' }
- { path: 'examples/google_adk/sse_function_call_example.py', name: 'Google ADK SSE Function Call' }

# LlamaIndex examples
# - { path: 'examples/llamaindex/llamaindex_example.py', name: 'LlamaIndex' }
Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: "v0.2.1"
rev: "v0.12.9"
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
Expand Down
3 changes: 1 addition & 2 deletions agentops/client/api/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@
class TokenFetcher(Protocol):
"""Protocol for token fetching functions"""

def __call__(self, api_key: str) -> str:
...
def __call__(self, api_key: str) -> str: ...


class BaseApiClient:
Expand Down
6 changes: 3 additions & 3 deletions agentops/client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ class Client:
config: Config
_initialized: bool
_init_trace_context: Optional[TraceContext] = None # Stores the context of the auto-started trace
_legacy_session_for_init_trace: Optional[
Session
] = None # Stores the legacy Session wrapper for the auto-started trace
_legacy_session_for_init_trace: Optional[Session] = (
None # Stores the legacy Session wrapper for the auto-started trace
)

__instance = None # Class variable for singleton pattern

Expand Down
18 changes: 9 additions & 9 deletions agentops/instrumentation/agentic/google_adk/patch.py
Original file line number Diff line number Diff line change
Expand Up @@ -304,16 +304,16 @@ def _extract_llm_attributes(llm_request_dict: dict, llm_response: Any) -> dict:
elif "function_call" in part:
# This is a function call in the response
func_call = part["function_call"]
attributes[
MessageAttributes.COMPLETION_TOOL_CALL_NAME.format(i=0, j=tool_call_index)
] = func_call.get("name", "")
attributes[
MessageAttributes.COMPLETION_TOOL_CALL_ARGUMENTS.format(i=0, j=tool_call_index)
] = json.dumps(func_call.get("args", {}))
attributes[MessageAttributes.COMPLETION_TOOL_CALL_NAME.format(i=0, j=tool_call_index)] = (
func_call.get("name", "")
)
attributes[MessageAttributes.COMPLETION_TOOL_CALL_ARGUMENTS.format(i=0, j=tool_call_index)] = (
json.dumps(func_call.get("args", {}))
)
if "id" in func_call:
attributes[
MessageAttributes.COMPLETION_TOOL_CALL_ID.format(i=0, j=tool_call_index)
] = func_call["id"]
attributes[MessageAttributes.COMPLETION_TOOL_CALL_ID.format(i=0, j=tool_call_index)] = (
func_call["id"]
)
tool_call_index += 1

if text_parts:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,9 @@ def get_raw_response_attributes(response: Dict[str, Any]) -> Dict[str, Any]:
result[MessageAttributes.COMPLETION_TOOL_CALL_NAME.format(i=j, j=k)] = function.get(
"name", ""
)
result[
MessageAttributes.COMPLETION_TOOL_CALL_ARGUMENTS.format(i=j, j=k)
] = function.get("arguments", "")
result[MessageAttributes.COMPLETION_TOOL_CALL_ARGUMENTS.format(i=j, j=k)] = (
function.get("arguments", "")
)

return result

Expand Down
12 changes: 6 additions & 6 deletions agentops/instrumentation/agentic/smolagents/attributes/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,13 +102,13 @@ def get_model_attributes(
if hasattr(return_value, "tool_calls") and return_value.tool_calls:
for j, tool_call in enumerate(return_value.tool_calls):
if hasattr(tool_call, "function"):
attributes[
MessageAttributes.COMPLETION_TOOL_CALL_NAME.format(i=0, j=j)
] = tool_call.function.name
attributes[MessageAttributes.COMPLETION_TOOL_CALL_NAME.format(i=0, j=j)] = (
tool_call.function.name
)
if hasattr(tool_call.function, "arguments"):
attributes[
MessageAttributes.COMPLETION_TOOL_CALL_ARGUMENTS.format(i=0, j=j)
] = tool_call.function.arguments
attributes[MessageAttributes.COMPLETION_TOOL_CALL_ARGUMENTS.format(i=0, j=j)] = (
tool_call.function.arguments
)
if hasattr(tool_call, "id"):
attributes[MessageAttributes.COMPLETION_TOOL_CALL_ID.format(i=0, j=j)] = tool_call.id

Expand Down
2 changes: 1 addition & 1 deletion agentops/instrumentation/agentic/xpander/instrumentor.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
✅ Span creation: Using tracer.make_span() instead of manual span creation
✅ Error handling: Using _finish_span_success/_finish_span_error utilities
✅ Attribute management: Using existing SpanAttributeManager
✅ Serialization: Using safe_serialize and model_to_dict utilities
✅ Serialization: Using safe_serialize and model_to_dict utilities
✅ Attribute setting: Using _update_span utility

RUNTIME-SPECIFIC LOGIC KEPT (Cannot be replaced):
Expand Down
3 changes: 1 addition & 2 deletions agentops/instrumentation/common/attributes.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,7 @@ class IndexedAttribute(Protocol):
formatting of attribute keys based on the indices.
"""

def format(self, *, i: int, j: Optional[int] = None) -> str:
...
def format(self, *, i: int, j: Optional[int] = None) -> str: ...


IndexedAttributeMap = Dict[IndexedAttribute, str] # target_attribute_key: source_attribute
Expand Down
4 changes: 3 additions & 1 deletion agentops/logging/instrument_logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

print_logger = None


def setup_print_logger() -> None:
"""
Instruments the built-in print function and configures logging to use a memory buffer.
Expand All @@ -29,8 +30,9 @@ def setup_print_logger() -> None:

# Ensure the new logger doesn't propagate to root
buffer_logger.propagate = False

global print_logger

def print_logger(*args: Any, **kwargs: Any) -> None:
"""
Custom print function that logs to buffer and console.
Expand Down
2 changes: 1 addition & 1 deletion agentops/sdk/exporters.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ def export(self, spans: Sequence[ReadableSpan]) -> SpanExportResult:
self._last_auth_failure = time.time()

logger.warning(
f"JWT token expired during span export: {e}. " f"Will retry in {self._auth_failure_threshold} seconds."
f"JWT token expired during span export: {e}. Will retry in {self._auth_failure_threshold} seconds."
)
return SpanExportResult.FAILURE

Expand Down
2 changes: 1 addition & 1 deletion examples/ag2/ag2_async_agent.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@
},
"outputs": [],
"source": [
"# Define an asynchronous function that simulates async processing \n",
"# Define an asynchronous function that simulates async processing\n",
"async def simulate_async_processing(task_name: str, delay: float = 1.0) -> str:\n",
" \"\"\"\n",
" Simulate some asynchronous processing (e.g., API calls, file operations, etc.)\n",
Expand Down
17 changes: 10 additions & 7 deletions examples/ag2/ag2_async_agent.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# AG2 Async Agent Chat with Automated Responses
#
# This notebook demonstrates how to leverage asynchronous programming with AG2 agents
# to create automated conversations between AI agents, eliminating the need for human
# This notebook demonstrates how to leverage asynchronous programming with AG2 agents
# to create automated conversations between AI agents, eliminating the need for human
# input while maintaining full traceability.
#
# Overview
Expand All @@ -12,9 +12,9 @@
# 3. Automate the entire conversation flow without requiring manual intervention
# 4. Track all interactions using AgentOps for monitoring and analysis
#
# By using async operations and automated responses, you can create fully autonomous
# agent conversations that simulate real-world scenarios. This is particularly useful
# for testing, prototyping, and creating demos where you want to showcase agent
# By using async operations and automated responses, you can create fully autonomous
# agent conversations that simulate real-world scenarios. This is particularly useful
# for testing, prototyping, and creating demos where you want to showcase agent
# capabilities without manual input.

# %pip install agentops
Expand All @@ -38,7 +38,8 @@
agentops.init(auto_start_session=False, trace_name="AG2 Async Demo")
tracer = agentops.start_trace(trace_name="AG2 Async Agent Demo", tags=["ag2-async-demo", "agentops-example"])

# Define an asynchronous function that simulates async processing

# Define an asynchronous function that simulates async processing
async def simulate_async_processing(task_name: str, delay: float = 1.0) -> str:
"""
Simulate some asynchronous processing (e.g., API calls, file operations, etc.)
Expand All @@ -48,6 +49,7 @@ async def simulate_async_processing(task_name: str, delay: float = 1.0) -> str:
print(f"✅ Completed async task: {task_name}")
return f"Processed: {task_name}"


# Define a custom UserProxyAgent that simulates automated user responses
class AutomatedUserProxyAgent(UserProxyAgent):
def __init__(self, name: str, **kwargs):
Expand Down Expand Up @@ -81,6 +83,7 @@ async def a_receive(
):
await super().a_receive(message, sender, request_reply, silent)


# Define an AssistantAgent that simulates async processing before responding
class AsyncAssistantAgent(AssistantAgent):
async def a_receive(
Expand Down Expand Up @@ -149,7 +152,7 @@ async def main():

print("\n🎉 Demo completed successfully!")


# Run the main async demo
nest_asyncio.apply()
asyncio.run(main())

17 changes: 5 additions & 12 deletions examples/ag2/groupchat.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -87,17 +87,13 @@
"researcher = autogen.AssistantAgent(\n",
" name=\"researcher\",\n",
" llm_config=llm_config,\n",
" system_message=\"You are a researcher who specializes in finding accurate information.\"\n",
" system_message=\"You are a researcher who specializes in finding accurate information.\",\n",
")\n",
"coder = autogen.AssistantAgent(\n",
" name=\"coder\",\n",
" llm_config=llm_config,\n",
" system_message=\"You are an expert programmer who writes clean, efficient code.\"\n",
" name=\"coder\", llm_config=llm_config, system_message=\"You are an expert programmer who writes clean, efficient code.\"\n",
")\n",
"critic = autogen.AssistantAgent(\n",
" name=\"critic\",\n",
" llm_config=llm_config,\n",
" system_message=\"You review solutions and provide constructive feedback.\"\n",
" name=\"critic\", llm_config=llm_config, system_message=\"You review solutions and provide constructive feedback.\"\n",
")"
]
},
Expand All @@ -120,13 +116,10 @@
"groupchat = autogen.GroupChat(\n",
" agents=[user_proxy, researcher, coder, critic],\n",
" messages=[],\n",
" max_round=4 # Limits the total number of chat rounds\n",
" max_round=4, # Limits the total number of chat rounds\n",
")\n",
"# The manager coordinates the group chat and LLM configuration\n",
"manager = autogen.GroupChatManager(\n",
" groupchat=groupchat,\n",
" llm_config=llm_config\n",
")\n",
"manager = autogen.GroupChatManager(groupchat=groupchat, llm_config=llm_config)\n",
"# Start the group chat with an initial task and a maximum number of user turns\n",
"user_proxy.initiate_chat(\n",
" manager,\n",
Expand Down
18 changes: 5 additions & 13 deletions examples/ag2/groupchat.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,13 @@
researcher = autogen.AssistantAgent(
name="researcher",
llm_config=llm_config,
system_message="You are a researcher who specializes in finding accurate information."
system_message="You are a researcher who specializes in finding accurate information.",
)
coder = autogen.AssistantAgent(
name="coder",
llm_config=llm_config,
system_message="You are an expert programmer who writes clean, efficient code."
name="coder", llm_config=llm_config, system_message="You are an expert programmer who writes clean, efficient code."
)
critic = autogen.AssistantAgent(
name="critic",
llm_config=llm_config,
system_message="You review solutions and provide constructive feedback."
name="critic", llm_config=llm_config, system_message="You review solutions and provide constructive feedback."
)

# The user proxy agent simulates a human participant in the chat
Expand All @@ -65,13 +61,10 @@
groupchat = autogen.GroupChat(
agents=[user_proxy, researcher, coder, critic],
messages=[],
max_round=4 # Limits the total number of chat rounds
max_round=4, # Limits the total number of chat rounds
)
# The manager coordinates the group chat and LLM configuration
manager = autogen.GroupChatManager(
groupchat=groupchat,
llm_config=llm_config
)
manager = autogen.GroupChatManager(groupchat=groupchat, llm_config=llm_config)
# Start the group chat with an initial task and a maximum number of user turns
user_proxy.initiate_chat(
manager,
Expand All @@ -89,4 +82,3 @@
except agentops.ValidationError as e:
print(f"\n❌ Error validating spans: {e}")
raise

3 changes: 2 additions & 1 deletion examples/dspy/dspy_calculator.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import os
import time

import dspy
from dspy import Tool
Expand All @@ -16,9 +15,11 @@
lm = dspy.LM("openai/gpt-4o-mini", temperature=0.5)
dspy.configure(lm=lm, callbacks=[handler])


def multiplier(*, a: int, b: int) -> int:
return a * b


multiplier = Tool(multiplier)

agent = dspy.ProgramOfThought("question -> answer: int")
Expand Down
2 changes: 1 addition & 1 deletion examples/google_adk/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
google-adk==1.0.0
google-adk
google-genai==1.16.1
nest-asyncio==1.6.0
agentops
Expand Down
8 changes: 6 additions & 2 deletions examples/google_adk/sse_function_call_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@
StreamingMode: Optional[object] = None
try:
from google.adk.runners import RunConfig as _RunConfig, StreamingMode as _StreamingMode # type: ignore

RunConfig = _RunConfig
StreamingMode = _StreamingMode
except Exception:
try:
from google.adk.types import RunConfig as _RunConfig2, StreamingMode as _StreamingMode2 # type: ignore

RunConfig = _RunConfig2
StreamingMode = _StreamingMode2
except Exception:
Expand Down Expand Up @@ -73,7 +75,9 @@ async def main():
run_config_kw["run_config"] = RunConfig(streaming_mode=StreamingMode.SSE, response_modalities=["TEXT"]) # type: ignore

final_text = None
async for event in runner.run_async(user_id=USER_ID, session_id=SESSION_ID, new_message=user_message, **run_config_kw):
async for event in runner.run_async(
user_id=USER_ID, session_id=SESSION_ID, new_message=user_message, **run_config_kw
):
# Print out any parts safely; this will include function_call parts when they occur
if hasattr(event, "content") and event.content and getattr(event.content, "parts", None):
for part in event.content.parts:
Expand All @@ -91,4 +95,4 @@ async def main():


if __name__ == "__main__":
asyncio.run(main())
asyncio.run(main())
6 changes: 3 additions & 3 deletions examples/langgraph/langgraph_example.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -169,9 +169,9 @@
" tool_args = tool_call[\"args\"]\n",
"\n",
" # Find and execute the requested tool\n",
" for tool in tools:\n",
" if tool.name == tool_name:\n",
" result = tool.invoke(tool_args)\n",
" for available_tool in tools:\n",
" if available_tool.name == tool_name:\n",
" result = available_tool.invoke(tool_args)\n",
" tool_messages.append(ToolMessage(content=str(result), tool_call_id=tool_call[\"id\"]))\n",
" break\n",
"\n",
Expand Down
1 change: 0 additions & 1 deletion examples/smolagents/multi_smolagents_system.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,6 @@
"metadata": {},
"outputs": [],
"source": [
"\n",
"agentops.init(auto_start_session=False)\n",
"tracer = agentops.start_trace(\n",
" trace_name=\"Orchestrate a Multi-Agent System\", tags=[\"smolagents\", \"example\", \"multi-agent\", \"agentops-example\"]\n",
Expand Down
2 changes: 1 addition & 1 deletion examples/xpander/coding_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ async def on_execution_request(execution_task: AgentExecution) -> AgentExecution
email = getattr(user, "email", "")
user_info = f"👤 From user: {name}\n📧 Email: {email}"

IncomingEvent = f"\n📨 Incoming message: {execution_task.input.text}\n" f"{user_info}"
IncomingEvent = f"\n📨 Incoming message: {execution_task.input.text}\n{user_info}"

logger.info(f"[on_execution_request] IncomingEvent: {IncomingEvent}")
logger.info(f"[on_execution_request] Calling agent_backend.init_task with execution={execution_task.model_dump()}")
Expand Down
6 changes: 3 additions & 3 deletions tests/unit/instrumentation/openai_core/test_instrumentor.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,9 @@ def test_instrument_method_wraps_response_api(self, instrumentor):
instrumentor_obj._custom_wrap()

# Verify wrap_function_wrapper was called for Response API methods
assert (
mock_wfw.call_count >= 2
), f"Expected at least 2 calls to wrap_function_wrapper, got {mock_wfw.call_count}"
assert mock_wfw.call_count >= 2, (
f"Expected at least 2 calls to wrap_function_wrapper, got {mock_wfw.call_count}"
)

# Find Response API calls
response_api_calls = []
Expand Down
3 changes: 1 addition & 2 deletions tests/unit/sdk/instrumentation_tester.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,7 @@ def reset_trace_globals():

class HasAttributesViaProperty(Protocol):
@property
def attributes(self) -> Attributes:
...
def attributes(self) -> Attributes: ...


class HasAttributesViaAttr(Protocol):
Expand Down
Loading
Loading