Rewording
This commit is contained in:
@@ -733,7 +733,8 @@ def create_metric_views() -> list[View]:
|
||||
|
||||
|
||||
# Token recognized in the OTEL_SEMCONV_STABILITY_OPT_IN env var that opts into the GenAI
|
||||
# conventions above the v1.36.0 stable release (collectively "experimental"; see
|
||||
# conventions above the v1.36.0 baseline (referred to here as "latest", since even the
|
||||
# baseline is not itself a stable release; see
|
||||
# https://github.com/open-telemetry/semantic-conventions/blob/v1.37.0/docs/gen-ai).
|
||||
GEN_AI_LATEST_EXPERIMENTAL_OPT_IN: Final[str] = "gen_ai_latest_experimental"
|
||||
|
||||
@@ -779,17 +780,18 @@ class ObservabilitySettings:
|
||||
Can be set via environment variable ENABLE_SENSITIVE_DATA.
|
||||
enable_console_exporters: Enable console exporters for traces, logs, and metrics.
|
||||
Default is False. Can be set via environment variable ENABLE_CONSOLE_EXPORTERS.
|
||||
enable_message_events: Emit the stable v1.36.0 GenAI message events (``gen_ai.system.message``,
|
||||
``gen_ai.user.message``, ``gen_ai.assistant.message``, ``gen_ai.tool.message``, ``gen_ai.choice``).
|
||||
Default is True. Can be set via environment variable ENABLE_MESSAGE_EVENTS. Only takes effect
|
||||
when sensitive data capture is enabled.
|
||||
enable_message_events: Emit the baseline v1.36.0 GenAI message events (``gen_ai.system.message``,
|
||||
``gen_ai.user.message``, ``gen_ai.assistant.message``, ``gen_ai.tool.message``, ``gen_ai.choice``)
|
||||
for model invocation. Default is True. Can be set via environment variable ENABLE_MESSAGE_EVENTS.
|
||||
Only takes effect when sensitive data capture is enabled.
|
||||
otel_semconv_stability_opt_in: A comma-separated list of category-specific values, following the
|
||||
standard OpenTelemetry comma-separated opt-in list format, currently only containing a single
|
||||
token ``"gen_ai_latest_experimental"``. v1.36.0 is the OTel-recommended stable release; every
|
||||
version above it is collectively "experimental". The default, unlike upstream OpenTelemetry which
|
||||
defaults to stable-only, ``"gen_ai_latest_experimental"`` selects the conventions above v1.36.0;
|
||||
a list that omits that token (e.g. ``""``) selects the v1.36.0 conventions instead. Can be set via
|
||||
environment variable OTEL_SEMCONV_STABILITY_OPT_IN.
|
||||
token ``"gen_ai_latest_experimental"``. v1.36.0 is the OTel-recommended baseline; every
|
||||
version above it is referred to here as "latest" (per OTel's own stability warning, even the
|
||||
baseline is not a stable release of the GenAI conventions). The default, unlike upstream
|
||||
OpenTelemetry which defaults to the baseline, ``"gen_ai_latest_experimental"`` selects the latest
|
||||
conventions above v1.36.0; a list that omits that token (e.g. ``""``) selects the v1.36.0
|
||||
conventions instead. Can be set via environment variable OTEL_SEMCONV_STABILITY_OPT_IN.
|
||||
vs_code_extension_port: The port the AI Toolkit or Microsoft Foundry VS Code extensions are listening on.
|
||||
Default is None.
|
||||
Can be set via environment variable VS_CODE_EXTENSION_PORT.
|
||||
@@ -891,15 +893,15 @@ class ObservabilitySettings:
|
||||
|
||||
@property
|
||||
def use_latest_experimental_gen_ai_semconv(self) -> bool:
|
||||
"""Whether to emit the GenAI semantic conventions above the v1.36.0 stable release.
|
||||
"""Whether to emit the GenAI semantic conventions above the v1.36.0 baseline.
|
||||
|
||||
v1.36.0 is the OTel-recommended stable release; every version above it is collectively
|
||||
"experimental".
|
||||
v1.36.0 is the OTel-recommended baseline; every version above it is referred to here as
|
||||
"latest".
|
||||
|
||||
Computed from ``otel_semconv_stability_opt_in`` (env var ``OTEL_SEMCONV_STABILITY_OPT_IN``), a
|
||||
comma-separated opt-in list per the standard OpenTelemetry format. Agent Framework defaults this
|
||||
to True (opted into the conventions above v1.36.0) when the setting is unset, which differs from
|
||||
upstream OpenTelemetry's default of stable-only.
|
||||
upstream OpenTelemetry's default of retaining the baseline conventions.
|
||||
"""
|
||||
if self.otel_semconv_stability_opt_in is None:
|
||||
return True
|
||||
@@ -1341,13 +1343,13 @@ def configure_otel_providers(
|
||||
the environment variable ENABLE_SENSITIVE_DATA if set. Default is None.
|
||||
enable_console_exporters: Enable console exporters for traces, logs, and metrics.
|
||||
Overrides the environment variable ENABLE_CONSOLE_EXPORTERS if set. Default is None.
|
||||
enable_message_events: Emit the stable v1.36.0 GenAI message events (``gen_ai.system.message``, etc.).
|
||||
Overrides the environment variable ENABLE_MESSAGE_EVENTS if set. Default is None, which resolves
|
||||
to True (events enabled).
|
||||
enable_message_events: Emit the baseline v1.36.0 GenAI message events (``gen_ai.system.message``, etc.)
|
||||
for model invocation. Overrides the environment variable ENABLE_MESSAGE_EVENTS if set. Default is
|
||||
None, which resolves to True (events enabled).
|
||||
otel_semconv_stability_opt_in: a comma-separated list of category-specific values (see
|
||||
``ObservabilitySettings.otel_semconv_stability_opt_in`` for the full explanation). Overrides the
|
||||
environment variable OTEL_SEMCONV_STABILITY_OPT_IN if set. Default is None, which resolves to the
|
||||
conventions above the v1.36.0 stable release.
|
||||
conventions above the v1.36.0 baseline.
|
||||
exporters: A list of custom exporters for logs, metrics or spans, or any combination.
|
||||
These will be added in addition to exporters configured via environment variables.
|
||||
Default is None.
|
||||
@@ -1652,7 +1654,7 @@ class ChatTelemetryLayer(Generic[OptionsCoT]):
|
||||
|
||||
if OBSERVABILITY_SETTINGS.SENSITIVE_DATA_ENABLED and messages and span.is_recording():
|
||||
system_instructions = _get_instructions_from_options(opts)
|
||||
_capture_current_agent_system_instructions_experimental(
|
||||
_capture_current_agent_system_instructions_latest_experimental(
|
||||
agent_span,
|
||||
span,
|
||||
system_instructions,
|
||||
@@ -1665,7 +1667,7 @@ class ChatTelemetryLayer(Generic[OptionsCoT]):
|
||||
messages=messages,
|
||||
system_instructions=system_instructions,
|
||||
)
|
||||
_capture_message_span_attributes_experimental(
|
||||
_capture_message_span_attributes_latest_experimental(
|
||||
span=span,
|
||||
messages=messages,
|
||||
system_instructions=system_instructions,
|
||||
@@ -1752,7 +1754,7 @@ class ChatTelemetryLayer(Generic[OptionsCoT]):
|
||||
finish_reason=finish_reason,
|
||||
output=True,
|
||||
)
|
||||
_capture_message_span_attributes_experimental(
|
||||
_capture_message_span_attributes_latest_experimental(
|
||||
span=span,
|
||||
messages=response.messages,
|
||||
finish_reason=finish_reason,
|
||||
@@ -1783,7 +1785,7 @@ class ChatTelemetryLayer(Generic[OptionsCoT]):
|
||||
with _get_span(attributes=attributes, span_name_attribute=OtelAttr.REQUEST_MODEL) as span:
|
||||
if OBSERVABILITY_SETTINGS.SENSITIVE_DATA_ENABLED and messages and span.is_recording():
|
||||
system_instructions = _get_instructions_from_options(opts)
|
||||
_capture_current_agent_system_instructions_experimental(
|
||||
_capture_current_agent_system_instructions_latest_experimental(
|
||||
agent_span,
|
||||
span,
|
||||
system_instructions,
|
||||
@@ -1793,7 +1795,7 @@ class ChatTelemetryLayer(Generic[OptionsCoT]):
|
||||
messages=messages,
|
||||
system_instructions=system_instructions,
|
||||
)
|
||||
_capture_message_span_attributes_experimental(
|
||||
_capture_message_span_attributes_latest_experimental(
|
||||
span=span,
|
||||
messages=messages,
|
||||
system_instructions=system_instructions,
|
||||
@@ -1834,7 +1836,7 @@ class ChatTelemetryLayer(Generic[OptionsCoT]):
|
||||
finish_reason=finish_reason,
|
||||
output=True,
|
||||
)
|
||||
_capture_message_span_attributes_experimental(
|
||||
_capture_message_span_attributes_latest_experimental(
|
||||
span=span,
|
||||
messages=response.messages,
|
||||
finish_reason=finish_reason,
|
||||
@@ -1993,7 +1995,7 @@ class AgentTelemetryLayer:
|
||||
span = _start_streaming_span(attributes, OtelAttr.AGENT_NAME)
|
||||
|
||||
if OBSERVABILITY_SETTINGS.SENSITIVE_DATA_ENABLED and messages and span.is_recording():
|
||||
_capture_message_span_attributes_experimental(
|
||||
_capture_message_span_attributes_latest_experimental(
|
||||
span=span,
|
||||
messages=messages,
|
||||
system_instructions=_get_instructions_from_options(dict(merged_options)),
|
||||
@@ -2066,7 +2068,7 @@ class AgentTelemetryLayer:
|
||||
and response.messages
|
||||
and span.is_recording()
|
||||
):
|
||||
_capture_message_span_attributes_experimental(
|
||||
_capture_message_span_attributes_latest_experimental(
|
||||
span=span,
|
||||
messages=response.messages,
|
||||
output=True,
|
||||
@@ -2131,7 +2133,7 @@ class AgentTelemetryLayer:
|
||||
with _get_span(attributes=attributes, span_name_attribute=OtelAttr.AGENT_NAME) as span:
|
||||
try:
|
||||
if OBSERVABILITY_SETTINGS.SENSITIVE_DATA_ENABLED and messages and span.is_recording():
|
||||
_capture_message_span_attributes_experimental(
|
||||
_capture_message_span_attributes_latest_experimental(
|
||||
span=span,
|
||||
messages=messages,
|
||||
system_instructions=_get_instructions_from_options(dict(merged_options)),
|
||||
@@ -2163,7 +2165,7 @@ class AgentTelemetryLayer:
|
||||
and response.messages
|
||||
and span.is_recording()
|
||||
):
|
||||
_capture_message_span_attributes_experimental(
|
||||
_capture_message_span_attributes_latest_experimental(
|
||||
span=span,
|
||||
messages=response.messages,
|
||||
output=True,
|
||||
@@ -2789,7 +2791,7 @@ def capture_exception(span: trace.Span, exception: Exception, timestamp: int | N
|
||||
span.set_status(status=trace.StatusCode.ERROR, description=repr(exception))
|
||||
|
||||
|
||||
def _capture_system_instructions_experimental(span: trace.Span, system_instructions: str | list[str] | None) -> None:
|
||||
def _capture_system_instructions_latest_experimental(span: trace.Span, system_instructions: str | list[str] | None) -> None:
|
||||
"""Capture system instructions on a span."""
|
||||
if not OBSERVABILITY_SETTINGS.use_latest_experimental_gen_ai_semconv or not system_instructions:
|
||||
return
|
||||
@@ -2802,7 +2804,7 @@ def _capture_system_instructions_experimental(span: trace.Span, system_instructi
|
||||
)
|
||||
|
||||
|
||||
def _capture_current_agent_system_instructions_experimental(
|
||||
def _capture_current_agent_system_instructions_latest_experimental(
|
||||
agent_span: trace.Span,
|
||||
chat_span: trace.Span,
|
||||
system_instructions: str | list[str] | None,
|
||||
@@ -2834,7 +2836,7 @@ def _capture_current_agent_system_instructions_experimental(
|
||||
):
|
||||
return
|
||||
|
||||
_capture_system_instructions_experimental(agent_span, system_instructions)
|
||||
_capture_system_instructions_latest_experimental(agent_span, system_instructions)
|
||||
|
||||
|
||||
def _normalize_instructions(system_instructions: str | list[str]) -> list[str]:
|
||||
@@ -2881,7 +2883,7 @@ def _capture_message_events_v1_36(
|
||||
output: bool = False,
|
||||
finish_reason: FinishReason | None = None,
|
||||
) -> None:
|
||||
"""Emit stable v1.36.0 GenAI events for a chat request or response."""
|
||||
"""Emit baseline v1.36.0 GenAI events for a model invocation."""
|
||||
if not OBSERVABILITY_SETTINGS.enable_message_events:
|
||||
return
|
||||
|
||||
@@ -2931,7 +2933,7 @@ def _emit_otel_event_v1_36(
|
||||
)
|
||||
|
||||
|
||||
def _capture_message_span_attributes_experimental(
|
||||
def _capture_message_span_attributes_latest_experimental(
|
||||
span: trace.Span,
|
||||
messages: AgentRunInputs,
|
||||
*,
|
||||
@@ -2939,24 +2941,24 @@ def _capture_message_span_attributes_experimental(
|
||||
output: bool = False,
|
||||
finish_reason: FinishReason | None = None,
|
||||
) -> None:
|
||||
"""Capture the latest-experimental GenAI message span attributes."""
|
||||
"""Capture the latest (above-baseline) GenAI message span attributes."""
|
||||
if not OBSERVABILITY_SETTINGS.use_latest_experimental_gen_ai_semconv:
|
||||
return
|
||||
|
||||
from ._types import normalize_messages
|
||||
|
||||
otel_messages = [_to_otel_message_experimental(message) for message in normalize_messages(messages)]
|
||||
otel_messages = [_to_otel_message_latest_experimental(message) for message in normalize_messages(messages)]
|
||||
if finish_reason and otel_messages:
|
||||
otel_messages[-1]["finish_reason"] = FINISH_REASON_MAP.get(finish_reason, finish_reason)
|
||||
span.set_attribute(
|
||||
OtelAttr.OUTPUT_MESSAGES if output else OtelAttr.INPUT_MESSAGES,
|
||||
json.dumps(otel_messages, ensure_ascii=False),
|
||||
)
|
||||
_capture_system_instructions_experimental(span, system_instructions)
|
||||
_capture_system_instructions_latest_experimental(span, system_instructions)
|
||||
|
||||
|
||||
def _to_otel_input_events_v1_36(message: Message) -> list[tuple[OtelAttr, dict[str, Any]]]:
|
||||
"""Create stable v1.36.0 event names and bodies for an input message."""
|
||||
"""Create baseline v1.36.0 event names and bodies for an input message."""
|
||||
event_name = ROLE_EVENT_MAP.get(message.role)
|
||||
if event_name is None:
|
||||
return []
|
||||
@@ -2988,7 +2990,7 @@ def _to_otel_input_events_v1_36(message: Message) -> list[tuple[OtelAttr, dict[s
|
||||
|
||||
|
||||
def _to_otel_choice_v1_36(message: Message, index: int, finish_reason: str) -> dict[str, Any]:
|
||||
"""Create a stable v1.36.0 choice event body."""
|
||||
"""Create a baseline v1.36.0 choice event body."""
|
||||
choice_message: dict[str, Any] = {}
|
||||
if message.text:
|
||||
choice_message["content"] = message.text
|
||||
@@ -3005,7 +3007,7 @@ def _to_otel_choice_v1_36(message: Message, index: int, finish_reason: str) -> d
|
||||
|
||||
|
||||
def _to_otel_tool_calls_v1_36(message: Message) -> list[dict[str, Any]]:
|
||||
"""Create stable v1.36.0 function-call structures for a message."""
|
||||
"""Create baseline v1.36.0 function-call structures for a message."""
|
||||
return [
|
||||
{
|
||||
"id": content.call_id,
|
||||
@@ -3020,15 +3022,15 @@ def _to_otel_tool_calls_v1_36(message: Message) -> list[dict[str, Any]]:
|
||||
]
|
||||
|
||||
|
||||
def _to_otel_message_experimental(message: Message) -> dict[str, Any]:
|
||||
def _to_otel_message_latest_experimental(message: Message) -> dict[str, Any]:
|
||||
"""Create a otel representation of a message."""
|
||||
return {
|
||||
"role": message.role,
|
||||
"parts": [_to_otel_part_experimental(content) for content in message.contents],
|
||||
"parts": [_to_otel_part_latest_experimental(content) for content in message.contents],
|
||||
}
|
||||
|
||||
|
||||
def _to_otel_part_experimental(content: Content) -> dict[str, Any] | None:
|
||||
def _to_otel_part_latest_experimental(content: Content) -> dict[str, Any] | None:
|
||||
"""Create a otel representation of a Content."""
|
||||
from ._types import _get_data_bytes_as_str # pyright: ignore[reportPrivateUsage]
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ from agent_framework.observability import (
|
||||
MessageListTimestampFilter,
|
||||
OtelAttr,
|
||||
_capture_message_events_v1_36,
|
||||
_capture_message_span_attributes_experimental,
|
||||
_capture_message_span_attributes_latest_experimental,
|
||||
_get_instructions_from_options,
|
||||
_to_otel_choice_v1_36,
|
||||
_to_otel_input_events_v1_36,
|
||||
@@ -369,10 +369,10 @@ async def test_chat_client_observability_with_instructions(
|
||||
|
||||
|
||||
@pytest.mark.parametrize("enable_sensitive_data", [True], indirect=True)
|
||||
async def test_chat_client_stable_semconv_omits_system_instructions(
|
||||
async def test_chat_client_baseline_semconv_omits_system_instructions(
|
||||
mock_chat_client, span_exporter: InMemorySpanExporter, enable_sensitive_data
|
||||
):
|
||||
"""Test that stable v1.36.0 telemetry omits the post-v1.36.0 system instructions attribute."""
|
||||
"""Test that baseline v1.36.0 telemetry omits the post-v1.36.0 system instructions attribute."""
|
||||
import agent_framework.observability as observability
|
||||
|
||||
observability.OBSERVABILITY_SETTINGS.otel_semconv_stability_opt_in = ""
|
||||
@@ -509,7 +509,7 @@ async def test_chat_client_streaming_input_events_correlated_to_chat_span(
|
||||
) -> None:
|
||||
"""Regression guard: streaming input events must carry the chat span's trace/span id.
|
||||
|
||||
``_capture_message_events_v1_36`` emits the stable v1.36.0 GenAI message events via the
|
||||
``_capture_message_events_v1_36`` emits the baseline v1.36.0 GenAI message events via the
|
||||
native OTel event logger, which derives trace/span correlation from whatever span is
|
||||
current in the ambient context at emit time. In the streaming path the chat span is
|
||||
started with ``_start_streaming_span`` (not attached as current), so those events must be
|
||||
@@ -1950,7 +1950,7 @@ def test_enable_instrumentation_reads_env_sensitive_data(monkeypatch):
|
||||
|
||||
|
||||
def test_semconv_defaults_to_latest_experimental_when_unset(monkeypatch):
|
||||
"""OTEL_SEMCONV_STABILITY_OPT_IN unset → MAF defaults to the latest experimental conventions."""
|
||||
"""OTEL_SEMCONV_STABILITY_OPT_IN unset → MAF defaults to the latest conventions."""
|
||||
from agent_framework.observability import ObservabilitySettings
|
||||
|
||||
monkeypatch.delenv("OTEL_SEMCONV_STABILITY_OPT_IN", raising=False)
|
||||
@@ -1960,8 +1960,8 @@ def test_semconv_defaults_to_latest_experimental_when_unset(monkeypatch):
|
||||
assert settings.use_latest_experimental_gen_ai_semconv is True
|
||||
|
||||
|
||||
def test_semconv_explicit_empty_opts_into_stable(monkeypatch):
|
||||
"""Explicitly setting OTEL_SEMCONV_STABILITY_OPT_IN='' opts into the stable v1.36.0 conventions."""
|
||||
def test_semconv_explicit_empty_opts_into_baseline(monkeypatch):
|
||||
"""Explicitly setting OTEL_SEMCONV_STABILITY_OPT_IN='' opts into the baseline v1.36.0 conventions."""
|
||||
from agent_framework.observability import ObservabilitySettings
|
||||
|
||||
monkeypatch.setenv("OTEL_SEMCONV_STABILITY_OPT_IN", "")
|
||||
@@ -1971,7 +1971,7 @@ def test_semconv_explicit_empty_opts_into_stable(monkeypatch):
|
||||
|
||||
|
||||
def test_semconv_explicit_token_opts_into_latest_experimental(monkeypatch):
|
||||
"""Explicitly including 'gen_ai_latest_experimental' opts into the latest experimental conventions."""
|
||||
"""Explicitly including 'gen_ai_latest_experimental' opts into the latest conventions."""
|
||||
from agent_framework.observability import ObservabilitySettings
|
||||
|
||||
monkeypatch.setenv("OTEL_SEMCONV_STABILITY_OPT_IN", "gen_ai_latest_experimental")
|
||||
@@ -1993,14 +1993,14 @@ def test_semconv_multi_value_list_checks_for_gen_ai_token(monkeypatch):
|
||||
assert settings.use_latest_experimental_gen_ai_semconv is False
|
||||
|
||||
|
||||
def test_stable_semconv_skips_current_agent_system_instruction_checks(span_exporter: InMemorySpanExporter):
|
||||
"""Stable v1.36.0 returns before inspecting spans for the unsupported system instructions attribute."""
|
||||
def test_baseline_semconv_skips_current_agent_system_instruction_checks(span_exporter: InMemorySpanExporter):
|
||||
"""Baseline v1.36.0 returns before inspecting spans for the unsupported system instructions attribute."""
|
||||
import agent_framework.observability as observability
|
||||
|
||||
observability.OBSERVABILITY_SETTINGS.otel_semconv_stability_opt_in = ""
|
||||
agent_span = Mock()
|
||||
|
||||
observability._capture_current_agent_system_instructions_experimental( # pyright: ignore[reportPrivateUsage]
|
||||
observability._capture_current_agent_system_instructions_latest_experimental( # pyright: ignore[reportPrivateUsage]
|
||||
agent_span,
|
||||
Mock(),
|
||||
"You are a helpful assistant.",
|
||||
@@ -2020,7 +2020,7 @@ def test_enable_message_events_defaults_true(monkeypatch):
|
||||
|
||||
|
||||
def test_enable_message_events_can_be_disabled(monkeypatch):
|
||||
"""ENABLE_MESSAGE_EVENTS=false disables the stable v1.36.0 message events."""
|
||||
"""ENABLE_MESSAGE_EVENTS=false disables the baseline v1.36.0 message events."""
|
||||
from agent_framework.observability import ObservabilitySettings
|
||||
|
||||
monkeypatch.setenv("ENABLE_MESSAGE_EVENTS", "false")
|
||||
@@ -2041,8 +2041,8 @@ def test_get_span_attributes_uses_provider_name_under_latest_semconv(span_export
|
||||
|
||||
|
||||
@pytest.mark.parametrize("enable_sensitive_data", [False], indirect=True)
|
||||
def test_get_span_attributes_uses_system_under_stable_semconv(span_exporter: InMemorySpanExporter):
|
||||
"""Under the stable v1.36.0 semconv, the provider attribute reverts to gen_ai.system."""
|
||||
def test_get_span_attributes_uses_system_under_baseline_semconv(span_exporter: InMemorySpanExporter):
|
||||
"""Under the baseline v1.36.0 semconv, the provider attribute reverts to gen_ai.system."""
|
||||
import agent_framework.observability as observability
|
||||
|
||||
observability.OBSERVABILITY_SETTINGS.otel_semconv_stability_opt_in = ""
|
||||
@@ -2053,10 +2053,10 @@ def test_get_span_attributes_uses_system_under_stable_semconv(span_exporter: InM
|
||||
|
||||
|
||||
@pytest.mark.parametrize("enable_sensitive_data", [False], indirect=True)
|
||||
def test_get_span_attributes_omits_post_v1_36_attributes_under_stable_semconv(
|
||||
def test_get_span_attributes_omits_post_v1_36_attributes_under_baseline_semconv(
|
||||
span_exporter: InMemorySpanExporter,
|
||||
):
|
||||
"""Stable v1.36.0 omits attributes introduced by later GenAI conventions."""
|
||||
"""Baseline v1.36.0 omits attributes introduced by later GenAI conventions."""
|
||||
import agent_framework.observability as observability
|
||||
|
||||
observability.OBSERVABILITY_SETTINGS.otel_semconv_stability_opt_in = ""
|
||||
@@ -2072,10 +2072,10 @@ def test_get_span_attributes_omits_post_v1_36_attributes_under_stable_semconv(
|
||||
|
||||
|
||||
@pytest.mark.parametrize("enable_sensitive_data", [True], indirect=True)
|
||||
async def test_chat_client_observability_provider_name_under_stable_semconv(
|
||||
async def test_chat_client_observability_provider_name_under_baseline_semconv(
|
||||
mock_chat_client, span_exporter: InMemorySpanExporter
|
||||
):
|
||||
"""Chat spans report gen_ai.system (not gen_ai.provider.name) under the stable v1.36.0 semconv."""
|
||||
"""Chat spans report gen_ai.system (not gen_ai.provider.name) under the baseline v1.36.0 semconv."""
|
||||
import agent_framework.observability as observability
|
||||
|
||||
observability.OBSERVABILITY_SETTINGS.otel_semconv_stability_opt_in = ""
|
||||
@@ -2248,10 +2248,10 @@ def test_disable_instrumentation_in_all(monkeypatch):
|
||||
def test_to_otel_part_text():
|
||||
"""Test _to_otel_part with text content."""
|
||||
from agent_framework import Content
|
||||
from agent_framework.observability import _to_otel_part_experimental
|
||||
from agent_framework.observability import _to_otel_part_latest_experimental
|
||||
|
||||
content = Content(type="text", text="Hello world")
|
||||
result = _to_otel_part_experimental(content)
|
||||
result = _to_otel_part_latest_experimental(content)
|
||||
|
||||
assert result == {"type": "text", "content": "Hello world"}
|
||||
|
||||
@@ -2259,10 +2259,10 @@ def test_to_otel_part_text():
|
||||
def test_to_otel_part_text_reasoning():
|
||||
"""Test _to_otel_part with text_reasoning content."""
|
||||
from agent_framework import Content
|
||||
from agent_framework.observability import _to_otel_part_experimental
|
||||
from agent_framework.observability import _to_otel_part_latest_experimental
|
||||
|
||||
content = Content(type="text_reasoning", text="Thinking about this...")
|
||||
result = _to_otel_part_experimental(content)
|
||||
result = _to_otel_part_latest_experimental(content)
|
||||
|
||||
assert result == {"type": "reasoning", "content": "Thinking about this..."}
|
||||
|
||||
@@ -2270,10 +2270,10 @@ def test_to_otel_part_text_reasoning():
|
||||
def test_to_otel_part_uri():
|
||||
"""Test _to_otel_part with uri content."""
|
||||
from agent_framework import Content
|
||||
from agent_framework.observability import _to_otel_part_experimental
|
||||
from agent_framework.observability import _to_otel_part_latest_experimental
|
||||
|
||||
content = Content(type="uri", uri="https://example.com/image.png", media_type="image/png")
|
||||
result = _to_otel_part_experimental(content)
|
||||
result = _to_otel_part_latest_experimental(content)
|
||||
|
||||
assert result == {
|
||||
"type": "uri",
|
||||
@@ -2286,10 +2286,10 @@ def test_to_otel_part_uri():
|
||||
def test_to_otel_part_uri_no_media_type():
|
||||
"""Test _to_otel_part with uri content without media_type."""
|
||||
from agent_framework import Content
|
||||
from agent_framework.observability import _to_otel_part_experimental
|
||||
from agent_framework.observability import _to_otel_part_latest_experimental
|
||||
|
||||
content = Content(type="uri", uri="https://example.com/file")
|
||||
result = _to_otel_part_experimental(content)
|
||||
result = _to_otel_part_latest_experimental(content)
|
||||
|
||||
assert result == {
|
||||
"type": "uri",
|
||||
@@ -2302,11 +2302,11 @@ def test_to_otel_part_uri_no_media_type():
|
||||
def test_to_otel_part_data():
|
||||
"""Test _to_otel_part with data content."""
|
||||
from agent_framework import Content
|
||||
from agent_framework.observability import _to_otel_part_experimental
|
||||
from agent_framework.observability import _to_otel_part_latest_experimental
|
||||
|
||||
data = b"binary data"
|
||||
content = Content.from_data(data=data, media_type="application/octet-stream")
|
||||
result = _to_otel_part_experimental(content)
|
||||
result = _to_otel_part_latest_experimental(content)
|
||||
|
||||
assert result["type"] == "blob" # type: ignore[index] # pyrefly: ignore[unsupported-operation] # ty: ignore[not-subscriptable]
|
||||
assert result["mime_type"] == "application/octet-stream" # type: ignore[index] # pyrefly: ignore[unsupported-operation] # ty: ignore[not-subscriptable]
|
||||
@@ -2316,10 +2316,10 @@ def test_to_otel_part_data():
|
||||
def test_to_otel_part_function_call():
|
||||
"""Test _to_otel_part with function_call content."""
|
||||
from agent_framework import Content
|
||||
from agent_framework.observability import _to_otel_part_experimental
|
||||
from agent_framework.observability import _to_otel_part_latest_experimental
|
||||
|
||||
content = Content(type="function_call", call_id="call_123", name="test_function", arguments='{"arg1": "value1"}')
|
||||
result = _to_otel_part_experimental(content)
|
||||
result = _to_otel_part_latest_experimental(content)
|
||||
|
||||
assert result == {
|
||||
"type": "tool_call",
|
||||
@@ -2332,11 +2332,11 @@ def test_to_otel_part_function_call():
|
||||
def test_to_otel_part_function_call_reuses_prepared_arguments():
|
||||
"""Test _to_otel_part does not re-serialize function-call arguments in the observability hot path."""
|
||||
from agent_framework import Content
|
||||
from agent_framework.observability import _to_otel_part_experimental
|
||||
from agent_framework.observability import _to_otel_part_latest_experimental
|
||||
|
||||
arguments = {"payload": object()}
|
||||
content = Content(type="function_call", call_id="call_789", name="handoff", arguments=arguments)
|
||||
result = _to_otel_part_experimental(content)
|
||||
result = _to_otel_part_latest_experimental(content)
|
||||
|
||||
assert result is not None
|
||||
assert result["arguments"] is arguments
|
||||
@@ -2391,27 +2391,27 @@ def test_make_json_safe_dict_with_non_string_keys():
|
||||
def test_to_otel_part_function_result():
|
||||
"""Test _to_otel_part with function_result content."""
|
||||
from agent_framework import Content
|
||||
from agent_framework.observability import _to_otel_part_experimental
|
||||
from agent_framework.observability import _to_otel_part_latest_experimental
|
||||
|
||||
content = Content(type="function_result", call_id="call_123", result="Success")
|
||||
result = _to_otel_part_experimental(content)
|
||||
result = _to_otel_part_latest_experimental(content)
|
||||
|
||||
assert result["type"] == "tool_call_response" # type: ignore[index] # pyrefly: ignore[unsupported-operation] # ty: ignore[not-subscriptable]
|
||||
assert result["id"] == "call_123" # type: ignore[index] # pyrefly: ignore[unsupported-operation] # ty: ignore[not-subscriptable]
|
||||
|
||||
|
||||
# region Test stable v1.36.0 event conversion
|
||||
# region Test baseline v1.36.0 event conversion
|
||||
|
||||
|
||||
def test_to_otel_v1_36_user_message_body():
|
||||
"""Stable user events use content rather than the experimental parts shape."""
|
||||
"""Baseline user events use content rather than the experimental parts shape."""
|
||||
events = _to_otel_input_events_v1_36(Message(role="user", contents=["Hello", "world"]))
|
||||
|
||||
assert events == [(OtelAttr.USER_MESSAGE, {"content": "Hello world"})]
|
||||
|
||||
|
||||
def test_to_otel_v1_36_assistant_tool_call_body():
|
||||
"""Stable assistant events use the v1.36 function-call nesting."""
|
||||
"""Baseline assistant events use the v1.36 function-call nesting."""
|
||||
message = Message(
|
||||
role="assistant",
|
||||
contents=[Content.from_function_call(call_id="call_123", name="get_weather", arguments='{"city":"Paris"}')],
|
||||
@@ -2436,7 +2436,7 @@ def test_to_otel_v1_36_assistant_tool_call_body():
|
||||
|
||||
|
||||
def test_to_otel_v1_36_tool_results_emit_one_event_per_call():
|
||||
"""Each stable tool event has the one required tool-call id."""
|
||||
"""Each baseline tool event has the one required tool-call id."""
|
||||
message = Message(
|
||||
role="tool",
|
||||
contents=[
|
||||
@@ -2454,14 +2454,14 @@ def test_to_otel_v1_36_tool_results_emit_one_event_per_call():
|
||||
|
||||
|
||||
def test_to_otel_v1_36_tool_message_without_call_id_is_skipped():
|
||||
"""A stable tool event is not emitted when its required call id is unavailable."""
|
||||
"""A baseline tool event is not emitted when its required call id is unavailable."""
|
||||
events = _to_otel_input_events_v1_36(Message(role="tool", contents=["Uncorrelated result"]))
|
||||
|
||||
assert events == []
|
||||
|
||||
|
||||
def test_to_otel_v1_36_choice_body():
|
||||
"""Stable choices contain index, finish reason, and a nested message."""
|
||||
"""Baseline choices contain index, finish reason, and a nested message."""
|
||||
body = _to_otel_choice_v1_36(
|
||||
Message(role="assistant", contents=["Done"]),
|
||||
index=1,
|
||||
@@ -2825,8 +2825,8 @@ def test_get_response_attributes_with_additional_usage():
|
||||
assert result[OtelAttr.REASONING_OUTPUT_TOKENS] == 30
|
||||
|
||||
|
||||
def test_get_response_attributes_omits_post_v1_36_usage_under_stable_semconv(monkeypatch: pytest.MonkeyPatch):
|
||||
"""Stable v1.36.0 keeps total usage while omitting newer token breakdowns."""
|
||||
def test_get_response_attributes_omits_post_v1_36_usage_under_baseline_semconv(monkeypatch: pytest.MonkeyPatch):
|
||||
"""Baseline v1.36.0 keeps total usage while omitting newer token breakdowns."""
|
||||
from unittest.mock import Mock
|
||||
|
||||
import agent_framework.observability as observability
|
||||
@@ -3011,11 +3011,11 @@ def test_observability_settings_configure_already_setup(monkeypatch):
|
||||
def test_to_otel_part_generic():
|
||||
"""Test _to_otel_part with unknown content type uses to_dict fallback."""
|
||||
from agent_framework import Content
|
||||
from agent_framework.observability import _to_otel_part_experimental
|
||||
from agent_framework.observability import _to_otel_part_latest_experimental
|
||||
|
||||
# Create a content with type that falls to default case
|
||||
content = Content(type="annotations", text="some text") # type: ignore[arg-type] # pyrefly: ignore[bad-argument-type] # ty: ignore[invalid-argument-type]
|
||||
result = _to_otel_part_experimental(content)
|
||||
result = _to_otel_part_latest_experimental(content)
|
||||
|
||||
# Should return result from to_dict
|
||||
assert result is not None
|
||||
@@ -3412,8 +3412,8 @@ async def test_capture_messages_with_finish_reason(mock_chat_client, span_export
|
||||
|
||||
|
||||
@pytest.mark.parametrize("enable_sensitive_data", [True], indirect=True)
|
||||
def test_capture_messages_stable_semconv_emits_events_only(span_exporter: InMemorySpanExporter):
|
||||
"""Stable v1.36.0 conventions (opt-in list without the experimental token): events only, no span attribute."""
|
||||
def test_capture_messages_baseline_semconv_emits_events_only(span_exporter: InMemorySpanExporter):
|
||||
"""Baseline v1.36.0 conventions (opt-in list without the experimental token): events only, no span attribute."""
|
||||
from opentelemetry import trace
|
||||
|
||||
import agent_framework.observability as observability
|
||||
@@ -3461,7 +3461,7 @@ def test_capture_messages_latest_experimental_emits_span_attribute_only_when_eve
|
||||
patch("agent_framework.observability.otel_event_logger.emit") as mock_emit,
|
||||
tracer.start_as_current_span("test_span") as span,
|
||||
):
|
||||
observability._capture_message_span_attributes_experimental( # type: ignore[reportPrivateUsage]
|
||||
observability._capture_message_span_attributes_latest_experimental( # type: ignore[reportPrivateUsage]
|
||||
span=span,
|
||||
messages=[Message(role="user", contents=["Test"])],
|
||||
)
|
||||
@@ -3494,7 +3494,7 @@ def test_capture_messages_defaults_emit_both_events_and_span_attribute(span_expo
|
||||
provider_name="test_provider",
|
||||
messages=[Message(role="user", contents=["Test"])],
|
||||
)
|
||||
observability._capture_message_span_attributes_experimental( # type: ignore[reportPrivateUsage]
|
||||
observability._capture_message_span_attributes_latest_experimental( # type: ignore[reportPrivateUsage]
|
||||
span=span,
|
||||
messages=[Message(role="user", contents=["Test"])],
|
||||
)
|
||||
@@ -3510,8 +3510,8 @@ def test_capture_messages_defaults_emit_both_events_and_span_attribute(span_expo
|
||||
|
||||
|
||||
@pytest.mark.parametrize("enable_sensitive_data", [True], indirect=True)
|
||||
def test_capture_messages_stable_semconv_emits_one_event_per_choice(span_exporter: InMemorySpanExporter):
|
||||
"""Stable v1.36 emits each model choice with its required index and finish reason."""
|
||||
def test_capture_messages_baseline_semconv_emits_one_event_per_choice(span_exporter: InMemorySpanExporter):
|
||||
"""Baseline v1.36 emits each model choice with its required index and finish reason."""
|
||||
from opentelemetry import trace
|
||||
|
||||
import agent_framework.observability as observability
|
||||
@@ -3542,7 +3542,7 @@ def test_capture_messages_stable_semconv_emits_one_event_per_choice(span_exporte
|
||||
|
||||
@pytest.mark.parametrize("enable_sensitive_data", [True], indirect=True)
|
||||
def test_capture_messages_preserves_custom_finish_reason(span_exporter: InMemorySpanExporter):
|
||||
"""Custom finish reasons remain available to stable events and experimental attributes."""
|
||||
"""Custom finish reasons remain available to baseline events and experimental attributes."""
|
||||
import json
|
||||
|
||||
from opentelemetry import trace
|
||||
@@ -3560,7 +3560,7 @@ def test_capture_messages_preserves_custom_finish_reason(span_exporter: InMemory
|
||||
output=True,
|
||||
finish_reason=cast(Any, "guardrail"),
|
||||
)
|
||||
observability._capture_message_span_attributes_experimental( # type: ignore[reportPrivateUsage]
|
||||
observability._capture_message_span_attributes_latest_experimental( # type: ignore[reportPrivateUsage]
|
||||
span=span,
|
||||
messages=[Message(role="assistant", contents=["Done"])],
|
||||
output=True,
|
||||
@@ -4636,7 +4636,7 @@ async def test_system_instructions_preserves_non_ascii_characters(span_exporter:
|
||||
span_exporter.clear()
|
||||
|
||||
with tracer.start_as_current_span("test_span") as span:
|
||||
_capture_message_span_attributes_experimental(
|
||||
_capture_message_span_attributes_latest_experimental(
|
||||
span=span,
|
||||
messages=[Message(role="user", contents=["Test"])],
|
||||
system_instructions=chinese_text,
|
||||
@@ -4691,7 +4691,7 @@ def test_capture_messages_with_prepared_request_info_function_call_arguments(spa
|
||||
span_exporter.clear()
|
||||
tracer = trace.get_tracer("test")
|
||||
with tracer.start_as_current_span("test_span") as span:
|
||||
_capture_message_span_attributes_experimental(span=span, messages=[msg])
|
||||
_capture_message_span_attributes_latest_experimental(span=span, messages=[msg])
|
||||
|
||||
spans = span_exporter.get_finished_spans()
|
||||
span = spans[0] # type: ignore[assignment]
|
||||
@@ -4704,7 +4704,7 @@ def test_capture_messages_with_prepared_request_info_function_call_arguments(spa
|
||||
def test_capture_messages_emits_framework_instructions_separately_from_history(
|
||||
span_exporter: InMemorySpanExporter,
|
||||
):
|
||||
"""Test separate framework instructions use their own stable event and experimental span attribute."""
|
||||
"""Test separate framework instructions use their own baseline event and experimental span attribute."""
|
||||
import json
|
||||
|
||||
from opentelemetry import trace
|
||||
@@ -4721,7 +4721,7 @@ def test_capture_messages_emits_framework_instructions_separately_from_history(
|
||||
messages=[Message(role="user", contents=["Test"])],
|
||||
system_instructions="Framework system instruction",
|
||||
)
|
||||
_capture_message_span_attributes_experimental(
|
||||
_capture_message_span_attributes_latest_experimental(
|
||||
span=span,
|
||||
messages=[Message(role="user", contents=["Test"])],
|
||||
system_instructions="Framework system instruction",
|
||||
@@ -4745,7 +4745,7 @@ def test_capture_messages_emits_framework_instructions_separately_from_history(
|
||||
def test_capture_messages_preserves_framework_instructions_and_system_history(
|
||||
span_exporter: InMemorySpanExporter,
|
||||
):
|
||||
"""Test stable events preserve separate instructions and original system history."""
|
||||
"""Test baseline events preserve separate instructions and original system history."""
|
||||
import json
|
||||
|
||||
from opentelemetry import trace
|
||||
@@ -4765,7 +4765,7 @@ def test_capture_messages_preserves_framework_instructions_and_system_history(
|
||||
],
|
||||
system_instructions="Framework system instruction",
|
||||
)
|
||||
_capture_message_span_attributes_experimental(
|
||||
_capture_message_span_attributes_latest_experimental(
|
||||
span=span,
|
||||
messages=[
|
||||
Message(role="system", contents=["Original system message"]),
|
||||
@@ -6658,7 +6658,7 @@ async def test_chat_capture_messages_skipped_when_span_not_recording(
|
||||
patch("agent_framework.observability.get_tracer", return_value=NoOpTracer()),
|
||||
patch("agent_framework.observability._capture_message_events_v1_36") as mock_capture_v1_36,
|
||||
patch(
|
||||
"agent_framework.observability._capture_message_span_attributes_experimental"
|
||||
"agent_framework.observability._capture_message_span_attributes_latest_experimental"
|
||||
) as mock_capture_experimental,
|
||||
patch("agent_framework.observability._capture_response") as mock_capture_response,
|
||||
):
|
||||
@@ -6687,7 +6687,7 @@ async def test_chat_streaming_capture_messages_skipped_when_span_not_recording(
|
||||
patch("agent_framework.observability.get_tracer", return_value=NoOpTracer()),
|
||||
patch("agent_framework.observability._capture_message_events_v1_36") as mock_capture_v1_36,
|
||||
patch(
|
||||
"agent_framework.observability._capture_message_span_attributes_experimental"
|
||||
"agent_framework.observability._capture_message_span_attributes_latest_experimental"
|
||||
) as mock_capture_experimental,
|
||||
patch("agent_framework.observability._capture_response") as mock_capture_response,
|
||||
):
|
||||
@@ -6715,7 +6715,7 @@ async def test_agent_capture_messages_skipped_when_span_not_recording(
|
||||
|
||||
with (
|
||||
patch("agent_framework.observability.get_tracer", return_value=NoOpTracer()),
|
||||
patch("agent_framework.observability._capture_message_span_attributes_experimental") as mock_capture,
|
||||
patch("agent_framework.observability._capture_message_span_attributes_latest_experimental") as mock_capture,
|
||||
patch("agent_framework.observability._capture_response") as mock_capture_response,
|
||||
):
|
||||
response = await agent.run("Test message")
|
||||
@@ -6737,7 +6737,7 @@ async def test_agent_streaming_capture_messages_skipped_when_span_not_recording(
|
||||
|
||||
with (
|
||||
patch("agent_framework.observability.get_tracer", return_value=NoOpTracer()),
|
||||
patch("agent_framework.observability._capture_message_span_attributes_experimental") as mock_capture,
|
||||
patch("agent_framework.observability._capture_message_span_attributes_latest_experimental") as mock_capture,
|
||||
patch("agent_framework.observability._capture_response") as mock_capture_response,
|
||||
):
|
||||
updates: list[Any] = []
|
||||
@@ -6763,7 +6763,7 @@ async def test_chat_capture_messages_called_when_span_recording(
|
||||
with (
|
||||
patch("agent_framework.observability._capture_message_events_v1_36") as mock_capture_v1_36,
|
||||
patch(
|
||||
"agent_framework.observability._capture_message_span_attributes_experimental"
|
||||
"agent_framework.observability._capture_message_span_attributes_latest_experimental"
|
||||
) as mock_capture_experimental,
|
||||
patch("agent_framework.observability._capture_response") as mock_capture_response,
|
||||
):
|
||||
|
||||
@@ -653,8 +653,8 @@ async def test_tool_invoke_telemetry_sensitive_disabled(span_exporter: InMemoryS
|
||||
|
||||
|
||||
@pytest.mark.parametrize("enable_sensitive_data", [True], indirect=True)
|
||||
async def test_tool_invoke_telemetry_omits_tool_call_attrs_under_stable_semconv(span_exporter: InMemorySpanExporter):
|
||||
"""gen_ai.tool.call.arguments/result were introduced above v1.36.0; omit them under the stable semconv."""
|
||||
async def test_tool_invoke_telemetry_omits_tool_call_attrs_under_baseline_semconv(span_exporter: InMemorySpanExporter):
|
||||
"""gen_ai.tool.call.arguments/result were introduced above v1.36.0; omit them under the baseline semconv."""
|
||||
import agent_framework.observability as observability
|
||||
|
||||
observability.OBSERVABILITY_SETTINGS.otel_semconv_stability_opt_in = ""
|
||||
|
||||
@@ -199,8 +199,8 @@ Agent Framework reads the following environment variables:
|
||||
| `ENABLE_INSTRUMENTATION` | `true` | Set to `false` to disable native instrumentation. See [Disabling instrumentation](#disabling-instrumentation) for the programmatic alternative with sticky semantics. |
|
||||
| `ENABLE_SENSITIVE_DATA` | `false` | Set to `true` to emit sensitive data (prompts, responses, etc.). |
|
||||
| `ENABLE_CONSOLE_EXPORTERS` | `false` | Set to `true` to add console exporters. Only used by `configure_otel_providers()`. |
|
||||
| `ENABLE_MESSAGE_EVENTS` | `true` | Set to `false` to stop emitting the stable v1.36.0 GenAI message events (`gen_ai.system.message`, etc.). **Has no effect unless `ENABLE_SENSITIVE_DATA=true`.** See [GenAI semantic-conventions versioning](#genai-semantic-conventions-versioning). |
|
||||
| `OTEL_SEMCONV_STABILITY_OPT_IN` | unset (conventions above v1.36.0) | A comma-separated list of category-specific values, following the standard OpenTelemetry comma-separated opt-in list format, currently only containing a single token ``"gen_ai_latest_experimental"``. v1.36.0 is the OTel-recommended stable release; every version above it is collectively "experimental". The default, unlike upstream OpenTelemetry which defaults to stable-only, ``"gen_ai_latest_experimental"`` selects the conventions above v1.36.0; a list that omits that token (e.g. ``""``) selects the v1.36.0 conventions instead. See [GenAI semantic-conventions versioning](#genai-semantic-conventions-versioning). |
|
||||
| `ENABLE_MESSAGE_EVENTS` | `true` | Set to `false` to stop emitting the baseline v1.36.0 GenAI message events (`gen_ai.system.message`, etc.) for model invocation. **Has no effect unless `ENABLE_SENSITIVE_DATA=true`.** See [GenAI semantic-conventions versioning](#genai-semantic-conventions-versioning). |
|
||||
| `OTEL_SEMCONV_STABILITY_OPT_IN` | unset (conventions above v1.36.0) | A comma-separated list of category-specific values, following the standard OpenTelemetry comma-separated opt-in list format, currently only containing a single token ``"gen_ai_latest_experimental"``. v1.36.0 is the OTel-recommended baseline; every version above it is referred to here as "latest" (even the baseline is an expeirmental release). The default, unlike upstream OpenTelemetry which retains the baseline conventions, ``"gen_ai_latest_experimental"`` selects the latest conventions above v1.36.0; a list that omits that token (e.g. ``""``) selects the v1.36.0 conventions instead. See [GenAI semantic-conventions versioning](#genai-semantic-conventions-versioning). |
|
||||
| `VS_CODE_EXTENSION_PORT` | unset | Port used by the [AI Toolkit for VS Code](https://marketplace.visualstudio.com/items?itemName=ms-windows-ai-studio.windows-ai-studio#tracing) tracing integration. Only used by `configure_otel_providers()`. |
|
||||
|
||||
You can also call `enable_sensitive_telemetry()` from `agent_framework.observability` to opt in to sensitive-data capture programmatically.
|
||||
@@ -209,9 +209,9 @@ You can also call `enable_sensitive_telemetry()` from `agent_framework.observabi
|
||||
|
||||
### GenAI semantic-conventions versioning
|
||||
|
||||
[v1.36.0](https://github.com/open-telemetry/semantic-conventions/blob/v1.36.0/docs/gen-ai) is the OpenTelemetry-recommended **stable** release of the [GenAI semantic conventions](https://opentelemetry.io/docs/specs/semconv/gen-ai/). Every release above it (v1.37.0 and later) is collectively **experimental** and, per OTel's own [stability warning](https://github.com/open-telemetry/semantic-conventions/blob/v1.37.0/docs/gen-ai), keeps changing in more than one way. `OTEL_SEMCONV_STABILITY_OPT_IN` is the OTel-standard switch between these two rule sets, and Agent Framework applies it consistently across every attribute/representation it knows differs between the two:
|
||||
[v1.36.0](https://github.com/open-telemetry/semantic-conventions/blob/v1.36.0/docs/gen-ai) is the OpenTelemetry-recommended **baseline** for existing GenAI instrumentations. Releases above it (v1.37.0 and later) are referred to as **latest** and, per OTel's own [stability warning](https://github.com/open-telemetry/semantic-conventions/blob/v1.37.0/docs/gen-ai), keep changing in more than one way. `OTEL_SEMCONV_STABILITY_OPT_IN` is the OTel-standard switch between these two rule sets, and Agent Framework applies it consistently across every attribute/representation it knows differs between the two:
|
||||
|
||||
| Aspect | v1.36.0 (stable) | Above v1.36.0 (experimental, the default) |
|
||||
| Aspect | v1.36.0 (baseline) | Above v1.36.0 (latest, the default) |
|
||||
|--------|------------------|--------------------------------------------|
|
||||
| Input/output message representation | Log-record **events** (`gen_ai.system.message`, `gen_ai.user.message`, `gen_ai.assistant.message`, `gen_ai.tool.message`, `gen_ai.choice`) | `gen_ai.input.messages`/`gen_ai.output.messages` **span attributes** |
|
||||
| Provider-identifying attribute | `gen_ai.system` | `gen_ai.provider.name` |
|
||||
@@ -221,17 +221,18 @@ You can also call `enable_sensitive_telemetry()` from `agent_framework.observabi
|
||||
|
||||
> **`ENABLE_SENSITIVE_DATA=true` is a prerequisite for the message-representation and tool-call-attribute rows above.** Chat content (prompts, responses, tool arguments/results) is only ever captured when sensitive-data capture is enabled (see [`ENABLE_SENSITIVE_DATA`](#environment-variables) above); the provider-attribute rename applies regardless, since `gen_ai.system`/`gen_ai.provider.name` is not sensitive data. If `ENABLE_SENSITIVE_DATA` is `false` (the default), `ENABLE_MESSAGE_EVENTS` has nothing to switch and is effectively ignored, and no `gen_ai.tool.call.*` attributes are emitted under either semconv version.
|
||||
|
||||
Agent Framework defaults to the conventions above v1.36.0 (unlike upstream OpenTelemetry, which defaults to stable-only) because most users already depend on them, and — to avoid a breaking change for anyone consuming the older message events — also keeps emitting those events by default via `ENABLE_MESSAGE_EVENTS`. `ENABLE_MESSAGE_EVENTS` is controlled independently of `OTEL_SEMCONV_STABILITY_OPT_IN`:
|
||||
Agent Framework defaults to the conventions above v1.36.0 (unlike upstream OpenTelemetry, which retains the baseline conventions) because most users already depend on them, and — to avoid a breaking change for anyone consuming the older message events for modelinvocation — also keeps emitting those events by default via `ENABLE_MESSAGE_EVENTS`. `ENABLE_MESSAGE_EVENTS` is controlled independently of `OTEL_SEMCONV_STABILITY_OPT_IN`:
|
||||
|
||||
```bash
|
||||
# Capture agent/chat client/tool input and output contents (default: false):
|
||||
export ENABLE_SENSITIVE_DATA=true
|
||||
|
||||
# Opt into the stable v1.36.0 conventions only (default: "gen_ai_latest_experimental"):
|
||||
# Opt into the baseline v1.36.0 conventions only (default: "gen_ai_latest_experimental"):
|
||||
export OTEL_SEMCONV_STABILITY_OPT_IN=""
|
||||
|
||||
# Agent Framework still emits the stable v1.36.0 message events even when the semconv opt-in
|
||||
# is set to experimental for compatibility reasons. To stop emitting those events (default: true):
|
||||
# Agent Framework still emits the baseline v1.36.0 message events for model invocations even
|
||||
# when the semconv opt-in is set to latest for compatibility reasons. To stop emitting those
|
||||
# events (default: true):
|
||||
export ENABLE_MESSAGE_EVENTS=false
|
||||
```
|
||||
|
||||
|
||||
Reference in New Issue
Block a user