Skip to content

Commit 17c6d96

Browse files
committed
Merge remote-tracking branch 'origin/main' into fix/stream-wrapper-getattr-headers
2 parents a2fe60e + 0ea0849 commit 17c6d96

10 files changed

Lines changed: 78 additions & 56 deletions

File tree

docs/semconv-reference.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,7 @@ Note: Including high-cardinality values into metrics association-properties may
282282
| Attribute | Type | Description | OTel Semconv |
283283
|---|---|---|---|
284284
| `gen_ai.agent.name` | string | Human-readable agent name | [Standard](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/gen-ai/gen-ai-agent-spans.md) |
285-
| `gen_ai.agent.id` | string | Unique agent identifier | [Standard](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/gen-ai/gen-ai-agent-spans.md) |
285+
| `gen_ai.agent.id` | string | Unique agent identifier. **Span-only** — excluded from metric dimensions due to unbounded per-invocation cardinality (value equals the span ID). | [Standard](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/gen-ai/gen-ai-agent-spans.md) |
286286
| `gen_ai.agent.description` | string | Agent description | [Standard](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/gen-ai/gen-ai-agent-spans.md) |
287287
| `gen_ai.agent.version` | string | Agent version | [Standard](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/gen-ai/gen-ai-agent-spans.md) |
288288
| `gen_ai.agent.tools` | string[] | Available tool names | **SDOT extension** |
@@ -555,7 +555,7 @@ These attributes follow the current [OTel Gen AI semantic conventions](https://g
555555
| Category | Attributes |
556556
|---|---|
557557
| **Core** | `gen_ai.operation.name`, `gen_ai.provider.name`, `gen_ai.request.model`, `gen_ai.response.model`, `gen_ai.response.id`, `gen_ai.output.type` |
558-
| **Agent** | `gen_ai.agent.name`, `gen_ai.agent.id`, `gen_ai.agent.description`, `gen_ai.agent.version` |
558+
| **Agent** | `gen_ai.agent.name`, `gen_ai.agent.description`, `gen_ai.agent.version` (`gen_ai.agent.id` is **span-only** — see note above) |
559559
| **Workflow** | `gen_ai.workflow.name` |
560560
| **Conversation** | `gen_ai.conversation.id`, `gen_ai.data_source.id` |
561561
| **Tokens** | `gen_ai.usage.input_tokens`, `gen_ai.usage.output_tokens`, `gen_ai.usage.cache_creation.input_tokens`, `gen_ai.usage.cache_read.input_tokens` |

instrumentation-genai/opentelemetry-instrumentation-openai-agents-v2/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
44

55
## [Unreleased]
66

7+
### Fixed
8+
- Corrected package name in READMEs from `splunk-otel-instrumentation-openai-agents-v2` to `splunk-otel-instrumentation-openai-agents` to match `pyproject.toml`.
9+
- Updated README env vars to align with upstream `OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT` values (`span_only`, `event_only`, `span_and_event`), removed deprecated `CAPTURE_MESSAGE_CONTENT_MODE` flag.
10+
711
### Changed
812

913
- **Always populate messages on Python objects**`_build_content_payload()` now always captures messages on `LLMInvocation` regardless of `OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT`. The emitter layer controls what reaches telemetry, enabling evaluators to access full content even in `NO_CONTENT` mode.

instrumentation-genai/opentelemetry-instrumentation-openai-agents-v2/README.md

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# OpenTelemetry OpenAI Agents Instrumentation (Alpha)
22

3-
[![PyPI](https://badge.fury.io/py/splunk-otel-instrumentation-openai-agents-v2.svg)](https://pypi.org/project/splunk-otel-instrumentation-openai-agents-v2/)
3+
[![PyPI](https://badge.fury.io/py/splunk-otel-instrumentation-openai-agents.svg)](https://pypi.org/project/splunk-otel-instrumentation-openai-agents/)
44

55
This package provides OpenTelemetry instrumentation for
66
[OpenAI Agents SDK](https://github.com/openai/openai-agents-python),
@@ -13,7 +13,7 @@ Status: Alpha (APIs and produced telemetry are subject to change).
1313
## Installation
1414

1515
```bash
16-
pip install splunk-otel-instrumentation-openai-agents-v2
16+
pip install splunk-otel-instrumentation-openai-agents
1717
```
1818

1919
## Quick Start
@@ -76,19 +76,35 @@ rich attributes about the operation.
7676

7777
## Configuration
7878

79-
### Environment Variables
79+
### Enabling message content
80+
81+
Message content such as prompts, completions, tool arguments, and return values
82+
are not captured by default. To enable content capturing, set the environment variable
83+
`OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT` to one of the following values:
84+
85+
- `true` — Legacy. Treated as `span_and_event` for backward compatibility.
86+
- `span_only` — Capture content on span attributes only.
87+
- `event_only` — Capture content on event attributes only.
88+
- `span_and_event` — Capture content on both span and event attributes.
8089

8190
```bash
82-
# Capture message content (disabled by default)
83-
export OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=true
91+
export OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=span_and_event
92+
```
8493

85-
# Content capture mode
86-
export OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT_MODE=SPAN_AND_EVENT
94+
### Other environment variables
95+
96+
```bash
97+
# Capture tool/function definitions on LLM spans (disabled by default)
98+
export OTEL_INSTRUMENTATION_GENAI_CAPTURE_TOOL_DEFINITIONS=true
8799

88100
# Disable metrics
89101
export OTEL_INSTRUMENTATION_OPENAI_AGENTS_CAPTURE_METRICS=false
90102
```
91103

104+
> **Backward compatibility**: The deprecated
105+
> `OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT_MODE` variable is still
106+
> honored when the primary variable is set to a truthy value (`true`/`1`/`yes`/`on`).
107+
92108
### Instrumentation Options
93109

94110
```python

instrumentation-genai/opentelemetry-instrumentation-openai-agents-v2/examples/travel-planner/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ Install the Splunk Distribution of OpenTelemetry packages:
3737

3838
```bash
3939
# Core instrumentation packages
40-
pip install splunk-otel-instrumentation-openai-agents-v2
40+
pip install splunk-otel-instrumentation-openai-agents
4141
pip install splunk-otel-util-genai
4242

4343
# Splunk-specific emitters (required for Splunk Observability Cloud)

instrumentation-genai/opentelemetry-instrumentation-openai-v2/CHANGELOG.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2828
- Add `gen_ai.request.stream` attribute for streaming requests
2929
- Add `gen_ai.response.time_to_first_chunk` attribute and metric for streaming requests
3030

31+
### Fixed
32+
33+
- Fix PyPI badge, install command, and references in README.rst to use correct
34+
`splunk-otel-instrumentation-openai` package name instead of upstream
35+
- Fix project URLs in pyproject.toml to point to SDOT repo (`signalfx/splunk-otel-python-contrib`)
36+
3137
### Changed
3238

3339
- **Always populate messages and tool arguments on Python objects**`input_messages`, `output_messages`, and tool call `arguments` are now always set on `LLMInvocation`/`ToolCall` regardless of `OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT`. The emitter layer controls what reaches telemetry, enabling evaluators to access full content even in `NO_CONTENT` mode.
@@ -47,7 +53,7 @@ Initial release of `splunk-otel-instrumentation-openai` package.
4753
- Update tool call handling
4854
([#135](https://github.com/signalfx/splunk-otel-python-contrib/pull/135))
4955
- Add suppression key handling
50-
([#155](https://github.com/signalfx/splunk-otel-python-contrib/pull/135))
56+
([#155](https://github.com/signalfx/splunk-otel-python-contrib/pull/155))
5157
- Move events/logs and metrics to handler-based emitters
5258
([#158](https://github.com/signalfx/splunk-otel-python-contrib/pull/158))
5359
- Fix service tier attribute names: use `GEN_AI_OPENAI_REQUEST_SERVICE_TIER` for request

instrumentation-genai/opentelemetry-instrumentation-openai-v2/README.rst

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ OpenTelemetry OpenAI Instrumentation
33

44
|pypi|
55

6-
.. |pypi| image:: https://badge.fury.io/py/opentelemetry-instrumentation-openai-v2.svg
7-
:target: https://pypi.org/project/opentelemetry-instrumentation-openai-v2/
6+
.. |pypi| image:: https://badge.fury.io/py/splunk-otel-instrumentation-openai.svg
7+
:target: https://pypi.org/project/splunk-otel-instrumentation-openai/
88

99
This library allows tracing LLM requests and logging of messages made by the
1010
`OpenAI Python API library <https://pypi.org/project/openai/>`_. It also captures
@@ -40,7 +40,7 @@ If your application is already instrumented with OpenTelemetry, add this
4040
package to your requirements.
4141
::
4242

43-
pip install opentelemetry-instrumentation-openai-v2
43+
pip install splunk-otel-instrumentation-openai
4444

4545
If you don't have an OpenAI application, yet, try our `examples <examples>`_
4646
which only need a valid OpenAI API key.
@@ -89,6 +89,24 @@ Message content such as the contents of the prompt, completion, function argumen
8989
are not captured by default. To capture message content as log events, set the environment variable
9090
`OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT` to `true`.
9191

92+
See the `upstream OpenTelemetry documentation <https://github.com/open-telemetry/opentelemetry-python-contrib/tree/main/instrumentation-genai/opentelemetry-instrumentation-openai-v2#enabling-message-content>`_ for more details.
93+
94+
Suppressing nested instrumentation
95+
***********************************
96+
97+
When using multiple instrumentations together (e.g., LangChain + OpenAI), the higher-level
98+
instrumentation automatically sets ``SUPPRESS_LANGUAGE_MODEL_INSTRUMENTATION_KEY`` in the
99+
OpenTelemetry context to prevent duplicate spans for the same underlying LLM call.
100+
101+
This is handled transparently — **no user configuration is needed**. For example, when
102+
LangChain instrumentation is active alongside OpenAI instrumentation, you will see
103+
LangChain spans without redundant nested OpenAI spans.
104+
105+
.. note::
106+
107+
This is not an environment variable and cannot be configured for zero-code instrumentation.
108+
It is a context key managed programmatically by instrumentation libraries.
109+
92110
Uninstrument
93111
************
94112

@@ -106,7 +124,7 @@ To uninstrument clients, call the uninstrument method:
106124
107125
References
108126
----------
109-
* `OpenTelemetry OpenAI Instrumentation <https://opentelemetry-python-contrib.readthedocs.io/en/latest/instrumentation-genai/openai.html>`_
127+
* `Splunk OpenTelemetry OpenAI Instrumentation <https://github.com/signalfx/splunk-otel-python-contrib/tree/main/instrumentation-genai/opentelemetry-instrumentation-openai-v2>`_
110128
* `OpenTelemetry Project <https://opentelemetry.io/>`_
111129
* `OpenTelemetry Python Examples <https://github.com/open-telemetry/opentelemetry-python/tree/main/docs/examples>`_
112130

instrumentation-genai/opentelemetry-instrumentation-openai-v2/pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ instruments = [
3535
openai = "opentelemetry.instrumentation.openai_v2:OpenAIInstrumentor"
3636

3737
[project.urls]
38-
Homepage = "https://github.com/open-telemetry/opentelemetry-python-contrib/tree/main/instrumentation-genai/opentelemetry-instrumentation-openai-v2"
39-
Repository = "https://github.com/open-telemetry/opentelemetry-python-contrib"
38+
Homepage = "https://github.com/signalfx/splunk-otel-python-contrib/tree/main/instrumentation-genai/opentelemetry-instrumentation-openai-v2"
39+
Repository = "https://github.com/signalfx/splunk-otel-python-contrib"
4040

4141
[tool.hatch.version]
4242
path = "src/opentelemetry/instrumentation/openai_v2/version.py"

util/opentelemetry-util-genai/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ All notable changes to this repository are documented in this file.
1010

1111
### Changed
1212

13+
- **`gen_ai.agent.id` removed from all GenAI metric dimensions** — The attribute was set to the span ID (unique per invocation), causing unbounded metric cardinality. It remains available on spans where per-invocation identity is expected and useful. `gen_ai.agent.name` is unaffected.
1314
- **`OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT` now accepts mode values directly** — Accepts `NO_CONTENT`, `SPAN_ONLY`, `EVENT_ONLY`, `SPAN_AND_EVENT` in addition to legacy `true`/`false`. Aligns with upstream OpenTelemetry GenAI conventions.
1415
- **Removed experimental mode gating** — Content capture no longer requires an experimental stability flag.
1516

util/opentelemetry-util-genai/src/opentelemetry/util/genai/emitters/metrics.py

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,6 @@ def on_end(self, obj: Any) -> None:
105105
metric_attrs[GenAI.GEN_AI_AGENT_NAME] = (
106106
llm_invocation.agent_name
107107
)
108-
if llm_invocation.agent_id:
109-
metric_attrs[GenAI.GEN_AI_AGENT_ID] = llm_invocation.agent_id
110108

111109
# Add session context if configured
112110
metric_attrs.update(get_context_metric_attributes(llm_invocation))
@@ -170,10 +168,6 @@ def on_end(self, obj: Any) -> None:
170168
metric_attrs[GenAI.GEN_AI_AGENT_NAME] = (
171169
embedding_invocation.agent_name
172170
)
173-
if embedding_invocation.agent_id:
174-
metric_attrs[GenAI.GEN_AI_AGENT_ID] = (
175-
embedding_invocation.agent_id
176-
)
177171

178172
# Add session context if configured
179173
metric_attrs.update(
@@ -224,8 +218,6 @@ def on_error(self, error: Error, obj: Any) -> None:
224218
metric_attrs[GenAI.GEN_AI_AGENT_NAME] = (
225219
llm_invocation.agent_name
226220
)
227-
if llm_invocation.agent_id:
228-
metric_attrs[GenAI.GEN_AI_AGENT_ID] = llm_invocation.agent_id
229221
if getattr(error, "type", None) is not None:
230222
metric_attrs[ErrorAttributes.ERROR_TYPE] = (
231223
error.type.__qualname__
@@ -252,8 +244,6 @@ def on_error(self, error: Error, obj: Any) -> None:
252244
metric_attrs[GenAI.GEN_AI_TOOL_NAME] = obj.name
253245
if obj.agent_name:
254246
metric_attrs[GenAI.GEN_AI_AGENT_NAME] = obj.agent_name
255-
if obj.agent_id:
256-
metric_attrs[GenAI.GEN_AI_AGENT_ID] = obj.agent_id
257247
if getattr(error, "type", None) is not None:
258248
metric_attrs[ErrorAttributes.ERROR_TYPE] = (
259249
error.type.__qualname__
@@ -289,8 +279,6 @@ def on_error(self, error: Error, obj: Any) -> None:
289279
metric_attrs[GenAI.GEN_AI_AGENT_NAME] = (
290280
tool_invocation.agent_name
291281
)
292-
if tool_invocation.agent_id:
293-
metric_attrs[GenAI.GEN_AI_AGENT_ID] = tool_invocation.agent_id
294282
if getattr(error, "type", None) is not None:
295283
metric_attrs[ErrorAttributes.ERROR_TYPE] = (
296284
error.type.__qualname__
@@ -319,10 +307,6 @@ def on_error(self, error: Error, obj: Any) -> None:
319307
metric_attrs[GenAI.GEN_AI_AGENT_NAME] = (
320308
embedding_invocation.agent_name
321309
)
322-
if embedding_invocation.agent_id:
323-
metric_attrs[GenAI.GEN_AI_AGENT_ID] = (
324-
embedding_invocation.agent_id
325-
)
326310
if getattr(error, "type", None) is not None:
327311
metric_attrs[ErrorAttributes.ERROR_TYPE] = (
328312
error.type.__qualname__
@@ -395,11 +379,6 @@ def _record_agent_metrics(
395379
metric_attrs = {
396380
GenAI.GEN_AI_OPERATION_NAME: agent.operation,
397381
GenAI.GEN_AI_AGENT_NAME: agent.name,
398-
GenAI.GEN_AI_AGENT_ID: (
399-
f"{agent.span_id:016x}"
400-
if agent.span_id is not None
401-
else str(id(agent))
402-
),
403382
}
404383
if agent.agent_type:
405384
metric_attrs["gen_ai.agent.type"] = agent.agent_type
@@ -437,8 +416,6 @@ def _record_retrieval_metrics(
437416
# Add agent context if available
438417
if retrieval.agent_name:
439418
metric_attrs[GenAI.GEN_AI_AGENT_NAME] = retrieval.agent_name
440-
if retrieval.agent_id:
441-
metric_attrs[GenAI.GEN_AI_AGENT_ID] = retrieval.agent_id
442419
# Add error type if present
443420
if error is not None and getattr(error, "type", None) is not None:
444421
metric_attrs[ErrorAttributes.ERROR_TYPE] = error.type.__qualname__
@@ -467,8 +444,6 @@ def _record_execute_tool_metrics(self, tool: ToolCall) -> None:
467444
metric_attrs[GenAI.GEN_AI_TOOL_NAME] = tool.name
468445
if tool.agent_name:
469446
metric_attrs[GenAI.GEN_AI_AGENT_NAME] = tool.agent_name
470-
if tool.agent_id:
471-
metric_attrs[GenAI.GEN_AI_AGENT_ID] = tool.agent_id
472447
metric_attrs.update(get_context_metric_attributes(tool))
473448
_record_duration(
474449
self._duration_histogram,

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

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -260,8 +260,7 @@ def test_llm_metrics_include_agent_identity_when_present(self):
260260
agent_id="agent-123",
261261
)
262262
metrics_list = self._collect_metrics()
263-
# Collect token usage and duration datapoints and assert agent attrs present
264-
# We flatten all datapoints for easier searching
263+
# agent.name (bounded cardinality) is kept; agent.id (per-invocation) is excluded
265264
found_token_agent = False
266265
found_duration_agent = False
267266
for metric in metrics_list:
@@ -270,28 +269,29 @@ def test_llm_metrics_include_agent_identity_when_present(self):
270269
"gen_ai.client.operation.duration",
271270
):
272271
continue
273-
# metric.data.data_points for Histogram-like metrics
274272
data = getattr(metric, "data", None)
275273
if not data:
276274
continue
277275
data_points = getattr(data, "data_points", []) or []
278276
for dp in data_points:
279277
attrs = getattr(dp, "attributes", {}) or {}
280-
if (
281-
attrs.get("gen_ai.agent.name") == "router_agent"
282-
and attrs.get("gen_ai.agent.id") == "agent-123"
283-
):
278+
if attrs.get("gen_ai.agent.name") == "router_agent":
279+
self.assertNotIn(
280+
"gen_ai.agent.id",
281+
attrs,
282+
"gen_ai.agent.id must not appear on metric data points",
283+
)
284284
if metric.name == "gen_ai.client.token.usage":
285285
found_token_agent = True
286286
if metric.name == "gen_ai.client.operation.duration":
287287
found_duration_agent = True
288288
self.assertTrue(
289289
found_token_agent,
290-
"Expected token usage metric datapoint to include agent.name and agent.id",
290+
"Expected token usage metric datapoint to include agent.name",
291291
)
292292
self.assertTrue(
293293
found_duration_agent,
294-
"Expected operation duration metric datapoint to include agent.name and agent.id",
294+
"Expected operation duration metric datapoint to include agent.name",
295295
)
296296

297297
def test_llm_metrics_include_server_attributes(self):
@@ -391,15 +391,17 @@ def test_llm_metrics_inherit_agent_identity_from_context(self):
391391
continue
392392
for dp in getattr(data, "data_points", []) or []:
393393
attrs = getattr(dp, "attributes", {}) or {}
394-
if (
395-
attrs.get("gen_ai.agent.name") == "context_agent"
396-
and attrs.get("gen_ai.agent.id") == "agent-123"
397-
):
394+
if attrs.get("gen_ai.agent.name") == "context_agent":
395+
self.assertNotIn(
396+
"gen_ai.agent.id",
397+
attrs,
398+
"gen_ai.agent.id must not appear on metric data points",
399+
)
398400
inherited = True
399401
break
400402
self.assertTrue(
401403
inherited,
402-
"Expected metrics to inherit agent identity from active agent context",
404+
"Expected metrics to inherit agent.name from active agent context",
403405
)
404406

405407
def test_llm_duration_metric_includes_error_type_on_failure(self):

0 commit comments

Comments
 (0)