Skip to content

Commit 2a2750e

Browse files
authored
Merge branch 'main' into semconv-celery
2 parents 16e04ef + 2ecc2d2 commit 2a2750e

File tree

8 files changed

+268
-5
lines changed

8 files changed

+268
-5
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3232

3333
### Added
3434

35+
- `opentelemetry-util-genai` Add a utility to parse the `OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT` environment variable.
36+
Add `gen_ai_latest_experimental` as a new value to the Sem Conv stability flag ([#3716](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3716)).
3537
- `opentelemetry-instrumentation-confluent-kafka` Add support for confluent-kafka <=2.11.0
3638
([#3685](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3685))
3739
- `opentelemetry-instrumentation-system-metrics`: Add `cpython.gc.collected_objects` and `cpython.gc.uncollectable_objects` metrics

opentelemetry-instrumentation/src/opentelemetry/instrumentation/_semconv.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -162,9 +162,10 @@
162162
OTEL_SEMCONV_STABILITY_OPT_IN = "OTEL_SEMCONV_STABILITY_OPT_IN"
163163

164164

165-
class _OpenTelemetryStabilitySignalType:
165+
class _OpenTelemetryStabilitySignalType(Enum):
166166
HTTP = "http"
167167
DATABASE = "database"
168+
GEN_AI = "gen_ai"
168169

169170

170171
class _StabilityMode(Enum):
@@ -173,6 +174,7 @@ class _StabilityMode(Enum):
173174
HTTP_DUP = "http/dup"
174175
DATABASE = "database"
175176
DATABASE_DUP = "database/dup"
177+
GEN_AI_LATEST_EXPERIMENTAL = "gen_ai_latest_experimental"
176178

177179

178180
def _report_new(mode: _StabilityMode):
@@ -195,14 +197,15 @@ def _initialize(cls):
195197
return
196198

197199
# Users can pass in comma delimited string for opt-in options
198-
# Only values for http and database stability are supported for now
200+
# Only values for http, gen ai, and database stability are supported for now
199201
opt_in = os.environ.get(OTEL_SEMCONV_STABILITY_OPT_IN)
200202

201203
if not opt_in:
202204
# early return in case of default
203205
cls._OTEL_SEMCONV_STABILITY_SIGNAL_MAPPING = {
204206
_OpenTelemetryStabilitySignalType.HTTP: _StabilityMode.DEFAULT,
205207
_OpenTelemetryStabilitySignalType.DATABASE: _StabilityMode.DEFAULT,
208+
_OpenTelemetryStabilitySignalType.GEN_AI: _StabilityMode.DEFAULT,
206209
}
207210
cls._initialized = True
208211
return
@@ -215,14 +218,21 @@ def _initialize(cls):
215218
opt_in_list, _StabilityMode.HTTP, _StabilityMode.HTTP_DUP
216219
)
217220

221+
cls._OTEL_SEMCONV_STABILITY_SIGNAL_MAPPING[
222+
_OpenTelemetryStabilitySignalType.GEN_AI
223+
] = cls._filter_mode(
224+
opt_in_list,
225+
_StabilityMode.DEFAULT,
226+
_StabilityMode.GEN_AI_LATEST_EXPERIMENTAL,
227+
)
228+
218229
cls._OTEL_SEMCONV_STABILITY_SIGNAL_MAPPING[
219230
_OpenTelemetryStabilitySignalType.DATABASE
220231
] = cls._filter_mode(
221232
opt_in_list,
222233
_StabilityMode.DATABASE,
223234
_StabilityMode.DATABASE_DUP,
224235
)
225-
226236
cls._initialized = True
227237

228238
@staticmethod

opentelemetry-instrumentation/tests/test_semconv.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ def test_default_mode(self):
5454
),
5555
_StabilityMode.DEFAULT,
5656
)
57+
self.assertEqual(
58+
_OpenTelemetrySemanticConventionStability._get_opentelemetry_stability_opt_in_mode(
59+
_OpenTelemetryStabilitySignalType.GEN_AI
60+
),
61+
_StabilityMode.DEFAULT,
62+
)
5763

5864
@stability_mode("http")
5965
def test_http_stable_mode(self):
@@ -91,7 +97,16 @@ def test_database_dup_mode(self):
9197
_StabilityMode.DATABASE_DUP,
9298
)
9399

