Skip to content

Commit bd9856e

Browse files
committed
Add compatibility with opentelemtry-api>=1.35
1 parent 7a985a6 commit bd9856e

File tree

9 files changed

+380
-457
lines changed

9 files changed

+380
-457
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 `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].
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_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._events import EventLoggerProvider
289+
from opentelemetry.sdk._logs import LoggerProvider
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-
event_logger_provider=EventLoggerProvider(),
296+
event_logger_provider=LoggerProvider(),
297297
)
298298

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

pydantic_ai_slim/pydantic_ai/messages.py

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

1111
import pydantic
1212
import pydantic_core
13-
from opentelemetry._events import Event # pyright: ignore[reportPrivateImportUsage]
13+
from opentelemetry._logs import LogRecord # 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) -> Event:
80-
return Event(
81-
'gen_ai.system.message',
79+
def otel_event(self, settings: InstrumentationSettings) -> LogRecord:
80+
return LogRecord(
81+
event_name='gen_ai.system.message',
8282
body={'role': 'system', **({'content': self.content} if settings.include_content else {})},
8383
)
8484

@@ -410,7 +410,7 @@ class UserPromptPart:
410410
part_kind: Literal['user-prompt'] = 'user-prompt'
411411
"""Part type identifier, this is available on all parts as a discriminator."""
412412

413-
def otel_event(self, settings: InstrumentationSettings) -> Event:
413+
def otel_event(self, settings: InstrumentationSettings) -> LogRecord:
414414
content: str | list[dict[str, Any] | str] | dict[str, Any]
415415
if isinstance(self.content, str):
416416
content = self.content if settings.include_content else {'kind': 'text'}
@@ -428,7 +428,7 @@ def otel_event(self, settings: InstrumentationSettings) -> Event:
428428
content.append(converted_part)
429429
else:
430430
content.append({'kind': part.kind}) # pragma: no cover
431-
return Event('gen_ai.user.message', body={'content': content, 'role': 'user'})
431+
return LogRecord(event_name='gen_ai.user.message', body={'content': content, 'role': 'user'})
432432

433433
__repr__ = _utils.dataclasses_no_defaults_repr
434434

@@ -475,9 +475,9 @@ def model_response_object(self) -> dict[str, Any]:
475475
else:
476476
return {'return_value': tool_return_ta.dump_python(self.content, mode='json')}
477477

478-
def otel_event(self, settings: InstrumentationSettings) -> Event:
479-
return Event(
480-
'gen_ai.tool.message',
478+
def otel_event(self, settings: InstrumentationSettings) -> LogRecord:
479+
return LogRecord(
480+
event_name='gen_ai.tool.message',
481481
body={
482482
**({'content': self.content} if settings.include_content else {}),
483483
'role': 'tool',
@@ -542,12 +542,12 @@ def model_response(self) -> str:
542542
description = f'{len(self.content)} validation errors: {json_errors.decode()}'
543543
return f'{description}\n\nFix the errors and try again.'
544544

545-
def otel_event(self, settings: InstrumentationSettings) -> Event:
545+
def otel_event(self, settings: InstrumentationSettings) -> LogRecord:
546546
if self.tool_name is None:
547-
return Event('gen_ai.user.message', body={'content': self.model_response(), 'role': 'user'})
547+
return LogRecord(event_name='gen_ai.user.message', body={'content': self.model_response(), 'role': 'user'})
548548
else:
549-
return Event(
550-
'gen_ai.tool.message',
549+
return LogRecord(
550+
event_name='gen_ai.tool.message',
551551
body={
552552
**({'content': self.model_response()} if settings.include_content else {}),
553553
'role': 'tool',
@@ -726,13 +726,13 @@ class ModelResponse:
726726
vendor_id: str | None = None
727727
"""Vendor ID as specified by the model provider. This can be used to track the specific request to the model."""
728728

729-
def otel_events(self, settings: InstrumentationSettings) -> list[Event]:
729+
def otel_events(self, settings: InstrumentationSettings) -> list[LogRecord]:
730730
"""Return OpenTelemetry events for the response."""
731-
result: list[Event] = []
731+
result: list[LogRecord] = []
732732

733733
def new_event_body():
734734
new_body: dict[str, Any] = {'role': 'assistant'}
735-
ev = Event('gen_ai.assistant.message', body=new_body)
735+
ev = LogRecord(event_name='gen_ai.assistant.message', body=new_body)
736736
result.append(ev)
737737
return new_body
738738

pydantic_ai_slim/pydantic_ai/models/instrumented.py

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@
77
from typing import Any, Callable, Literal
88
from urllib.parse import urlparse
99

10-
from opentelemetry._events import (
11-
Event, # pyright: ignore[reportPrivateImportUsage]
12-
EventLogger, # pyright: ignore[reportPrivateImportUsage]
13-
EventLoggerProvider, # pyright: ignore[reportPrivateImportUsage]
14-
get_event_logger_provider, # pyright: ignore[reportPrivateImportUsage]
10+
from opentelemetry._logs import (
11+
Logger, # pyright: ignore[reportPrivateImportUsage]
12+
LoggerProvider, # pyright: ignore[reportPrivateImportUsage]
13+
LogRecord, # pyright: ignore[reportPrivateImportUsage]
14+
get_logger_provider, # pyright: ignore[reportPrivateImportUsage]
1515
)
1616
from opentelemetry.metrics import MeterProvider, get_meter_provider
1717
from opentelemetry.trace import Span, Tracer, TracerProvider, get_tracer_provider
@@ -80,7 +80,7 @@ class InstrumentationSettings:
8080
"""
8181

8282
tracer: Tracer = field(repr=False)
83-
event_logger: EventLogger = field(repr=False)
83+
event_logger: Logger = field(repr=False)
8484
event_mode: Literal['attributes', 'logs'] = 'attributes'
8585
include_binary_content: bool = True
8686

@@ -90,7 +90,7 @@ def __init__(
9090
event_mode: Literal['attributes', 'logs'] = 'attributes',
9191
tracer_provider: TracerProvider | None = None,
9292
meter_provider: MeterProvider | None = None,
93-
event_logger_provider: EventLoggerProvider | None = None,
93+
event_logger_provider: LoggerProvider | None = None,
9494
include_binary_content: bool = True,
9595
include_content: bool = True,
9696
):
@@ -117,11 +117,11 @@ def __init__(
117117

118118
tracer_provider = tracer_provider or get_tracer_provider()
119119
meter_provider = meter_provider or get_meter_provider()
120-
event_logger_provider = event_logger_provider or get_event_logger_provider()
120+
event_logger_provider = event_logger_provider or get_logger_provider()
121121
scope_name = 'pydantic-ai'
122122
self.tracer = tracer_provider.get_tracer(scope_name, __version__)
123123
self.meter = meter_provider.get_meter(scope_name, __version__)
124-
self.event_logger = event_logger_provider.get_event_logger(scope_name, __version__)
124+
self.event_logger = event_logger_provider.get_logger(scope_name, __version__)
125125
self.event_mode = event_mode
126126
self.include_binary_content = include_binary_content
127127
self.include_content = include_content
@@ -144,7 +144,7 @@ def __init__(
144144
**tokens_histogram_kwargs, # pyright: ignore
145145
)
146146

147-
def messages_to_otel_events(self, messages: list[ModelMessage]) -> list[Event]:
147+
def messages_to_otel_events(self, messages: list[ModelMessage]) -> list[LogRecord]:
148148
"""Convert a list of model messages to OpenTelemetry events.
149149
150150
Args:
@@ -153,13 +153,15 @@ def messages_to_otel_events(self, messages: list[ModelMessage]) -> list[Event]:
153153
Returns:
154154
A list of OpenTelemetry events.
155155
"""
156-
events: list[Event] = []
156+
events: list[LogRecord] = []
157157
instructions = InstrumentedModel._get_instructions(messages) # pyright: ignore [reportPrivateUsage]
158158
if instructions is not None:
159-
events.append(Event('gen_ai.system.message', body={'content': instructions, 'role': 'system'}))
159+
events.append(
160+
LogRecord(event_name='gen_ai.system.message', body={'content': instructions, 'role': 'system'})
161+
)
160162

161163
for message_index, message in enumerate(messages):
162-
message_events: list[Event] = []
164+
message_events: list[LogRecord] = []
163165
if isinstance(message, ModelRequest):
164166
for part in message.parts:
165167
if hasattr(part, 'otel_event'):
@@ -297,8 +299,8 @@ def _record_metrics():
297299
events = self.instrumentation_settings.messages_to_otel_events(messages)
298300
for event in self.instrumentation_settings.messages_to_otel_events([response]):
299301
events.append(
300-
Event(
301-
'gen_ai.choice',
302+
LogRecord(
303+
event_name='gen_ai.choice',
302304
body={
303305
# TODO finish_reason
304306
'index': 0,
@@ -327,7 +329,7 @@ def _record_metrics():
327329
# to prevent them from being redundantly recorded in the span itself by logfire.
328330
record_metrics()
329331

330-
def _emit_events(self, span: Span, events: list[Event]) -> None:
332+
def _emit_events(self, span: Span, events: list[LogRecord]) -> None:
331333
if self.instrumentation_settings.event_mode == 'logs':
332334
for event in events:
333335
self.instrumentation_settings.event_logger.emit(event)
@@ -368,11 +370,11 @@ def model_attributes(model: Model):
368370
return attributes
369371

370372
@staticmethod
371-
def event_to_dict(event: Event) -> dict[str, Any]:
373+
def event_to_dict(event: LogRecord) -> dict[str, Any]:
372374
if not event.body:
373375
body = {} # pragma: no cover
374376
elif isinstance(event.body, Mapping):
375-
body = event.body # type: ignore
377+
body = event.body
376378
else:
377379
body = {'body': event.body}
378380
return {**body, **(event.attributes or {})}

pydantic_ai_slim/pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,15 +54,15 @@ dependencies = [
5454
"pydantic>=2.10",
5555
"pydantic-graph=={{ version }}",
5656
"exceptiongroup; python_version < '3.11'",
57-
"opentelemetry-api>=1.28.0",
57+
"opentelemetry-api>=1.35.0",
5858
"typing-inspection>=0.4.0",
5959
]
6060

6161
[tool.hatch.metadata.hooks.uv-dynamic-versioning.optional-dependencies]
6262
# WARNING if you add optional groups, please update docs/install.md
6363
logfire = ["logfire>=3.11.0"]
6464
# Models
65-
openai = ["openai>=1.86.0"]
65+
openai = ["openai>=1.92.0"]
6666
cohere = ["cohere>=5.16.0; platform_system != 'Emscripten'"]
6767
vertexai = ["google-auth>=2.36.0", "requests>=2.32.2"]
6868
google = ["google-genai>=1.24.0"]

tests/models/cassettes/test_model_names/test_known_model_names.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ interactions:
1515
response:
1616
headers:
1717
content-length:
18-
- '545'
18+
- '550'
1919
content-security-policy:
2020
- default-src 'none'; frame-ancestors 'none'
2121
content-type:
@@ -46,6 +46,7 @@ interactions:
4646
- text-to-text
4747
- model_id: claude-4-sonnet
4848
regions:
49+
- eu
4950
- us
5051
type:
5152
- text-to-text

tests/models/test_fallback.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ def test_first_failed_instrumented(capfire: CaptureLogfire) -> None:
135135
'gen_ai.usage.input_tokens': 51,
136136
'gen_ai.usage.output_tokens': 1,
137137
'gen_ai.response.model': 'function:success_response:',
138-
'events': '[{"content": "hello", "role": "user", "gen_ai.system": "function", "gen_ai.message.index": 0, "event.name": "gen_ai.user.message"}, {"index": 0, "message": {"role": "assistant", "content": "success"}, "gen_ai.system": "function", "event.name": "gen_ai.choice"}]',
138+
'events': '[{"content": "hello", "role": "user", "gen_ai.system": "function", "gen_ai.message.index": 0}, {"index": 0, "message": {"role": "assistant", "content": "success"}, "gen_ai.system": "function"}]',
139139
'logfire.json_schema': '{"type": "object", "properties": {"events": {"type": "array"}, "model_request_parameters": {"type": "object"}}}',
140140
},
141141
},
@@ -151,7 +151,7 @@ def test_first_failed_instrumented(capfire: CaptureLogfire) -> None:
151151
'logfire.msg': 'agent run',
152152
'logfire.span_type': 'span',
153153
'gen_ai.usage.input_tokens': 51,
154-
'all_messages_events': '[{"content": "hello", "role": "user", "gen_ai.message.index": 0, "event.name": "gen_ai.user.message"}, {"role": "assistant", "content": "success", "gen_ai.message.index": 1, "event.name": "gen_ai.assistant.message"}]',
154+
'all_messages_events': '[{"content": "hello", "role": "user", "gen_ai.message.index": 0}, {"role": "assistant", "content": "success", "gen_ai.message.index": 1}]',
155155
'gen_ai.usage.output_tokens': 1,
156156
'final_result': 'success',
157157
'logfire.json_schema': '{"type": "object", "properties": {"all_messages_events": {"type": "array"}, "final_result": {"type": "object"}}}',
@@ -208,7 +208,7 @@ async def test_first_failed_instrumented_stream(capfire: CaptureLogfire) -> None
208208
'gen_ai.usage.input_tokens': 50,
209209
'gen_ai.usage.output_tokens': 2,
210210
'gen_ai.response.model': 'function::success_response_stream',
211-
'events': '[{"content": "input", "role": "user", "gen_ai.system": "function", "gen_ai.message.index": 0, "event.name": "gen_ai.user.message"}, {"index": 0, "message": {"role": "assistant", "content": "hello world"}, "gen_ai.system": "function", "event.name": "gen_ai.choice"}]',
211+
'events': '[{"content": "input", "role": "user", "gen_ai.system": "function", "gen_ai.message.index": 0}, {"index": 0, "message": {"role": "assistant", "content": "hello world"}, "gen_ai.system": "function"}]',
212212
'logfire.json_schema': '{"type": "object", "properties": {"events": {"type": "array"}, "model_request_parameters": {"type": "object"}}}',
213213
},
214214
},
@@ -225,7 +225,7 @@ async def test_first_failed_instrumented_stream(capfire: CaptureLogfire) -> None
225225
'logfire.span_type': 'span',
226226
'gen_ai.usage.input_tokens': 50,
227227
'gen_ai.usage.output_tokens': 2,
228-
'all_messages_events': '[{"content": "input", "role": "user", "gen_ai.message.index": 0, "event.name": "gen_ai.user.message"}, {"role": "assistant", "content": "hello world", "gen_ai.message.index": 1, "event.name": "gen_ai.assistant.message"}]',
228+
'all_messages_events': '[{"content": "input", "role": "user", "gen_ai.message.index": 0}, {"role": "assistant", "content": "hello world", "gen_ai.message.index": 1}]',
229229
'logfire.json_schema': '{"type": "object", "properties": {"all_messages_events": {"type": "array"}, "final_result": {"type": "object"}}}',
230230
},
231231
},
@@ -277,6 +277,7 @@ 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:',
280281
},
281282
'events': [
282283
{
@@ -302,7 +303,7 @@ def test_all_failed_instrumented(capfire: CaptureLogfire) -> None:
302303
'agent_name': 'agent',
303304
'logfire.msg': 'agent run',
304305
'logfire.span_type': 'span',
305-
'all_messages_events': '[{"content": "hello", "role": "user", "gen_ai.message.index": 0, "event.name": "gen_ai.user.message"}]',
306+
'all_messages_events': '[{"content": "hello", "role": "user", "gen_ai.message.index": 0}]',
306307
'logfire.json_schema': '{"type": "object", "properties": {"all_messages_events": {"type": "array"}, "final_result": {"type": "object"}}}',
307308
'logfire.level_num': 17,
308309
},

0 commit comments

Comments
 (0)