Skip to main content

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:

  1. Langfuse relies on openinference-instrumentation-dspy for auto-instrumenting DSPy
  2. The instrumentation wraps dspy.LM.__call__, which only returns parsed output (not token usage)
  3. Token usage is available in dspy.LM.forward() response, but the wrapper never sees it

LangfuseInstrumentedLm solves this by overriding forward() and aforward() to:

  1. Call the parent method to get the full LiteLLM response
  2. Extract token usage from the response
  3. Set OpenInference span attributes on the current span
  4. 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

langfuse_instrumented_dspy_lm-0.1.0.tar.gz (217.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

File details

Details for the file langfuse_instrumented_dspy_lm-0.1.0.tar.gz.

File metadata

File hashes

Hashes for langfuse_instrumented_dspy_lm-0.1.0.tar.gz
Algorithm Hash digest
SHA256 36c1d851cfc496e1cd44277c73f5a1022e394f86feb8718f6dea5802f29aa13e
MD5 743afbbbecb4cb587cd1229ae5b5764c
BLAKE2b-256 4590cd48b99e7daa09374768b8538187af8e69dbfecae15f5104702fa0897519

See more details on using hashes here.

File details

Details for the file langfuse_instrumented_dspy_lm-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for langfuse_instrumented_dspy_lm-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 197bf2e063562f858f0a710139fd92f561221817dfd3b9c3c1678cf612dcfd3c
MD5 af3827fe7b9f67064973bc6c16d9e71e
BLAKE2b-256 3af37d2817edeff6703fa9941a40385164ad4a762cee77e061f486b36d76581c

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page