Skip to content

Commit c5f64a1

Browse files
feat(enrichment): Introduce is_gen_ai_span checking gen_ai.operation.name in preference over span.op (#104318)
Closes https://linear.app/getsentry/issue/TET-1526/update-span-buffer-logic-to-use-gen-aioperationname
1 parent 45764b6 commit c5f64a1

File tree

3 files changed

+61
-8
lines changed

3 files changed

+61
-8
lines changed

src/sentry/spans/consumers/process_segments/enrichment.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@
44

55
from sentry_kafka_schemas.schema_types.ingest_spans_v1 import SpanEvent
66

7-
from sentry.spans.consumers.process_segments.types import Attribute, attribute_value, get_span_op
7+
from sentry.spans.consumers.process_segments.types import (
8+
Attribute,
9+
attribute_value,
10+
get_span_op,
11+
is_gen_ai_span,
12+
)
813

914
# Keys of shared sentry attributes that are shared across all spans in a segment. This list
1015
# is taken from `extract_shared_tags` in Relay.
@@ -122,7 +127,7 @@ def get_value(key: str) -> Any:
122127
if attributes.get(key) is None:
123128
attributes[key] = value
124129

125-
if get_span_op(span).startswith("gen_ai.") and "gen_ai.agent.name" not in attributes:
130+
if is_gen_ai_span(span) and "gen_ai.agent.name" not in attributes:
126131
if (parent_span_id := span.get("parent_span_id")) is not None:
127132
parent_span = self._spans_by_id.get(parent_span_id)
128133
if (

src/sentry/spans/consumers/process_segments/types.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,9 @@ def attribute_value(span: Mapping[str, Any], key: str) -> Any:
4040
attributes = span.get("attributes") or {}
4141
attr: dict[str, Any] = attributes.get(key) or {}
4242
return attr.get("value")
43+
44+
45+
def is_gen_ai_span(span: SpanEvent) -> bool:
46+
return attribute_value(span, "gen_ai.operation.name") is not None or get_span_op(
47+
span
48+
).startswith("gen_ai.")

tests/sentry/spans/consumers/process_segments/test_enrichment.py

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -493,7 +493,9 @@ def test_enrich_gen_ai_agent_name_from_immediate_parent() -> None:
493493
parent_span_id="aaaaaaaaaaaaaaaa",
494494
start_timestamp=1609455601.0,
495495
end_timestamp=1609455602.0,
496-
span_op="gen_ai.execute_tool",
496+
attributes={
497+
"gen_ai.operation.name": {"type": "string", "value": "execute_tool"},
498+
},
497499
)
498500

499501
spans = [parent_span, child_span]
@@ -525,9 +527,9 @@ def test_enrich_gen_ai_agent_name_not_overwritten() -> None:
525527
parent_span_id="aaaaaaaaaaaaaaaa",
526528
start_timestamp=1609455601.0,
527529
end_timestamp=1609455602.0,
528-
span_op="gen_ai.handoff",
529530
attributes={
530531
"gen_ai.agent.name": {"type": "string", "value": "ChildAgent"},
532+
"gen_ai.operation.name": {"type": "string", "value": "handoff"},
531533
},
532534
)
533535

@@ -557,7 +559,9 @@ def test_enrich_gen_ai_agent_name_not_set_without_ancestor() -> None:
557559
parent_span_id="aaaaaaaaaaaaaaaa",
558560
start_timestamp=1609455601.0,
559561
end_timestamp=1609455602.0,
560-
span_op="gen_ai.execute_tool",
562+
attributes={
563+
"gen_ai.operation.name": {"type": "string", "value": "execute_tool"},
564+
},
561565
)
562566

563567
spans = [parent_span, child_span]
@@ -598,7 +602,9 @@ def test_enrich_gen_ai_agent_name_not_from_sibling() -> None:
598602
parent_span_id="aaaaaaaaaaaaaaaa",
599603
start_timestamp=1609455602.5,
600604
end_timestamp=1609455603.5,
601-
span_op="gen_ai.execute_tool",
605+
attributes={
606+
"gen_ai.operation.name": {"type": "string", "value": "execute_tool"},
607+
},
602608
)
603609

604610
spans = [parent_span, sibling_with_agent, target_child]
@@ -631,7 +637,9 @@ def test_enrich_gen_ai_agent_name_only_from_invoke_agent_parent() -> None:
631637
parent_span_id="aaaaaaaaaaaaaaaa",
632638
start_timestamp=1609455601.0,
633639
end_timestamp=1609455602.0,
634-
span_op="gen_ai.run",
640+
attributes={
641+
"gen_ai.operation.name": {"type": "string", "value": "run"},
642+
},
635643
)
636644

637645
spans = [parent_span, child_span]
@@ -672,7 +680,9 @@ def test_enrich_gen_ai_agent_name_not_from_grandparent() -> None:
672680
parent_span_id="bbbbbbbbbbbbbbbb",
673681
start_timestamp=1609455602.0,
674682
end_timestamp=1609455603.0,
675-
span_op="gen_ai.run",
683+
attributes={
684+
"gen_ai.operation.name": {"type": "string", "value": "run"},
685+
},
676686
)
677687

678688
spans = [grandparent_span, parent_span, child_span]
@@ -683,3 +693,35 @@ def test_enrich_gen_ai_agent_name_not_from_grandparent() -> None:
683693
assert attribute_value(grandparent, "gen_ai.agent.name") == "GrandparentAgent"
684694
assert attribute_value(parent, "gen_ai.agent.name") is None
685695
assert attribute_value(child, "gen_ai.agent.name") is None
696+
697+
698+
def test_enrich_gen_ai_agent_name_from_immediate_parent_fallback_to_span_op() -> None:
699+
"""Test that gen_ai.agent.name is inherited from the immediate parent with gen_ai.invoke_agent operation."""
700+
parent_span = build_mock_span(
701+
project_id=1,
702+
is_segment=True,
703+
span_id="aaaaaaaaaaaaaaaa",
704+
start_timestamp=1609455600.0,
705+
end_timestamp=1609455605.0,
706+
span_op="gen_ai.invoke_agent",
707+
attributes={
708+
"gen_ai.agent.name": {"type": "string", "value": "MyAgent"},
709+
},
710+
)
711+
712+
child_span = build_mock_span(
713+
project_id=1,
714+
span_id="bbbbbbbbbbbbbbbb",
715+
parent_span_id="aaaaaaaaaaaaaaaa",
716+
start_timestamp=1609455601.0,
717+
end_timestamp=1609455602.0,
718+
span_op="gen_ai.execute_tool",
719+
)
720+
721+
spans = [parent_span, child_span]
722+
_, enriched_spans = TreeEnricher.enrich_spans(spans)
723+
compatible_spans = [make_compatible(span) for span in enriched_spans]
724+
725+
parent, child = compatible_spans
726+
assert attribute_value(parent, "gen_ai.agent.name") == "MyAgent"
727+
assert attribute_value(child, "gen_ai.agent.name") == "MyAgent"

0 commit comments

Comments
 (0)