Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased

### Removed

- Remove the direct `pydantic` runtime dependency from Agno instrumentation.

## Version 0.6.0 (2026-06-03)

### Removed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ dependencies = [
"opentelemetry-instrumentation >= 0.58b0",
"opentelemetry-semantic-conventions >= 0.58b0",
"opentelemetry-util-genai",
"pydantic",
"wrapt >= 1.17.3, < 2.0.0",
]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@
from dataclasses import asdict, is_dataclass
from typing import Any, Mapping, Sequence

from pydantic import BaseModel

from opentelemetry.util.genai.extended_semconv.gen_ai_extended_attributes import (
GEN_AI_SESSION_ID,
GEN_AI_USER_ID,
Expand Down Expand Up @@ -62,8 +60,17 @@ def _to_dict(value: Any) -> dict[str, Any] | None:
return None
if isinstance(value, Mapping):
return dict(value)
if isinstance(value, BaseModel):
return value.model_dump(mode="json")
model_dump = getattr(value, "model_dump", None)
if callable(model_dump):
try:
return model_dump(mode="json")
except TypeError:
try:
return model_dump()
except Exception:
return None
except Exception:
return None
if is_dataclass(value):
return asdict(value)
to_dict = getattr(value, "to_dict", None)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,43 @@ def run_once(index: int):
assert len(model_spans) == 3


@pytest.mark.parametrize("content_capture_mode", [None, "NO_CONTENT"])
def test_content_capture_mode_does_not_gate_span_creation(
monkeypatch,
span_exporter: InMemorySpanExporter,
tracer_provider: trace_api.TracerProvider,
content_capture_mode: str | None,
):
AgnoInstrumentor().uninstrument()
if hasattr(get_extended_telemetry_handler, "_default_handler"):
delattr(get_extended_telemetry_handler, "_default_handler")
span_exporter.clear()

env_var = "OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT"
if content_capture_mode is None:
monkeypatch.delenv(env_var, raising=False)
else:
monkeypatch.setenv(env_var, content_capture_mode)

AgnoInstrumentor().instrument(tracer_provider=tracer_provider)

agent = Agent(name="NoContentAgent", model=EchoModel(), tools=[])
response = agent.run("Say hello without content")

assert response.content == "hello"
spans = _spans_by_name(span_exporter)
assert "invoke_agent NoContentAgent" in spans
assert "chat echo-model" in spans

agent_attrs = spans["invoke_agent NoContentAgent"].attributes
model_attrs = spans["chat echo-model"].attributes
assert agent_attrs["gen_ai.span.kind"] == "AGENT"
assert model_attrs["gen_ai.span.kind"] == "LLM"
assert "gen_ai.input.messages" not in agent_attrs
assert "gen_ai.output.messages" not in agent_attrs
assert "gen_ai.output.messages" not in model_attrs


def test_async_function_call_emits_tool_span(
span_exporter: InMemorySpanExporter,
):
Expand Down Expand Up @@ -508,6 +545,39 @@ def test_tool_result_messages_do_not_duplicate_text_parts():
assert parts[0].response == {"temperature": 21}


def test_model_dump_objects_are_serialized_without_pydantic_base_class():
class ModelDumpToolCall:
def model_dump(self, mode="json"):
assert mode == "json"
return {
"id": "call_1",
"function": {
"name": "get_weather",
"arguments": '{"city":"Hangzhou"}',
},
}

messages = convert_agent_input(
[
SimpleNamespace(
role="assistant",
content=None,
tool_calls=[ModelDumpToolCall()],
)
]
)

parts = messages[0].parts
tool_calls = [
part for part in parts if getattr(part, "type", None) == "tool_call"
]

assert len(tool_calls) == 1
assert tool_calls[0].id == "call_1"
assert tool_calls[0].name == "get_weather"
assert tool_calls[0].arguments == {"city": "Hangzhou"}


def test_missing_finish_reason_is_not_reported():
agent = Agent(name="NoFinishReasonAgent", model=EchoModel(), tools=[])
invocation = create_agent_invocation(agent, {"input": "hello"})
Expand Down
Loading