Skip to content

Commit 3bfedae

Browse files
author
Liudmila Molkova
committed
add content opt-in env var
1 parent f585a40 commit 3bfedae

13 files changed

+596
-94
lines changed

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ tox -e py312-test-instrumentation-aiopg
227227
Some of the tox targets install packages from the [OpenTelemetry Python Core Repository](https://github.com/open-telemetry/opentelemetry-python) via pip. The version of the packages installed defaults to the main branch in that repository when tox is run locally. It is possible to install packages tagged with a specific git commit hash by setting an environment variable before running tox as per the following example:
228228

229229
```sh
230-
CORE_REPO_SHA=966750aad4ba2a9c123b4f14785958ffe50b54ae tox
230+
CORE_REPO_SHA=c49ad57bfe35cfc69bfa863d74058ca9bec55fc3 tox
231231
```
232232

233233
The continuous integration overrides that environment variable with as per the configuration [here](https://github.com/open-telemetry/opentelemetry-python-contrib/blob/main/.github/workflows/test_0.yml#L14).
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Changelog
2+
3+
All notable changes to this project will be documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7+
8+
## Unreleased
9+
10+
- Update OpenAI instrumentation to Semantic Conventions v1.28.0: add new attributes
11+
and switch prompts and completions to log-based events.
12+
([#2925](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2925))
13+
14+
- Initial OpenAI instrumentation
15+
([#2759](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2759))

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
OpenTelemetry OpenAI Instrumentation
2-
====================================
2+
====================================
33

44
|pypi|
55

66
.. |pypi| image:: https://badge.fury.io/py/opentelemetry-instrumentation-openai-v2.svg
77
:target: https://pypi.org/project/opentelemetry-instrumentation-openai-v2/
88

9-
Instrumentation with OpenAI that supports the openai library and is
9+
Instrumentation with OpenAI that supports the OpenAI library and is
1010
specified to trace_integration using 'OpenAI'.
1111

1212

instrumentation/opentelemetry-instrumentation-openai-v2/src/opentelemetry/instrumentation/openai_v2/__init__.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
from opentelemetry._events import get_event_logger
4848
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
4949
from opentelemetry.instrumentation.openai_v2.package import _instruments
50+
from opentelemetry.instrumentation.openai_v2.utils import is_content_enabled
5051
from opentelemetry.instrumentation.utils import unwrap
5152
from opentelemetry.semconv.schemas import Schemas
5253
from opentelemetry.trace import get_tracer
@@ -74,10 +75,13 @@ def _instrument(self, **kwargs):
7475
schema_url=Schemas.V1_28_0.value,
7576
event_logger_provider=event_provider,
7677
)
78+
7779
wrap_function_wrapper(
7880
module="openai.resources.chat.completions",
7981
name="Completions.create",
80-
wrapper=chat_completions_create(tracer, event_logger),
82+
wrapper=chat_completions_create(
83+
tracer, event_logger, is_content_enabled()
84+
),
8185
)
8286

8387
def _uninstrument(self, **kwargs):

instrumentation/opentelemetry-instrumentation-openai-v2/src/opentelemetry/instrumentation/openai_v2/patch.py

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,9 @@
3737
)
3838

3939

40-
def chat_completions_create(tracer: Tracer, event_logger: EventLogger):
40+
def chat_completions_create(
41+
tracer: Tracer, event_logger: EventLogger, capture_content: bool
42+
):
4143
"""Wrap the `create` method of the `ChatCompletion` class to trace it."""
4244

4345
def traced_method(wrapped, instance, args, kwargs):
@@ -53,20 +55,22 @@ def traced_method(wrapped, instance, args, kwargs):
5355
if span.is_recording():
5456
for message in kwargs.get("messages", []):
5557
event_logger.emit(
56-
message_to_event(message, span.get_span_context())
58+
message_to_event(
59+
message, span.get_span_context(), capture_content
60+
)
5761
)
5862

5963
try:
6064
result = wrapped(*args, **kwargs)
6165
if is_streaming(kwargs):
6266
return StreamWrapper(
63-
result,
64-
span,
65-
event_logger,
67+
result, span, event_logger, capture_content
6668
)
6769
else:
6870
if span.is_recording():
69-
_set_response_attributes(span, result, event_logger)
71+
_set_response_attributes(
72+
span, result, event_logger, capture_content
73+
)
7074
span.end()
7175
return result
7276

@@ -83,7 +87,9 @@ def traced_method(wrapped, instance, args, kwargs):
8387

8488

8589
@silently_fail
86-
def _set_response_attributes(span, result, event_logger: EventLogger):
90+
def _set_response_attributes(
91+
span, result, event_logger: EventLogger, capture_content: bool
92+
):
8793
if not span.is_recording():
8894
return
8995

@@ -95,7 +101,9 @@ def _set_response_attributes(span, result, event_logger: EventLogger):
95101
choices = result.choices
96102
for choice in choices:
97103
event_logger.emit(
98-
choice_to_event(choice, span_ctx=span.get_span_context())
104+
choice_to_event(
105+
choice, span.get_span_context(), capture_content
106+
)
99107
)
100108

101109
finish_reasons = []
@@ -182,11 +190,13 @@ def __init__(
182190
stream: Stream,
183191
span: Span,
184192
event_logger: EventLogger,
193+
capture_content: bool,
185194
):
186195
self.stream = stream
187196
self.span = span
188197
self.choice_buffers = []
189198
self._span_started = False
199+
self.capture_content = capture_content
190200

191201
self.event_logger = event_logger
192202
self.setup()
@@ -238,18 +248,20 @@ def cleanup(self):
238248
choice = self.choice_buffers[c]
239249

240250
message = {"role": "assistant"}
241-
if choice.text_content:
251+
if self.capture_content and choice.text_content:
242252
message["content"] = "".join(choice.text_content)
243253
if choice.tool_calls_buffers:
244254
tool_calls = []
245255
for tool_call in choice.tool_calls_buffers:
256+
function = {"name": tool_call.function_name}
257+
if self.capture_content:
258+
function["arguments"] = "".join(
259+
tool_call.arguments
260+
)
246261
tool_call_dict = {
247262
"id": tool_call.tool_call_id,
248263
"type": "function",
249-
"function": {
250-
"name": tool_call.function_name,
251-
"arguments": "".join(tool_call.arguments),
252-
},
264+
"function": function,
253265
}
254266
tool_calls.append(tool_call_dict)
255267
message["tool_calls"] = tool_calls

instrumentation/opentelemetry-instrumentation-openai-v2/src/opentelemetry/instrumentation/openai_v2/utils.py

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
# limitations under the License.
1414

1515
import logging
16-
from typing import Any, Optional, Tuple, Union
16+
from os import environ
17+
from typing import Optional, Union
1718
from urllib.parse import urlparse
1819

1920
from httpx import URL
@@ -27,6 +28,18 @@
2728
server_attributes as ServerAttributes,
2829
)
2930

31+
OTEL_INSTRUMENTATION_OPENAI_CAPTURE_MESSAGE_CONTENT = (
32+
"OTEL_INSTRUMENTATION_OPENAI_CAPTURE_MESSAGE_CONTENT"
33+
)
34+
35+
36+
def is_content_enabled() -> bool:
37+
capture_content = environ.get(
38+
OTEL_INSTRUMENTATION_OPENAI_CAPTURE_MESSAGE_CONTENT, None
39+
)
40+
41+
return bool(capture_content)
42+
3043

3144
def silently_fail(func):
3245
"""
@@ -48,20 +61,7 @@ def wrapper(*args, **kwargs):
4861
return wrapper
4962

5063

51-
def extract_content_from_prompt_message(message) -> Tuple[str, Any]:
52-
content = get_property_value(message, "content")
53-
if content:
54-
return ("content", content)
55-
56-
tool_calls = extract_tool_calls(message)
57-
if tool_calls:
58-
return ("tool_calls", tool_calls)
59-
60-
else:
61-
return None
62-
63-
64-
def extract_tool_calls(item):
64+
def extract_tool_calls(item, capture_content):
6565
tool_calls = get_property_value(item, "tool_calls")
6666
if tool_calls is None:
6767
return
@@ -82,7 +82,7 @@ def extract_tool_calls(item):
8282
tool_call_dict["function"]["name"] = name
8383

8484
arguments = get_property_value(func, "arguments")
85-
if arguments:
85+
if capture_content and arguments:
8686
tool_call_dict["function"]["arguments"] = arguments.replace(
8787
"\n", ""
8888
)
@@ -120,18 +120,18 @@ def get_property_value(obj, property_name):
120120
return getattr(obj, property_name, None)
121121

122122

123-
def message_to_event(message, span_ctx):
123+
def message_to_event(message, span_ctx, capture_content):
124124
attributes = {
125125
GenAIAttributes.GEN_AI_SYSTEM: GenAIAttributes.GenAiSystemValues.OPENAI.value
126126
}
127127
role = get_property_value(message, "role")
128128
content = get_property_value(message, "content")
129129

130130
body = {}
131-
if content:
131+
if capture_content and content:
132132
body["content"] = content
133133
if role == "assistant":
134-
tool_calls = extract_tool_calls(message)
134+
tool_calls = extract_tool_calls(message, capture_content)
135135
if tool_calls:
136136
body = {"tool_calls": tool_calls}
137137
elif role == "tool":
@@ -142,14 +142,14 @@ def message_to_event(message, span_ctx):
142142
return Event(
143143
name=f"gen_ai.{role}.message",
144144
attributes=attributes,
145-
body=body,
145+
body=body if body else None,
146146
trace_id=span_ctx.trace_id,
147147
span_id=span_ctx.span_id,
148148
trace_flags=span_ctx.trace_flags,
149149
)
150150

151151

152-
def choice_to_event(choice, span_ctx):
152+
def choice_to_event(choice, span_ctx, capture_content):
153153
attributes = {
154154
GenAIAttributes.GEN_AI_SYSTEM: GenAIAttributes.GenAiSystemValues.OPENAI.value
155155
}
@@ -165,11 +165,11 @@ def choice_to_event(choice, span_ctx):
165165
if choice.message and choice.message.role
166166
else "assistant"
167167
}
168-
tool_calls = extract_tool_calls(choice.message)
168+
tool_calls = extract_tool_calls(choice.message, capture_content)
169169
if tool_calls:
170170
message["tool_calls"] = tool_calls
171171
content = get_property_value(choice.message, "content")
172-
if content:
172+
if capture_content and content:
173173
message["content"] = content
174174
body["message"] = message
175175

0 commit comments

Comments
 (0)