From d7e3056ea04bea858e5f5ed7d1f145153bf87f1e Mon Sep 17 00:00:00 2001 From: Liudmila Molkova Date: Sun, 14 Jun 2026 03:53:46 +0000 Subject: [PATCH 1/6] remove provider name from local agent span --- .../genai/langchain/callback_handler.py | 3 - .../tests/test_callback_handler.py | 1 - .../util/genai/_agent_invocation.py | 5 -- .../src/opentelemetry/util/genai/handler.py | 8 -- .../tests/test_handler_agent.py | 81 +++++++------------ .../tests/test_handler_completion_hook.py | 19 ++--- 6 files changed, 37 insertions(+), 80 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-genai-langchain/src/opentelemetry/instrumentation/genai/langchain/callback_handler.py b/instrumentation/opentelemetry-instrumentation-genai-langchain/src/opentelemetry/instrumentation/genai/langchain/callback_handler.py index 8779b3d4..6cbba597 100644 --- a/instrumentation/opentelemetry-instrumentation-genai-langchain/src/opentelemetry/instrumentation/genai/langchain/callback_handler.py +++ b/instrumentation/opentelemetry-instrumentation-genai-langchain/src/opentelemetry/instrumentation/genai/langchain/callback_handler.py @@ -92,9 +92,6 @@ def on_chain_start( ) if suggested_agent_name_lower != agent_invocation_name_lower: agent = self._telemetry_handler.invoke_local_agent( - provider=metadata.get("ls_provider", "unknown") - if metadata - else "unknown", agent_name=suggested_agent_name, ) agent.input_messages = make_input_message(inputs) diff --git a/instrumentation/opentelemetry-instrumentation-genai-langchain/tests/test_callback_handler.py b/instrumentation/opentelemetry-instrumentation-genai-langchain/tests/test_callback_handler.py index be5476e4..c4cdd564 100644 --- a/instrumentation/opentelemetry-instrumentation-genai-langchain/tests/test_callback_handler.py +++ b/instrumentation/opentelemetry-instrumentation-genai-langchain/tests/test_callback_handler.py @@ -165,7 +165,6 @@ def test_new_agent_span_created(self): ) telemetry.invoke_local_agent.assert_called_once_with( - provider="openai", agent_name="math_agent", ) assert agent_inv.agent_name == "math_agent" diff --git a/util/opentelemetry-util-genai/src/opentelemetry/util/genai/_agent_invocation.py b/util/opentelemetry-util-genai/src/opentelemetry/util/genai/_agent_invocation.py index eebb05c5..bd0c98ef 100644 --- a/util/opentelemetry-util-genai/src/opentelemetry/util/genai/_agent_invocation.py +++ b/util/opentelemetry-util-genai/src/opentelemetry/util/genai/_agent_invocation.py @@ -43,7 +43,6 @@ def __init__( metrics_recorder: InvocationMetricsRecorder, logger: Logger, completion_hook: CompletionHook, - provider: str, *, span_kind: SpanKind = SpanKind.INTERNAL, request_model: str | None = None, @@ -64,7 +63,6 @@ def __init__( else _operation_name, span_kind=span_kind, ) - self.provider = provider self.request_model = request_model self.server_address = server_address self.server_port = server_port @@ -111,7 +109,6 @@ def _get_base_attributes(self) -> dict[str, Any]: ) 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}, } @@ -127,7 +124,6 @@ def _get_common_attributes(self) -> dict[str, Any]: ) 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}, } @@ -178,7 +174,6 @@ 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), diff --git a/util/opentelemetry-util-genai/src/opentelemetry/util/genai/handler.py b/util/opentelemetry-util-genai/src/opentelemetry/util/genai/handler.py index ff3ebbba..01cca074 100644 --- a/util/opentelemetry-util-genai/src/opentelemetry/util/genai/handler.py +++ b/util/opentelemetry-util-genai/src/opentelemetry/util/genai/handler.py @@ -389,7 +389,6 @@ def tool( def start_invoke_local_agent( self, - provider: str, *, request_model: str | None = None, agent_name: str | None = None, @@ -409,7 +408,6 @@ def start_invoke_local_agent( self._metrics_recorder, self._logger, self._completion_hook, - provider, span_kind=SpanKind.INTERNAL, request_model=request_model, agent_name=agent_name, @@ -417,7 +415,6 @@ def start_invoke_local_agent( def start_invoke_remote_agent( self, - provider: str, *, request_model: str | None = None, server_address: str | None = None, @@ -439,7 +436,6 @@ def start_invoke_remote_agent( self._metrics_recorder, self._logger, self._completion_hook, - provider, span_kind=SpanKind.CLIENT, request_model=request_model, agent_name=agent_name, @@ -449,7 +445,6 @@ def start_invoke_remote_agent( def invoke_local_agent( self, - provider: str, *, request_model: str | None = None, agent_name: str | None = None, @@ -469,7 +464,6 @@ def invoke_local_agent( self._metrics_recorder, self._logger, self._completion_hook, - provider, span_kind=SpanKind.INTERNAL, request_model=request_model, agent_name=agent_name, @@ -477,7 +471,6 @@ def invoke_local_agent( def invoke_remote_agent( self, - provider: str, *, request_model: str | None = None, server_address: str | None = None, @@ -499,7 +492,6 @@ def invoke_remote_agent( self._metrics_recorder, self._logger, self._completion_hook, - provider, span_kind=SpanKind.CLIENT, request_model=request_model, agent_name=agent_name, diff --git a/util/opentelemetry-util-genai/tests/test_handler_agent.py b/util/opentelemetry-util-genai/tests/test_handler_agent.py index 9075b291..790be003 100644 --- a/util/opentelemetry-util-genai/tests/test_handler_agent.py +++ b/util/opentelemetry-util-genai/tests/test_handler_agent.py @@ -40,7 +40,6 @@ def setUp(self): def test_start_stop_creates_span(self): invocation = self.handler.invoke_local_agent( - "openai", request_model="gpt-4", agent_name="Math Tutor", ) @@ -52,11 +51,10 @@ def test_start_stop_creates_span(self): assert span.name == "invoke_agent Math Tutor" assert span.attributes[GenAI.GEN_AI_OPERATION_NAME] == "invoke_agent" assert span.attributes[GenAI.GEN_AI_AGENT_NAME] == "Math Tutor" - assert span.attributes[GenAI.GEN_AI_PROVIDER_NAME] == "openai" assert span.attributes[GenAI.GEN_AI_REQUEST_MODEL] == "gpt-4" def test_span_kind_internal(self): - invocation = self.handler.invoke_local_agent("openai") + invocation = self.handler.invoke_local_agent() invocation.stop() assert ( self.span_exporter.get_finished_spans()[0].kind @@ -64,7 +62,7 @@ def test_span_kind_internal(self): ) def test_no_server_attributes(self): - invocation = self.handler.invoke_local_agent("openai") + invocation = self.handler.invoke_local_agent() invocation.stop() attrs = self.span_exporter.get_finished_spans()[0].attributes assert server_attributes.SERVER_ADDRESS not in attrs @@ -72,7 +70,6 @@ def test_no_server_attributes(self): def test_all_attributes(self): invocation = self.handler.invoke_local_agent( - "openai", request_model="gpt-4", ) invocation.agent_name = "Full Agent" @@ -116,7 +113,7 @@ def test_all_attributes(self): assert attrs[GenAI.GEN_AI_RESPONSE_FINISH_REASONS] == ("stop",) def test_finish_reasons_multiple(self): - invocation = self.handler.invoke_local_agent("openai") + invocation = self.handler.invoke_local_agent() invocation.finish_reasons = ["stop", "length"] invocation.stop() attrs = self.span_exporter.get_finished_spans()[0].attributes @@ -126,7 +123,7 @@ def test_finish_reasons_multiple(self): ) def test_finish_reasons_empty_list_omitted(self): - invocation = self.handler.invoke_local_agent("openai") + invocation = self.handler.invoke_local_agent() invocation.finish_reasons = [] invocation.stop() attrs = self.span_exporter.get_finished_spans()[0].attributes @@ -134,7 +131,7 @@ def test_finish_reasons_empty_list_omitted(self): assert GenAI.GEN_AI_RESPONSE_FINISH_REASONS not in attrs def test_cache_token_attributes(self): - invocation = self.handler.invoke_local_agent("openai") + invocation = self.handler.invoke_local_agent() invocation.input_tokens = 100 invocation.cache_creation_input_tokens = 25 invocation.cache_read_input_tokens = 50 @@ -146,7 +143,7 @@ def test_cache_token_attributes(self): assert attrs[GenAI.GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS] == 50 def test_fail_sets_error_status(self): - invocation = self.handler.invoke_local_agent("openai") + invocation = self.handler.invoke_local_agent() invocation.fail(RuntimeError("agent crashed")) span = self.span_exporter.get_finished_spans()[0] @@ -155,7 +152,7 @@ def test_fail_sets_error_status(self): def test_context_manager_success(self): with self.handler.invoke_local_agent( - "openai", request_model="gpt-4", agent_name="CM Agent" + request_model="gpt-4", agent_name="CM Agent" ) as inv: inv.input_tokens = 10 inv.output_tokens = 20 @@ -167,7 +164,7 @@ def test_context_manager_success(self): def test_context_manager_error(self): with self.assertRaises(ValueError): - with self.handler.invoke_local_agent("openai"): + with self.handler.invoke_local_agent(): raise ValueError("test error") assert ( @@ -178,16 +175,15 @@ def test_context_manager_error(self): ) def test_context_manager_default_invocation(self): - with self.handler.invoke_local_agent("openai") as inv: + with self.handler.invoke_local_agent() as inv: inv.agent_name = "Dynamic Agent" assert len(self.span_exporter.get_finished_spans()) == 1 def test_default_values(self): - invocation = self.handler.invoke_local_agent("openai") + invocation = self.handler.invoke_local_agent() invocation.stop() assert invocation._operation_name == "invoke_agent" assert invocation.agent_name is None - assert invocation.provider == "openai" assert invocation.request_model is None assert not invocation.input_messages assert not invocation.output_messages @@ -198,7 +194,7 @@ def test_default_values(self): assert not invocation.attributes def test_with_messages(self): - invocation = self.handler.invoke_local_agent("openai") + invocation = self.handler.invoke_local_agent() invocation.input_messages = [ InputMessage(role="user", parts=[Text(content="Hello")]) ] @@ -214,7 +210,7 @@ def test_with_messages(self): assert invocation.input_messages[0].role == "user" def test_custom_attributes(self): - invocation = self.handler.invoke_local_agent("openai") + invocation = self.handler.invoke_local_agent() invocation.attributes["custom.key"] = "custom_value" invocation.stop() spans = self.span_exporter.get_finished_spans() @@ -226,7 +222,7 @@ def test_tool_definitions_type(self): description="Get the weather", parameters={"type": "object", "properties": {}}, ) - invocation = self.handler.invoke_local_agent("openai") + invocation = self.handler.invoke_local_agent() invocation.tool_definitions = [tool] invocation.stop() assert len(invocation.tool_definitions) == 1 @@ -234,23 +230,23 @@ def test_tool_definitions_type(self): assert invocation.tool_definitions[0].type == "function" def test_default_lists_are_independent(self): - inv1 = self.handler.invoke_local_agent("openai") - inv2 = self.handler.invoke_local_agent("openai") + inv1 = self.handler.invoke_local_agent() + inv2 = self.handler.invoke_local_agent() inv1.input_messages.append(InputMessage(role="user", parts=[])) assert len(inv2.input_messages) == 0 inv2.stop() inv1.stop() def test_default_attributes_are_independent(self): - inv1 = self.handler.invoke_local_agent("openai") - inv2 = self.handler.invoke_local_agent("openai") + inv1 = self.handler.invoke_local_agent() + inv2 = self.handler.invoke_local_agent() inv1.attributes["foo"] = "bar" assert "foo" not in inv2.attributes inv2.stop() inv1.stop() def test_agent_name_set_after_construction(self): - invocation = self.handler.invoke_local_agent("openai") + invocation = self.handler.invoke_local_agent() invocation.agent_name = "Named Agent" invocation.stop() span = self.span_exporter.get_finished_spans()[0] @@ -261,7 +257,7 @@ def test_agent_name_set_after_construction(self): def test_agent_name_passed_at_construction(self): invocation = self.handler.invoke_local_agent( - "openai", agent_name="Constructor Agent" + agent_name="Constructor Agent" ) invocation.stop() span = self.span_exporter.get_finished_spans()[0] @@ -293,9 +289,7 @@ def get_description(self): ) handler = TelemetryHandler(tracer_provider=sampler_provider) - invocation = handler.invoke_local_agent( - "openai", agent_name="Sampler Agent" - ) + invocation = handler.invoke_local_agent(agent_name="Sampler Agent") invocation.stop() assert captured_attributes[GenAI.GEN_AI_AGENT_NAME] == "Sampler Agent" @@ -325,7 +319,7 @@ def get_description(self): ) handler = TelemetryHandler(tracer_provider=sampler_provider) - invocation = handler.invoke_local_agent("openai") + invocation = handler.invoke_local_agent() invocation.stop() assert GenAI.GEN_AI_AGENT_NAME not in captured_attributes @@ -345,7 +339,7 @@ def setUp(self): return_value=ContentCapturingMode.SPAN_AND_EVENT, ) def test_system_instruction_on_span(self, _mock_cap): - invocation = self.handler.invoke_local_agent("openai") + invocation = self.handler.invoke_local_agent() invocation.system_instruction = [ Text(content="You are a helpful assistant."), ] @@ -364,7 +358,7 @@ def test_tool_definitions_on_span(self, _mock_cap): description="Get the weather", parameters={"type": "object", "properties": {}}, ) - invocation = self.handler.invoke_local_agent("openai") + invocation = self.handler.invoke_local_agent() invocation.tool_definitions = [tool] invocation.stop() @@ -376,7 +370,7 @@ def test_tool_definitions_on_span(self, _mock_cap): return_value=ContentCapturingMode.SPAN_AND_EVENT, ) def test_messages_on_span(self, _mock_cap): - invocation = self.handler.invoke_local_agent("openai") + invocation = self.handler.invoke_local_agent() invocation.input_messages = [ InputMessage(role="user", parts=[Text(content="Hello")]) ] @@ -394,7 +388,7 @@ def test_messages_on_span(self, _mock_cap): assert GenAI.GEN_AI_OUTPUT_MESSAGES in attrs def test_content_not_on_span_by_default(self): - invocation = self.handler.invoke_local_agent("openai") + invocation = self.handler.invoke_local_agent() invocation.system_instruction = [ Text(content="You are a helpful assistant."), ] @@ -418,7 +412,7 @@ def setUp(self): self.handler = TelemetryHandler(tracer_provider=tracer_provider) def test_span_kind_client(self): - invocation = self.handler.invoke_remote_agent("openai") + invocation = self.handler.invoke_remote_agent() invocation.stop() assert ( self.span_exporter.get_finished_spans()[0].kind == SpanKind.CLIENT @@ -426,7 +420,6 @@ def test_span_kind_client(self): def test_server_attributes(self): invocation = self.handler.invoke_remote_agent( - "openai", server_address="api.openai.com", server_port=443, ) @@ -437,7 +430,6 @@ def test_server_attributes(self): def test_all_attributes(self): invocation = self.handler.invoke_remote_agent( - "openai", request_model="gpt-4", server_address="api.openai.com", server_port=443, @@ -460,7 +452,7 @@ def test_all_attributes(self): assert attrs[GenAI.GEN_AI_REQUEST_MODEL] == "gpt-4" def test_fail_sets_error_status(self): - invocation = self.handler.invoke_remote_agent("openai") + invocation = self.handler.invoke_remote_agent() invocation.fail(RuntimeError("remote agent crashed")) span = self.span_exporter.get_finished_spans()[0] @@ -469,7 +461,6 @@ def test_fail_sets_error_status(self): def test_context_manager_success(self): with self.handler.invoke_remote_agent( - "openai", request_model="gpt-4", server_address="api.openai.com", agent_name="CM Remote Agent", @@ -482,7 +473,7 @@ def test_context_manager_success(self): def test_context_manager_error(self): with self.assertRaises(ValueError): - with self.handler.invoke_remote_agent("openai"): + with self.handler.invoke_remote_agent(): raise ValueError("remote error") assert ( @@ -521,7 +512,6 @@ def get_description(self): handler = TelemetryHandler(tracer_provider=sampler_provider) invocation = handler.invoke_remote_agent( - "test-provider", request_model="agent-model", agent_name="Math Tutor", server_address="agent.example.com", @@ -532,9 +522,6 @@ def get_description(self): assert ( captured_attributes[GenAI.GEN_AI_OPERATION_NAME] == "invoke_agent" ) - assert ( - captured_attributes[GenAI.GEN_AI_PROVIDER_NAME] == "test-provider" - ) assert captured_attributes[GenAI.GEN_AI_REQUEST_MODEL] == "agent-model" assert captured_attributes[GenAI.GEN_AI_AGENT_NAME] == "Math Tutor" assert ( @@ -551,9 +538,7 @@ def test_local_agent_records_duration_and_tokens(self) -> None: meter_provider=self.meter_provider, ) with patch("timeit.default_timer", return_value=1000.0): - invocation = handler.invoke_local_agent( - "prov", request_model="model" - ) + invocation = handler.invoke_local_agent(request_model="model") invocation.input_tokens = 5 invocation.output_tokens = 7 @@ -572,9 +557,6 @@ def test_local_agent_records_duration_and_tokens(self) -> None: self.assertEqual( duration_point.attributes[GenAI.GEN_AI_REQUEST_MODEL], "model" ) - self.assertEqual( - duration_point.attributes[GenAI.GEN_AI_PROVIDER_NAME], "prov" - ) self.assertAlmostEqual(duration_point.sum, 2.0, places=3) self.assertIn("gen_ai.client.token.usage", metrics) @@ -601,7 +583,6 @@ def test_remote_agent_records_duration_with_server_attrs(self) -> None: meter_provider=self.meter_provider, ) invocation = handler.invoke_remote_agent( - "prov", request_model="model", server_address="agent.example.com", server_port=443, @@ -623,9 +604,7 @@ def test_fail_agent_records_error_metric(self) -> None: meter_provider=self.meter_provider, ) with patch("timeit.default_timer", return_value=2000.0): - invocation = handler.invoke_local_agent( - "", request_model="err-model" - ) + invocation = handler.invoke_local_agent(request_model="err-model") invocation.input_tokens = 11 error = Error(message="boom", type=ValueError) diff --git a/util/opentelemetry-util-genai/tests/test_handler_completion_hook.py b/util/opentelemetry-util-genai/tests/test_handler_completion_hook.py index adb8197a..76ab255a 100644 --- a/util/opentelemetry-util-genai/tests/test_handler_completion_hook.py +++ b/util/opentelemetry-util-genai/tests/test_handler_completion_hook.py @@ -231,9 +231,7 @@ def test_local_agent_hook_called_on_stop_with_messages(self): ) ] - invocation = handler.invoke_local_agent( - "openai", request_model="gpt-4" - ) + invocation = handler.invoke_local_agent(request_model="gpt-4") invocation.agent_name = "Math Tutor" invocation.input_messages = input_messages invocation.output_messages = output_messages @@ -254,9 +252,7 @@ def test_local_agent_hook_called_on_fail(self): hook = MagicMock() handler = self._make_handler(hook) - invocation = handler.invoke_local_agent( - "openai", request_model="gpt-4" - ) + invocation = handler.invoke_local_agent(request_model="gpt-4") invocation.input_messages = [ InputMessage(role="user", parts=[Text(content="hello")]) ] @@ -290,7 +286,6 @@ def test_remote_agent_hook_called_on_stop_with_messages(self): ] invocation = handler.invoke_remote_agent( - "openai", request_model="gpt-4", server_address="api.openai.com", server_port=443, @@ -312,7 +307,7 @@ def test_remote_agent_hook_called_on_fail(self): hook = MagicMock() handler = self._make_handler(hook) - invocation = handler.invoke_remote_agent("openai") + invocation = handler.invoke_remote_agent() invocation.fail(RuntimeError("remote agent crashed")) hook.on_completion.assert_called_once() @@ -323,8 +318,8 @@ def test_agent_hook_called_with_empty_messages_when_none_set(self): hook = MagicMock() handler = self._make_handler(hook) - handler.invoke_local_agent("openai").stop() - handler.invoke_remote_agent("openai").stop() + handler.invoke_local_agent().stop() + handler.invoke_remote_agent().stop() for call in hook.on_completion.call_args_list: self.assertEqual(call.kwargs["inputs"], []) @@ -335,8 +330,8 @@ def test_agent_hook_called_with_empty_messages_when_none_set(self): def test_agent_hook_not_called_when_not_set(self): # No hook — stop should not raise handler = self._make_handler() - handler.invoke_local_agent("openai").stop() - handler.invoke_remote_agent("openai").stop() + handler.invoke_local_agent().stop() + handler.invoke_remote_agent().stop() def test_should_capture_content_false_by_default(self): for env_var, expected_content_capture in [ From 1ec80503c36c72111bf2fcc0210989a1ebf0015d Mon Sep 17 00:00:00 2001 From: Liudmila Molkova Date: Wed, 17 Jun 2026 19:24:52 -0700 Subject: [PATCH 2/6] improve rego --- policies/genai_span_validation.rego | 42 +++++++++++++++++++---------- versions.env | 2 +- 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/policies/genai_span_validation.rego b/policies/genai_span_validation.rego index a55fb8bd..9866b354 100644 --- a/policies/genai_span_validation.rego +++ b/policies/genai_span_validation.rego @@ -3,7 +3,7 @@ # (name, type, presence) for spans matching its definitions; this file adds # cross-cutting span-level invariants the registry can't easily express. # -# Two classes of rules, both keyed on `gen_ai.operation.name`: +# Three classes of rules, all keyed on `gen_ai.operation.name`: # # 1. Span name format → `violation` # (`{operation_name} {request_model}` for inference / embeddings, @@ -17,6 +17,9 @@ # semantic-conventions/docs/gen-ai/gen-ai-spans.md and # gen-ai-agent-spans.md (the MD flattens the YAML inheritance chain # via `extends:`, so it's the right place to source from). +# `invoke_agent` is the one operation whose manifest also depends on +# span kind: semconv defines separate internal (same-process) and +# client (remote) spans, and only the client span carries server.*. # # The "set when known" Recommended subset (sampling parameters like # `frequency_penalty`, `max_tokens`; provider-side caches; conditionally- @@ -99,21 +102,26 @@ deny contains _span_finding( # ─── Per-operation expected attributes (violation) ────────────────────────── -_expected_for_op["chat"] := _inference_expected +# `_expected_for_op(op, kind)` returns the expected-attribute manifest for a +# span given its `gen_ai.operation.name` and span kind. Most operations ignore +# kind (second arg is a wildcard); invoke_agent dispatches on it because +# semconv splits it into separate internal and client spans. Undefined (→ no +# violations) for an unmapped op or an unexpected agent span kind. +_expected_for_op("chat", _) := _inference_expected -_expected_for_op["generate_content"] := _inference_expected +_expected_for_op("generate_content", _) := _inference_expected -_expected_for_op["text_completion"] := _inference_expected +_expected_for_op("text_completion", _) := _inference_expected -_expected_for_op["embeddings"] := _embeddings_expected +_expected_for_op("embeddings", _) := _embeddings_expected -_expected_for_op["execute_tool"] := _execute_tool_expected +_expected_for_op("execute_tool", _) := _execute_tool_expected -_expected_for_op["invoke_agent"] := _invoke_agent_expected +_expected_for_op("invoke_agent", kind) := _invoke_agent_expected[kind] -_expected_for_op["create_agent"] := _create_agent_expected +_expected_for_op("create_agent", _) := _create_agent_expected -_expected_for_op["retrieval"] := _retrieval_expected +_expected_for_op("retrieval", _) := _retrieval_expected # Inference (chat / generate_content / text_completion). # Required: gen_ai.operation.name, gen_ai.provider.name. @@ -156,11 +164,17 @@ _execute_tool_expected := { "gen_ai.tool.type", } -# Invoke agent. -# Required: gen_ai.operation.name, gen_ai.provider.name. -_invoke_agent_expected := { +# Invoke agent (internal) +# Required: gen_ai.operation.name +_invoke_agent_expected["internal"] := { "gen_ai.operation.name", - "gen_ai.provider.name", +} + +# Invoke agent (client) +# Required: gen_ai.operation.name, server.address +_invoke_agent_expected["client"] := { + "gen_ai.operation.name", + "server.address", } # Create agent. After creation completes the provider returns an agent.id; @@ -193,7 +207,7 @@ deny contains _span_finding( ) if { input.sample.span op := _attr_value(input.sample.span, "gen_ai.operation.name") - expected := _expected_for_op[op] + expected := _expected_for_op(op, input.sample.span.kind) some attr_name in expected not _has_attr(input.sample.span, attr_name) } diff --git a/versions.env b/versions.env index d08850cd..5caa3a2e 100644 --- a/versions.env +++ b/versions.env @@ -6,4 +6,4 @@ WEAVER_VERSION=v0.23.0 # The genai semconv registry has no tagged releases yet, so we pin a SHA on `main`. # renovate: datasource=git-refs depName=open-telemetry/semantic-conventions-genai packageName=https://github.com/open-telemetry/semantic-conventions-genai.git versioning=git -SEMCONV_GENAI_REF=8508fbfa5189ae50c7e95aa2fcd90c5c4998cbc7 +SEMCONV_GENAI_REF=528c45308c35c4d0cc31d386238908b4a1e7fd8f From b8450808223746c6d985636919766c602b6f0cb2 Mon Sep 17 00:00:00 2001 From: Liudmila Molkova Date: Wed, 17 Jun 2026 19:39:06 -0700 Subject: [PATCH 3/6] undo unintentional changes and changelog --- .../.changelog/132.removed | 1 + util/opentelemetry-util-genai/.changelog/132.removed | 1 + .../src/opentelemetry/util/genai/_agent_invocation.py | 5 +++++ .../src/opentelemetry/util/genai/handler.py | 4 ++++ 4 files changed, 11 insertions(+) create mode 100644 instrumentation/opentelemetry-instrumentation-genai-langchain/.changelog/132.removed create mode 100644 util/opentelemetry-util-genai/.changelog/132.removed diff --git a/instrumentation/opentelemetry-instrumentation-genai-langchain/.changelog/132.removed b/instrumentation/opentelemetry-instrumentation-genai-langchain/.changelog/132.removed new file mode 100644 index 00000000..4edee7d8 --- /dev/null +++ b/instrumentation/opentelemetry-instrumentation-genai-langchain/.changelog/132.removed @@ -0,0 +1 @@ +Stopped setting `gen_ai.provider.name` on internal agent spans. diff --git a/util/opentelemetry-util-genai/.changelog/132.removed b/util/opentelemetry-util-genai/.changelog/132.removed new file mode 100644 index 00000000..c8fc51cc --- /dev/null +++ b/util/opentelemetry-util-genai/.changelog/132.removed @@ -0,0 +1 @@ +Removed the `provider` parameter from the internal agent invocation APIs and stopped emitting `gen_ai.provider.name` on internal agent spans and metrics. diff --git a/util/opentelemetry-util-genai/src/opentelemetry/util/genai/_agent_invocation.py b/util/opentelemetry-util-genai/src/opentelemetry/util/genai/_agent_invocation.py index bd0c98ef..623b10b5 100644 --- a/util/opentelemetry-util-genai/src/opentelemetry/util/genai/_agent_invocation.py +++ b/util/opentelemetry-util-genai/src/opentelemetry/util/genai/_agent_invocation.py @@ -44,6 +44,7 @@ def __init__( logger: Logger, completion_hook: CompletionHook, *, + provider: str | None = None, span_kind: SpanKind = SpanKind.INTERNAL, request_model: str | None = None, server_address: str | None = None, @@ -71,6 +72,7 @@ def __init__( self.agent_id: str | None = None self.agent_description: str | None = None self.agent_version: str | None = None + self.provider: str | None = provider self.conversation_id: str | None = None self.data_source_id: str | None = None @@ -102,6 +104,7 @@ def __init__( def _get_base_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), @@ -114,6 +117,7 @@ def _get_base_attributes(self) -> dict[str, Any]: def _get_common_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), @@ -174,6 +178,7 @@ 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), diff --git a/util/opentelemetry-util-genai/src/opentelemetry/util/genai/handler.py b/util/opentelemetry-util-genai/src/opentelemetry/util/genai/handler.py index 01cca074..e28d0e8d 100644 --- a/util/opentelemetry-util-genai/src/opentelemetry/util/genai/handler.py +++ b/util/opentelemetry-util-genai/src/opentelemetry/util/genai/handler.py @@ -415,6 +415,7 @@ def start_invoke_local_agent( def start_invoke_remote_agent( self, + provider: str, *, request_model: str | None = None, server_address: str | None = None, @@ -436,6 +437,7 @@ def start_invoke_remote_agent( self._metrics_recorder, self._logger, self._completion_hook, + provider=provider, span_kind=SpanKind.CLIENT, request_model=request_model, agent_name=agent_name, @@ -471,6 +473,7 @@ def invoke_local_agent( def invoke_remote_agent( self, + provider: str, *, request_model: str | None = None, server_address: str | None = None, @@ -492,6 +495,7 @@ def invoke_remote_agent( self._metrics_recorder, self._logger, self._completion_hook, + provider=provider, span_kind=SpanKind.CLIENT, request_model=request_model, agent_name=agent_name, From c0c43ad8aa77450e2e8f9c3214a07ff0a4a795e8 Mon Sep 17 00:00:00 2001 From: Liudmila Molkova Date: Wed, 17 Jun 2026 19:43:10 -0700 Subject: [PATCH 4/6] fix tests --- .../tests/test_handler_agent.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/util/opentelemetry-util-genai/tests/test_handler_agent.py b/util/opentelemetry-util-genai/tests/test_handler_agent.py index 790be003..431682f2 100644 --- a/util/opentelemetry-util-genai/tests/test_handler_agent.py +++ b/util/opentelemetry-util-genai/tests/test_handler_agent.py @@ -412,7 +412,7 @@ def setUp(self): self.handler = TelemetryHandler(tracer_provider=tracer_provider) def test_span_kind_client(self): - invocation = self.handler.invoke_remote_agent() + invocation = self.handler.invoke_remote_agent("openai") invocation.stop() assert ( self.span_exporter.get_finished_spans()[0].kind == SpanKind.CLIENT @@ -420,6 +420,7 @@ def test_span_kind_client(self): def test_server_attributes(self): invocation = self.handler.invoke_remote_agent( + "openai", server_address="api.openai.com", server_port=443, ) @@ -430,6 +431,7 @@ def test_server_attributes(self): def test_all_attributes(self): invocation = self.handler.invoke_remote_agent( + "openai", request_model="gpt-4", server_address="api.openai.com", server_port=443, @@ -452,7 +454,7 @@ def test_all_attributes(self): assert attrs[GenAI.GEN_AI_REQUEST_MODEL] == "gpt-4" def test_fail_sets_error_status(self): - invocation = self.handler.invoke_remote_agent() + invocation = self.handler.invoke_remote_agent("openai") invocation.fail(RuntimeError("remote agent crashed")) span = self.span_exporter.get_finished_spans()[0] @@ -461,6 +463,7 @@ def test_fail_sets_error_status(self): def test_context_manager_success(self): with self.handler.invoke_remote_agent( + "openai", request_model="gpt-4", server_address="api.openai.com", agent_name="CM Remote Agent", @@ -473,7 +476,7 @@ def test_context_manager_success(self): def test_context_manager_error(self): with self.assertRaises(ValueError): - with self.handler.invoke_remote_agent(): + with self.handler.invoke_remote_agent("openai"): raise ValueError("remote error") assert ( @@ -512,6 +515,7 @@ def get_description(self): handler = TelemetryHandler(tracer_provider=sampler_provider) invocation = handler.invoke_remote_agent( + "openai", request_model="agent-model", agent_name="Math Tutor", server_address="agent.example.com", @@ -583,6 +587,7 @@ def test_remote_agent_records_duration_with_server_attrs(self) -> None: meter_provider=self.meter_provider, ) invocation = handler.invoke_remote_agent( + "openai", request_model="model", server_address="agent.example.com", server_port=443, From e6365b8adb8f857bb69dd4e51f32ac4881724ddb Mon Sep 17 00:00:00 2001 From: Liudmila Molkova Date: Wed, 17 Jun 2026 19:47:03 -0700 Subject: [PATCH 5/6] fix tests --- .../src/opentelemetry/util/genai/_agent_invocation.py | 2 +- .../tests/test_handler_completion_hook.py | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/util/opentelemetry-util-genai/src/opentelemetry/util/genai/_agent_invocation.py b/util/opentelemetry-util-genai/src/opentelemetry/util/genai/_agent_invocation.py index 623b10b5..70fad50d 100644 --- a/util/opentelemetry-util-genai/src/opentelemetry/util/genai/_agent_invocation.py +++ b/util/opentelemetry-util-genai/src/opentelemetry/util/genai/_agent_invocation.py @@ -67,12 +67,12 @@ def __init__( self.request_model = request_model self.server_address = server_address self.server_port = server_port + self.provider = provider 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 - self.provider: str | None = provider self.conversation_id: str | None = None self.data_source_id: str | None = None diff --git a/util/opentelemetry-util-genai/tests/test_handler_completion_hook.py b/util/opentelemetry-util-genai/tests/test_handler_completion_hook.py index 76ab255a..300597b4 100644 --- a/util/opentelemetry-util-genai/tests/test_handler_completion_hook.py +++ b/util/opentelemetry-util-genai/tests/test_handler_completion_hook.py @@ -286,6 +286,7 @@ def test_remote_agent_hook_called_on_stop_with_messages(self): ] invocation = handler.invoke_remote_agent( + "openai", request_model="gpt-4", server_address="api.openai.com", server_port=443, @@ -307,7 +308,7 @@ def test_remote_agent_hook_called_on_fail(self): hook = MagicMock() handler = self._make_handler(hook) - invocation = handler.invoke_remote_agent() + invocation = handler.invoke_remote_agent("openai") invocation.fail(RuntimeError("remote agent crashed")) hook.on_completion.assert_called_once() @@ -319,7 +320,7 @@ def test_agent_hook_called_with_empty_messages_when_none_set(self): handler = self._make_handler(hook) handler.invoke_local_agent().stop() - handler.invoke_remote_agent().stop() + handler.invoke_remote_agent("openai").stop() for call in hook.on_completion.call_args_list: self.assertEqual(call.kwargs["inputs"], []) @@ -331,7 +332,7 @@ def test_agent_hook_not_called_when_not_set(self): # No hook — stop should not raise handler = self._make_handler() handler.invoke_local_agent().stop() - handler.invoke_remote_agent().stop() + handler.invoke_remote_agent("openai").stop() def test_should_capture_content_false_by_default(self): for env_var, expected_content_capture in [ From 65d6de9bc93f041bbb4d9b4bdaa98f825fcbf339 Mon Sep 17 00:00:00 2001 From: Liudmila Molkova Date: Wed, 17 Jun 2026 19:51:25 -0700 Subject: [PATCH 6/6] fix tests --- util/opentelemetry-util-genai/tests/test_handler_agent.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/util/opentelemetry-util-genai/tests/test_handler_agent.py b/util/opentelemetry-util-genai/tests/test_handler_agent.py index 431682f2..338786d1 100644 --- a/util/opentelemetry-util-genai/tests/test_handler_agent.py +++ b/util/opentelemetry-util-genai/tests/test_handler_agent.py @@ -515,7 +515,7 @@ def get_description(self): handler = TelemetryHandler(tracer_provider=sampler_provider) invocation = handler.invoke_remote_agent( - "openai", + "test-provider", request_model="agent-model", agent_name="Math Tutor", server_address="agent.example.com", @@ -526,6 +526,9 @@ def get_description(self): assert ( captured_attributes[GenAI.GEN_AI_OPERATION_NAME] == "invoke_agent" ) + assert ( + captured_attributes[GenAI.GEN_AI_PROVIDER_NAME] == "test-provider" + ) assert captured_attributes[GenAI.GEN_AI_REQUEST_MODEL] == "agent-model" assert captured_attributes[GenAI.GEN_AI_AGENT_NAME] == "Math Tutor" assert ( @@ -587,7 +590,7 @@ def test_remote_agent_records_duration_with_server_attrs(self) -> None: meter_provider=self.meter_provider, ) invocation = handler.invoke_remote_agent( - "openai", + "prov", request_model="model", server_address="agent.example.com", server_port=443,