Skip to content

Commit 96a8f0e

Browse files
committed
Set gen_ai.workflow.name span attribute on workflow invocations
WorkflowInvocation now sets gen_ai.workflow.name at span creation when the workflow name is known, so it is available for sampling. Also add a rego live-check manifest for invoke_workflow (expected attributes and span-name format) that enforces gen_ai.workflow.name is emitted.
1 parent 4cc083f commit 96a8f0e

4 files changed

Lines changed: 35 additions & 0 deletions

File tree

policies/genai_span_validation.rego

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ _span_name_keyed_attr["embeddings"] := "gen_ai.request.model"
6868
_span_name_keyed_attr["execute_tool"] := "gen_ai.tool.name"
6969
_span_name_keyed_attr["invoke_agent"] := "gen_ai.agent.name"
7070
_span_name_keyed_attr["create_agent"] := "gen_ai.agent.name"
71+
_span_name_keyed_attr["invoke_workflow"] := "gen_ai.workflow.name"
7172

7273
# Span name SHOULD be `{op}` (when the keyed attribute is absent) or
7374
# `{op} {value}` (when present). Mirrors the "SHOULD append when known"
@@ -123,6 +124,8 @@ _expected_for_op("create_agent", _) := _create_agent_expected
123124

124125
_expected_for_op("retrieval", _) := _retrieval_expected
125126

127+
_expected_for_op("invoke_workflow", _) := _invoke_workflow_expected
128+
126129
# Inference (chat / generate_content / text_completion).
127130
# Required: gen_ai.operation.name, gen_ai.provider.name.
128131
# Always-emit Recommended: response model/id, finish reasons, token usage,
@@ -191,6 +194,14 @@ _retrieval_expected := {
191194
"server.address",
192195
}
193196

197+
# Invoke workflow. Only gen_ai.operation.name is unconditionally required;
198+
# gen_ai.workflow.name is conditionally required "when available" but is
199+
# effectively always known where a workflow is instrumented, so flag it.
200+
_invoke_workflow_expected := {
201+
"gen_ai.operation.name",
202+
"gen_ai.workflow.name",
203+
}
204+
194205
# Per expected attribute, one violation if missing.
195206
deny contains _span_finding(
196207
"genai_expected_attribute_missing",
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Set the `gen_ai.workflow.name` span attribute on workflow invocations when the workflow name is known.

util/opentelemetry-util-genai/src/opentelemetry/util/genai/_workflow_invocation.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ def _get_base_attributes(self) -> dict[str, AttributeValue]:
6262
attrs: dict[str, AttributeValue] = {
6363
GenAI.GEN_AI_OPERATION_NAME: self._operation_name,
6464
}
65+
if self.name:
66+
attrs[GenAI.GEN_AI_WORKFLOW_NAME] = self.name
6567
return attrs
6668

6769
def _get_messages_for_span(self) -> dict[str, AttributeValue]:

util/opentelemetry-util-genai/tests/test_handler_workflow.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,24 @@ def test_start_workflow_records_monotonic_start(self) -> None:
8686
self.assertEqual(invocation._monotonic_start_s, 500.0)
8787
invocation.stop()
8888

89+
def test_start_workflow_sets_workflow_name_attribute(self) -> None:
90+
invocation = self.handler.workflow(name="my_pipeline")
91+
invocation.stop()
92+
93+
spans = self._get_finished_spans()
94+
value = spans[0].attributes[GenAI.GEN_AI_WORKFLOW_NAME]
95+
self.assertEqual(value, "my_pipeline")
96+
self.assertIsInstance(value, str)
97+
98+
def test_start_workflow_without_name_omits_workflow_name_attribute(
99+
self,
100+
) -> None:
101+
invocation = self.handler.workflow(name=None)
102+
invocation.stop()
103+
104+
spans = self._get_finished_spans()
105+
self.assertNotIn(GenAI.GEN_AI_WORKFLOW_NAME, spans[0].attributes)
106+
89107
# ------------------------------------------------------------------
90108
# stop_workflow
91109
# ------------------------------------------------------------------
@@ -217,6 +235,9 @@ def get_description(self):
217235
self.assertEqual(
218236
captured_attributes[GenAI.GEN_AI_OPERATION_NAME], "invoke_workflow"
219237
)
238+
self.assertEqual(
239+
captured_attributes[GenAI.GEN_AI_WORKFLOW_NAME], "my-workflow"
240+
)
220241

221242
spans = self._get_finished_spans()
222243
self.assertEqual(len(spans), 1)

0 commit comments

Comments
 (0)