94-
@stability_mode("database,http")
100+
@stability_mode("gen_ai_latest_experimental")
101+
def test_genai_latest_experimental(self):
102+
self.assertEqual(
103+
_OpenTelemetrySemanticConventionStability._get_opentelemetry_stability_opt_in_mode(
104+
_OpenTelemetryStabilitySignalType.GEN_AI
105+
),
106+
_StabilityMode.GEN_AI_LATEST_EXPERIMENTAL,
107+
)
108+
109+
@stability_mode("database,http,gen_ai_latest_experimental")
95110
def test_multiple_stability_database_http_modes(self):
96111
self.assertEqual(
97112
_OpenTelemetrySemanticConventionStability._get_opentelemetry_stability_opt_in_mode(
@@ -105,6 +120,12 @@ def test_multiple_stability_database_http_modes(self):
105120
),
106121
_StabilityMode.HTTP,
107122
)
123+
self.assertEqual(
124+
_OpenTelemetrySemanticConventionStability._get_opentelemetry_stability_opt_in_mode(
125+
_OpenTelemetryStabilitySignalType.GEN_AI
126+
),
127+
_StabilityMode.GEN_AI_LATEST_EXPERIMENTAL,
128+
)
108129

109130
@stability_mode("database,http/dup")
110131
def test_multiple_stability_database_http_dup_modes(self):

