Skip to content

Commit ce21660

Browse files
committed
Revert Otel Upgrade
1 parent 21ac93d commit ce21660

File tree

7 files changed

+169
-405
lines changed

7 files changed

+169
-405
lines changed

docs/logfire.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -283,17 +283,17 @@ Note that the OpenTelemetry Semantic Conventions are still experimental and are
283283

284284
### Setting OpenTelemetry SDK providers
285285

286-
By default, the global `TracerProvider` and `LoggerProvider` are used. These are set automatically by `logfire.configure()`. They can also be set by the `set_tracer_provider` and `set_logger_provider` functions in the OpenTelemetry Python SDK. You can set custom providers with [`InstrumentationSettings`][pydantic_ai.models.instrumented.InstrumentationSettings].
286+
By default, the global `TracerProvider` and `EventLoggerProvider` are used. These are set automatically by `logfire.configure()`. They can also be set by the `set_tracer_provider` and `set_event_logger_provider` functions in the OpenTelemetry Python SDK. You can set custom providers with [`InstrumentationSettings`][pydantic_ai.models.instrumented.InstrumentationSettings].
287287

288288
```python {title="instrumentation_settings_providers.py"}
289-
from opentelemetry.sdk._logs import LoggerProvider
289+
from opentelemetry.sdk._events import EventLoggerProvider
290290
from opentelemetry.sdk.trace import TracerProvider
291291

292292
from pydantic_ai.agent import Agent, InstrumentationSettings
293293

294294
instrumentation_settings = InstrumentationSettings(
295295
tracer_provider=TracerProvider(),
296-
logger_provider=LoggerProvider(),
296+
event_logger_provider=EventLoggerProvider(),
297297
)
298298

299299
agent = Agent('gpt-4o', instrument=instrumentation_settings)

pydantic_ai_slim/pydantic_ai/messages.py

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
import pydantic
1212
import pydantic_core
13-
from opentelemetry._logs import LogRecord # pyright: ignore[reportPrivateImportUsage]
13+
from opentelemetry._events import Event # pyright: ignore[reportPrivateImportUsage]
1414
from typing_extensions import TypeAlias, deprecated
1515

1616
from . import _utils
@@ -76,9 +76,9 @@ class SystemPromptPart:
7676
part_kind: Literal['system-prompt'] = 'system-prompt'
7777
"""Part type identifier, this is available on all parts as a discriminator."""
7878

79-
def otel_event(self, settings: InstrumentationSettings) -> LogRecord:
80-
return LogRecord(
81-
attributes={'event.name': 'gen_ai.system.message'},
79+
def otel_event(self, settings: InstrumentationSettings) -> Event:
80+
return Event(
81+
'gen_ai.system.message',
8282
body={'role': 'system', **({'content': self.content} if settings.include_content else {})},
8383
)
8484

@@ -418,7 +418,7 @@ class UserPromptPart:
418418
part_kind: Literal['user-prompt'] = 'user-prompt'
419419
"""Part type identifier, this is available on all parts as a discriminator."""
420420

421-
def otel_event(self, settings: InstrumentationSettings) -> LogRecord:
421+
def otel_event(self, settings: InstrumentationSettings) -> Event:
422422
content: str | list[dict[str, Any] | str] | dict[str, Any]
423423
if isinstance(self.content, str):
424424
content = self.content if settings.include_content else {'kind': 'text'}
@@ -436,7 +436,7 @@ def otel_event(self, settings: InstrumentationSettings) -> LogRecord:
436436
content.append(converted_part)
437437
else:
438438
content.append({'kind': part.kind}) # pragma: no cover
439-
return LogRecord(attributes={'event.name': 'gen_ai.user.message'}, body={'content': content, 'role': 'user'})
439+
return Event('gen_ai.user.message', body={'content': content, 'role': 'user'})
440440

441441
__repr__ = _utils.dataclasses_no_defaults_repr
442442

@@ -483,9 +483,9 @@ def model_response_object(self) -> dict[str, Any]:
483483
else:
484484
return {'return_value': tool_return_ta.dump_python(self.content, mode='json')}
485485

486-
def otel_event(self, settings: InstrumentationSettings) -> LogRecord:
487-
return LogRecord(
488-
attributes={'event.name': 'gen_ai.tool.message'},
486+
def otel_event(self, settings: InstrumentationSettings) -> Event:
487+
return Event(
488+
'gen_ai.tool.message',
489489
body={
490490
**({'content': self.content} if settings.include_content else {}),
491491
'role': 'tool',
@@ -550,15 +550,12 @@ def model_response(self) -> str:
550550
description = f'{len(self.content)} validation errors: {json_errors.decode()}'
551551
return f'{description}\n\nFix the errors and try again.'
552552

553-
def otel_event(self, settings: InstrumentationSettings) -> LogRecord:
553+
def otel_event(self, settings: InstrumentationSettings) -> Event:
554554
if self.tool_name is None:
555-
return LogRecord(
556-
attributes={'event.name': 'gen_ai.user.message'},
557-
body={'content': self.model_response(), 'role': 'user'},
558-
)
555+
return Event('gen_ai.user.message', body={'content': self.model_response(), 'role': 'user'})
559556
else:
560-
return LogRecord(
561-
attributes={'event.name': 'gen_ai.tool.message'},
557+
return Event(
558+
'gen_ai.tool.message',
562559
body={
563560
**({'content': self.model_response()} if settings.include_content else {}),
564561
'role': 'tool',
@@ -737,13 +734,13 @@ class ModelResponse:
737734
vendor_id: str | None = None
738735
"""Vendor ID as specified by the model provider. This can be used to track the specific request to the model."""
739736

740-
def otel_events(self, settings: InstrumentationSettings) -> list[LogRecord]:
737+
def otel_events(self, settings: InstrumentationSettings) -> list[Event]:
741738
"""Return OpenTelemetry events for the response."""
742-
result: list[LogRecord] = []
739+
result: list[Event] = []
743740

744741
def new_event_body():
745742
new_body: dict[str, Any] = {'role': 'assistant'}
746-
ev = LogRecord(attributes={'event.name': 'gen_ai.assistant.message'}, body=new_body)
743+
ev = Event('gen_ai.assistant.message', body=new_body)
747744
result.append(ev)
748745
return new_body
749746

pydantic_ai_slim/pydantic_ai/models/instrumented.py

Lines changed: 15 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,11 @@
88
from urllib.parse import urlparse
99

1010
from opentelemetry._events import (
11+
Event, # pyright: ignore[reportPrivateImportUsage]
1112
EventLogger, # pyright: ignore[reportPrivateImportUsage]
1213
EventLoggerProvider, # pyright: ignore[reportPrivateImportUsage]
13-
Event, # pyright: ignore[reportPrivateImportUsage]
1414
get_event_logger_provider, # pyright: ignore[reportPrivateImportUsage]
1515
)
16-
from opentelemetry._logs import (
17-
Logger, # pyright: ignore[reportPrivateImportUsage]
18-
LoggerProvider, # pyright: ignore[reportPrivateImportUsage]
19-
LogRecord, # pyright: ignore[reportPrivateImportUsage]
20-
get_logger_provider, # pyright: ignore[reportPrivateImportUsage]
21-
)
2216
from opentelemetry.metrics import MeterProvider, get_meter_provider
2317
from opentelemetry.trace import Span, Tracer, TracerProvider, get_tracer_provider
2418
from opentelemetry.util.types import AttributeValue
@@ -86,7 +80,7 @@ class InstrumentationSettings:
8680
"""
8781

8882
tracer: Tracer = field(repr=False)
89-
event_logger: Logger = field(repr=False)
83+
event_logger: EventLogger = field(repr=False)
9084
event_mode: Literal['attributes', 'logs'] = 'attributes'
9185
include_binary_content: bool = True
9286

@@ -96,7 +90,7 @@ def __init__(
9690
event_mode: Literal['attributes', 'logs'] = 'attributes',
9791
tracer_provider: TracerProvider | None = None,
9892
meter_provider: MeterProvider | None = None,
99-
event_logger_provider: LoggerProvider | EventLoggerProvider | None = None,
93+
event_logger_provider: EventLoggerProvider | None = None,
10094
include_binary_content: bool = True,
10195
include_content: bool = True,
10296
):
@@ -123,15 +117,11 @@ def __init__(
123117

124118
tracer_provider = tracer_provider or get_tracer_provider()
125119
meter_provider = meter_provider or get_meter_provider()
126-
event_logger_provider = event_logger_provider or get_logger_provider()
120+
event_logger_provider = event_logger_provider or get_event_logger_provider()
127121
scope_name = 'pydantic-ai'
128122
self.tracer = tracer_provider.get_tracer(scope_name, __version__)
129123
self.meter = meter_provider.get_meter(scope_name, __version__)
130-
try:
131-
self.event_logger = event_logger_provider.get_logger(scope_name, __version__)
132-
except AttributeError:
133-
# Older OTel/logfire versions don't support LoggerProvider
134-
self.event_logger = event_logger_provider.get_event_logger(scope_name, __version__)
124+
self.event_logger = event_logger_provider.get_event_logger(scope_name, __version__)
135125
self.event_mode = event_mode
136126
self.include_binary_content = include_binary_content
137127
self.include_content = include_content
@@ -154,7 +144,7 @@ def __init__(
154144
**tokens_histogram_kwargs, # pyright: ignore
155145
)
156146

157-
def messages_to_otel_events(self, messages: list[ModelMessage]) -> list[LogRecord]:
147+
def messages_to_otel_events(self, messages: list[ModelMessage]) -> list[Event]:
158148
"""Convert a list of model messages to OpenTelemetry events.
159149
160150
Args:
@@ -163,18 +153,18 @@ def messages_to_otel_events(self, messages: list[ModelMessage]) -> list[LogRecor
163153
Returns:
164154
A list of OpenTelemetry events.
165155
"""
166-
events: list[LogRecord] = []
156+
events: list[Event] = []
167157
instructions = InstrumentedModel._get_instructions(messages) # pyright: ignore [reportPrivateUsage]
168158
if instructions is not None:
169159
events.append(
170-
LogRecord(
171-
attributes={'event.name': 'gen_ai.system.message'},
160+
Event(
161+
'gen_ai.system.message',
172162
body={**({'content': instructions} if self.include_content else {}), 'role': 'system'},
173163
)
174164
)
175165

176166
for message_index, message in enumerate(messages):
177-
message_events: list[LogRecord] = []
167+
message_events: list[Event] = []
178168
if isinstance(message, ModelRequest):
179169
for part in message.parts:
180170
if hasattr(part, 'otel_event'):
@@ -312,8 +302,8 @@ def _record_metrics():
312302
events = self.instrumentation_settings.messages_to_otel_events(messages)
313303
for event in self.instrumentation_settings.messages_to_otel_events([response]):
314304
events.append(
315-
LogRecord(
316-
attributes={'event.name': 'gen_ai.choice'},
305+
Event(
306+
'gen_ai.choice',
317307
body={
318308
# TODO finish_reason
319309
'index': 0,
@@ -342,7 +332,7 @@ def _record_metrics():
342332
# to prevent them from being redundantly recorded in the span itself by logfire.
343333
record_metrics()
344334

345-
def _emit_events(self, span: Span, events: list[LogRecord] | list[LogRecord]) -> None:
335+
def _emit_events(self, span: Span, events: list[Event]) -> None:
346336
if self.instrumentation_settings.event_mode == 'logs':
347337
for event in events:
348338
self.instrumentation_settings.event_logger.emit(event)
@@ -383,11 +373,11 @@ def model_attributes(model: Model):
383373
return attributes
384374

385375
@staticmethod
386-
def event_to_dict(event: LogRecord) -> dict[str, Any]:
376+
def event_to_dict(event: Event) -> dict[str, Any]:
387377
if not event.body:
388378
body = {} # pragma: no cover
389379
elif isinstance(event.body, Mapping):
390-
body = event.body
380+
body = event.body # type: ignore
391381
else:
392382
body = {'body': event.body}
393383
return {**body, **(event.attributes or {})}

tests/models/test_fallback.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,6 @@ def test_all_failed_instrumented(capfire: CaptureLogfire) -> None:
277277
'logfire.span_type': 'span',
278278
'logfire.msg': 'chat fallback:function:failure_response:,function:failure_response:',
279279
'logfire.level_num': 17,
280-
'gen_ai.response.model': 'fallback:function:failure_response:,function:failure_response:',
281280
},
282281
'events': [
283282
{

0 commit comments

Comments
 (0)