Skip to content

Commit 448aff5

Browse files
authored
Python: Fix Model ID attribute not showing up in invoke_agent span (#2061)
* Best effort to surface the model id to invoke agent span * Fix tests * Fix tests
1 parent 12fc19b commit 448aff5

3 files changed

Lines changed: 52 additions & 5 deletions

File tree

python/packages/core/agent_framework/_agents.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -642,6 +642,7 @@ def __init__(
642642
max_tokens: The maximum number of tokens to generate.
643643
metadata: Additional metadata to include in the request.
644644
model_id: The model_id to use for the agent.
645+
This overrides the model_id set in the chat client if it contains one.
645646
presence_penalty: The presence penalty to use.
646647
response_format: The format of the response.
647648
seed: The random seed to use.
@@ -690,7 +691,7 @@ def __init__(
690691
self._local_mcp_tools = [tool for tool in normalized_tools if isinstance(tool, MCPTool)]
691692
agent_tools = [tool for tool in normalized_tools if not isinstance(tool, MCPTool)]
692693
self.chat_options = ChatOptions(
693-
model_id=model_id,
694+
model_id=model_id or (str(chat_client.model_id) if hasattr(chat_client, "model_id") else None),
694695
allow_multiple_tool_calls=allow_multiple_tool_calls,
695696
conversation_id=conversation_id,
696697
frequency_penalty=frequency_penalty,

python/packages/core/agent_framework/observability.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -846,6 +846,7 @@ async def trace_get_response(
846846
kwargs.get("model_id")
847847
or (chat_options.model_id if (chat_options := kwargs.get("chat_options")) else None)
848848
or getattr(self, "model_id", None)
849+
or "unknown"
849850
)
850851
service_url = str(
851852
service_url_func()
@@ -933,6 +934,7 @@ async def trace_get_streaming_response(
933934
kwargs.get("model_id")
934935
or (chat_options.model_id if (chat_options := kwargs.get("chat_options")) else None)
935936
or getattr(self, "model_id", None)
937+
or "unknown"
936938
)
937939
service_url = str(
938940
service_url_func()
@@ -1324,7 +1326,10 @@ def _get_span(
13241326
attributes: dict[str, Any],
13251327
span_name_attribute: str,
13261328
) -> Generator["trace.Span", Any, Any]:
1327-
"""Start a span for a agent run."""
1329+
"""Start a span for a agent run.
1330+
1331+
Note: `attributes` must contain the `span_name_attribute` key.
1332+
"""
13281333
span = get_tracer().start_span(f"{attributes[OtelAttr.OPERATION]} {attributes[span_name_attribute]}")
13291334
span.set_attributes(attributes)
13301335
with trace.use_span(
@@ -1353,7 +1358,8 @@ def _get_span_attributes(**kwargs: Any) -> dict[str, Any]:
13531358
attributes[SpanAttributes.LLM_SYSTEM] = system_name
13541359
if provider_name := kwargs.get("provider_name"):
13551360
attributes[OtelAttr.PROVIDER_NAME] = provider_name
1356-
attributes[SpanAttributes.LLM_REQUEST_MODEL] = kwargs.get("model", "unknown")
1361+
if model_id := kwargs.get("model", chat_options.model_id):
1362+
attributes[SpanAttributes.LLM_REQUEST_MODEL] = model_id
13571363
if service_url := kwargs.get("service_url"):
13581364
attributes[OtelAttr.ADDRESS] = service_url
13591365
if conversation_id := kwargs.get("conversation_id", chat_options.conversation_id):

python/packages/core/tests/core/test_observability.py

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,45 @@ async def test_chat_client_streaming_observability(
279279
assert span.attributes[OtelAttr.OUTPUT_MESSAGES] is not None
280280

281281

282+
async def test_chat_client_without_model_id_observability(mock_chat_client, span_exporter: InMemorySpanExporter):
283+
"""Test telemetry shouldn't fail when the model_id is not provided for unknown reason."""
284+
client = use_observability(mock_chat_client)()
285+
messages = [ChatMessage(role=Role.USER, text="Test")]
286+
span_exporter.clear()
287+
response = await client.get_response(messages=messages)
288+
289+
assert response is not None
290+
spans = span_exporter.get_finished_spans()
291+
assert len(spans) == 1
292+
span = spans[0]
293+
294+
assert span.name == "chat unknown"
295+
assert span.attributes[OtelAttr.OPERATION.value] == OtelAttr.CHAT_COMPLETION_OPERATION
296+
assert span.attributes[SpanAttributes.LLM_REQUEST_MODEL] == "unknown"
297+
298+
299+
async def test_chat_client_streaming_without_model_id_observability(
300+
mock_chat_client, span_exporter: InMemorySpanExporter
301+
):
302+
"""Test streaming telemetry shouldn't fail when the model_id is not provided for unknown reason."""
303+
client = use_observability(mock_chat_client)()
304+
messages = [ChatMessage(role=Role.USER, text="Test")]
305+
span_exporter.clear()
306+
# Collect all yielded updates
307+
updates = []
308+
async for update in client.get_streaming_response(messages=messages):
309+
updates.append(update)
310+
311+
# Verify we got the expected updates, this shouldn't be dependent on otel
312+
assert len(updates) == 2
313+
spans = span_exporter.get_finished_spans()
314+
assert len(spans) == 1
315+
span = spans[0]
316+
assert span.name == "chat unknown"
317+
assert span.attributes[OtelAttr.OPERATION.value] == OtelAttr.CHAT_COMPLETION_OPERATION
318+
assert span.attributes[SpanAttributes.LLM_REQUEST_MODEL] == "unknown"
319+
320+
282321
def test_prepend_user_agent_with_none_value():
283322
"""Test prepend user agent with None value in headers."""
284323
headers = {"User-Agent": None}
@@ -368,6 +407,7 @@ def __init__(self):
368407
self.name = "test_agent"
369408
self.display_name = "Test Agent"
370409
self.description = "Test agent description"
410+
self.chat_options = ChatOptions(model_id="TestModel")
371411

372412
async def run(self, messages=None, *, thread=None, **kwargs):
373413
return AgentRunResponse(
@@ -405,7 +445,7 @@ async def test_agent_instrumentation_enabled(
405445
assert span.attributes[OtelAttr.AGENT_ID] == "test_agent_id"
406446
assert span.attributes[OtelAttr.AGENT_NAME] == "Test Agent"
407447
assert span.attributes[OtelAttr.AGENT_DESCRIPTION] == "Test agent description"
408-
assert span.attributes[SpanAttributes.LLM_REQUEST_MODEL] == "unknown"
448+
assert span.attributes[SpanAttributes.LLM_REQUEST_MODEL] == "TestModel"
409449
assert span.attributes[OtelAttr.INPUT_TOKENS] == 15
410450
assert span.attributes[OtelAttr.OUTPUT_TOKENS] == 25
411451
if enable_sensitive_data:
@@ -433,7 +473,7 @@ async def test_agent_streaming_response_with_diagnostics_enabled_via_decorator(
433473
assert span.attributes[OtelAttr.AGENT_ID] == "test_agent_id"
434474
assert span.attributes[OtelAttr.AGENT_NAME] == "Test Agent"
435475
assert span.attributes[OtelAttr.AGENT_DESCRIPTION] == "Test agent description"
436-
assert span.attributes[SpanAttributes.LLM_REQUEST_MODEL] == "unknown"
476+
assert span.attributes[SpanAttributes.LLM_REQUEST_MODEL] == "TestModel"
437477
if enable_sensitive_data:
438478
assert span.attributes.get(OtelAttr.OUTPUT_MESSAGES) is not None # Streaming, so no usage yet
439479

0 commit comments

Comments
 (0)