diff --git a/src/agentevals/trace_attrs.py b/src/agentevals/trace_attrs.py index 35da68a..e4cd16a 100644 --- a/src/agentevals/trace_attrs.py +++ b/src/agentevals/trace_attrs.py @@ -81,3 +81,6 @@ GENAI_ATTRIBUTE_ALIASES: dict[str, list[str]] = { OTEL_GENAI_PROVIDER_NAME: [OTEL_GENAI_SYSTEM], } + +# agentevals custom attributes (repository-specific, outside OTel semconv) +AGENTEVALS_SESSION_ID = "agentevals.session_id" diff --git a/src/agentevals/utils/log_enrichment.py b/src/agentevals/utils/log_enrichment.py index eecd3d4..c58f4fa 100644 --- a/src/agentevals/utils/log_enrichment.py +++ b/src/agentevals/utils/log_enrichment.py @@ -7,7 +7,7 @@ from collections import defaultdict from ..trace_attrs import ( - OTEL_GENAI_AGENT_NAME, + AGENTEVALS_SESSION_ID, OTEL_GENAI_INPUT_MESSAGES, OTEL_GENAI_OUTPUT_MESSAGES, ) @@ -28,7 +28,7 @@ def enrich_spans_with_logs(spans: list[dict], logs: list[dict], session_id: str Args: spans: List of OTLP span dictionaries logs: List of GenAI log event dictionaries - session_id: Optional session ID to add as agent.name attribute + session_id: Optional session ID to add as the agentevals.session_id attribute Returns: List of enriched span dictionaries with message attributes added @@ -88,6 +88,22 @@ def _extract_messages_from_logs( return input_messages, output_messages +def _append_session_id(attrs: list[dict], session_id: str | None) -> None: + """Append the session ID attribute to *attrs* in place, if present. + + Written to the custom ``agentevals.session_id`` attribute rather than + ``gen_ai.agent.name`` so real agent names (from the SDK or OTLP) are + never overwritten. + """ + if session_id: + attrs.append( + { + "key": AGENTEVALS_SESSION_ID, + "value": {"stringValue": session_id}, + } + ) + + def _inject_messages( span: dict, input_messages: list[dict], @@ -113,13 +129,7 @@ def _inject_messages( "value": {"stringValue": json.dumps(output_messages)}, } ) - if session_id: - attrs.append( - { - "key": OTEL_GENAI_AGENT_NAME, - "value": {"stringValue": session_id}, - } - ) + _append_session_id(attrs, session_id) return span_copy @@ -148,12 +158,7 @@ def _enrich_per_span( span_copy = span.copy() if session_id: attrs = list(span_copy.get("attributes", [])) - attrs.append( - { - "key": OTEL_GENAI_AGENT_NAME, - "value": {"stringValue": session_id}, - } - ) + _append_session_id(attrs, session_id) span_copy["attributes"] = attrs enriched.append(span_copy) diff --git a/tests/test_log_enrichment.py b/tests/test_log_enrichment.py index 42361a7..d5830a0 100644 --- a/tests/test_log_enrichment.py +++ b/tests/test_log_enrichment.py @@ -84,8 +84,8 @@ def test_session_id_injected(self): logs = [_make_log("gen_ai.user.message", {"content": "hi"})] result = enrich_spans_with_logs(spans, logs, session_id="my-session") - agent_name = _get_injected_attr(result[0], "gen_ai.agent.name", parse_json=False) - assert agent_name == "my-session" + session_attr = _get_injected_attr(result[0], "agentevals.session_id", parse_json=False) + assert session_attr == "my-session" def test_choice_extracts_nested_content(self): spans = [_make_span()] @@ -139,6 +139,23 @@ def test_logs_matched_to_correct_span(self): msgs_s2 = _get_injected_attr(result[1], "gen_ai.input.messages") assert msgs_s2 == [{"role": "user", "content": "question 2"}] + def test_agent_name_preserved_with_session_id(self): + """Regression: real gen_ai.agent.name attr must survive enrichment, + session_id lands in its own attr, doesn't clobber it.""" + span = _make_span( + "s1", + attrs=[{"key": "gen_ai.agent.name", "value": {"stringValue": "roll_die_agent"}}], + ) + logs = [_make_log("gen_ai.user.message", {"content": "hi"}, span_id="s1")] + result = enrich_spans_with_logs([span], logs, session_id="sess-123") + + agent_name_attrs = [a for a in result[0]["attributes"] if a["key"] == "gen_ai.agent.name"] + assert len(agent_name_attrs) == 1, f"expected exactly one gen_ai.agent.name attr, found {len(agent_name_attrs)}" + assert agent_name_attrs[0]["value"]["stringValue"] == "roll_die_agent" + + session_attr = _get_injected_attr(result[0], "agentevals.session_id", parse_json=False) + assert session_attr == "sess-123" + def test_span_without_logs_not_enriched(self): spans = [_make_span("s1"), _make_span("s2")] logs = [ @@ -156,8 +173,8 @@ def test_session_id_on_all_spans(self): ] result = enrich_spans_with_logs(spans, logs, session_id="test") - assert _get_injected_attr(result[0], "gen_ai.agent.name", parse_json=False) == "test" - assert _get_injected_attr(result[1], "gen_ai.agent.name", parse_json=False) == "test" + assert _get_injected_attr(result[0], "agentevals.session_id", parse_json=False) == "test" + assert _get_injected_attr(result[1], "agentevals.session_id", parse_json=False) == "test" def test_tool_calls_in_assistant_message(self): spans = [_make_span("s1")]