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
10 changes: 8 additions & 2 deletions python/packages/core/agent_framework/_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@

from opentelemetry import trace
from opentelemetry.metrics import Histogram, NoOpHistogram
from opentelemetry.util.types import AttributeValue
from pydantic import BaseModel, Field, TypeAdapter, ValidationError, create_model
from pydantic_core import to_jsonable_python

Expand Down Expand Up @@ -1231,8 +1232,13 @@ async def invoke(
return parsed

span_attributes = get_function_span_attributes(self, tool_call_id=tool_call_id)
metric_attributes = dict(span_attributes)
metric_attributes[OtelAttr.MEASUREMENT_FUNCTION_TAG_NAME] = self.name
# Keep metric dimensions explicit so span-only attributes cannot flow into the histogram.
metric_attributes: dict[str, AttributeValue] = {
OtelAttr.OPERATION: OtelAttr.TOOL_EXECUTION_OPERATION,
OtelAttr.TOOL_NAME: self.name,
OtelAttr.TOOL_TYPE: "function",
OtelAttr.MEASUREMENT_FUNCTION_TAG_NAME: self.name,
}
# Filter out framework kwargs that are not JSON serializable.
serializable_kwargs = {
k: v
Expand Down
36 changes: 25 additions & 11 deletions python/packages/core/tests/core/test_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -1037,11 +1037,12 @@ def telemetry_test_tool(expression: str) -> str:
call_args = mock_histogram.record.call_args
assert call_args[0][0] > 0 # duration should be positive
attributes = call_args[1]["attributes"]
assert attributes[OtelAttr.MEASUREMENT_FUNCTION_TAG_NAME] == "telemetry_test_tool"
assert attributes[OtelAttr.TOOL_CALL_ID] == "test_call_id"
assert OtelAttr.TOOL_ARGUMENTS not in attributes
assert OtelAttr.TOOL_RESULT not in attributes
assert expression not in attributes.values()
assert attributes == {
OtelAttr.OPERATION: OtelAttr.TOOL_EXECUTION_OPERATION,
OtelAttr.TOOL_NAME: "telemetry_test_tool",
OtelAttr.TOOL_TYPE: "function",
OtelAttr.MEASUREMENT_FUNCTION_TAG_NAME: "telemetry_test_tool",
}


@pytest.mark.parametrize("enable_sensitive_data", [False], indirect=True)
Expand Down Expand Up @@ -1085,8 +1086,12 @@ def telemetry_test_tool(x: int, y: int) -> int:
call_args = mock_histogram.record.call_args
assert call_args[0][0] > 0 # duration should be positive
attributes = call_args[1]["attributes"]
assert attributes[OtelAttr.MEASUREMENT_FUNCTION_TAG_NAME] == "telemetry_test_tool"
assert attributes[OtelAttr.TOOL_CALL_ID] == "test_call_id"
assert attributes == {
OtelAttr.OPERATION: OtelAttr.TOOL_EXECUTION_OPERATION,
OtelAttr.TOOL_NAME: "telemetry_test_tool",
OtelAttr.TOOL_TYPE: "function",
OtelAttr.MEASUREMENT_FUNCTION_TAG_NAME: "telemetry_test_tool",
}


@pytest.mark.parametrize("enable_sensitive_data", [True], indirect=True)
Expand Down Expand Up @@ -1228,9 +1233,13 @@ def exception_test_tool(x: int, y: int) -> int:
mock_histogram.record.assert_called_once()
call_args = mock_histogram.record.call_args
attributes = call_args[1]["attributes"]
assert attributes[OtelAttr.ERROR_TYPE] == ValueError.__name__
assert OtelAttr.TOOL_ARGUMENTS not in attributes
assert OtelAttr.TOOL_RESULT not in attributes
assert attributes == {
OtelAttr.OPERATION: OtelAttr.TOOL_EXECUTION_OPERATION,
OtelAttr.TOOL_NAME: "exception_test_tool",
OtelAttr.TOOL_TYPE: "function",
OtelAttr.MEASUREMENT_FUNCTION_TAG_NAME: "exception_test_tool",
OtelAttr.ERROR_TYPE: ValueError.__name__,
}


async def test_tool_invoke_telemetry_async_function(span_exporter: InMemorySpanExporter):
Expand Down Expand Up @@ -1268,7 +1277,12 @@ async def async_telemetry_test(x: int, y: int) -> int:
mock_histogram.record.assert_called_once()
call_args = mock_histogram.record.call_args
attributes = call_args[1]["attributes"]
assert attributes[OtelAttr.MEASUREMENT_FUNCTION_TAG_NAME] == "async_telemetry_test"
assert attributes == {
OtelAttr.OPERATION: OtelAttr.TOOL_EXECUTION_OPERATION,
OtelAttr.TOOL_NAME: "async_telemetry_test",
OtelAttr.TOOL_TYPE: "function",
OtelAttr.MEASUREMENT_FUNCTION_TAG_NAME: "async_telemetry_test",
}


async def test_tool_invoke_invalid_pydantic_args():
Expand Down
Loading