Skip to content

Commit c320ca6

Browse files
committed
apply general invoke methods and resolved merge conflict
1 parent d28860a commit c320ca6

6 files changed

Lines changed: 42 additions & 109 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 `_BaseAgent`, `AgentCreation`, and `AgentInvocation` types with agent lifecycle support
10+
- Add `_BaseAgent` and `AgentInvocation` types with agent lifecycle support
1111
([#4274](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4274))
1212
- Add EmbeddingInvocation span lifecycle support
1313
([https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4219](#4219))

util/opentelemetry-util-genai/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ classifiers = [
2828
dependencies = [
2929
"opentelemetry-instrumentation ~= 0.60b0",
3030
"opentelemetry-semantic-conventions ~= 0.60b0",
31-
"opentelemetry-api>=1.39",
31+
"opentelemetry-api ~= 1.39",
3232
]
3333

3434
[project.entry-points.opentelemetry_genai_completion_hook]

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

Lines changed: 4 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -81,23 +81,20 @@
8181
from opentelemetry.util.genai.metrics import InvocationMetricsRecorder
8282
from opentelemetry.util.genai.span_utils import (
8383
_apply_agent_finish_attributes,
84-
_apply_creation_finish_attributes,
8584
_apply_embedding_finish_attributes,
8685
_apply_error_attributes,
8786
_apply_llm_finish_attributes,
88-
_get_base_agent_span_name,
87+
_get_agent_span_name,
8988
_get_embedding_span_name,
9089
_get_llm_span_name,
9190
_maybe_emit_llm_event,
9291
)
9392
from opentelemetry.util.genai.types import (
94-
AgentCreation,
9593
AgentInvocation,
9694
EmbeddingInvocation,
9795
Error,
9896
GenAIInvocation,
9997
LLMInvocation,
100-
_BaseAgent,
10198
)
10299
from opentelemetry.util.genai.version import __version__
103100

@@ -169,10 +166,8 @@ def _start(self, invocation: _T) -> _T:
169166
span_name = _get_llm_span_name(invocation)
170167
elif isinstance(invocation, EmbeddingInvocation):
171168
span_name = _get_embedding_span_name(invocation)
172-
elif isinstance(invocation, _BaseAgent):
173-
span_name = _get_base_agent_span_name(invocation)
174-
if isinstance(invocation, AgentInvocation) and not invocation.is_remote:
175-
kind = SpanKind.INTERNAL
169+
elif isinstance(invocation, AgentInvocation):
170+
span_name = _get_agent_span_name(invocation)
176171
else:
177172
span_name = ""
178173
span = self._tracer.start_span(
@@ -205,8 +200,6 @@ def _stop(self, invocation: _T) -> _T:
205200
self._record_embedding_metrics(invocation, span)
206201
elif isinstance(invocation, AgentInvocation):
207202
_apply_agent_finish_attributes(span, invocation)
208-
elif isinstance(invocation, AgentCreation):
209-
_apply_creation_finish_attributes(span, invocation)
210203
finally:
211204
# Detach context and end span even if finishing fails
212205
otel_context.detach(invocation.context_token)
@@ -240,9 +233,6 @@ def _fail(self, invocation: _T, error: Error) -> _T:
240233
elif isinstance(invocation, AgentInvocation):
241234
_apply_agent_finish_attributes(span, invocation)
242235
_apply_error_attributes(span, error, error_type)
243-
elif isinstance(invocation, AgentCreation):
244-
_apply_creation_finish_attributes(span, invocation)
245-
_apply_error_attributes(span, error, error_type)
246236
finally:
247237
# Detach context and end span even if finishing fails
248238
otel_context.detach(invocation.context_token)
@@ -326,9 +316,7 @@ def embedding(
326316
self.stop(invocation)
327317

328318
# Agent-specific convenience methods
329-
def start_agent(
330-
self, invocation: AgentInvocation
331-
) -> AgentInvocation:
319+
def start_agent(self, invocation: AgentInvocation) -> AgentInvocation:
332320
"""Start an agent invocation and create a pending span entry."""
333321
return self._start(invocation)
334322

@@ -364,28 +352,6 @@ def agent(
364352
raise
365353
self.stop(invocation)
366354

367-
@contextmanager
368-
def create_agent(
369-
self, creation: AgentCreation | None = None
370-
) -> Iterator[AgentCreation]:
371-
"""Context manager for agent creation.
372-
373-
Only set data attributes on the creation object, do not modify the span or context.
374-
375-
Starts the span on entry. On normal exit, finalizes the creation and ends the span.
376-
If an exception occurs inside the context, marks the span as error, ends it, and
377-
re-raises the original exception.
378-
"""
379-
if creation is None:
380-
creation = AgentCreation()
381-
self.start(creation)
382-
try:
383-
yield creation
384-
except Exception as exc:
385-
self.fail(creation, Error(message=str(exc), type=type(exc)))
386-
raise
387-
self.stop(creation)
388-
389355

390356
def get_telemetry_handler(
391357
tracer_provider: TracerProvider | None = None,

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

Lines changed: 22 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@
4040
LLMInvocation,
4141
MessagePart,
4242
OutputMessage,
43-
_BaseAgent,
4443
)
4544
from opentelemetry.util.genai.utils import (
4645
ContentCapturingMode,
@@ -390,51 +389,35 @@ def _get_embedding_response_attributes(
390389
return {key: value for key, value in optional_attrs if value is not None}
391390

392391

393-
def _get_base_agent_common_attributes(
394-
agent: _BaseAgent,
395-
) -> dict[str, Any]:
396-
"""Get common attributes shared by all agent operations (invoke_agent, create_agent)."""
397-
optional_attrs = (
398-
(GenAI.GEN_AI_REQUEST_MODEL, agent.request_model),
399-
(GenAI.GEN_AI_PROVIDER_NAME, agent.provider),
400-
(GenAI.GEN_AI_AGENT_NAME, agent.agent_name),
401-
(GenAI.GEN_AI_AGENT_ID, agent.agent_id),
402-
(GenAI.GEN_AI_AGENT_DESCRIPTION, agent.agent_description),
403-
(GenAI.GEN_AI_AGENT_VERSION, agent.agent_version),
404-
(server_attributes.SERVER_ADDRESS, agent.server_address),
405-
(server_attributes.SERVER_PORT, agent.server_port),
406-
)
407-
408-
return {
409-
GenAI.GEN_AI_OPERATION_NAME: agent.operation_name,
410-
**{key: value for key, value in optional_attrs if value is not None},
411-
}
412-
413-
414-
def _get_base_agent_span_name(agent: _BaseAgent) -> str:
415-
"""Get the span name for any agent operation."""
416-
if agent.agent_name:
417-
return f"{agent.operation_name} {agent.agent_name}"
418-
return agent.operation_name
392+
def _get_agent_span_name(invocation: AgentInvocation) -> str:
393+
"""Get the span name for an agent invocation."""
394+
if invocation.agent_name:
395+
return f"{invocation.operation_name} {invocation.agent_name}"
396+
return invocation.operation_name
419397

420398

421399
def _get_agent_common_attributes(
422400
invocation: AgentInvocation,
423401
) -> dict[str, Any]:
424402
"""Get common agent invocation attributes shared by finish() and error() paths."""
425-
attrs = _get_base_agent_common_attributes(invocation)
426-
427-
# Invoke-specific conditionally required attributes
428-
invoke_attrs = (
403+
optional_attrs = (
404+
(GenAI.GEN_AI_REQUEST_MODEL, invocation.request_model),
405+
(GenAI.GEN_AI_AGENT_NAME, invocation.agent_name),
406+
(GenAI.GEN_AI_AGENT_ID, invocation.agent_id),
407+
(GenAI.GEN_AI_AGENT_DESCRIPTION, invocation.agent_description),
408+
("gen_ai.agent.version", invocation.agent_version),
429409
(GenAI.GEN_AI_CONVERSATION_ID, invocation.conversation_id),
430410
(GenAI.GEN_AI_DATA_SOURCE_ID, invocation.data_source_id),
431411
(GenAI.GEN_AI_OUTPUT_TYPE, invocation.output_type),
432-
)
433-
attrs.update(
434-
{key: value for key, value in invoke_attrs if value is not None}
412+
(server_attributes.SERVER_ADDRESS, invocation.server_address),
413+
(server_attributes.SERVER_PORT, invocation.server_port),
435414
)
436415

437-
return attrs
416+
return {
417+
GenAI.GEN_AI_OPERATION_NAME: invocation.operation_name,
418+
GenAI.GEN_AI_PROVIDER_NAME: invocation.provider,
419+
**{key: value for key, value in optional_attrs if value is not None},
420+
}
438421

439422

440423
def _get_agent_request_attributes(
@@ -485,11 +468,11 @@ def _get_agent_response_attributes(
485468
(GenAI.GEN_AI_USAGE_INPUT_TOKENS, invocation.input_tokens),
486469
(GenAI.GEN_AI_USAGE_OUTPUT_TOKENS, invocation.output_tokens),
487470
(
488-
GenAI.GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS,
471+
"gen_ai.usage.cache_creation_input_tokens",
489472
invocation.cache_creation_input_tokens,
490473
),
491474
(
492-
GenAI.GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS,
475+
"gen_ai.usage.cache_read_input_tokens",
493476
invocation.cache_read_input_tokens,
494477
),
495478
)
@@ -501,7 +484,7 @@ def _apply_agent_finish_attributes(
501484
span: Span, invocation: AgentInvocation
502485
) -> None:
503486
"""Apply attributes/messages common to agent finish() paths."""
504-
span.update_name(_get_base_agent_span_name(invocation))
487+
span.update_name(_get_agent_span_name(invocation))
505488

506489
attributes: dict[str, Any] = {}
507490
attributes.update(_get_agent_common_attributes(invocation))
@@ -534,8 +517,7 @@ def _apply_agent_finish_attributes(
534517
"_get_embedding_request_attributes",
535518
"_get_embedding_response_attributes",
536519
"_get_embedding_span_name",
537-
"_get_base_agent_common_attributes",
538-
"_get_base_agent_span_name",
520+
"_get_agent_span_name",
539521
"_apply_agent_finish_attributes",
540522
"_get_system_instructions_for_span",
541523
"_get_agent_common_attributes",

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

Lines changed: 8 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -315,13 +315,6 @@ class EmbeddingInvocation(GenAIInvocation):
315315
class _BaseAgent(GenAIInvocation):
316316
"""
317317
Shared base class for agent lifecycle types.
318-
319-
Contains fields common to all agent operations: identity, provider,
320-
model, system instructions, server info, and telemetry plumbing.
321-
322-
Follows semconv for GenAI agent spans:
323-
https://github.com/open-telemetry/semantic-conventions/blob/main/docs/gen-ai/gen-ai-agent-spans.md
324-
325318
Do not instantiate directly — use AgentInvocation.
326319
"""
327320

@@ -340,26 +333,13 @@ class _BaseAgent(GenAIInvocation):
340333
server_address: str | None = None
341334
server_port: int | None = None
342335

343-
attributes: dict[str, Any] = field(default_factory=_new_str_any_dict)
344-
"""
345-
Additional attributes to set on spans and/or events.
346-
"""
347-
# Monotonic start time in seconds (from timeit.default_timer) used
348-
# for duration calculations to avoid mixing clock sources. This is
349-
# populated by the TelemetryHandler when starting an invocation.
350-
monotonic_start_s: float | None = None
351-
352336

353337
@dataclass
354338
class AgentInvocation(_BaseAgent):
355339
"""
356-
Represents an agent invocation (invoke_agent operation).
357-
358-
Follows semconv for GenAI agent spans:
359-
https://github.com/open-telemetry/semantic-conventions/blob/main/docs/gen-ai/gen-ai-agent-spans.md#invoke-agent-span
360-
361-
When creating an AgentInvocation object, only update the data attributes.
362-
The span and context_token attributes are set by the TelemetryHandler.
340+
Represents a single agent invocation. When creating an AgentInvocation object,
341+
only update the data attributes. The span and context_token attributes are
342+
set by the TelemetryHandler.
363343
"""
364344

365345
operation_name: str = GenAI.GenAiOperationNameValues.INVOKE_AGENT.value
@@ -392,6 +372,11 @@ class AgentInvocation(_BaseAgent):
392372
)
393373
tool_definitions: list[dict[str, Any]] | None = None
394374

375+
attributes: dict[str, Any] = field(default_factory=_new_str_any_dict)
376+
"""
377+
Additional attributes to set on spans and/or events. These attributes
378+
will not be set on metrics.
379+
"""
395380
metric_attributes: dict[str, Any] = field(
396381
default_factory=_new_str_any_dict
397382
)

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ def test_start_stop_creates_span(self) -> None:
6363

6464
def test_span_kind_client_by_default(self) -> None:
6565
handler = self._make_handler()
66-
invocation = AgentInvocation(agent_name="Agent")
66+
invocation = AgentInvocation(agent_name="Agent", provider="openai")
6767
handler.start_agent(invocation)
6868
handler.stop_agent(invocation)
6969
self.assertEqual(
@@ -123,10 +123,8 @@ def test_cache_token_attributes(self) -> None:
123123

124124
attrs = self.span_exporter.get_finished_spans()[0].attributes
125125
self.assertEqual(attrs[GenAI.GEN_AI_USAGE_INPUT_TOKENS], 100)
126-
self.assertEqual(
127-
attrs[GenAI.GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS], 25
128-
)
129-
self.assertEqual(attrs[GenAI.GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS], 50)
126+
self.assertEqual(attrs["gen_ai.usage.cache_creation_input_tokens"], 25)
127+
self.assertEqual(attrs["gen_ai.usage.cache_read_input_tokens"], 50)
130128

131129
def test_fail_sets_error_status(self) -> None:
132130
handler = self._make_handler()
@@ -157,7 +155,9 @@ def test_context_manager_success(self) -> None:
157155
def test_context_manager_error(self) -> None:
158156
handler = self._make_handler()
159157
with self.assertRaises(ValueError):
160-
with handler.agent(AgentInvocation(agent_name="Agent")):
158+
with handler.agent(
159+
AgentInvocation(agent_name="Agent", provider="openai")
160+
):
161161
raise ValueError("test error")
162162

163163
self.assertEqual(

0 commit comments

Comments
 (0)