Skip to content

Commit fd90fc6

Browse files
authored
Document InstrumentedModel (#1187)
1 parent 2b1aee8 commit fd90fc6

File tree

8 files changed

+42
-12
lines changed

8 files changed

+42
-12
lines changed

docs/api/models/instrumented.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# pydantic_ai.models.instrumented
2+
3+
::: pydantic_ai.models.instrumented

docs/api/models/wrapper.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# pydantic_ai.models.wrapper
2+
3+
::: pydantic_ai.models.wrapper

docs/logfire.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ Note that the OpenTelemetry Semantic Conventions are still experimental and are
159159

160160
## Setting OpenTelemetry SDK providers
161161

162-
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`:
162+
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].
163163

164164
```python {title="instrumentation_settings_providers.py"}
165165
from opentelemetry.sdk._events import EventLoggerProvider
@@ -172,3 +172,14 @@ instrumentation_settings = InstrumentationSettings(
172172
event_logger_provider=EventLoggerProvider(),
173173
)
174174
```
175+
176+
## Instrumenting a specific `Model`
177+
178+
```python {title="instrumented_model_example.py"}
179+
from pydantic_ai import Agent
180+
from pydantic_ai.models.instrumented import InstrumentationSettings, InstrumentedModel
181+
182+
settings = InstrumentationSettings()
183+
model = InstrumentedModel('gpt-4o', settings)
184+
agent = Agent(model)
185+
```

mkdocs.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,12 @@ nav:
6565
- api/models/gemini.md
6666
- api/models/vertexai.md
6767
- api/models/groq.md
68+
- api/models/instrumented.md
6869
- api/models/mistral.md
6970
- api/models/test.md
7071
- api/models/function.md
7172
- api/models/fallback.md
73+
- api/models/wrapper.md
7274
- api/providers.md
7375
- api/pydantic_graph/graph.md
7476
- api/pydantic_graph/nodes.md

pydantic_ai_slim/pydantic_ai/agent.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ def __init__(
195195
If this isn't set, then the last value set by
196196
[`Agent.instrument_all()`][pydantic_ai.Agent.instrument_all]
197197
will be used, which defaults to False.
198+
See the [Debugging and Monitoring guide](https://ai.pydantic.dev/logfire/) for more info.
198199
"""
199200
if model is None or defer_model_check:
200201
self.model = model
@@ -445,7 +446,7 @@ async def main():
445446
usage_limits = usage_limits or _usage.UsageLimits()
446447

447448
if isinstance(model_used, InstrumentedModel):
448-
tracer = model_used.options.tracer
449+
tracer = model_used.settings.tracer
449450
else:
450451
tracer = NoOpTracer()
451452
agent_name = self.name or 'agent'

pydantic_ai_slim/pydantic_ai/models/instrumented.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,9 @@ class InstrumentationSettings:
5252
5353
- `Agent(instrument=...)`
5454
- [`Agent.instrument_all()`][pydantic_ai.agent.Agent.instrument_all]
55-
- `InstrumentedModel`
55+
- [`InstrumentedModel`][pydantic_ai.models.instrumented.InstrumentedModel]
56+
57+
See the [Debugging and Monitoring guide](https://ai.pydantic.dev/logfire/) for more info.
5658
"""
5759

5860
tracer: Tracer = field(repr=False)
@@ -94,17 +96,21 @@ def __init__(
9496

9597
@dataclass
9698
class InstrumentedModel(WrapperModel):
97-
"""Model which is instrumented with OpenTelemetry."""
99+
"""Model which wraps another model so that requests are instrumented with OpenTelemetry.
100+
101+
See the [Debugging and Monitoring guide](https://ai.pydantic.dev/logfire/) for more info.
102+
"""
98103

99-
options: InstrumentationSettings
104+
settings: InstrumentationSettings
105+
"""Configuration for instrumenting requests."""
100106

101107
def __init__(
102108
self,
103109
wrapped: Model | KnownModelName,
104110
options: InstrumentationSettings | None = None,
105111
) -> None:
106112
super().__init__(wrapped)
107-
self.options = options or InstrumentationSettings()
113+
self.settings = options or InstrumentationSettings()
108114

109115
async def request(
110116
self,
@@ -156,7 +162,7 @@ def _instrument(
156162
if isinstance(value := model_settings.get(key), (float, int)):
157163
attributes[f'gen_ai.request.{key}'] = value
158164

159-
with self.options.tracer.start_as_current_span(span_name, attributes=attributes) as span:
165+
with self.settings.tracer.start_as_current_span(span_name, attributes=attributes) as span:
160166

161167
def finish(response: ModelResponse, usage: Usage):
162168
if not span.is_recording():
@@ -190,9 +196,9 @@ def finish(response: ModelResponse, usage: Usage):
190196
yield finish
191197

192198
def _emit_events(self, span: Span, events: list[Event]) -> None:
193-
if self.options.event_mode == 'logs':
199+
if self.settings.event_mode == 'logs':
194200
for event in events:
195-
self.options.event_logger.emit(event)
201+
self.settings.event_logger.emit(event)
196202
else:
197203
attr_name = 'events'
198204
span.set_attributes(

pydantic_ai_slim/pydantic_ai/models/wrapper.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,13 @@
1313

1414
@dataclass(init=False)
1515
class WrapperModel(Model):
16-
"""Model which wraps another model."""
16+
"""Model which wraps another model.
17+
18+
Does nothing on its own, used as a base class.
19+
"""
1720

1821
wrapped: Model
22+
"""The underlying model being wrapped."""
1923

2024
def __init__(self, wrapped: Model | KnownModelName):
2125
self.wrapped = infer_model(wrapped)

tests/test_logfire.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -230,14 +230,14 @@ def get_model():
230230
m = get_model()
231231
assert isinstance(m, InstrumentedModel)
232232
assert m.wrapped is model
233-
assert m.options.event_mode == InstrumentationSettings().event_mode
233+
assert m.settings.event_mode == InstrumentationSettings().event_mode
234234

235235
options = InstrumentationSettings(event_mode='logs')
236236
Agent.instrument_all(options)
237237
m = get_model()
238238
assert isinstance(m, InstrumentedModel)
239239
assert m.wrapped is model
240-
assert m.options is options
240+
assert m.settings is options
241241

242242
Agent.instrument_all(False)
243243
assert get_model() is model

0 commit comments

Comments
 (0)