Skip to content

Commit 64c2693

Browse files
authored
elastic-opentelemetry-instrumentation-openai: Drop span events support (elastic#38)
1 parent 1720368 commit 64c2693

File tree

25 files changed

+205
-3907
lines changed

25 files changed

+205
-3907
lines changed

instrumentation/elastic-opentelemetry-instrumentation-openai/README.md

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,6 @@ opentelemetry-instrument python use_openai.py
2727
2828
# You can record more information about prompts as log events by enabling content capture.
2929
OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=true opentelemetry-instrument python use_openai.py
30-
31-
# You can record more information about prompts as span events by enabling content capture.
32-
OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=true ELASTIC_OTEL_GENAI_EVENTS=span opentelemetry-instrument python use_openai.py
3330
```
3431

3532
Or manual instrumentation:
@@ -55,8 +52,7 @@ chat_completion = client.chat.completions.create(model="gpt-4o-mini", messages=m
5552

5653
### Instrumentation specific environment variable configuration
5754

58-
- `ELASTIC_OTEL_GENAI_EVENTS` (default: `span`): when set to `log` exports GenAI events as
59-
log events instead of span events.
55+
None
6056

6157
### Elastic specific semantic conventions
6258

instrumentation/elastic-opentelemetry-instrumentation-openai/src/opentelemetry/instrumentation/openai/__init__.py

Lines changed: 4 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
# See the License for the specific language governing permissions and
1515
# limitations under the License.
1616

17-
import json
1817
import logging
1918
import os
2019
from timeit import default_timer
@@ -26,14 +25,12 @@
2625
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
2726
from opentelemetry.instrumentation.utils import unwrap
2827
from opentelemetry.instrumentation.openai.environment_variables import (
29-
ELASTIC_OTEL_GENAI_EVENTS,
3028
OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT,
3129
)
3230
from opentelemetry.instrumentation.openai.helpers import (
3331
_get_embeddings_span_attributes_from_wrapper,
3432
_get_event_attributes,
3533
_get_span_attributes_from_wrapper,
36-
_message_from_choice,
3734
_record_token_usage_metrics,
3835
_record_operation_duration_metric,
3936
_send_log_events_from_messages,
@@ -46,10 +43,6 @@
4643
from opentelemetry.instrumentation.openai.version import __version__
4744
from opentelemetry.instrumentation.openai.wrappers import StreamWrapper
4845
from opentelemetry.metrics import get_meter
49-
from opentelemetry.semconv._incubating.attributes.gen_ai_attributes import (
50-
GEN_AI_COMPLETION,
51-
GEN_AI_PROMPT,
52-
)
5346
from opentelemetry.semconv._incubating.metrics.gen_ai_metrics import (
5447
create_gen_ai_client_token_usage,
5548
create_gen_ai_client_operation_duration,
@@ -86,13 +79,6 @@ def _instrument(self, **kwargs):
8679
== "true"
8780
)
8881

89-
# we support 3 values for deciding how to send events:
90-
# - "latest" to match latest semconv, as 1.28.0 it's log
91-
# - "log" to send log events (default)
92-
# - "span" to send span events
93-
genai_events = os.environ.get(ELASTIC_OTEL_GENAI_EVENTS, "latest").lower()
94-
self.event_kind = "span" if genai_events == "span" else "log"
95-
9682
tracer_provider = kwargs.get("tracer_provider")
9783
self.tracer = get_tracer(
9884
__name__,
@@ -165,13 +151,7 @@ def _chat_completion_wrapper(self, wrapped, instance, args, kwargs):
165151
if self.capture_message_content:
166152
messages = kwargs.get("messages", [])
167153

168-
if self.event_kind == "log":
169-
_send_log_events_from_messages(self.event_logger, messages=messages, attributes=event_attributes)
170-
elif span.is_recording():
171-
try:
172-
span.add_event(EVENT_GEN_AI_CONTENT_PROMPT, attributes={GEN_AI_PROMPT: json.dumps(messages)})
173-
except TypeError:
174-
logger.error(f"Failed to serialize {EVENT_GEN_AI_CONTENT_PROMPT}")
154+
_send_log_events_from_messages(self.event_logger, messages=messages, attributes=event_attributes)
175155

176156
start_time = default_timer()
177157
try:
@@ -188,7 +168,6 @@ def _chat_completion_wrapper(self, wrapped, instance, args, kwargs):
188168
stream=result,
189169
span=span,
190170
capture_message_content=self.capture_message_content,
191-
event_kind=self.event_kind,
192171
event_attributes=event_attributes,
193172
event_logger=self.event_logger,
194173
start_time=start_time,
@@ -205,19 +184,7 @@ def _chat_completion_wrapper(self, wrapped, instance, args, kwargs):
205184
_record_operation_duration_metric(self.operation_duration_metric, span, start_time)
206185

207186
if self.capture_message_content:
208-
if self.event_kind == "log":
209-
_send_log_events_from_choices(
210-
self.event_logger, choices=result.choices, attributes=event_attributes
211-
)
212-
elif span.is_recording():
213-
# same format as the prompt
214-
completion = [_message_from_choice(choice) for choice in result.choices]
215-
try:
216-
span.add_event(
217-
EVENT_GEN_AI_CONTENT_COMPLETION, attributes={GEN_AI_COMPLETION: json.dumps(completion)}
218-
)
219-
except TypeError:
220-
logger.error(f"Failed to serialize {EVENT_GEN_AI_CONTENT_COMPLETION}")
187+
_send_log_events_from_choices(self.event_logger, choices=result.choices, attributes=event_attributes)
221188

222189
span.end()
223190

@@ -239,14 +206,7 @@ async def _async_chat_completion_wrapper(self, wrapped, instance, args, kwargs):
239206
) as span:
240207
if self.capture_message_content:
241208
messages = kwargs.get("messages", [])
242-
243-
if self.event_kind == "log":
244-
_send_log_events_from_messages(self.event_logger, messages=messages, attributes=event_attributes)
245-
elif span.is_recording():
246-
try:
247-
span.add_event(EVENT_GEN_AI_CONTENT_PROMPT, attributes={GEN_AI_PROMPT: json.dumps(messages)})
248-
except TypeError:
249-
logger.error(f"Failed to serialize {EVENT_GEN_AI_CONTENT_PROMPT}")
209+
_send_log_events_from_messages(self.event_logger, messages=messages, attributes=event_attributes)
250210

251211
start_time = default_timer()
252212
try:
@@ -263,7 +223,6 @@ async def _async_chat_completion_wrapper(self, wrapped, instance, args, kwargs):
263223
stream=result,
264224
span=span,
265225
capture_message_content=self.capture_message_content,
266-
event_kind=self.event_kind,
267226
event_attributes=event_attributes,
268227
event_logger=self.event_logger,
269228
start_time=start_time,
@@ -280,19 +239,7 @@ async def _async_chat_completion_wrapper(self, wrapped, instance, args, kwargs):
280239
_record_operation_duration_metric(self.operation_duration_metric, span, start_time)
281240

282241
if self.capture_message_content:
283-
if self.event_kind == "log":
284-
_send_log_events_from_choices(
285-
self.event_logger, choices=result.choices, attributes=event_attributes
286-
)
287-
elif span.is_recording():
288-
# same format as the prompt
289-
completion = [_message_from_choice(choice) for choice in result.choices]
290-
try:
291-
span.add_event(
292-
EVENT_GEN_AI_CONTENT_COMPLETION, attributes={GEN_AI_COMPLETION: json.dumps(completion)}
293-
)
294-
except TypeError:
295-
logger.error(f"Failed to serialize {EVENT_GEN_AI_CONTENT_COMPLETION}")
242+
_send_log_events_from_choices(self.event_logger, choices=result.choices, attributes=event_attributes)
296243

297244
span.end()
298245

instrumentation/elastic-opentelemetry-instrumentation-openai/src/opentelemetry/instrumentation/openai/environment_variables.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,3 @@
1515
# limitations under the License.
1616

1717
OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT = "OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT"
18-
19-
ELASTIC_OTEL_GENAI_EVENTS = "ELASTIC_OTEL_GENAI_EVENTS"

instrumentation/elastic-opentelemetry-instrumentation-openai/src/opentelemetry/instrumentation/openai/helpers.py

Lines changed: 0 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -79,62 +79,6 @@ def _set_embeddings_span_attributes_from_response(span: Span, model: str, usage:
7979
span.set_attribute(GEN_AI_USAGE_INPUT_TOKENS, usage.prompt_tokens)
8080

8181

82-
def _message_from_choice(choice):
83-
"""Format a choice into a message of the same shape of the prompt"""
84-
if tool_calls := getattr(choice.message, "tool_calls", None):
85-
return {
86-
"role": choice.message.role,
87-
"content": "",
88-
"tool_calls": [
89-
{
90-
"id": tool_call.id,
91-
"type": tool_call.type,
92-
"function": {
93-
"name": tool_call.function.name,
94-
"arguments": tool_call.function.arguments,
95-
},
96-
}
97-
for tool_call in tool_calls
98-
],
99-
}
100-
else:
101-
return {"role": choice.message.role, "content": choice.message.content}
102-
103-
104-
def _message_from_stream_choices(choices):
105-
"""Format an iterable of choices into a message of the same shape of the prompt"""
106-
messages = {}
107-
tool_calls = {}
108-
for choice in choices:
109-
messages.setdefault(choice.index, {"role": None, "content": ""})
110-
message = messages[choice.index]
111-
if choice.delta.role:
112-
message["role"] = choice.delta.role
113-
if choice.delta.content:
114-
message["content"] += choice.delta.content
115-
116-
if choice.delta.tool_calls:
117-
for call in choice.delta.tool_calls:
118-
tool_calls.setdefault(choice.index, {})
119-
tool_calls[choice.index].setdefault(call.index, {"function": {"arguments": ""}})
120-
tool_call = tool_calls[choice.index][call.index]
121-
if call.function.arguments:
122-
tool_call["function"]["arguments"] += call.function.arguments
123-
if call.function.name:
124-
tool_call["function"]["name"] = call.function.name
125-
if call.id:
126-
tool_call["id"] = call.id
127-
if call.type:
128-
tool_call["type"] = call.type
129-
130-
for message_index in tool_calls:
131-
message = messages[message_index]
132-
message["tool_calls"] = [arguments for _, arguments in sorted(tool_calls[message_index].items())]
133-
134-
# assumes there's only one message
135-
return [message for _, message in sorted(messages.items())][0]
136-
137-
13882
def _attributes_from_client(client) -> Attributes:
13983
span_attributes = {}
14084

instrumentation/elastic-opentelemetry-instrumentation-openai/src/opentelemetry/instrumentation/openai/wrappers.py

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -14,23 +14,17 @@
1414
# See the License for the specific language governing permissions and
1515
# limitations under the License.
1616

17-
import json
1817
import logging
19-
from typing import Literal
2018

2119
from opentelemetry._events import EventLogger
2220
from opentelemetry.instrumentation.openai.helpers import (
23-
_message_from_stream_choices,
2421
_record_token_usage_metrics,
2522
_record_operation_duration_metric,
2623
_set_span_attributes_from_response,
2724
_send_log_events_from_stream_choices,
2825
)
2926
from opentelemetry.metrics import Histogram
3027
from opentelemetry.semconv.attributes.error_attributes import ERROR_TYPE
31-
from opentelemetry.semconv._incubating.attributes.gen_ai_attributes import (
32-
GEN_AI_COMPLETION,
33-
)
3428
from opentelemetry.trace import Span
3529
from opentelemetry.trace.status import StatusCode
3630
from opentelemetry.util.types import Attributes
@@ -46,7 +40,6 @@ def __init__(
4640
stream,
4741
span: Span,
4842
capture_message_content: bool,
49-
event_kind: Literal["log", "span"],
5043
event_attributes: Attributes,
5144
event_logger: EventLogger,
5245
start_time: float,
@@ -56,7 +49,6 @@ def __init__(
5649
self.stream = stream
5750
self.span = span
5851
self.capture_message_content = capture_message_content
59-
self.event_kind = event_kind
6052
self.event_attributes = event_attributes
6153
self.event_logger = event_logger
6254
self.token_usage_metric = token_usage_metric
@@ -85,19 +77,9 @@ def end(self, exc=None):
8577
_record_token_usage_metrics(self.token_usage_metric, self.span, self.usage)
8678

8779
if self.capture_message_content:
88-
if self.event_kind == "log":
89-
_send_log_events_from_stream_choices(
90-
self.event_logger, choices=self.choices, span=self.span, attributes=self.event_attributes
91-
)
92-
elif self.span.is_recording():
93-
# same format as the prompt
94-
completion = [_message_from_stream_choices(self.choices)]
95-
try:
96-
self.span.add_event(
97-
EVENT_GEN_AI_CONTENT_COMPLETION, attributes={GEN_AI_COMPLETION: json.dumps(completion)}
98-
)
99-
except TypeError:
100-
logger.error(f"Failed to serialize {EVENT_GEN_AI_CONTENT_COMPLETION}")
80+
_send_log_events_from_stream_choices(
81+
self.event_logger, choices=self.choices, span=self.span, attributes=self.event_attributes
82+
)
10183

10284
self.span.end()
10385

0 commit comments

Comments
 (0)