Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -161,10 +161,10 @@ def process_event(self, event: "ResponseStreamEvent[TextFormatT]") -> None:
event, "response", None
)

if response and not self._self_invocation.request_model:
if response and not self._self_invocation.response_model_name:
model = response.model
if model:
self._self_invocation.request_model = model
self._self_invocation.response_model_name = model

if event_type == "response.completed":
self._stop(response)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,6 @@ def test_model_omitted_when_missing(handler, span_exporter):
invocation = create_embedding_invocation(handler, {}, _make_client())
invocation.stop()

assert invocation.request_model is None

spans = span_exporter.get_finished_spans()
assert len(spans) == 1
span = spans[0]
Expand All @@ -77,8 +75,6 @@ def test_model_preserved_when_provided(handler, span_exporter):
)
invocation.stop()

assert invocation.request_model == "text-embedding-3-small"

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

attributes validated below


spans = span_exporter.get_finished_spans()
assert len(spans) == 1
span = spans[0]
Expand Down
2 changes: 2 additions & 0 deletions util/opentelemetry-util-genai/.changelog/150.changed
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Restricted start-time sampling attributes to invocation construction time;
they should not be settable after start.
Comment on lines +1 to +2
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,12 @@ def __init__(
else _operation_name,
span_kind=span_kind,
)
self.request_model = request_model
self.server_address = server_address
self.server_port = server_port
self.provider = provider
self._provider: str | None = provider
self._request_model: str | None = request_model
self._server_address: str | None = server_address
self._server_port: int | None = server_port
Comment on lines +67 to +70