util/opentelemetry-util-genai/CHANGELOG.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,7 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8-
## Unreleased
8+
## Unreleased
9+
10+
Repurpose the `OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT` environment variable when GEN AI stability mode is set to `gen_ai_latest_experimental`,
11+
to take on an enum (`NO_CONTENT/SPAN_ONLY/EVENT_ONLY/SPAN_AND_EVENT`) instead of a boolean. Add a utility function to help parse this environment variable.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Copyright The OpenTelemetry Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT = (
16+
"OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT"
17+
)
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# Copyright The OpenTelemetry Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
16+
from dataclasses import dataclass
17+
from enum import Enum
18+
from typing import Any, Literal, Optional, Union
19+
20+
21+
class ContentCapturingMode(Enum):
22+
# Do not capture content (default).
23+
NO_CONTENT = 0
24+
# Only capture content in spans.
25+
SPAN_ONLY = 1
26+
# Only capture content in events.
27+
EVENT_ONLY = 2
28+
# Capture content in both spans and events.
29+
SPAN_AND_EVENT = 3
30+
31+
32+
@dataclass()
33+
class ToolCall:
34+
arguments: Any
35+
name: str
36+
id: Optional[str]
37+
type: Literal["tool_call"] = "tool_call"
38+
39+
40+
@dataclass()
41+
class ToolCallResponse:
42+
response: Any
43+
id: Optional[str]
44+
type: Literal["tool_call_response"] = "tool_call_response"
45+
46+
47+
FinishReason = Literal[
48+
"content_filter", "error", "length", "stop", "tool_calls"
49+
]
50+
51+
52+
@dataclass()
53+
class Text:
54+
content: str
55+
type: Literal["text"] = "text"
56+
57+
58+
MessagePart = Union[Text, ToolCall, ToolCallResponse, Any]
59+
60+
61+
@dataclass()
62+
class InputMessage:
63+
role: str
64+
parts: list[MessagePart]
65+
66+
67+
@dataclass()
68+
class OutputMessage:
69+
role: str
70+
parts: list[MessagePart]
71+
finish_reason: Union[str, FinishReason]
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# Copyright The OpenTelemetry Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import logging
16+
import os
17+
18+
from opentelemetry.instrumentation._semconv import (
19+
_OpenTelemetrySemanticConventionStability,
20+
_OpenTelemetryStabilitySignalType,
21+
_StabilityMode,
22+
)
23+
from opentelemetry.util.genai.environment_variables import (
24+
OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT,
25+
)
26+
from opentelemetry.util.genai.types import ContentCapturingMode
27+
28+
logger = logging.getLogger(__name__)
29+
30+
31+
def get_content_capturing_mode() -> ContentCapturingMode:
32+
"""This function should not be called when GEN_AI stability mode is set to DEFAULT.
33+
34+
When the GEN_AI stability mode is DEFAULT this function will raise a ValueError -- see the code below."""
35+
envvar = os.environ.get(OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT)
36+
if (
37+
_OpenTelemetrySemanticConventionStability._get_opentelemetry_stability_opt_in_mode(
38+
_OpenTelemetryStabilitySignalType.GEN_AI,
39+
)
40+
== _StabilityMode.DEFAULT
41+
):
42+
raise ValueError(
43+
"This function should never be called when StabilityMode is default."
44+
)
45+
if not envvar:
46+
return ContentCapturingMode.NO_CONTENT
47+
try:
48+
return ContentCapturingMode[envvar.upper()]
49+
except KeyError:
50+
logger.warning(
51+
"%s is not a valid option for `%s` environment variable. Must be one of %s. Defaulting to `NO_CONTENT`.",
52+
envvar,
53+
OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT,
54+
", ".join(e.name for e in ContentCapturingMode),
55+
)
56+
return ContentCapturingMode.NO_CONTENT
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# Copyright The OpenTelemetry Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import os
16+
import unittest
17+
from unittest.mock import patch
18+
19+
from opentelemetry.instrumentation._semconv import (
20+
OTEL_SEMCONV_STABILITY_OPT_IN,
21+
_OpenTelemetrySemanticConventionStability,
22+
)
23+
from opentelemetry.util.genai.environment_variables import (
24+
OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT,
25+
)
26+
from opentelemetry.util.genai.types import ContentCapturingMode
27+
from opentelemetry.util.genai.utils import get_content_capturing_mode
28+
29+
30+
def patch_env_vars(stability_mode, content_capturing):
31+
def decorator(test_case):
32+
@patch.dict(
33+
os.environ,
34+
{
35+
OTEL_SEMCONV_STABILITY_OPT_IN: stability_mode,
36+
OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT: content_capturing,
37+
},
38+
)
39+
def wrapper(*args, **kwargs):
40+
# Reset state.
41+
_OpenTelemetrySemanticConventionStability._initialized = False
42+
_OpenTelemetrySemanticConventionStability._initialize()
43+
return test_case(*args, **kwargs)
44+
45+
return wrapper
46+
47+
return decorator
48+
49+
50+
class TestVersion(unittest.TestCase):
51+
@patch_env_vars(
52+
stability_mode="gen_ai_latest_experimental",
53+
content_capturing="SPAN_ONLY",
54+
)
55+
def test_get_content_capturing_mode_parses_valid_envvar(self): # pylint: disable=no-self-use
56+
assert get_content_capturing_mode() == ContentCapturingMode.SPAN_ONLY
57+
58+
@patch_env_vars(
59+
stability_mode="gen_ai_latest_experimental", content_capturing=""
60+
)
61+
def test_empty_content_capturing_envvar(self): # pylint: disable=no-self-use
62+
assert get_content_capturing_mode() == ContentCapturingMode.NO_CONTENT
63+
64+
@patch_env_vars(stability_mode="default", content_capturing="True")
65+
def test_get_content_capturing_mode_raises_exception_when_semconv_stability_default(
66+
self,
67+
): # pylint: disable=no-self-use
68+
with self.assertRaises(ValueError):
69+
get_content_capturing_mode()
70+
71+
@patch_env_vars(
72+
stability_mode="gen_ai_latest_experimental",
73+
content_capturing="INVALID_VALUE",
74+
)
75+
def test_get_content_capturing_mode_raises_exception_on_invalid_envvar(
76+
self,
77+
): # pylint: disable=no-self-use
78+
with self.assertLogs(level="WARNING") as cm:
79+
assert (
80+
get_content_capturing_mode() == ContentCapturingMode.NO_CONTENT
81+
)
82+
self.assertEqual(len(cm.output), 1)
83+
self.assertIn("INVALID_VALUE is not a valid option for ", cm.output[0])

0 commit comments

Comments
 (0)