Skip to content

Commit 2c758c7

Browse files
authored
Add workflow type in genai utils (#4310)
* Add workflow type in genai utils * fixed errors * fixed errors * fixed test * fixed test * fixed precommit * moved to unreleased * reordered
1 parent 22879d6 commit 2c758c7

File tree

3 files changed

+94
-0
lines changed

3 files changed

+94
-0
lines changed

util/opentelemetry-util-genai/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
- Populate schema_url on metrics
1111
([#4320](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4320))
12+
- Add workflow invocation type to genAI utils
13+
([https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4310](#4310))
1214

1315
## Version 0.3b0 (2026-02-20)
1416

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,23 @@ class GenAIInvocation:
203203
attributes: dict[str, Any] = field(default_factory=_new_str_any_dict)
204204

205205

206+
@dataclass
207+
class WorkflowInvocation(GenAIInvocation):
208+
"""
209+
Represents predetermined static sequence of operations eg: Agent, LLM, tool, and retrieval invocations.
210+
A workflow groups multiple operations together, accepting input(s) and producing final output(s).
211+
"""
212+
213+
name: str = ""
214+
operation_name: str = "invoke_workflow"
215+
input_messages: list[InputMessage] = field(
216+
default_factory=_new_input_messages
217+
)
218+
output_messages: list[OutputMessage] = field(
219+
default_factory=_new_output_messages
220+
)
221+
222+
206223
@dataclass
207224
class LLMInvocation(GenAIInvocation):
208225
"""
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
from opentelemetry.util.genai.types import (
2+
InputMessage,
3+
OutputMessage,
4+
Text,
5+
WorkflowInvocation,
6+
)
7+
8+
9+
class TestWorkflowInvocation: # pylint: disable=no-self-use
10+
def test_default_values(self):
11+
invocation = WorkflowInvocation()
12+
assert invocation.name == ""
13+
assert invocation.operation_name == "invoke_workflow"
14+
assert not invocation.input_messages
15+
assert not invocation.output_messages
16+
assert invocation.span is None
17+
assert invocation.context_token is None
18+
assert not invocation.attributes
19+
20+
def test_custom_name(self):
21+
invocation = WorkflowInvocation(name="customer_support_pipeline")
22+
assert invocation.name == "customer_support_pipeline"
23+
24+
def test_custom_operation_name(self):
25+
invocation = WorkflowInvocation(operation_name="run_pipeline")
26+
assert invocation.operation_name == "run_pipeline"
27+
28+
def test_with_input_messages(self):
29+
msg = InputMessage(role="user", parts=[Text(content="hello")])
30+
invocation = WorkflowInvocation(input_messages=[msg])
31+
assert len(invocation.input_messages) == 1
32+
assert invocation.input_messages[0].role == "user"
33+
34+
def test_with_output_messages(self):
35+
msg = OutputMessage(
36+
role="assistant", parts=[Text(content="hi")], finish_reason="stop"
37+
)
38+
invocation = WorkflowInvocation(output_messages=[msg])
39+
assert len(invocation.output_messages) == 1
40+
assert invocation.output_messages[0].finish_reason == "stop"
41+
42+
def test_inherits_genai_invocation(self):
43+
invocation = WorkflowInvocation(attributes={"key": "value"})
44+
assert invocation.attributes == {"key": "value"}
45+
46+
def test_default_lists_are_independent(self):
47+
"""Ensure default factory creates separate list instances."""
48+
inv1 = WorkflowInvocation()
49+
inv2 = WorkflowInvocation()
50+
inv1.input_messages.append(InputMessage(role="user", parts=[]))
51+
assert len(inv2.input_messages) == 0
52+
53+
def test_default_attributes_are_independent(self):
54+
inv1 = WorkflowInvocation()
55+
inv2 = WorkflowInvocation()
56+
inv1.attributes["foo"] = "bar"
57+
assert "foo" not in inv2.attributes
58+
59+
def test_full_construction(self):
60+
inp = InputMessage(role="user", parts=[Text(content="query")])
61+
out = OutputMessage(
62+
role="assistant",
63+
parts=[Text(content="answer")],
64+
finish_reason="stop",
65+
)
66+
invocation = WorkflowInvocation(
67+
name="my_workflow",
68+
operation_name="invoke_workflow",
69+
input_messages=[inp],
70+
output_messages=[out],
71+
)
72+
assert invocation.name == "my_workflow"
73+
assert len(invocation.input_messages) == 1
74+
assert len(invocation.output_messages) == 1
75+
assert invocation.output_messages[0].parts[0].content == "answer"

0 commit comments

Comments
 (0)