self.agent_name: str | None = agent_name
self._agent_name: str | None = agent_name
self.agent_id: str | None = None
self.agent_description: str | None = None
self.agent_version: str | None = None
Expand Down Expand Up @@ -99,37 +99,35 @@ def __init__(
self.system_instruction: list[MessagePart] = []
self.tool_definitions: list[ToolDefinition] | None = None

self._start(self._get_base_attributes())
self._start(self._get_start_attributes())

def _get_base_attributes(self) -> dict[str, Any]:
@property
def agent_name(self) -> str | None:

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this one is used by langchain, convert to read-only property

"""The agent name provided at construction time."""
return self._agent_name

def _get_start_attributes(self) -> dict[str, Any]:
"""Return sampling-relevant attributes available at span creation time."""
optional_attrs = (
(GenAI.GEN_AI_PROVIDER_NAME, self.provider),
(GenAI.GEN_AI_REQUEST_MODEL, self.request_model),
(GenAI.GEN_AI_AGENT_NAME, self.agent_name),
(server_attributes.SERVER_ADDRESS, self.server_address),
(server_attributes.SERVER_PORT, self.server_port),
(GenAI.GEN_AI_REQUEST_MODEL, self._request_model),
(GenAI.GEN_AI_AGENT_NAME, self._agent_name),
(server_attributes.SERVER_ADDRESS, self._server_address),
(server_attributes.SERVER_PORT, self._server_port),
)
return {
GenAI.GEN_AI_OPERATION_NAME: self._operation_name,
GenAI.GEN_AI_PROVIDER_NAME: self._provider,
**{k: v for k, v in optional_attrs if v is not None},
}
Comment on lines 111 to 121

def _get_common_attributes(self) -> dict[str, Any]:
def _get_agent_attributes(self) -> dict[str, Any]:
"""Return agent attributes not known at span creation time."""
optional_attrs = (
(GenAI.GEN_AI_PROVIDER_NAME, self.provider),
(GenAI.GEN_AI_REQUEST_MODEL, self.request_model),
(server_attributes.SERVER_ADDRESS, self.server_address),
(server_attributes.SERVER_PORT, self.server_port),
(GenAI.GEN_AI_AGENT_NAME, self.agent_name),
(GenAI.GEN_AI_AGENT_ID, self.agent_id),
(GenAI.GEN_AI_AGENT_DESCRIPTION, self.agent_description),
(GenAI.GEN_AI_AGENT_VERSION, self.agent_version),
)
return {
GenAI.GEN_AI_OPERATION_NAME: self._operation_name,
**{k: v for k, v in optional_attrs if v is not None},
}
return {k: v for k, v in optional_attrs if v is not None}

def _get_request_attributes(self) -> dict[str, Any]:
optional_attrs = (
Expand Down Expand Up @@ -178,10 +176,10 @@ def _get_content_attributes_for_span(self) -> dict[str, Any]:

def _get_metric_attributes(self) -> dict[str, Any]:
optional_attrs = (
(GenAI.GEN_AI_PROVIDER_NAME, self.provider),
(GenAI.GEN_AI_REQUEST_MODEL, self.request_model),
(server_attributes.SERVER_ADDRESS, self.server_address),
(server_attributes.SERVER_PORT, self.server_port),
(GenAI.GEN_AI_PROVIDER_NAME, self._provider),
(GenAI.GEN_AI_REQUEST_MODEL, self._request_model),
(server_attributes.SERVER_ADDRESS, self._server_address),
(server_attributes.SERVER_PORT, self._server_port),
)
attrs: dict[str, Any] = {
GenAI.GEN_AI_OPERATION_NAME: self._operation_name,
Expand All @@ -205,7 +203,7 @@ def _apply_finish(self, error: Error | None = None) -> None:
self._apply_error_attributes(error)

attributes: dict[str, Any] = {}
attributes.update(self._get_common_attributes())
attributes.update(self._get_agent_attributes())
attributes.update(self._get_request_attributes())
attributes.update(self._get_response_attributes())
attributes.update(self._get_usage_attributes())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,25 +48,26 @@ def __init__(
else _operation_name,
span_kind=SpanKind.CLIENT,
)
self.provider = provider # e.g., azure.ai.openai, openai, aws.bedrock
self.request_model = request_model
self.server_address = server_address
self.server_port = server_port
# e.g., azure.ai.openai, openai, aws.bedrock
self._provider: str = provider
self._request_model: str | None = request_model
self._server_address: str | None = server_address
self._server_port: int | None = server_port
Comment on lines +51 to +55
# encoding_formats can be multi-value -> combinational cardinality risk.
# Keep on spans/events only.
self.encoding_formats: list[str] | None = None
self.input_tokens: int | None = None
self.dimension_count: int | None = None
self.response_model_name: str | None = None
self._start(self._get_base_attributes())
self._start(self._get_start_attributes())

def _get_base_attributes(self) -> dict[str, Any]:
def _get_start_attributes(self) -> dict[str, Any]:
"""Return sampling-relevant attributes available at span creation time."""
optional_attrs = (
(GenAI.GEN_AI_REQUEST_MODEL, self.request_model),
(GenAI.GEN_AI_PROVIDER_NAME, self.provider),
(server_attributes.SERVER_ADDRESS, self.server_address),
(server_attributes.SERVER_PORT, self.server_port),
(GenAI.GEN_AI_REQUEST_MODEL, self._request_model),
(GenAI.GEN_AI_PROVIDER_NAME, self._provider),
(server_attributes.SERVER_ADDRESS, self._server_address),
(server_attributes.SERVER_PORT, self._server_port),
)
return {
GenAI.GEN_AI_OPERATION_NAME: self._operation_name,
Expand All @@ -75,11 +76,11 @@ def _get_base_attributes(self) -> dict[str, Any]:

def _get_metric_attributes(self) -> dict[str, Any]:
optional_attrs = (
(GenAI.GEN_AI_PROVIDER_NAME, self.provider),
(GenAI.GEN_AI_REQUEST_MODEL, self.request_model),
(GenAI.GEN_AI_PROVIDER_NAME, self._provider),
(GenAI.GEN_AI_REQUEST_MODEL, self._request_model),
(GenAI.GEN_AI_RESPONSE_MODEL, self.response_model_name),
(server_attributes.SERVER_ADDRESS, self.server_address),
(server_attributes.SERVER_PORT, self.server_port),
(server_attributes.SERVER_ADDRESS, self._server_address),
(server_attributes.SERVER_PORT, self._server_port),
)
attrs: dict[str, AttributeValue] = {
GenAI.GEN_AI_OPERATION_NAME: self._operation_name,
Expand All @@ -95,22 +96,13 @@ def _get_metric_token_counts(self) -> dict[str, int]:

def _apply_finish(self, error: Error | None = None) -> None:
optional_attrs = (
(GenAI.GEN_AI_PROVIDER_NAME, self.provider),
(server_attributes.SERVER_ADDRESS, self.server_address),
(server_attributes.SERVER_PORT, self.server_port),
(GenAI.GEN_AI_REQUEST_MODEL, self.request_model),
(GenAI.GEN_AI_EMBEDDINGS_DIMENSION_COUNT, self.dimension_count),
(GenAI.GEN_AI_REQUEST_ENCODING_FORMATS, self.encoding_formats),
(GenAI.GEN_AI_RESPONSE_MODEL, self.response_model_name),
(GenAI.GEN_AI_USAGE_INPUT_TOKENS, self.input_tokens),
)
attributes: dict[str, Any] = {
GenAI.GEN_AI_OPERATION_NAME: self._operation_name,
**{
key: value
for key, value in optional_attrs
if value is not None
},
key: value for key, value in optional_attrs if value is not None
}
if error is not None:
self._apply_error_attributes(error)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,10 @@ def __init__(
else operation_name,
span_kind=SpanKind.CLIENT,
)
self.provider = provider
self.request_model = request_model
self.server_address = server_address
self.server_port = server_port
self._provider: str = provider
self._request_model: str | None = request_model
self._server_address: str | None = server_address
self._server_port: int | None = server_port
Comment on lines +70 to +73

self.input_messages: list[InputMessage] = []
self.output_messages: list[OutputMessage] = []
Expand All @@ -95,7 +95,7 @@ def __init__(
self.top_k: float | None = None
self.request_choice_count: int | None = None
self.output_type: str | None = None
self._start(self._get_base_attributes())
self._start(self._get_start_attributes())

def _get_message_attributes(self, *, for_span: bool) -> dict[str, Any]:
return get_content_attributes(
Expand All @@ -118,20 +118,20 @@ def _get_finish_reasons(self) -> list[str] | None:
return reasons or None
return None

def _get_base_attributes(self) -> dict[str, Any]:
def _get_start_attributes(self) -> dict[str, Any]:
optional_attrs = (
(GenAI.GEN_AI_REQUEST_MODEL, self.request_model),
(GenAI.GEN_AI_PROVIDER_NAME, self.provider),
(server_attributes.SERVER_ADDRESS, self.server_address),
(server_attributes.SERVER_PORT, self.server_port),
(GenAI.GEN_AI_REQUEST_MODEL, self._request_model),
(GenAI.GEN_AI_PROVIDER_NAME, self._provider),
(server_attributes.SERVER_ADDRESS, self._server_address),
(server_attributes.SERVER_PORT, self._server_port),
)
return {
GenAI.GEN_AI_OPERATION_NAME: self._operation_name,
**{k: v for k, v in optional_attrs if v is not None},
}

def _get_attributes(self) -> dict[str, Any]:
attrs = self._get_base_attributes()
attrs = self._get_start_attributes()
if self.output_tokens is None and self.thinking_tokens is None:
output_tokens = None
else:
Expand Down Expand Up @@ -171,7 +171,7 @@ def _get_attributes(self) -> dict[str, Any]:
return attrs

def _get_metric_attributes(self) -> dict[str, Any]:
attrs = self._get_base_attributes()
attrs = self._get_start_attributes()
if self.response_model_name is not None:
attrs[GenAI.GEN_AI_RESPONSE_MODEL] = self.response_model_name
attrs.update(self.metric_attributes)
Expand Down Expand Up @@ -302,8 +302,8 @@ def _sync_to_invocation(self) -> None:
inv = self._inference_invocation
if inv is None:
return
inv.provider = self.provider or ""
inv.request_model = self.request_model
# Start attributes (provider, request_model, server_address, server_port)
# are fixed at construction in _start_with_handler and cannot be reassigned.
inv.input_messages = self.input_messages
inv.output_messages = self.output_messages
inv.system_instruction = self.system_instruction
Expand All @@ -319,8 +319,6 @@ def _sync_to_invocation(self) -> None:
inv.max_tokens = self.max_tokens
inv.stop_sequences = self.stop_sequences
inv.seed = self.seed
inv.server_address = self.server_address
inv.server_port = self.server_port
inv.attributes = self.attributes
inv.metric_attributes = self.metric_attributes

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,24 +68,24 @@ def __init__(
else _operation_name,
span_kind=SpanKind.CLIENT,
)
self.data_source_id = data_source_id
self.provider = provider
self.request_model = request_model
self.server_address = server_address
self.server_port = server_port
self._data_source_id: str | None = data_source_id
self._provider: str | None = provider
self._request_model: str | None = request_model
self._server_address: str | None = server_address
self._server_port: int | None = server_port
Comment on lines +71 to +75
self.top_k: float | None = None
self.query_text: str | None = None
self.documents: Sequence[Mapping[str, Any]] | None = None
self._start(self._get_base_attributes())
self._start(self._get_start_attributes())

def _get_base_attributes(self) -> dict[str, AttributeValue]:
def _get_start_attributes(self) -> dict[str, AttributeValue]:
"""Return sampling-relevant attributes available at span creation time."""
optional_attrs: tuple[tuple[str, AttributeValue | None], ...] = (
(GenAI.GEN_AI_DATA_SOURCE_ID, self.data_source_id),
(GenAI.GEN_AI_PROVIDER_NAME, self.provider),
(GenAI.GEN_AI_REQUEST_MODEL, self.request_model),
(server_attributes.SERVER_ADDRESS, self.server_address),
(server_attributes.SERVER_PORT, self.server_port),
(GenAI.GEN_AI_DATA_SOURCE_ID, self._data_source_id),
(GenAI.GEN_AI_PROVIDER_NAME, self._provider),
(GenAI.GEN_AI_REQUEST_MODEL, self._request_model),
(server_attributes.SERVER_ADDRESS, self._server_address),
(server_attributes.SERVER_PORT, self._server_port),
)
return {
GenAI.GEN_AI_OPERATION_NAME: self._operation_name,
Expand All @@ -95,10 +95,10 @@ def _get_base_attributes(self) -> dict[str, AttributeValue]:
def _get_metric_attributes(self) -> dict[str, AttributeValue]:
# data_source_id intentionally excluded — high cardinality
optional_attrs: tuple[tuple[str, AttributeValue | None], ...] = (
(GenAI.GEN_AI_PROVIDER_NAME, self.provider),
(GenAI.GEN_AI_REQUEST_MODEL, self.request_model),
(server_attributes.SERVER_ADDRESS, self.server_address),
(server_attributes.SERVER_PORT, self.server_port),
(GenAI.GEN_AI_PROVIDER_NAME, self._provider),
(GenAI.GEN_AI_REQUEST_MODEL, self._request_model),
(server_attributes.SERVER_ADDRESS, self._server_address),
(server_attributes.SERVER_PORT, self._server_port),
)
attrs: dict[str, AttributeValue] = {
GenAI.GEN_AI_OPERATION_NAME: self._operation_name,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,25 +62,25 @@ def __init__(
span_name=f"{_operation_name} {name}" if name else _operation_name,
)
self.should_capture_content_on_span = should_capture_content_on_spans()
self.name = name
self._name: str = name
self.tool_result: AttributeValue | None = None
# Since arguments and tool_result can be expensive to serialize,
# it's recommended to check the content capture flag in the
# instrumentation library before assigning these attributes
# to the invocation.
self.arguments: AttributeValue | None = None
self.tool_call_id = tool_call_id
self.tool_type = tool_type
self.tool_description = tool_description
self._start(self._get_base_attributes())
self._tool_call_id: str | None = tool_call_id
self._tool_type: str | None = tool_type
self._tool_description: str | None = tool_description
self._start(self._get_start_attributes())
Comment on lines 64 to +75

def _get_base_attributes(self) -> dict[str, Any]:
def _get_start_attributes(self) -> dict[str, Any]:
"""Return sampling-relevant attributes available at span creation time."""
optional_attrs = (
(GenAI.GEN_AI_TOOL_NAME, self.name),
(GenAI.GEN_AI_TOOL_CALL_ID, self.tool_call_id),
(GenAI.GEN_AI_TOOL_TYPE, self.tool_type),
(GenAI.GEN_AI_TOOL_DESCRIPTION, self.tool_description),
(GenAI.GEN_AI_TOOL_NAME, self._name),
(GenAI.GEN_AI_TOOL_CALL_ID, self._tool_call_id),
(GenAI.GEN_AI_TOOL_TYPE, self._tool_type),
(GenAI.GEN_AI_TOOL_DESCRIPTION, self._tool_description),
)
return {
GenAI.GEN_AI_OPERATION_NAME: self._operation_name,
Expand All @@ -98,10 +98,6 @@ def _apply_finish(self, error: Error | None = None) -> None:
if error is not None:
self._apply_error_attributes(error)
optional_attrs = (
(GenAI.GEN_AI_TOOL_NAME, self.name),
(GenAI.GEN_AI_TOOL_CALL_ID, self.tool_call_id),
(GenAI.GEN_AI_TOOL_TYPE, self.tool_type),
(GenAI.GEN_AI_TOOL_DESCRIPTION, self.tool_description),
(
GenAI.GEN_AI_TOOL_CALL_ARGUMENTS,
self.arguments
Expand All @@ -116,8 +112,7 @@ def _apply_finish(self, error: Error | None = None) -> None:
),
)
attributes: dict[str, Any] = {
GenAI.GEN_AI_OPERATION_NAME: self._operation_name,
**{k: v for k, v in optional_attrs if v is not None},
k: v for k, v in optional_attrs if v is not None
}
attributes.update(self.attributes)
self.span.set_attributes(attributes)
Expand Down
Loading