Skip to content

Commit 5bb4bbc

Browse files
authored
Update logfire.instrument_pydantic_ai() parameters (#1480)
1 parent 4c1abdf commit 5bb4bbc

File tree

3 files changed

+82
-26
lines changed

3 files changed

+82
-26
lines changed

logfire/_internal/integrations/pydantic_ai.py

Lines changed: 38 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import annotations
22

3+
import inspect
34
from typing import Any, Literal
45

56
from pydantic_ai import Agent
@@ -13,27 +14,47 @@
1314
def instrument_pydantic_ai(
1415
logfire_instance: Logfire,
1516
obj: Agent | Model | None,
17+
include_binary_content: bool | None,
18+
include_content: bool | None,
19+
version: Literal[1, 2, 3] | None,
1620
event_mode: Literal['attributes', 'logs'] | None,
1721
**kwargs: Any,
1822
) -> None | InstrumentedModel:
19-
if event_mode is None: # pragma: no branch
20-
event_mode = InstrumentationSettings.event_mode
21-
kwargs = dict(
22-
tracer_provider=logfire_instance.config.get_tracer_provider(),
23-
event_logger_provider=logfire_instance.config.get_event_logger_provider(),
24-
event_mode=event_mode,
25-
**kwargs,
23+
# Correctly handling all past and future versions is tricky.
24+
# Since we provide these rather than the user, only include them if
25+
# InstrumentationSettings has such parameters to prevent errors/warnings.
26+
expected_kwarg_names = inspect.signature(InstrumentationSettings.__init__).parameters
27+
final_kwargs: dict[str, Any] = {
28+
k: v
29+
for k, v in dict(
30+
tracer_provider=logfire_instance.config.get_tracer_provider(),
31+
meter_provider=logfire_instance.config.get_meter_provider(), # not in old versions
32+
event_logger_provider=logfire_instance.config.get_event_logger_provider(), # may be removed in the future
33+
).items()
34+
if k in expected_kwarg_names
35+
}
36+
37+
# Now add known parameters that the user provided explicitly.
38+
# None of these have `None` as a valid value, so we assume the user didn't pass that.
39+
# Include even if not in expected_kwarg_names, to let Pydantic AI produce an error or warning.
40+
final_kwargs.update(
41+
{
42+
k: v
43+
for k, v in dict(
44+
include_binary_content=include_binary_content,
45+
include_content=include_content,
46+
version=version,
47+
event_mode=event_mode,
48+
).items()
49+
if v is not None
50+
}
2651
)
27-
try:
28-
settings = InstrumentationSettings(
29-
meter_provider=logfire_instance.config.get_meter_provider(),
30-
**kwargs,
31-
)
32-
except TypeError: # pragma: no cover
33-
# Handle older pydantic-ai versions that do not support meter_provider.
34-
settings = InstrumentationSettings(
35-
**kwargs,
36-
)
52+
53+
# Finally, add any other kwargs the user provided. These are mainly for future compatibility.
54+
# They also provide an escape hatch to override the tracer/meter/event_logger providers.
55+
final_kwargs.update(kwargs)
56+
57+
settings = InstrumentationSettings(**final_kwargs)
3758
if isinstance(obj, Agent):
3859
obj.instrument = settings
3960
elif isinstance(obj, Model):

logfire/_internal/main.py

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -988,8 +988,10 @@ def instrument_pydantic_ai(
988988
obj: pydantic_ai.Agent | None = None,
989989
/,
990990
*,
991-
event_mode: Literal['attributes', 'logs'] = 'attributes',
992991
include_binary_content: bool | None = None,
992+
include_content: bool | None = None,
993+
version: Literal[1, 2, 3] | None = None,
994+
event_mode: Literal['attributes', 'logs'] | None = None,
993995
**kwargs: Any,
994996
) -> None: ...
995997

@@ -999,8 +1001,10 @@ def instrument_pydantic_ai(
9991001
obj: pydantic_ai.models.Model,
10001002
/,
10011003
*,
1002-
event_mode: Literal['attributes', 'logs'] = 'attributes',
10031004
include_binary_content: bool | None = None,
1005+
include_content: bool | None = None,
1006+
version: Literal[1, 2, 3] | None = None,
1007+
event_mode: Literal['attributes', 'logs'] | None = None,
10041008
**kwargs: Any,
10051009
) -> pydantic_ai.models.Model: ...
10061010

@@ -1009,8 +1013,10 @@ def instrument_pydantic_ai(
10091013
obj: pydantic_ai.Agent | pydantic_ai.models.Model | None = None,
10101014
/,
10111015
*,
1012-
event_mode: Literal['attributes', 'logs'] | None = None,
10131016
include_binary_content: bool | None = None,
1017+
include_content: bool | None = None,
1018+
version: Literal[1, 2, 3] | None = None,
1019+
event_mode: Literal['attributes', 'logs'] | None = None,
10141020
**kwargs: Any,
10151021
) -> pydantic_ai.models.Model | None:
10161022
"""Instrument Pydantic AI.
@@ -1020,10 +1026,24 @@ def instrument_pydantic_ai(
10201026
By default, all agents are instrumented.
10211027
You can also pass a specific model or agent.
10221028
If you pass a model, a new instrumented model will be returned.
1023-
event_mode: See the [Pydantic AI docs](https://ai.pydantic.dev/logfire/#data-format).
1024-
The default is whatever the default is in your version of Pydantic AI.
1025-
include_binary_content: Whether to include base64 encoded binary content (e.g. images) in the events.
1029+
include_binary_content: Whether to include base64 encoded binary content (e.g. images) in the telemetry.
10261030
On by default. Requires Pydantic AI 0.2.5 or newer.
1031+
include_content: Whether to include prompts, completions, and tool call arguments and responses
1032+
in the telemetry. On by default. Requires Pydantic AI 0.3.4 or newer.
1033+
version: Version of the data format. This is unrelated to the Pydantic AI package version.
1034+
Requires Pydantic AI 0.7.5 or newer.
1035+
Version 1 is based on the legacy event-based OpenTelemetry GenAI spec
1036+
and will be removed in a future release.
1037+
The parameter `event_mode` is only relevant for version 1.
1038+
Version 2 uses the newer OpenTelemetry GenAI spec and stores messages in the following attributes:
1039+
- `gen_ai.system_instructions` for instructions passed to the agent.
1040+
- `gen_ai.input.messages` and `gen_ai.output.messages` on model request spans.
1041+
- `pydantic_ai.all_messages` on agent run spans.
1042+
Version 3 changes the names of some attributes and spans but not the shape of the data.
1043+
The default version depends on Pydantic AI.
1044+
event_mode: The mode for emitting events in version 1.
1045+
If `'attributes'`, events are attached to the span as attributes.
1046+
If `'logs'`, events are emitted as OpenTelemetry log-based events.
10271047
kwargs: Additional keyword arguments to pass to
10281048
[`InstrumentationSettings`](https://ai.pydantic.dev/api/models/instrumented/#pydantic_ai.models.instrumented.InstrumentationSettings)
10291049
for future compatibility.
@@ -1032,13 +1052,13 @@ def instrument_pydantic_ai(
10321052

10331053
self._warn_if_not_initialized_for_instrumentation()
10341054

1035-
if include_binary_content is not None:
1036-
kwargs['include_binary_content'] = include_binary_content
1037-
10381055
return instrument_pydantic_ai(
10391056
self,
10401057
obj=obj,
10411058
event_mode=event_mode,
1059+
version=version,
1060+
include_content=include_content,
1061+
include_binary_content=include_binary_content,
10421062
**kwargs,
10431063
)
10441064

tests/otel_integrations/test_pydantic_ai.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,21 @@ def get_model(a: Agent):
7878
m2 = get_model(agent2)
7979
assert m2 is model
8080

81+
# Test all known parameters
82+
logfire_inst.instrument_pydantic_ai(
83+
include_binary_content=False,
84+
include_content=False,
85+
version=1,
86+
event_mode='logs',
87+
)
88+
m = get_model(agent2)
89+
assert isinstance(m, InstrumentedModel)
90+
assert m.instrumentation_settings.version == 1
91+
assert not m.instrumentation_settings.include_binary_content
92+
assert not m.instrumentation_settings.include_content
93+
assert m.instrumentation_settings.event_mode == 'logs'
94+
Agent.instrument_all(False)
95+
8196

8297
def test_invalid_instrument_pydantic_ai():
8398
with pytest.raises(TypeError):

0 commit comments

Comments
 (0)