Add `gen_ai_latest_experimental` to the Sem Conv stability flag. Add `ContentCapturingMode` enum (#3716)
* initial commit * lint * Fix tests * Fix typecheck * Update opentelemetry-instrumentation/src/opentelemetry/instrumentation/_semconv.py Co-authored-by: Emídio Neto <9735060+emdneto@users.noreply.github.com> * Update util/opentelemetry-util-genai/src/opentelemetry/util/genai/utils.py Co-authored-by: Riccardo Magliocchetti <riccardo.magliocchetti@gmail.com> * Use ValueError * Rename enum * Update util/opentelemetry-util-genai/tests/test_utils.py Co-authored-by: Aaron Abbott <aaronabbott@google.com> * Update util/opentelemetry-util-genai/tests/test_utils.py Co-authored-by: Aaron Abbott <aaronabbott@google.com> * Update util/opentelemetry-util-genai/tests/test_utils.py Co-authored-by: Aaron Abbott <aaronabbott@google.com> * Update util/opentelemetry-util-genai/tests/test_utils.py Co-authored-by: Aaron Abbott <aaronabbott@google.com> * Default env var to NO_CONTENT when invalid envvar * Address comments * Address comment * Fix typecheck * don't change typecheck, * Fix linter * Address comments * Fix order of args.. --------- Co-authored-by: Emídio Neto <9735060+emdneto@users.noreply.github.com> Co-authored-by: Riccardo Magliocchetti <riccardo.magliocchetti@gmail.com> Co-authored-by: Aaron Abbott <aaronabbott@google.com>
This commit is contained in:
parent
b1b9505043
commit
2ecc2d2404
|
|
@ -32,6 +32,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
|
- `opentelemetry-util-genai` Add a utility to parse the `OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT` environment variable.
|
||||||
|
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)).
|
||||||
- `opentelemetry-instrumentation-confluent-kafka` Add support for confluent-kafka <=2.11.0
|
- `opentelemetry-instrumentation-confluent-kafka` Add support for confluent-kafka <=2.11.0
|
||||||
([#3685](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3685))
|
([#3685](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3685))
|
||||||
- `opentelemetry-instrumentation-system-metrics`: Add `cpython.gc.collected_objects` and `cpython.gc.uncollectable_objects` metrics
|
- `opentelemetry-instrumentation-system-metrics`: Add `cpython.gc.collected_objects` and `cpython.gc.uncollectable_objects` metrics
|
||||||
|
|
|
||||||
|
|
@ -162,9 +162,10 @@ _server_active_requests_count_attrs_new = [
|
||||||
OTEL_SEMCONV_STABILITY_OPT_IN = "OTEL_SEMCONV_STABILITY_OPT_IN"
|
OTEL_SEMCONV_STABILITY_OPT_IN = "OTEL_SEMCONV_STABILITY_OPT_IN"
|
||||||
|
|
||||||
|
|
||||||
class _OpenTelemetryStabilitySignalType:
|
class _OpenTelemetryStabilitySignalType(Enum):
|
||||||
HTTP = "http"
|
HTTP = "http"
|
||||||
DATABASE = "database"
|
DATABASE = "database"
|
||||||
|
GEN_AI = "gen_ai"
|
||||||
|
|
||||||
|
|
||||||
class _StabilityMode(Enum):
|
class _StabilityMode(Enum):
|
||||||
|
|
@ -173,6 +174,7 @@ class _StabilityMode(Enum):
|
||||||
HTTP_DUP = "http/dup"
|
HTTP_DUP = "http/dup"
|
||||||
DATABASE = "database"
|
DATABASE = "database"
|
||||||
DATABASE_DUP = "database/dup"
|
DATABASE_DUP = "database/dup"
|
||||||
|
GEN_AI_LATEST_EXPERIMENTAL = "gen_ai_latest_experimental"
|
||||||
|
|
||||||
|
|
||||||
def _report_new(mode: _StabilityMode):
|
def _report_new(mode: _StabilityMode):
|
||||||
|
|
@ -195,7 +197,7 @@ class _OpenTelemetrySemanticConventionStability:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Users can pass in comma delimited string for opt-in options
|
# Users can pass in comma delimited string for opt-in options
|
||||||
# Only values for http and database stability are supported for now
|
# Only values for http, gen ai, and database stability are supported for now
|
||||||
opt_in = os.environ.get(OTEL_SEMCONV_STABILITY_OPT_IN)
|
opt_in = os.environ.get(OTEL_SEMCONV_STABILITY_OPT_IN)
|
||||||
|
|
||||||
if not opt_in:
|
if not opt_in:
|
||||||
|
|
@ -203,6 +205,7 @@ class _OpenTelemetrySemanticConventionStability:
|
||||||
cls._OTEL_SEMCONV_STABILITY_SIGNAL_MAPPING = {
|
cls._OTEL_SEMCONV_STABILITY_SIGNAL_MAPPING = {
|
||||||
_OpenTelemetryStabilitySignalType.HTTP: _StabilityMode.DEFAULT,
|
_OpenTelemetryStabilitySignalType.HTTP: _StabilityMode.DEFAULT,
|
||||||
_OpenTelemetryStabilitySignalType.DATABASE: _StabilityMode.DEFAULT,
|
_OpenTelemetryStabilitySignalType.DATABASE: _StabilityMode.DEFAULT,
|
||||||
|
_OpenTelemetryStabilitySignalType.GEN_AI: _StabilityMode.DEFAULT,
|
||||||
}
|
}
|
||||||
cls._initialized = True
|
cls._initialized = True
|
||||||
return
|
return
|
||||||
|
|
@ -215,6 +218,14 @@ class _OpenTelemetrySemanticConventionStability:
|
||||||
opt_in_list, _StabilityMode.HTTP, _StabilityMode.HTTP_DUP
|
opt_in_list, _StabilityMode.HTTP, _StabilityMode.HTTP_DUP
|
||||||
)
|
)
|
||||||
|
|
||||||
|
cls._OTEL_SEMCONV_STABILITY_SIGNAL_MAPPING[
|
||||||
|
_OpenTelemetryStabilitySignalType.GEN_AI
|
||||||
|
] = cls._filter_mode(
|
||||||
|
opt_in_list,
|
||||||
|
_StabilityMode.DEFAULT,
|
||||||
|
_StabilityMode.GEN_AI_LATEST_EXPERIMENTAL,
|
||||||
|
)
|
||||||
|
|
||||||
cls._OTEL_SEMCONV_STABILITY_SIGNAL_MAPPING[
|
cls._OTEL_SEMCONV_STABILITY_SIGNAL_MAPPING[
|
||||||
_OpenTelemetryStabilitySignalType.DATABASE
|
_OpenTelemetryStabilitySignalType.DATABASE
|
||||||
] = cls._filter_mode(
|
] = cls._filter_mode(
|
||||||
|
|
@ -222,7 +233,6 @@ class _OpenTelemetrySemanticConventionStability:
|
||||||
_StabilityMode.DATABASE,
|
_StabilityMode.DATABASE,
|
||||||
_StabilityMode.DATABASE_DUP,
|
_StabilityMode.DATABASE_DUP,
|
||||||
)
|
)
|
||||||
|
|
||||||
cls._initialized = True
|
cls._initialized = True
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,12 @@ class TestOpenTelemetrySemConvStability(TestCase):
|
||||||
),
|
),
|
||||||
_StabilityMode.DEFAULT,
|
_StabilityMode.DEFAULT,
|
||||||
)
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
_OpenTelemetrySemanticConventionStability._get_opentelemetry_stability_opt_in_mode(
|
||||||
|
_OpenTelemetryStabilitySignalType.GEN_AI
|
||||||
|
),
|
||||||
|
_StabilityMode.DEFAULT,
|
||||||
|
)
|
||||||
|
|
||||||
@stability_mode("http")
|
@stability_mode("http")
|
||||||
def test_http_stable_mode(self):
|
def test_http_stable_mode(self):
|
||||||
|
|
@ -91,7 +97,16 @@ class TestOpenTelemetrySemConvStability(TestCase):
|
||||||
_StabilityMode.DATABASE_DUP,
|
_StabilityMode.DATABASE_DUP,
|
||||||
)
|
)
|
||||||
|
|
||||||
@stability_mode("database,http")
|
@stability_mode("gen_ai_latest_experimental")
|
||||||
|
def test_genai_latest_experimental(self):
|
||||||
|
self.assertEqual(
|
||||||
|
_OpenTelemetrySemanticConventionStability._get_opentelemetry_stability_opt_in_mode(
|
||||||
|
_OpenTelemetryStabilitySignalType.GEN_AI
|
||||||
|
),
|
||||||
|
_StabilityMode.GEN_AI_LATEST_EXPERIMENTAL,
|
||||||
|
)
|
||||||
|
|
||||||
|
@stability_mode("database,http,gen_ai_latest_experimental")
|
||||||
def test_multiple_stability_database_http_modes(self):
|
def test_multiple_stability_database_http_modes(self):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
_OpenTelemetrySemanticConventionStability._get_opentelemetry_stability_opt_in_mode(
|
_OpenTelemetrySemanticConventionStability._get_opentelemetry_stability_opt_in_mode(
|
||||||
|
|
@ -105,6 +120,12 @@ class TestOpenTelemetrySemConvStability(TestCase):
|
||||||
),
|
),
|
||||||
_StabilityMode.HTTP,
|
_StabilityMode.HTTP,
|
||||||
)
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
_OpenTelemetrySemanticConventionStability._get_opentelemetry_stability_opt_in_mode(
|
||||||
|
_OpenTelemetryStabilitySignalType.GEN_AI
|
||||||
|
),
|
||||||
|
_StabilityMode.GEN_AI_LATEST_EXPERIMENTAL,
|
||||||
|
)
|
||||||
|
|
||||||
@stability_mode("database,http/dup")
|
@stability_mode("database,http/dup")
|
||||||
def test_multiple_stability_database_http_dup_modes(self):
|
def test_multiple_stability_database_http_dup_modes(self):
|
||||||
|
|
|
||||||
|
|
@ -6,3 +6,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
Repurpose the `OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT` environment variable when GEN AI stability mode is set to `gen_ai_latest_experimental`,
|
||||||
|
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.
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Copyright The OpenTelemetry Authors
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT = (
|
||||||
|
"OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT"
|
||||||
|
)
|
||||||
|
|
@ -14,22 +14,34 @@
|
||||||
|
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
from enum import Enum
|
||||||
from typing import Any, Literal, Optional, Union
|
from typing import Any, Literal, Optional, Union
|
||||||
|
|
||||||
|
|
||||||
|
class ContentCapturingMode(Enum):
|
||||||
|
# Do not capture content (default).
|
||||||
|
NO_CONTENT = 0
|
||||||
|
# Only capture content in spans.
|
||||||
|
SPAN_ONLY = 1
|
||||||
|
# Only capture content in events.
|
||||||
|
EVENT_ONLY = 2
|
||||||
|
# Capture content in both spans and events.
|
||||||
|
SPAN_AND_EVENT = 3
|
||||||
|
|
||||||
|
|
||||||
@dataclass()
|
@dataclass()
|
||||||
class ToolCall:
|
class ToolCall:
|
||||||
type: Literal["tool_call"] = "tool_call"
|
|
||||||
arguments: Any
|
arguments: Any
|
||||||
name: str
|
name: str
|
||||||
id: Optional[str]
|
id: Optional[str]
|
||||||
|
type: Literal["tool_call"] = "tool_call"
|
||||||
|
|
||||||
|
|
||||||
@dataclass()
|
@dataclass()
|
||||||
class ToolCallResponse:
|
class ToolCallResponse:
|
||||||
type: Literal["tool_call_response"] = "tool_call_response"
|
|
||||||
response: Any
|
response: Any
|
||||||
id: Optional[str]
|
id: Optional[str]
|
||||||
|
type: Literal["tool_call_response"] = "tool_call_response"
|
||||||
|
|
||||||
|
|
||||||
FinishReason = Literal[
|
FinishReason = Literal[
|
||||||
|
|
@ -39,8 +51,8 @@ FinishReason = Literal[
|
||||||
|
|
||||||
@dataclass()
|
@dataclass()
|
||||||
class Text:
|
class Text:
|
||||||
type: Literal["text"] = "text"
|
|
||||||
content: str
|
content: str
|
||||||
|
type: Literal["text"] = "text"
|
||||||
|
|
||||||
|
|
||||||
MessagePart = Union[Text, ToolCall, ToolCallResponse, Any]
|
MessagePart = Union[Text, ToolCall, ToolCallResponse, Any]
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
# Copyright The OpenTelemetry Authors
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
|
from opentelemetry.instrumentation._semconv import (
|
||||||
|
_OpenTelemetrySemanticConventionStability,
|
||||||
|
_OpenTelemetryStabilitySignalType,
|
||||||
|
_StabilityMode,
|
||||||
|
)
|
||||||
|
from opentelemetry.util.genai.environment_variables import (
|
||||||
|
OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT,
|
||||||
|
)
|
||||||
|
from opentelemetry.util.genai.types import ContentCapturingMode
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def get_content_capturing_mode() -> ContentCapturingMode:
|
||||||
|
"""This function should not be called when GEN_AI stability mode is set to DEFAULT.
|
||||||
|
|
||||||
|
When the GEN_AI stability mode is DEFAULT this function will raise a ValueError -- see the code below."""
|
||||||
|
envvar = os.environ.get(OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT)
|
||||||
|
if (
|
||||||
|
_OpenTelemetrySemanticConventionStability._get_opentelemetry_stability_opt_in_mode(
|
||||||
|
_OpenTelemetryStabilitySignalType.GEN_AI,
|
||||||
|
)
|
||||||
|
== _StabilityMode.DEFAULT
|
||||||
|
):
|
||||||
|
raise ValueError(
|
||||||
|
"This function should never be called when StabilityMode is default."
|
||||||
|
)
|
||||||
|
if not envvar:
|
||||||
|
return ContentCapturingMode.NO_CONTENT
|
||||||
|
try:
|
||||||
|
return ContentCapturingMode[envvar.upper()]
|
||||||
|
except KeyError:
|
||||||
|
logger.warning(
|
||||||
|
"%s is not a valid option for `%s` environment variable. Must be one of %s. Defaulting to `NO_CONTENT`.",
|
||||||
|
envvar,
|
||||||
|
OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT,
|
||||||
|
", ".join(e.name for e in ContentCapturingMode),
|
||||||
|
)
|
||||||
|
return ContentCapturingMode.NO_CONTENT
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
# Copyright The OpenTelemetry Authors
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
import os
|
||||||
|
import unittest
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from opentelemetry.instrumentation._semconv import (
|
||||||
|
OTEL_SEMCONV_STABILITY_OPT_IN,
|
||||||
|
_OpenTelemetrySemanticConventionStability,
|
||||||
|
)
|
||||||
|
from opentelemetry.util.genai.environment_variables import (
|
||||||
|
OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT,
|
||||||
|
)
|
||||||
|
from opentelemetry.util.genai.types import ContentCapturingMode
|
||||||
|
from opentelemetry.util.genai.utils import get_content_capturing_mode
|
||||||
|
|
||||||
|
|
||||||
|
def patch_env_vars(stability_mode, content_capturing):
|
||||||
|
def decorator(test_case):
|
||||||
|
@patch.dict(
|
||||||
|
os.environ,
|
||||||
|
{
|
||||||
|
OTEL_SEMCONV_STABILITY_OPT_IN: stability_mode,
|
||||||
|
OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT: content_capturing,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
# Reset state.
|
||||||
|
_OpenTelemetrySemanticConventionStability._initialized = False
|
||||||
|
_OpenTelemetrySemanticConventionStability._initialize()
|
||||||
|
return test_case(*args, **kwargs)
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
class TestVersion(unittest.TestCase):
|
||||||
|
@patch_env_vars(
|
||||||
|
stability_mode="gen_ai_latest_experimental",
|
||||||
|
content_capturing="SPAN_ONLY",
|
||||||
|
)
|
||||||
|
def test_get_content_capturing_mode_parses_valid_envvar(self): # pylint: disable=no-self-use
|
||||||
|
assert get_content_capturing_mode() == ContentCapturingMode.SPAN_ONLY
|
||||||
|
|
||||||
|
@patch_env_vars(
|
||||||
|
stability_mode="gen_ai_latest_experimental", content_capturing=""
|
||||||
|
)
|
||||||
|
def test_empty_content_capturing_envvar(self): # pylint: disable=no-self-use
|
||||||
|
assert get_content_capturing_mode() == ContentCapturingMode.NO_CONTENT
|
||||||
|
|
||||||
|
@patch_env_vars(stability_mode="default", content_capturing="True")
|
||||||
|
def test_get_content_capturing_mode_raises_exception_when_semconv_stability_default(
|
||||||
|
self,
|
||||||
|
): # pylint: disable=no-self-use
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
get_content_capturing_mode()
|
||||||
|
|
||||||
|
@patch_env_vars(
|
||||||
|
stability_mode="gen_ai_latest_experimental",
|
||||||
|
content_capturing="INVALID_VALUE",
|
||||||
|
)
|
||||||
|
def test_get_content_capturing_mode_raises_exception_on_invalid_envvar(
|
||||||
|
self,
|
||||||
|
): # pylint: disable=no-self-use
|
||||||
|
with self.assertLogs(level="WARNING") as cm:
|
||||||
|
assert (
|
||||||
|
get_content_capturing_mode() == ContentCapturingMode.NO_CONTENT
|
||||||
|
)
|
||||||
|
self.assertEqual(len(cm.output), 1)
|
||||||
|
self.assertIn("INVALID_VALUE is not a valid option for ", cm.output[0])
|
||||||
Loading…
Reference in New Issue