diff --git a/python/packages/core/agent_framework/observability.py b/python/packages/core/agent_framework/observability.py index 1654799d87..48bf1314f8 100644 --- a/python/packages/core/agent_framework/observability.py +++ b/python/packages/core/agent_framework/observability.py @@ -2093,6 +2093,15 @@ def _get_instructions_from_options(options: Any) -> str | list[str] | None: return None +def _serialize_tool_definitions(tools: Any) -> list[str] | None: + from agent_framework._tools import _tools_to_dict + + tools_dict = _tools_to_dict(tools) + if not tools_dict: + return None + return [json.dumps(tool_def, ensure_ascii=False) for tool_def in tools_dict] + + # Mapping configuration for extracting span attributes # Each entry: source_keys -> (otel_attribute_key, transform_func, check_options_first, default_value) # - source_keys: single key or list of keys to check (first non-None value wins) @@ -2128,11 +2137,7 @@ def _get_instructions_from_options(options: Any) -> str | list[str] | None: # Tools with validation - returns None if no valid tools "tools": ( OtelAttr.TOOL_DEFINITIONS, - lambda tools: ( - json.dumps(tools_dict, ensure_ascii=False) - if (tools_dict := __import__("agent_framework._tools", fromlist=["_tools_to_dict"])._tools_to_dict(tools)) - else None - ), + _serialize_tool_definitions, True, None, ), diff --git a/python/packages/core/tests/core/test_observability.py b/python/packages/core/tests/core/test_observability.py index 94d4ee3bad..8617fa86ec 100644 --- a/python/packages/core/tests/core/test_observability.py +++ b/python/packages/core/tests/core/test_observability.py @@ -1,5 +1,6 @@ # Copyright (c) Microsoft. All rights reserved. +import json import logging from collections.abc import AsyncIterable, Awaitable, MutableSequence, Sequence from typing import Any @@ -2895,6 +2896,22 @@ def test_get_span_attributes_with_agent_info(): assert attrs[OtelAttr.AGENT_DESCRIPTION] == "A test agent" +def test_get_span_attributes_serializes_tool_definitions_individually(): + from agent_framework.observability import OtelAttr, _get_span_attributes + + @tool + def lookup_weather(city: str) -> str: + return f"sunny in {city}" + + attrs = _get_span_attributes(tools=[lookup_weather]) + definitions = attrs[OtelAttr.TOOL_DEFINITIONS] + + assert isinstance(definitions, list) + assert len(definitions) == 1 + definition = json.loads(definitions[0]) + assert definition["function"]["name"] == "lookup_weather" + + # region Test _capture_response