Skip to content

Commit b526afd

Browse files
committed
feat(genai-utils): add finish_reasons to AgentInvocation per semconv
1 parent c655e25 commit b526afd

3 files changed

Lines changed: 38 additions & 2 deletions

File tree

util/opentelemetry-util-genai/CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## Unreleased
99

10-
- Add `AgentInvocation` type with `invoke_agent` span lifecycle via `start_invoke_local_agent` / `start_invoke_remote_agent` factory methods and `invoke_local_agent` / `invoke_remote_agent` context managers; add shared `_content.py` helper to deduplicate message serialization between `AgentInvocation` and `InferenceInvocation`; add `tool_definitions`, `cache_creation_input_tokens`, and `cache_read_input_tokens` to `InferenceInvocation`
10+
- Add `AgentInvocation` type with `invoke_agent` span lifecycle via `start_invoke_local_agent` / `start_invoke_remote_agent` factory methods and `invoke_local_agent` / `invoke_remote_agent` context managers; add `finish_reasons` field to `AgentInvocation` per semconv `gen_ai.response.finish_reasons` (Recommended); add shared `_content.py` helper to deduplicate message serialization between `AgentInvocation` and `InferenceInvocation`; add `tool_definitions`, `cache_creation_input_tokens`, and `cache_read_input_tokens` to `InferenceInvocation`; add `getattr` fallback for `GEN_AI_AGENT_VERSION` semconv constant
1111
([#4274](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4274))
1212
- Add metrics support for EmbeddingInvocation
1313
([#4377](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4377))

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

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@
3535
ToolDefinition,
3636
)
3737

38+
_GEN_AI_AGENT_VERSION: str = getattr(
39+
GenAI,
40+
"GEN_AI_AGENT_VERSION",
41+
"gen_ai.agent.version",
42+
)
3843
_GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS: str = getattr(
3944
GenAI,
4045
"GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS",
@@ -108,6 +113,8 @@ def __init__(
108113
self.seed: int | None = None
109114
self.choice_count: int | None = None
110115

116+
self.finish_reasons: list[str] | None = None
117+
111118
self.input_tokens: int | None = None
112119
self.output_tokens: int | None = None
113120
self.cache_creation_input_tokens: int | None = None
@@ -128,7 +135,7 @@ def _get_common_attributes(self) -> dict[str, Any]:
128135
(GenAI.GEN_AI_AGENT_NAME, self.agent_name),
129136
(GenAI.GEN_AI_AGENT_ID, self.agent_id),
130137
(GenAI.GEN_AI_AGENT_DESCRIPTION, self.agent_description),
131-
(GenAI.GEN_AI_AGENT_VERSION, self.agent_version),
138+
(_GEN_AI_AGENT_VERSION, self.agent_version),
132139
)
133140
return {
134141
GenAI.GEN_AI_OPERATION_NAME: self._operation_name,
@@ -152,6 +159,15 @@ def _get_request_attributes(self) -> dict[str, Any]:
152159
)
153160
return {k: v for k, v in optional_attrs if v is not None}
154161

162+
def _get_response_attributes(self) -> dict[str, Any]:
163+
optional_attrs = (
164+
(
165+
GenAI.GEN_AI_RESPONSE_FINISH_REASONS,
166+
self.finish_reasons or None,
167+
),
168+
)
169+
return {k: v for k, v in optional_attrs if v is not None}
170+
155171
def _get_usage_attributes(self) -> dict[str, Any]:
156172
optional_attrs = (
157173
(GenAI.GEN_AI_USAGE_INPUT_TOKENS, self.input_tokens),
@@ -211,6 +227,7 @@ def _apply_finish(self, error: Error | None = None) -> None:
211227
attributes: dict[str, Any] = {}
212228
attributes.update(self._get_common_attributes())
213229
attributes.update(self._get_request_attributes())
230+
attributes.update(self._get_response_attributes())
214231
attributes.update(self._get_usage_attributes())
215232
attributes.update(self._get_content_attributes_for_span())
216233
attributes.update(self.attributes)

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ def test_all_attributes(self):
8686
invocation.stop_sequences = ["END", "STOP"]
8787
invocation.seed = 42
8888
invocation.choice_count = 3
89+
invocation.finish_reasons = ["stop"]
8990
invocation.input_tokens = 100
9091
invocation.output_tokens = 200
9192
invocation.stop()
@@ -108,6 +109,24 @@ def test_all_attributes(self):
108109
assert attrs[GenAI.GEN_AI_REQUEST_STOP_SEQUENCES] == ("END", "STOP")
109110
assert attrs[GenAI.GEN_AI_REQUEST_SEED] == 42
110111
assert attrs[GenAI.GEN_AI_REQUEST_CHOICE_COUNT] == 3
112+
assert attrs[GenAI.GEN_AI_RESPONSE_FINISH_REASONS] == ("stop",)
113+
114+
def test_finish_reasons_multiple(self):
115+
invocation = self.handler.start_invoke_local_agent("openai")
116+
invocation.finish_reasons = ["stop", "length"]
117+
invocation.stop()
118+
attrs = self.span_exporter.get_finished_spans()[0].attributes
119+
assert attrs[GenAI.GEN_AI_RESPONSE_FINISH_REASONS] == (
120+
"stop",
121+
"length",
122+
)
123+
124+
def test_finish_reasons_empty_list_omitted(self):
125+
invocation = self.handler.start_invoke_local_agent("openai")
126+
invocation.finish_reasons = []
127+
invocation.stop()
128+
attrs = self.span_exporter.get_finished_spans()[0].attributes
129+
assert GenAI.GEN_AI_RESPONSE_FINISH_REASONS not in attrs
111130

112131
def test_no_response_model_or_finish_reasons(self):
113132
invocation = self.handler.start_invoke_local_agent("openai")

0 commit comments

Comments
 (0)