diff --git a/.github/workflows/examples-integration-test.yml b/.github/workflows/examples-integration-test.yml index ff2bd16a9..fadc4bce9 100644 --- a/.github/workflows/examples-integration-test.yml +++ b/.github/workflows/examples-integration-test.yml @@ -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' } diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c66484907..b7fdaf751 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -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] diff --git a/agentops/client/api/base.py b/agentops/client/api/base.py index e20f73970..b7b8f03c9 100644 --- a/agentops/client/api/base.py +++ b/agentops/client/api/base.py @@ -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: diff --git a/agentops/client/client.py b/agentops/client/client.py index d66e898c7..87d6afd3d 100644 --- a/agentops/client/client.py +++ b/agentops/client/client.py @@ -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 diff --git a/agentops/instrumentation/agentic/google_adk/patch.py b/agentops/instrumentation/agentic/google_adk/patch.py index 8ebd7fe6e..172c41ab7 100644 --- a/agentops/instrumentation/agentic/google_adk/patch.py +++ b/agentops/instrumentation/agentic/google_adk/patch.py @@ -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: diff --git a/agentops/instrumentation/agentic/openai_agents/attributes/completion.py b/agentops/instrumentation/agentic/openai_agents/attributes/completion.py index 1722109df..ea351b64b 100644 --- a/agentops/instrumentation/agentic/openai_agents/attributes/completion.py +++ b/agentops/instrumentation/agentic/openai_agents/attributes/completion.py @@ -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 diff --git a/agentops/instrumentation/agentic/smolagents/attributes/model.py b/agentops/instrumentation/agentic/smolagents/attributes/model.py index 15513babf..3fab36a9c 100644 --- a/agentops/instrumentation/agentic/smolagents/attributes/model.py +++ b/agentops/instrumentation/agentic/smolagents/attributes/model.py @@ -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 diff --git a/agentops/instrumentation/agentic/xpander/instrumentor.py b/agentops/instrumentation/agentic/xpander/instrumentor.py index 05d66c6a8..099764e60 100644 --- a/agentops/instrumentation/agentic/xpander/instrumentor.py +++ b/agentops/instrumentation/agentic/xpander/instrumentor.py @@ -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): diff --git a/agentops/instrumentation/common/attributes.py b/agentops/instrumentation/common/attributes.py index 809121923..e55bb6e6a 100644 --- a/agentops/instrumentation/common/attributes.py +++ b/agentops/instrumentation/common/attributes.py @@ -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 diff --git a/agentops/logging/instrument_logging.py b/agentops/logging/instrument_logging.py index 6b75abbfe..0c5cca53c 100644 --- a/agentops/logging/instrument_logging.py +++ b/agentops/logging/instrument_logging.py @@ -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. @@ -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. diff --git a/agentops/sdk/exporters.py b/agentops/sdk/exporters.py index 268217c97..69e872135 100644 --- a/agentops/sdk/exporters.py +++ b/agentops/sdk/exporters.py @@ -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 diff --git a/examples/ag2/ag2_async_agent.ipynb b/examples/ag2/ag2_async_agent.ipynb index 17d6f6982..7c0638e41 100755 --- a/examples/ag2/ag2_async_agent.ipynb +++ b/examples/ag2/ag2_async_agent.ipynb @@ -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", diff --git a/examples/ag2/ag2_async_agent.py b/examples/ag2/ag2_async_agent.py index 9f05ac205..88f0d6f11 100644 --- a/examples/ag2/ag2_async_agent.py +++ b/examples/ag2/ag2_async_agent.py @@ -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 @@ -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 @@ -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.) @@ -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): @@ -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( @@ -149,7 +152,7 @@ async def main(): print("\n🎉 Demo completed successfully!") + # Run the main async demo nest_asyncio.apply() asyncio.run(main()) - diff --git a/examples/ag2/groupchat.ipynb b/examples/ag2/groupchat.ipynb index a69825323..c0d173877 100644 --- a/examples/ag2/groupchat.ipynb +++ b/examples/ag2/groupchat.ipynb @@ -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", ")" ] }, @@ -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", diff --git a/examples/ag2/groupchat.py b/examples/ag2/groupchat.py index 06835805b..853116694 100644 --- a/examples/ag2/groupchat.py +++ b/examples/ag2/groupchat.py @@ -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 @@ -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, @@ -89,4 +82,3 @@ except agentops.ValidationError as e: print(f"\n❌ Error validating spans: {e}") raise - diff --git a/examples/dspy/dspy_calculator.py b/examples/dspy/dspy_calculator.py index 3b7cb4fd4..210ac4a1e 100644 --- a/examples/dspy/dspy_calculator.py +++ b/examples/dspy/dspy_calculator.py @@ -1,5 +1,4 @@ import os -import time import dspy from dspy import Tool @@ -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") diff --git a/examples/google_adk/requirements.txt b/examples/google_adk/requirements.txt index a6941f206..82cb02dad 100644 --- a/examples/google_adk/requirements.txt +++ b/examples/google_adk/requirements.txt @@ -1,4 +1,4 @@ -google-adk==1.0.0 +google-adk google-genai==1.16.1 nest-asyncio==1.6.0 agentops diff --git a/examples/google_adk/sse_function_call_example.py b/examples/google_adk/sse_function_call_example.py index d2f778a95..38a9a6c74 100644 --- a/examples/google_adk/sse_function_call_example.py +++ b/examples/google_adk/sse_function_call_example.py @@ -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: @@ -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: @@ -91,4 +95,4 @@ async def main(): if __name__ == "__main__": - asyncio.run(main()) \ No newline at end of file + asyncio.run(main()) diff --git a/examples/langgraph/langgraph_example.ipynb b/examples/langgraph/langgraph_example.ipynb index 8ab1e8caa..40c01d7c5 100644 --- a/examples/langgraph/langgraph_example.ipynb +++ b/examples/langgraph/langgraph_example.ipynb @@ -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", diff --git a/examples/smolagents/multi_smolagents_system.ipynb b/examples/smolagents/multi_smolagents_system.ipynb index d01c6d6d1..c25c3948e 100644 --- a/examples/smolagents/multi_smolagents_system.ipynb +++ b/examples/smolagents/multi_smolagents_system.ipynb @@ -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", diff --git a/examples/xpander/coding_agent.py b/examples/xpander/coding_agent.py index 77bf72d03..a937136ae 100644 --- a/examples/xpander/coding_agent.py +++ b/examples/xpander/coding_agent.py @@ -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()}") diff --git a/tests/unit/instrumentation/openai_core/test_instrumentor.py b/tests/unit/instrumentation/openai_core/test_instrumentor.py index 7a17152c4..6d3b917c9 100644 --- a/tests/unit/instrumentation/openai_core/test_instrumentor.py +++ b/tests/unit/instrumentation/openai_core/test_instrumentor.py @@ -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 = [] diff --git a/tests/unit/sdk/instrumentation_tester.py b/tests/unit/sdk/instrumentation_tester.py index 606a91bfb..4175270d6 100644 --- a/tests/unit/sdk/instrumentation_tester.py +++ b/tests/unit/sdk/instrumentation_tester.py @@ -45,8 +45,7 @@ def reset_trace_globals(): class HasAttributesViaProperty(Protocol): @property - def attributes(self) -> Attributes: - ... + def attributes(self) -> Attributes: ... class HasAttributesViaAttr(Protocol): diff --git a/uv.lock b/uv.lock index 2ba92f275..ff53a6bb8 100644 --- a/uv.lock +++ b/uv.lock @@ -15,7 +15,7 @@ constraints = [ [[package]] name = "agentops" -version = "0.4.19" +version = "0.4.20" source = { editable = "." } dependencies = [ { name = "aiohttp" },