-
Notifications
You must be signed in to change notification settings - Fork 840
improve instrumentation docs #1625
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
bc6a341
e5a7d6b
32a6ca8
eb0c853
9831316
96d4b76
826287f
ba76fc8
ad4e358
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,7 +15,7 @@ LLM Observability tools that just let you understand how your model is performin | |
|
||
## Pydantic Logfire | ||
|
||
[Pydantic Logfire](https://pydantic.dev/logfire) is an observability platform developed by the team who created and maintain Pydantic and PydanticAI. Logfire aims to let you understand your entire application: Gen AI, classic predictive AI, HTTP traffic, database queries and everything else a modern application needs. | ||
[Pydantic Logfire](https://pydantic.dev/logfire) is an observability platform developed by the team who created and maintain Pydantic and PydanticAI. Logfire aims to let you understand your entire application: Gen AI, classic predictive AI, HTTP traffic, database queries and everything else a modern application needs, all using OpenTelemetry. | ||
|
||
!!! tip "Pydantic Logfire is a commercial product" | ||
Logfire is a commercially supported, hosted platform with an extremely generous and perpetual [free tier](https://pydantic.dev/pricing/). | ||
|
@@ -27,15 +27,17 @@ Here's an example showing details of running the [Weather Agent](examples/weathe | |
|
||
 | ||
|
||
A trace is generated for the agent run, and spans are emitted for each model request and tool call. | ||
|
||
## Using Logfire | ||
|
||
To use logfire, you'll need a logfire [account](https://logfire.pydantic.dev), and logfire installed: | ||
To use Logfire, you'll need a Logfire [account](https://logfire.pydantic.dev), and the Logfire Python SDK installed: | ||
|
||
```bash | ||
pip/uv-add "pydantic-ai[logfire]" | ||
``` | ||
|
||
Then authenticate your local environment with logfire: | ||
Then authenticate your local environment with Logfire: | ||
|
||
```bash | ||
py-cli logfire auth | ||
|
@@ -49,34 +51,40 @@ py-cli logfire projects new | |
|
||
(Or use an existing project with `logfire projects use`) | ||
|
||
Then add logfire to your code: | ||
|
||
```python {title="adding_logfire.py"} | ||
import logfire | ||
This will write to a `.logfire` directory in the current working directory, which the Logfire SDK will use for configuration at run time. | ||
|
||
logfire.configure() | ||
``` | ||
With that, you can start using Logfire to instrument PydanticAI code: | ||
|
||
and enable instrumentation in your agent: | ||
```python {title="instrument_pydantic_ai.py" hl_lines="1 5 6"} | ||
import logfire | ||
|
||
```python {title="instrument_agent.py"} | ||
from pydantic_ai import Agent | ||
|
||
agent = Agent('openai:gpt-4o', instrument=True) | ||
# or instrument all agents to avoid needing to add `instrument=True` to each agent: | ||
Agent.instrument_all() | ||
logfire.configure() # (1)! | ||
logfire.instrument_pydantic_ai() # (2)! | ||
|
||
agent = Agent('openai:gpt-4o', instructions='Be concise, reply with one sentence.') | ||
result = agent.run_sync('Where does "hello world" come from?') # (3)! | ||
print(result.output) | ||
""" | ||
The first known use of "hello, world" was in a 1974 textbook about the C programming language. | ||
""" | ||
``` | ||
|
||
The [logfire documentation](https://logfire.pydantic.dev/docs/) has more details on how to use logfire, | ||
including how to instrument other libraries like [Pydantic](https://logfire.pydantic.dev/docs/integrations/pydantic/), | ||
[HTTPX](https://logfire.pydantic.dev/docs/integrations/http-clients/httpx/) and [FastAPI](https://logfire.pydantic.dev/docs/integrations/web-frameworks/fastapi/). | ||
1. [`logfire.configure()`][logfire.configure] configures the SDK, by default it will find the write token from the `.logfire` directory, but you can also pass a token directly. | ||
2. [`logfire.instrument_pydantic_ai()`][logfire.Logfire.instrument_pydantic_ai] enables instrumentation of PydanticAI. | ||
3. Since we've enabled instrumentation, a trace will be generated for each run, with spans emitted for models calls and tool function execution | ||
|
||
_(This example is complete, it can be run "as is")_ | ||
|
||
Since Logfire is built on [OpenTelemetry](https://opentelemetry.io/), you can use the Logfire Python SDK to send data to any OpenTelemetry collector. | ||
Which will display in Logfire thus: | ||
|
||
Once you have logfire set up, there are two primary ways it can help you understand your application: | ||
 | ||
|
||
* **Debugging** — Using the live view to see what's happening in your application in real-time. | ||
* **Monitoring** — Using SQL and dashboards to observe the behavior of your application, Logfire is effectively a SQL database that stores information about how your application is running. | ||
The [logfire documentation](https://logfire.pydantic.dev/docs/) has more details on how to use Logfire, | ||
including how to instrument other libraries like [HTTPX](https://logfire.pydantic.dev/docs/integrations/http-clients/httpx/) and [FastAPI](https://logfire.pydantic.dev/docs/integrations/web-frameworks/fastapi/). | ||
|
||
Since Logfire is built on [OpenTelemetry](https://opentelemetry.io/), you can use the Logfire Python SDK to send data to any OpenTelemetry collector, see [below](#using-opentelemetry). | ||
|
||
### Debugging | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The video here really needs updating |
||
|
||
|
@@ -90,65 +98,161 @@ We can also query data with SQL in Logfire to monitor the performance of an appl | |
|
||
 | ||
|
||
### Monitoring HTTPX Requests | ||
### Monitoring HTTP Requests | ||
|
||
In order to monitor HTTPX requests made by models, you can use `logfire`'s [HTTPX](https://logfire.pydantic.dev/docs/integrations/http-clients/httpx/) integration. | ||
!!! tip ""F**k you, show me the prompt."" | ||
As per Hamel Husain's influential 2024 blog post ["Fuck You, Show Me The Prompt."](https://hamel.dev/blog/posts/prompt/) | ||
samuelcolvin marked this conversation as resolved.
Show resolved
Hide resolved
|
||
(bear with the capitalization, the point is valid), it's often useful to be able to view the raw HTTP requests and responses made to model providers. | ||
|
||
Instrumentation is as easy as adding the following three lines to your application: | ||
To observe raw HTTP requests made to model providers, you can use `logfire`'s [HTTPX instrumentation](https://logfire.pydantic.dev/docs/integrations/http-clients/httpx/) since all provider SDKs use the [HTTPX](https://www.python-httpx.org/) library internally. | ||
|
||
```py {title="instrument_httpx.py" test="skip" lint="skip"} | ||
import logfire | ||
logfire.configure() | ||
logfire.instrument_httpx(capture_all=True) # (1)! | ||
|
||
=== "With HTTP instrumentation" | ||
|
||
```py {title="with_logfire_instrument_httpx.py" hl_lines="7"} | ||
import logfire | ||
|
||
from pydantic_ai import Agent | ||
|
||
logfire.configure() | ||
logfire.instrument_pydantic_ai() | ||
logfire.instrument_httpx(capture_all=True) # (1)! | ||
agent = Agent('openai:gpt-4o') | ||
result = agent.run_sync('What is the capital of France?') | ||
print(result.output) | ||
#> Paris | ||
``` | ||
|
||
1. See the [`logfire.instrument_httpx` docs][logfire.Logfire.instrument_httpx] more details, `capture_all=True` means both headers and body are captured for both the request and response. | ||
|
||
 | ||
|
||
=== "Without HTTP instrumentation" | ||
|
||
```py {title="without_logfire_instrument_httpx.py"} | ||
import logfire | ||
|
||
from pydantic_ai import Agent | ||
|
||
logfire.configure() | ||
logfire.instrument_pydantic_ai() | ||
|
||
agent = Agent('openai:gpt-4o') | ||
result = agent.run_sync('What is the capital of France?') | ||
print(result.output) | ||
#> Paris | ||
``` | ||
|
||
 | ||
|
||
## Using OpenTelemetry | ||
|
||
PydanticAI's instrumentation uses [OpenTelemetry](https://opentelemetry.io/) (OTel), which Logfire is based on. | ||
|
||
This means you can debug and monitor PydanticAI with any OpenTelemetry backend. | ||
|
||
PydanticAI follows the [OpenTelemetry Semantic Conventions for Generative AI systems](https://opentelemetry.io/docs/specs/semconv/gen-ai/), so while we think you'll have the best experience using the Logfire platform :wink:, you should be able to use any OTel service with GenAI support. | ||
|
||
### Logfire with an alternative OTel backend | ||
|
||
You can use the Logfire SDK completely freely and send the data to any OpenTelemetry backend. | ||
|
||
Here's an example of configuring the Logfire library to send data to the excellent [otel-tui](https://github.com/ymtdzzz/otel-tui) — an open source terminal based OTel backend and viewer (no association with Pydantic). | ||
|
||
Run `otel-tui` with docker (see [the otel-tui readme](https://github.com/ymtdzzz/otel-tui) for more instructions): | ||
|
||
```txt title="Terminal" | ||
docker run --rm -it -p 4318:4318 --name otel-tui ymtdzzz/otel-tui:latest | ||
``` | ||
|
||
1. See the [logfire docs](https://logfire.pydantic.dev/docs/integrations/http-clients/httpx/) for more `httpx` instrumentation details. | ||
then run, | ||
|
||
In particular, this can help you to trace specific requests, responses, and headers: | ||
```python {title="otel_tui.py" hl_lines="7 8" test="skip"} | ||
import os | ||
|
||
```py {title="instrument_httpx_example.py", test="skip" lint="skip"} | ||
import logfire | ||
|
||
from pydantic_ai import Agent | ||
|
||
logfire.configure() | ||
logfire.instrument_httpx(capture_all=True) # (1)! | ||
os.environ['OTEL_EXPORTER_OTLP_ENDPOINT'] = 'http://localhost:4318' # (1)! | ||
logfire.configure(send_to_logfire=False) # (2)! | ||
logfire.instrument_pydantic_ai() | ||
logfire.instrument_httpx(capture_all=True) | ||
|
||
agent = Agent('openai:gpt-4o', instrument=True) | ||
agent = Agent('openai:gpt-4o') | ||
result = agent.run_sync('What is the capital of France?') | ||
print(result.output) | ||
# > The capital of France is Paris. | ||
#> Paris | ||
``` | ||
|
||
1. Capture all of headers, request body, and response body. | ||
1. Set the `OTEL_EXPORTER_OTLP_ENDPOINT` environment variable to the URL of your OpenTelemetry backend. If you're using a backend that requires authentication, you may need to set [other environment variables](https://opentelemetry.io/docs/languages/sdk-configuration/otlp-exporter/). Of course, these can also be set outside the process, e.g. with `export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318`. | ||
2. We [configure][logfire.configure] Logfire to disable sending data to the Logfire OTel backend itself. If you removed `send_to_logfire=False`, data would be sent to both Logfire and your OpenTelemetry backend. | ||
|
||
=== "With `httpx` instrumentation" | ||
Running the above code will send tracing data to `otel-tui`, which will display like this: | ||
|
||
 | ||
 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is missing the httpx instrumentation spans There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's right. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
||
=== "Without `httpx` instrumentation" | ||
Running the [weather agent](examples/weather-agent.md) example connected to `otel-tui` shows how it can be used to visualise a more complex trace: | ||
|
||
 | ||
 | ||
|
||
!!! tip | ||
`httpx` instrumentation might be of particular utility if you're using a custom `httpx` client in your model in order to get insights into your custom requests. | ||
For more information on using the Logfire SDK to send data to alternative backends, see | ||
[the Logfire documentation](https://logfire.pydantic.dev/docs/how-to-guides/alternative-backends/). | ||
|
||
## Using OpenTelemetry | ||
### OTel without Logfire | ||
|
||
You can also emit OpenTelemetry data from PydanticAI without using Logfire at all. | ||
|
||
To do this, you'll need to install and configure the OpenTelemetry packages you need. To run the following examples, use | ||
|
||
PydanticAI's instrumentation uses [OpenTelemetry](https://opentelemetry.io/), which Logfire is based on. You can use the Logfire SDK completely freely and follow the [Alternative backends](https://logfire.pydantic.dev/docs/how-to-guides/alternative-backends/) guide to send the data to any OpenTelemetry collector, such as a self-hosted Jaeger instance. Or you can skip Logfire entirely and use the OpenTelemetry Python SDK directly. | ||
```txt title="Terminal" | ||
uv run \ | ||
--with 'pydantic-ai-slim[openai]' \ | ||
--with opentelemetry-sdk \ | ||
--with opentelemetry-exporter-otlp \ | ||
raw_otel.py | ||
``` | ||
|
||
```python {title="raw_otel.py" test="skip"} | ||
import os | ||
|
||
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter | ||
from opentelemetry.sdk.trace import TracerProvider | ||
from opentelemetry.sdk.trace.export import BatchSpanProcessor | ||
from opentelemetry.trace import set_tracer_provider | ||
|
||
from pydantic_ai.agent import Agent | ||
|
||
os.environ['OTEL_EXPORTER_OTLP_ENDPOINT'] = 'http://localhost:4318' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I understand it might still be worth showing this this way, but I just want to point out that:
|
||
exporter = OTLPSpanExporter() | ||
span_processor = BatchSpanProcessor(exporter) | ||
tracer_provider = TracerProvider() | ||
tracer_provider.add_span_processor(span_processor) | ||
|
||
set_tracer_provider(tracer_provider) | ||
|
||
Agent.instrument_all() | ||
agent = Agent('openai:gpt-4o') | ||
result = agent.run_sync('What is the capital of France?') | ||
print(result.output) | ||
#> Paris | ||
``` | ||
|
||
## Data format | ||
|
||
PydanticAI follows the [OpenTelemetry Semantic Conventions for Generative AI systems](https://opentelemetry.io/docs/specs/semconv/gen-ai/), with one caveat. The semantic conventions specify that messages should be captured as individual events (logs) that are children of the request span. By default, PydanticAI instead collects these events into a JSON array which is set as a single large attribute called `events` on the request span. To change this, use [`InstrumentationSettings(event_mode='logs')`][pydantic_ai.agent.InstrumentationSettings]. | ||
|
||
```python {title="instrumentation_settings_event_mode.py"} | ||
from pydantic_ai import Agent | ||
from pydantic_ai.agent import InstrumentationSettings | ||
import logfire | ||
|
||
instrumentation_settings = InstrumentationSettings(event_mode='logs') | ||
from pydantic_ai import Agent | ||
|
||
agent = Agent('openai:gpt-4o', instrument=instrumentation_settings) | ||
# or instrument all agents: | ||
Agent.instrument_all(instrumentation_settings) | ||
logfire.configure() | ||
logfire.instrument_pydantic_ai(event_mode='logs') | ||
agent = Agent('openai:gpt-4o') | ||
result = agent.run_sync('What is the capital of France?') | ||
print(result.output) | ||
#> Paris | ||
``` | ||
|
||
For now, this won't look as good in the Logfire UI, but we're working on it. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems like you've removed all mentions of
Agent(instrument=...)
which seems extremeThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's still in the API docs.