Skip to content

Commit 7a3e157

Browse files
bdsaglamalexmojaki
andauthored
Add DSPy integration to logfire (#1625)
Co-authored-by: Alex Hall <[email protected]>
1 parent 8daff1b commit 7a3e157

File tree

12 files changed

+1777
-2
lines changed

12 files changed

+1777
-2
lines changed

docs/integrations/llms/dspy.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
---
2+
title: "Pydantic Logfire Integrations: DSPy"
3+
description: "Instrument DSPy with Pydantic Logfire using OpenInference for end-to-end LLM workflow tracing."
4+
integration: logfire
5+
---
6+
# DSPy
7+
8+
**Logfire** supports instrumenting [DSPy](https://dspy.ai/) with the
9+
[`logfire.instrument_dspy()`][logfire.Logfire.instrument_dspy] method.
10+
11+
## Installation
12+
13+
Install `logfire` with the `dspy` extra and the DSPy package:
14+
15+
{{ install_logfire(extras=['dspy']) }}
16+
17+
```bash
18+
pip install dspy-ai
19+
```
20+
21+
## Usage
22+
23+
```python hl_lines="6"
24+
import dspy
25+
26+
import logfire
27+
28+
logfire.configure()
29+
logfire.instrument_dspy()
30+
31+
lm = dspy.LM("openai/gpt-5-mini")
32+
dspy.configure(lm=lm)
33+
34+
class ExtractInfo(dspy.Signature):
35+
"""Extract structured information from text."""
36+
37+
text: str = dspy.InputField()
38+
title: str = dspy.OutputField()
39+
headings: list[str] = dspy.OutputField()
40+
entities: list[dict[str, str]] = dspy.OutputField(desc="a list of entities and their metadata")
41+
42+
module = dspy.Predict(ExtractInfo)
43+
44+
text = "Apple Inc. announced its latest iPhone 14 today." \
45+
"The CEO, Tim Cook, highlighted its new features in a press release."
46+
response = module(text=text)
47+
48+
print(response.title)
49+
print(response.headings)
50+
print(response.entities)
51+
```
52+
53+
[`logfire.instrument_dspy()`][logfire.Logfire.instrument_dspy] uses the `DSPyInstrumentor().instrument()` method of
54+
the [`openinference-instrumentation-dspy`](https://pypi.org/project/openinference-instrumentation-dspy/) package.

logfire-api/logfire_api/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,8 @@ def instrument_google_genai(self, *args, **kwargs) -> None: ...
191191

192192
def instrument_litellm(self, *args, **kwargs) -> None: ...
193193

194+
def instrument_dspy(self, *args, **kwargs) -> None: ...
195+
194196
def instrument_aiohttp_client(self, *args, **kwargs) -> None: ...
195197

196198
def instrument_aiohttp_server(self, *args, **kwargs) -> None: ...
@@ -229,6 +231,7 @@ def shutdown(self, *args, **kwargs) -> None: ...
229231
instrument_anthropic = DEFAULT_LOGFIRE_INSTANCE.instrument_anthropic
230232
instrument_google_genai = DEFAULT_LOGFIRE_INSTANCE.instrument_google_genai
231233
instrument_litellm = DEFAULT_LOGFIRE_INSTANCE.instrument_litellm
234+
instrument_dspy = DEFAULT_LOGFIRE_INSTANCE.instrument_dspy
232235
instrument_asyncpg = DEFAULT_LOGFIRE_INSTANCE.instrument_asyncpg
233236
instrument_print = DEFAULT_LOGFIRE_INSTANCE.instrument_print
234237
instrument_celery = DEFAULT_LOGFIRE_INSTANCE.instrument_celery

logfire-api/logfire_api/__init__.pyi

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ from logfire.propagate import attach_context as attach_context, get_context as g
1515
from logfire.sampling import SamplingOptions as SamplingOptions
1616
from typing import Any
1717

18-
__all__ = ['Logfire', 'LogfireSpan', 'LevelName', 'AdvancedOptions', 'ConsoleOptions', 'CodeSource', 'PydanticPlugin', 'configure', 'span', 'instrument', 'log', 'trace', 'debug', 'notice', 'info', 'warn', 'warning', 'error', 'exception', 'fatal', 'force_flush', 'log_slow_async_callbacks', 'install_auto_tracing', 'instrument_asgi', 'instrument_wsgi', 'instrument_pydantic', 'instrument_pydantic_ai', 'instrument_fastapi', 'instrument_openai', 'instrument_openai_agents', 'instrument_anthropic', 'instrument_google_genai', 'instrument_litellm', 'instrument_print', 'instrument_asyncpg', 'instrument_httpx', 'instrument_celery', 'instrument_requests', 'instrument_psycopg', 'instrument_django', 'instrument_flask', 'instrument_starlette', 'instrument_aiohttp_client', 'instrument_aiohttp_server', 'instrument_sqlalchemy', 'instrument_sqlite3', 'instrument_aws_lambda', 'instrument_redis', 'instrument_pymongo', 'instrument_mysql', 'instrument_surrealdb', 'instrument_system_metrics', 'instrument_mcp', 'AutoTraceModule', 'with_tags', 'with_settings', 'suppress_scopes', 'shutdown', 'no_auto_trace', 'ScrubMatch', 'ScrubbingOptions', 'VERSION', 'add_non_user_code_prefix', 'suppress_instrumentation', 'StructlogProcessor', 'LogfireLoggingHandler', 'loguru_handler', 'SamplingOptions', 'MetricsOptions', 'logfire_info', 'get_baggage', 'set_baggage', 'get_context', 'attach_context']
18+
__all__ = ['Logfire', 'LogfireSpan', 'LevelName', 'AdvancedOptions', 'ConsoleOptions', 'CodeSource', 'PydanticPlugin', 'configure', 'span', 'instrument', 'log', 'trace', 'debug', 'notice', 'info', 'warn', 'warning', 'error', 'exception', 'fatal', 'force_flush', 'log_slow_async_callbacks', 'install_auto_tracing', 'instrument_asgi', 'instrument_wsgi', 'instrument_pydantic', 'instrument_pydantic_ai', 'instrument_fastapi', 'instrument_openai', 'instrument_openai_agents', 'instrument_anthropic', 'instrument_google_genai', 'instrument_litellm', 'instrument_dspy', 'instrument_print', 'instrument_asyncpg', 'instrument_httpx', 'instrument_celery', 'instrument_requests', 'instrument_psycopg', 'instrument_django', 'instrument_flask', 'instrument_starlette', 'instrument_aiohttp_client', 'instrument_aiohttp_server', 'instrument_sqlalchemy', 'instrument_sqlite3', 'instrument_aws_lambda', 'instrument_redis', 'instrument_pymongo', 'instrument_mysql', 'instrument_surrealdb', 'instrument_system_metrics', 'instrument_mcp', 'AutoTraceModule', 'with_tags', 'with_settings', 'suppress_scopes', 'shutdown', 'no_auto_trace', 'ScrubMatch', 'ScrubbingOptions', 'VERSION', 'add_non_user_code_prefix', 'suppress_instrumentation', 'StructlogProcessor', 'LogfireLoggingHandler', 'loguru_handler', 'SamplingOptions', 'MetricsOptions', 'logfire_info', 'get_baggage', 'set_baggage', 'get_context', 'attach_context']
1919

2020
DEFAULT_LOGFIRE_INSTANCE = Logfire()
2121
span = DEFAULT_LOGFIRE_INSTANCE.span
@@ -33,6 +33,7 @@ instrument_openai_agents = DEFAULT_LOGFIRE_INSTANCE.instrument_openai_agents
3333
instrument_anthropic = DEFAULT_LOGFIRE_INSTANCE.instrument_anthropic
3434
instrument_google_genai = DEFAULT_LOGFIRE_INSTANCE.instrument_google_genai
3535
instrument_litellm = DEFAULT_LOGFIRE_INSTANCE.instrument_litellm
36+
instrument_dspy = DEFAULT_LOGFIRE_INSTANCE.instrument_dspy
3637
instrument_print = DEFAULT_LOGFIRE_INSTANCE.instrument_print
3738
instrument_asyncpg = DEFAULT_LOGFIRE_INSTANCE.instrument_asyncpg
3839
instrument_httpx = DEFAULT_LOGFIRE_INSTANCE.instrument_httpx

logfire/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
instrument_anthropic = DEFAULT_LOGFIRE_INSTANCE.instrument_anthropic
3838
instrument_google_genai = DEFAULT_LOGFIRE_INSTANCE.instrument_google_genai
3939
instrument_litellm = DEFAULT_LOGFIRE_INSTANCE.instrument_litellm
40+
instrument_dspy = DEFAULT_LOGFIRE_INSTANCE.instrument_dspy
4041
instrument_print = DEFAULT_LOGFIRE_INSTANCE.instrument_print
4142
instrument_asyncpg = DEFAULT_LOGFIRE_INSTANCE.instrument_asyncpg
4243
instrument_httpx = DEFAULT_LOGFIRE_INSTANCE.instrument_httpx
@@ -132,6 +133,7 @@ def loguru_handler() -> Any:
132133
'instrument_anthropic',
133134
'instrument_google_genai',
134135
'instrument_litellm',
136+
'instrument_dspy',
135137
'instrument_print',
136138
'instrument_asyncpg',
137139
'instrument_httpx',
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from typing import Any
2+
3+
import logfire
4+
5+
try:
6+
from openinference.instrumentation.dspy import DSPyInstrumentor
7+
except ImportError:
8+
raise RuntimeError(
9+
'The `logfire.instrument_dspy()` method '
10+
'requires the `openinference-instrumentation-dspy` package.\n'
11+
'You can install this with:\n'
12+
" pip install 'logfire[dspy]'"
13+
)
14+
15+
16+
def instrument_dspy(logfire_instance: logfire.Logfire, **kwargs: Any):
17+
DSPyInstrumentor().instrument(
18+
tracer_provider=logfire_instance.config.get_tracer_provider(),
19+
**kwargs,
20+
)

logfire/_internal/main.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1375,6 +1375,18 @@ def instrument_litellm(self, **kwargs: Any):
13751375
self._warn_if_not_initialized_for_instrumentation()
13761376
instrument_litellm(self, **kwargs)
13771377

1378+
def instrument_dspy(self, **kwargs: Any):
1379+
"""Instrument [DSPy](https://dspy.ai/).
1380+
1381+
Uses the `DSPyInstrumentor().instrument()` method of the
1382+
[`openinference-instrumentation-dspy`](https://pypi.org/project/openinference-instrumentation-dspy/)
1383+
package, to which it passes `**kwargs`.
1384+
"""
1385+
from .integrations.dspy import instrument_dspy
1386+
1387+
self._warn_if_not_initialized_for_instrumentation()
1388+
instrument_dspy(self, **kwargs)
1389+
13781390
def instrument_print(self) -> AbstractContextManager[None]:
13791391
"""Instrument the built-in `print` function so that calls to it are logged.
13801392

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ nav:
120120
- Anthropic: integrations/llms/anthropic.md
121121
- LangChain: integrations/llms/langchain.md
122122
- LiteLLM: integrations/llms/litellm.md
123+
- DSPy: integrations/llms/dspy.md
123124
- MCP: integrations/llms/mcp.md
124125
- Claude Agent SDK: integrations/llms/claude-agent-sdk.md
125126
- LLamaIndex: integrations/llms/llamaindex.md

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ sqlite3 = ["opentelemetry-instrumentation-sqlite3 >= 0.42b0"]
8181
aws-lambda = ["opentelemetry-instrumentation-aws-lambda >= 0.42b0"]
8282
google-genai = ["opentelemetry-instrumentation-google-genai >= 0.4b0"]
8383
litellm = ["openinference-instrumentation-litellm >= 0"]
84+
dspy = ["openinference-instrumentation-dspy >= 0"]
8485

8586
[project.urls]
8687
Homepage = "https://logfire.pydantic.dev/"
@@ -183,6 +184,8 @@ dev = [
183184
"google-genai >= 0",
184185
"openinference-instrumentation-litellm >= 0",
185186
"litellm != 1.80.9",
187+
"openinference-instrumentation-dspy >= 0; python_version >= '3.10'",
188+
"dspy >= 3.1.0; python_version >= '3.10'",
186189
"pip >= 0",
187190
"surrealdb >= 0",
188191
]

0 commit comments

Comments
 (0)