Skip to content
6 changes: 5 additions & 1 deletion python/packages/ag-ui/tests/ag_ui/test_endpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -5472,7 +5472,11 @@ async def test_agent_endpoint_correlates_gen_ai_spans_with_supplied_thread_id(
monkeypatch.setattr(
observability,
"OBSERVABILITY_SETTINGS",
SimpleNamespace(ENABLED=True, SENSITIVE_DATA_ENABLED=False),
SimpleNamespace(
ENABLED=True,
SENSITIVE_DATA_ENABLED=False,
use_latest_experimental_gen_ai_semconv=True,
),
)
monkeypatch.setattr(observability, "get_tracer", lambda *args, **kwargs: tracer_provider.get_tracer("test"))

Expand Down
6 changes: 5 additions & 1 deletion python/packages/ag-ui/tests/ag_ui/test_workflow_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,11 @@ async def test_workflow_and_agent_spans_use_supplied_agui_thread_id(monkeypatch:
monkeypatch.setattr(
observability,
"OBSERVABILITY_SETTINGS",
SimpleNamespace(ENABLED=True, SENSITIVE_DATA_ENABLED=False),
SimpleNamespace(
ENABLED=True,
SENSITIVE_DATA_ENABLED=False,
use_latest_experimental_gen_ai_semconv=True,
),
)
monkeypatch.setattr(observability, "get_tracer", lambda *args, **kwargs: tracer_provider.get_tracer("test"))

Expand Down
3 changes: 2 additions & 1 deletion python/packages/core/agent_framework/_sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
from dataclasses import dataclass
from functools import partial
from pathlib import Path
from typing import TYPE_CHECKING, Any, ClassVar, Literal, TypeAlias, TypeVar, cast, TypeGuard
from typing import TYPE_CHECKING, Any, ClassVar, Literal, TypeAlias, TypeVar, cast

import msgspec

Expand Down Expand Up @@ -251,6 +251,7 @@ class _StateTypeRegistration:
encoder: StateEncoder
decoder: StateDecoder


_STATE_TYPE_REGISTRY: dict[str, _StateTypeRegistration] = {}
_STATE_CLASS_REGISTRY: dict[type[Any], _StateTypeRegistration] = {}

Expand Down
11 changes: 8 additions & 3 deletions python/packages/core/agent_framework/_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -746,7 +746,10 @@ async def invoke(
"response_format",
}
}
if OBSERVABILITY_SETTINGS.SENSITIVE_DATA_ENABLED:
# gen_ai.tool.call.arguments/result were introduced above v1.36.0; only emit them
# as span attributes when that semconv version is active.
emit_tool_call_attrs = OBSERVABILITY_SETTINGS.emit_tool_call_attributes
if emit_tool_call_attrs:
attributes.update({
OtelAttr.TOOL_ARGUMENTS: (
json.dumps(serializable_kwargs, default=str, ensure_ascii=False) if serializable_kwargs else "None"
Expand All @@ -773,8 +776,9 @@ async def invoke(
logger.info(f"Function {self.name} succeeded.")
if OBSERVABILITY_SETTINGS.SENSITIVE_DATA_ENABLED:
result_str = str(result)
span.set_attribute(OtelAttr.TOOL_RESULT, result_str)
logger.debug(f"Function result: {result_str}")
if emit_tool_call_attrs:
span.set_attribute(OtelAttr.TOOL_RESULT, result_str)
return result
try:
parsed = parser(result)
Expand All @@ -786,8 +790,9 @@ async def invoke(
logger.info(f"Function {self.name} succeeded.")
if OBSERVABILITY_SETTINGS.SENSITIVE_DATA_ENABLED:
result_str = "\n".join(c.text or "" for c in parsed if c.type == "text") or str(parsed)
span.set_attribute(OtelAttr.TOOL_RESULT, result_str)
logger.debug(f"Function result: {result_str}")
if emit_tool_call_attrs:
span.set_attribute(OtelAttr.TOOL_RESULT, result_str)
return parsed
finally:
duration = (end_time_stamp or perf_counter()) - start_time_stamp
Expand Down
392 changes: 329 additions & 63 deletions python/packages/core/agent_framework/observability.py

Large diffs are not rendered by default.

25 changes: 25 additions & 0 deletions python/packages/core/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
from typing import Any
from unittest.mock import patch

from opentelemetry._logs import get_logger_provider, set_logger_provider
from opentelemetry.sdk._logs import LoggerProvider
from opentelemetry.sdk._logs.export import InMemoryLogRecordExporter, SimpleLogRecordProcessor
from opentelemetry.sdk.trace.export import SimpleSpanProcessor, SpanExporter
from opentelemetry.sdk.trace.export.in_memory_span_exporter import InMemorySpanExporter
from pytest import fixture
Expand All @@ -29,6 +32,8 @@ def span_exporter(monkeypatch, enable_instrumentation: bool, enable_sensitive_da
"ENABLE_INSTRUMENTATION",
"ENABLE_SENSITIVE_DATA",
"ENABLE_CONSOLE_EXPORTERS",
"ENABLE_MESSAGE_EVENTS",
"OTEL_SEMCONV_STABILITY_OPT_IN",
"OTEL_EXPORTER_OTLP_ENDPOINT",
"OTEL_EXPORTER_OTLP_TRACES_ENDPOINT",
"OTEL_EXPORTER_OTLP_METRICS_ENDPOINT",
Expand Down Expand Up @@ -87,3 +92,23 @@ def span_exporter(monkeypatch, enable_instrumentation: bool, enable_sensitive_da
yield exporter
# Clean up
exporter.clear()


@fixture
def log_record_exporter(span_exporter: SpanExporter) -> Generator[InMemoryLogRecordExporter]:
"""Fixture providing an in-memory exporter for OTel log records (e.g. gen_ai message events).

Depends on ``span_exporter`` so ObservabilitySettings/env vars are configured first. The global
OTel LoggerProvider can only be set once per process, so on later test runs this just attaches
another processor to whichever LoggerProvider a previous test already installed.
"""
exporter = InMemoryLogRecordExporter()
set_logger_provider(LoggerProvider())
provider = get_logger_provider()
if not hasattr(provider, "add_log_record_processor"):
raise RuntimeError("Logger provider does not support adding log record processors.")
provider.add_log_record_processor(SimpleLogRecordProcessor(exporter)) # type: ignore

yield exporter
# Clean up
exporter.clear()
Loading
Loading