Custom DSPy LM class for token usage tracking with Langfuse
Project description
langfuse-instrumented-dspy-lm
Track token usage and costs in your DSPy + Langfuse applications.
Using DSPy with Langfuse but not seeing token counts or costs in your traces? You're not alone. The standard integration doesn't capture this data. This library fixes that with a drop-in replacement for dspy.LM.
Quick Start
Follow the Langfuse DSPy integration guide, but use LangfuseInstrumentedLm instead of dspy.LM in the final step.
Step 1: Install Packages
pip install langfuse dspy openinference-instrumentation-dspy langfuse-instrumented-dspy-lm
Step 2: Configure Environment
import os
os.environ["LANGFUSE_PUBLIC_KEY"] = "pk-lf-..."
os.environ["LANGFUSE_SECRET_KEY"] = "sk-lf-..."
os.environ["LANGFUSE_HOST"] = "https://cloud.langfuse.com" # or your self-hosted URL
# Your LLM provider API key
os.environ["OPENAI_API_KEY"] = "sk-..." # or ANTHROPIC_API_KEY, etc.
Step 3: Enable DSPy Tracing
from openinference.instrumentation.dspy import DSPyInstrumentor
DSPyInstrumentor().instrument()
Step 4: Configure DSPy with Instrumented LM
import dspy
from langfuse_instrumented_dspy_lm import LangfuseInstrumentedLm
# Use LangfuseInstrumentedLm instead of dspy.LM
lm = LangfuseInstrumentedLm("openai/gpt-4o-mini", max_tokens=1000)
dspy.configure(lm=lm)
Step 5: Use DSPy as Normal
qa = dspy.Predict("question -> answer")
result = qa(question="What is the capital of France?")
print(result.answer)
That's it! Your Langfuse traces will now include token usage and cost data.
Complete Example
import os
import dspy
from openinference.instrumentation.dspy import DSPyInstrumentor
from langfuse_instrumented_dspy_lm import LangfuseInstrumentedLm
# Configure environment
os.environ["LANGFUSE_PUBLIC_KEY"] = "pk-lf-..."
os.environ["LANGFUSE_SECRET_KEY"] = "sk-lf-..."
os.environ["LANGFUSE_HOST"] = "https://cloud.langfuse.com"
os.environ["OPENAI_API_KEY"] = "sk-..."
# Enable tracing
DSPyInstrumentor().instrument()
# Configure DSPy with instrumented LM
dspy.configure(
lm=LangfuseInstrumentedLm(
"openai/gpt-4o-mini",
max_tokens=1000,
temperature=0.7,
)
)
# Use DSPy - token usage is now tracked!
qa = dspy.Predict("question -> answer")
result = qa(question="What is the capital of France?")
print(result.answer)
Using with @observe() Decorator
Combine with Langfuse's decorator for hierarchical tracing:
from langfuse.decorators import langfuse_context, observe
@observe()
def answer_question(question: str) -> str:
qa = dspy.Predict("question -> answer")
result = qa(question=question)
return result.answer
answer = answer_question("What is the capital of France?")
# Flush to ensure traces are sent
langfuse_context.flush()
Supported Models
Any model supported by LiteLLM works:
# OpenAI
LangfuseInstrumentedLm("openai/gpt-4o-mini")
# Anthropic
LangfuseInstrumentedLm("anthropic/claude-3-5-sonnet-20241022")
# Google
LangfuseInstrumentedLm("gemini/gemini-2.0-flash")
# Azure OpenAI
LangfuseInstrumentedLm("azure/gpt-4o-mini")
Captured Metrics
| Metric | Description |
|---|---|
| Prompt tokens | Input token count |
| Completion tokens | Output token count |
| Total tokens | Combined token count |
| Cost | Total cost (when available) |
| Reasoning tokens | Thinking/reasoning tokens (o1, etc.) |
| Cached tokens | Prompt cache hits |
For Developers
Why This Library Exists
When using Langfuse with DSPy via the standard integration, token usage and cost per LLM call are not captured. This happens because:
- Langfuse relies on
openinference-instrumentation-dspyfor auto-instrumenting DSPy - The instrumentation wraps
dspy.LM.__call__, which only returns parsed output (not token usage) - Token usage is available in
dspy.LM.forward()response, but the wrapper never sees it
LangfuseInstrumentedLm solves this by overriding forward() and aforward() to:
- Call the parent method to get the full LiteLLM response
- Extract token usage from the response
- Set OpenInference span attributes on the current span
- Return the response unchanged
DSPy Application
|
v
DSPyInstrumentor wrapper (creates span)
|
v
LangfuseInstrumentedLm.__call__() [inherited]
|
v
LangfuseInstrumentedLm.forward() <-- captures tokens here
|
+-- super().forward() -> LiteLLM ModelResponse
+-- _set_span_attributes(response)
+-- return response
API Reference
class LangfuseInstrumentedLm(dspy.LM):
"""Drop-in replacement for dspy.LM with token tracking."""
Constructor accepts all dspy.LM / LiteLLM parameters:
lm = LangfuseInstrumentedLm(
model="openai/gpt-4o-mini",
max_tokens=1000,
temperature=0.7,
cache=False, # Disable to get fresh token counts each call
)
Development Setup
git clone https://github.com/unravel-team/langfuse-instrumented-dspy-lm.git
cd langfuse-instrumented-dspy-lm
uv sync --group dev
Commands
# Lint
uv run ruff check src/
uv run ruff format src/
# Build
uv build
# Test import
uv run python -c "from langfuse_instrumented_dspy_lm import LangfuseInstrumentedLm; print('OK')"
Publishing
# Test with TestPyPI
uv build
uv publish --index-url https://test.pypi.org/legacy/ --token <TOKEN>
# Publish to PyPI
uv build
uv publish --token <PYPI_TOKEN>
Contributing
Contributions welcome! Please open an issue or pull request.
License
MIT - see LICENSE
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file langfuse_instrumented_dspy_lm-0.1.0.tar.gz.
File metadata
- Download URL: langfuse_instrumented_dspy_lm-0.1.0.tar.gz
- Upload date:
- Size: 217.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.8.20
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
36c1d851cfc496e1cd44277c73f5a1022e394f86feb8718f6dea5802f29aa13e
|
|
| MD5 |
743afbbbecb4cb587cd1229ae5b5764c
|
|
| BLAKE2b-256 |
4590cd48b99e7daa09374768b8538187af8e69dbfecae15f5104702fa0897519
|
File details
Details for the file langfuse_instrumented_dspy_lm-0.1.0-py3-none-any.whl.
File metadata
- Download URL: langfuse_instrumented_dspy_lm-0.1.0-py3-none-any.whl
- Upload date:
- Size: 6.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.8.20
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
197bf2e063562f858f0a710139fd92f561221817dfd3b9c3c1678cf612dcfd3c
|
|
| MD5 |
af3827fe7b9f67064973bc6c16d9e71e
|
|
| BLAKE2b-256 |
3af37d2817edeff6703fa9941a40385164ad4a762cee77e061f486b36d76581c
|