Skip to content

Commit 61c1f53

Browse files
authored
refactor(genai-util): pass sampling attributes at span creation for all invocation types (#4553)
1 parent b6b82e8 commit 61c1f53

10 files changed

Lines changed: 340 additions & 53 deletions

File tree

util/opentelemetry-util-genai/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
- Change `InferenceInvocation` init params to only accept base params
1111
- Pass in `attributes` on invocation `_start` so samplers have access to attributes.
1212
([#4538](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4538))
13+
- Apply attribute for sampling on instantiation of all invocation types.
14+
([#4553](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4553))
1315

1416
## Version 0.4b0 (2026-05-01)
1517

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

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,7 @@ def __init__(
5757
request_model: str | None = None,
5858
server_address: str | None = None,
5959
server_port: int | None = None,
60-
attributes: dict[str, Any] | None = None,
61-
metric_attributes: dict[str, Any] | None = None,
60+
agent_name: str | None = None,
6261
) -> None:
6362
"""Use handler.start_invoke_local_agent() or handler.start_invoke_remote_agent() instead of calling this directly."""
6463
_operation_name = GenAI.GenAiOperationNameValues.INVOKE_AGENT.value
@@ -68,17 +67,17 @@ def __init__(
6867
logger,
6968
completion_hook,
7069
operation_name=_operation_name,
71-
span_name=_operation_name,
70+
span_name=f"{_operation_name} {agent_name}"
71+
if agent_name
72+
else _operation_name,
7273
span_kind=span_kind,
73-
attributes=attributes,
74-
metric_attributes=metric_attributes,
7574
)
7675
self.provider = provider
7776
self.request_model = request_model
7877
self.server_address = server_address
7978
self.server_port = server_port
8079

81-
self.agent_name: str | None = None
80+
self.agent_name: str | None = agent_name
8281
self.agent_id: str | None = None
8382
self.agent_description: str | None = None
8483
self.agent_version: str | None = None
@@ -108,7 +107,21 @@ def __init__(
108107
self.system_instruction: list[MessagePart] = []
109108
self.tool_definitions: list[ToolDefinition] | None = None
110109

111-
self._start()
110+
self._start(self._get_base_attributes())
111+
112+
def _get_base_attributes(self) -> dict[str, Any]:
113+
"""Return sampling-relevant attributes available at span creation time."""
114+
optional_attrs = (
115+
(GenAI.GEN_AI_REQUEST_MODEL, self.request_model),
116+
(GenAI.GEN_AI_AGENT_NAME, self.agent_name),
117+
(server_attributes.SERVER_ADDRESS, self.server_address),
118+
(server_attributes.SERVER_PORT, self.server_port),
119+
)
120+
return {
121+
GenAI.GEN_AI_OPERATION_NAME: self._operation_name,
122+
GenAI.GEN_AI_PROVIDER_NAME: self.provider,
123+
**{k: v for k, v in optional_attrs if v is not None},
124+
}
112125

113126
def _get_common_attributes(self) -> dict[str, Any]:
114127
optional_attrs = (
@@ -199,10 +212,6 @@ def _apply_finish(self, error: Error | None = None) -> None:
199212
if error is not None:
200213
self._apply_error_attributes(error)
201214

202-
# Update span name if agent_name was set after construction
203-
if self.agent_name:
204-
self.span.update_name(f"{self._operation_name} {self.agent_name}")
205-
206215
attributes: dict[str, Any] = {}
207216
attributes.update(self._get_common_attributes())
208217
attributes.update(self._get_request_attributes())

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

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ class EmbeddingInvocation(GenAIInvocation):
2424
context manager rather than constructing this directly.
2525
"""
2626

27-
def __init__( # pylint: disable=too-many-locals
27+
def __init__(
2828
self,
2929
tracer: Tracer,
3030
metrics_recorder: InvocationMetricsRecorder,
@@ -35,12 +35,6 @@ def __init__( # pylint: disable=too-many-locals
3535
request_model: str | None = None,
3636
server_address: str | None = None,
3737
server_port: int | None = None,
38-
encoding_formats: list[str] | None = None,
39-
input_tokens: int | None = None,
40-
dimension_count: int | None = None,
41-
response_model_name: str | None = None,
42-
attributes: dict[str, Any] | None = None,
43-
metric_attributes: dict[str, Any] | None = None,
4438
) -> None:
4539
"""Use handler.start_embedding(provider) or handler.embedding(provider) instead of calling this directly."""
4640
_operation_name = GenAI.GenAiOperationNameValues.EMBEDDINGS.value
@@ -54,20 +48,31 @@ def __init__( # pylint: disable=too-many-locals
5448
if request_model
5549
else _operation_name,
5650
span_kind=SpanKind.CLIENT,
57-
attributes=attributes,
58-
metric_attributes=metric_attributes,
5951
)
6052
self.provider = provider # e.g., azure.ai.openai, openai, aws.bedrock
6153
self.request_model = request_model
6254
self.server_address = server_address
6355
self.server_port = server_port
6456
# encoding_formats can be multi-value -> combinational cardinality risk.
6557
# Keep on spans/events only.
66-
self.encoding_formats = encoding_formats
67-
self.input_tokens = input_tokens
68-
self.dimension_count = dimension_count
69-
self.response_model_name = response_model_name
70-
self._start()
58+
self.encoding_formats: list[str] | None = None
59+
self.input_tokens: int | None = None
60+
self.dimension_count: int | None = None
61+
self.response_model_name: str | None = None
62+
self._start(self._get_base_attributes())
63+
64+
def _get_base_attributes(self) -> dict[str, Any]:
65+
"""Return sampling-relevant attributes available at span creation time."""
66+
optional_attrs = (
67+
(GenAI.GEN_AI_REQUEST_MODEL, self.request_model),
68+
(GenAI.GEN_AI_PROVIDER_NAME, self.provider),
69+
(server_attributes.SERVER_ADDRESS, self.server_address),
70+
(server_attributes.SERVER_PORT, self.server_port),
71+
)
72+
return {
73+
GenAI.GEN_AI_OPERATION_NAME: self._operation_name,
74+
**{k: v for k, v in optional_attrs if v is not None},
75+
}
7176

7277
def _get_metric_attributes(self) -> dict[str, Any]:
7378
optional_attrs = (

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

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,6 @@ def __init__(
4747
tool_call_id: str | None = None,
4848
tool_type: str | None = None,
4949
tool_description: str | None = None,
50-
tool_result: Any = None,
51-
attributes: dict[str, Any] | None = None,
52-
metric_attributes: dict[str, Any] | None = None,
5350
) -> None:
5451
"""Use handler.start_tool(name) or handler.tool(name) instead of calling this directly."""
5552
_operation_name = GenAI.GenAiOperationNameValues.EXECUTE_TOOL.value
@@ -60,16 +57,27 @@ def __init__(
6057
completion_hook,
6158
operation_name=_operation_name,
6259
span_name=f"{_operation_name} {name}" if name else _operation_name,
63-
attributes=attributes,
64-
metric_attributes=metric_attributes,
6560
)
6661
self.name = name
6762
self.arguments = arguments
6863
self.tool_call_id = tool_call_id
6964
self.tool_type = tool_type
7065
self.tool_description = tool_description
71-
self.tool_result = tool_result
72-
self._start()
66+
self.tool_result: Any = None
67+
self._start(self._get_base_attributes())
68+
69+
def _get_base_attributes(self) -> dict[str, Any]:
70+
"""Return sampling-relevant attributes available at span creation time."""
71+
optional_attrs = (
72+
(GenAI.GEN_AI_TOOL_NAME, self.name),
73+
(GenAI.GEN_AI_TOOL_CALL_ID, self.tool_call_id),
74+
(GenAI.GEN_AI_TOOL_TYPE, self.tool_type),
75+
(GenAI.GEN_AI_TOOL_DESCRIPTION, self.tool_description),
76+
)
77+
return {
78+
GenAI.GEN_AI_OPERATION_NAME: self._operation_name,
79+
**{k: v for k, v in optional_attrs if v is not None},
80+
}
7381

7482
def _get_metric_attributes(self) -> dict[str, Any]:
7583
attrs: dict[str, Any] = {

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

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,6 @@ def __init__(
4343
logger: Logger,
4444
completion_hook: CompletionHook,
4545
name: str | None,
46-
*,
47-
input_messages: list[InputMessage] | None = None,
48-
output_messages: list[OutputMessage] | None = None,
49-
attributes: dict[str, Any] | None = None,
50-
metric_attributes: dict[str, Any] | None = None,
5146
) -> None:
5247
"""Use handler.start_workflow(name) or handler.workflow(name) instead of calling this directly."""
5348
_operation_name = "invoke_workflow"
@@ -59,17 +54,18 @@ def __init__(
5954
operation_name=_operation_name,
6055
span_name=f"{_operation_name} {name}" if name else _operation_name,
6156
span_kind=SpanKind.INTERNAL,
62-
attributes=attributes,
63-
metric_attributes=metric_attributes,
6457
)
6558
self.name = name
66-
self.input_messages: list[InputMessage] = (
67-
[] if input_messages is None else input_messages
68-
)
69-
self.output_messages: list[OutputMessage] = (
70-
[] if output_messages is None else output_messages
71-
)
72-
self._start()
59+
self.input_messages: list[InputMessage] = []
60+
self.output_messages: list[OutputMessage] = []
61+
self._start(self._get_base_attributes())
62+
63+
def _get_base_attributes(self) -> dict[str, Any]:
64+
"""Return sampling-relevant attributes available at span creation time."""
65+
attrs: dict[str, Any] = {
66+
GenAI.GEN_AI_OPERATION_NAME: self._operation_name,
67+
}
68+
return attrs
7369

7470
def _get_messages_for_span(self) -> dict[str, Any]:
7571
if not is_experimental_mode() or get_content_capturing_mode() not in (

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,7 @@ def start_invoke_local_agent(
342342
provider: str,
343343
*,
344344
request_model: str | None = None,
345+
agent_name: str | None = None,
345346
) -> AgentInvocation:
346347
"""Create and start a local agent invocation (INTERNAL span kind).
347348
@@ -358,6 +359,7 @@ def start_invoke_local_agent(
358359
provider,
359360
span_kind=SpanKind.INTERNAL,
360361
request_model=request_model,
362+
agent_name=agent_name,
361363
)
362364

363365
def start_invoke_remote_agent(
@@ -367,6 +369,7 @@ def start_invoke_remote_agent(
367369
request_model: str | None = None,
368370
server_address: str | None = None,
369371
server_port: int | None = None,
372+
agent_name: str | None = None,
370373
) -> AgentInvocation:
371374
"""Create and start a remote agent invocation (CLIENT span kind).
372375
@@ -383,6 +386,7 @@ def start_invoke_remote_agent(
383386
provider,
384387
span_kind=SpanKind.CLIENT,
385388
request_model=request_model,
389+
agent_name=agent_name,
386390
server_address=server_address,
387391
server_port=server_port,
388392
)
@@ -392,6 +396,7 @@ def invoke_local_agent(
392396
provider: str,
393397
*,
394398
request_model: str | None = None,
399+
agent_name: str | None = None,
395400
) -> AbstractContextManager[AgentInvocation]:
396401
"""Context manager for local agent invocations (INTERNAL span kind).
397402
@@ -406,6 +411,7 @@ def invoke_local_agent(
406411
return self.start_invoke_local_agent(
407412
provider,
408413
request_model=request_model,
414+
agent_name=agent_name,
409415
)._managed()
410416

411417
def invoke_remote_agent(
@@ -415,6 +421,7 @@ def invoke_remote_agent(
415421
request_model: str | None = None,
416422
server_address: str | None = None,
417423
server_port: int | None = None,
424+
agent_name: str | None = None,
418425
) -> AbstractContextManager[AgentInvocation]:
419426
"""Context manager for remote agent invocations (CLIENT span kind).
420427
@@ -429,6 +436,7 @@ def invoke_remote_agent(
429436
return self.start_invoke_remote_agent(
430437
provider,
431438
request_model=request_model,
439+
agent_name=agent_name,
432440
server_address=server_address,
433441
server_port=server_port,
434442
)._managed()

0 commit comments

Comments
 (0)