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
6 changes: 6 additions & 0 deletions A365_DOCUMENTATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,12 @@ Supported semantic attributes:

### ExecuteToolScope

Pass `Request(session_id="session-123")` to correlate execute-tool and inference
spans using `microsoft.session.id`, as with invoke-agent spans. The session ID is
recorded directly from the request without requiring a baggage builder or span
processor. Omitting it leaves the attribute unset, and `conversation_id` remains
an independent correlation field.

```python
from microsoft.opentelemetry.a365.core import ExecuteToolScope, ToolCallDetails, Request

Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@
- Log blob deletion-check filesystem errors without interrupting replay of
other valid Agent365 records in the same batch. Verify deletion with `stat()`
so Python 3.14 does not silently treat access failures as missing blobs.
- Include the request's session ID on Agent365 tool and inference spans without requiring a baggage processor.
([#310](https://github.com/microsoft/opentelemetry-distro-python/pull/310))

### Features Added
- Record full endpoint URLs on Agent365 tool spans with `ServiceEndpoint.from_url()`.
Expand Down
2 changes: 2 additions & 0 deletions src/microsoft/opentelemetry/a365/core/execute_tool_scope.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
GEN_AI_TOOL_TYPE_KEY,
SERVER_ADDRESS_KEY,
SERVER_PORT_KEY,
SESSION_ID_KEY,
URL_FULL_KEY,
USER_EMAIL_KEY,
USER_ID_KEY,
Expand Down Expand Up @@ -147,6 +148,7 @@ def __init__(
self.set_tag_maybe(GEN_AI_TOOL_CALL_ID_KEY, tool_call_id)
self.set_tag_maybe(GEN_AI_TOOL_DESCRIPTION_KEY, description)
self.set_tag_maybe(GEN_AI_CONVERSATION_ID_KEY, request.conversation_id)
self.set_tag_maybe(SESSION_ID_KEY, request.session_id)

if endpoint:
self.set_tag_maybe(SERVER_ADDRESS_KEY, endpoint.hostname)
Expand Down
2 changes: 2 additions & 0 deletions src/microsoft/opentelemetry/a365/core/inference_scope.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
GEN_AI_USAGE_OUTPUT_TOKENS_KEY,
SERVER_ADDRESS_KEY,
SERVER_PORT_KEY,
SESSION_ID_KEY,
USER_EMAIL_KEY,
USER_ID_KEY,
USER_NAME_KEY,
Expand Down Expand Up @@ -109,6 +110,7 @@ def __init__(
if request.content is not None:
self.record_input_messages(request.content)
self.set_tag_maybe(GEN_AI_CONVERSATION_ID_KEY, request.conversation_id)
self.set_tag_maybe(SESSION_ID_KEY, request.session_id)

self.set_tag_maybe(GEN_AI_OPERATION_NAME_KEY, operation_name)
self.set_tag_maybe(GEN_AI_REQUEST_MODEL_KEY, details.model)
Expand Down
21 changes: 21 additions & 0 deletions tests/a365/test_execute_tool_scope.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,27 @@ def test_unserializable_typed_payloads_are_safe_when_telemetry_is_disabled(self)
self.assertIsNone(raw_scope._span)


@pytest.mark.parametrize("session_id", ["session-123", None])
def test_execute_tool_records_request_session_without_a_baggage_processor(monkeypatch, session_id):
provider = TracerProvider()
monkeypatch.setenv("ENABLE_OBSERVABILITY", "true")
monkeypatch.setattr(ExecuteToolScope, "_tracer", provider.get_tracer("Agent365Sdk"))
try:
with ExecuteToolScope.start(
Request(session_id=session_id, conversation_id="conversation-123"),
ToolCallDetails(tool_name="lookup"),
AgentDetails("agent"),
) as scope:
attrs = dict(scope._span.attributes)
assert attrs["gen_ai.conversation.id"] == "conversation-123"
if session_id is None:
assert "microsoft.session.id" not in attrs
else:
assert attrs["microsoft.session.id"] == session_id
finally:
provider.shutdown()


@pytest.mark.parametrize(
"url,host,port",
[
Expand Down
17 changes: 17 additions & 0 deletions tests/a365/test_inference_scope.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import os
from typing import Any, cast

import pytest

from microsoft.opentelemetry.a365.core import (
AgentDetails,
InferenceCallDetails,
Expand Down Expand Up @@ -113,3 +115,18 @@ def test_inference_scope_records_late_response_details() -> None:
assert attrs["gen_ai.usage.cache_write.input_tokens"] == 6
finally:
scope.dispose()


@pytest.mark.parametrize("session_id", ["session-123", None])
def test_inference_records_request_session_without_a_baggage_processor(session_id) -> None:
with InferenceScope.start(
Request(session_id=session_id, conversation_id="conversation-123"),
InferenceCallDetails(operationName=InferenceOperationType.CHAT, model="model", providerName="provider"),
AgentDetails("agent"),
) as scope:
attrs = _span_attributes(scope)
assert attrs["gen_ai.conversation.id"] == "conversation-123"
if session_id is None:
assert "microsoft.session.id" not in attrs
else:
assert attrs["microsoft.session.id"] == session_id
Loading