Skip to main content

DSPy LM integration for Claude Code CLI - route DSPy requests through local Claude Code agent

Project description

dspy-coding-agent-lms

DSPy Language Model integration for Claude Code CLI - route DSPy requests through your local Claude Code agent instead of using Anthropic APIs directly.

Overview

dspy-coding-agent-lms provides a DSPy-compatible language model that invokes the Claude Code CLI for each request, enabling:

  • Agentic capabilities: Access Claude Code's file editing, bash execution, and tool use within DSPy programs
  • Structured output: Use Claude Code's built-in JSON schema validation instead of string parsing
  • Permission control: Fine-grained control over which tools Claude can use
  • Cost tracking: Automatic transcript capture with usage and cost metrics
  • Local execution: Run Claude Code locally with your existing authentication

Installation

Prerequisites

  1. Claude Code CLI - Install and authenticate:

    npm install -g @anthropic-ai/claude-code
    claude login  # or use ANTHROPIC_API_KEY
    
  2. Python 3.11+

Install the package

# Using uv (recommended)
uv add dspy-coding-agent-lms

# Using pip
pip install dspy-coding-agent-lms

Quick Start

import dspy
from dspy_coding_agent_lms import ClaudeCodeLM

# Initialize the LM
lm = ClaudeCodeLM(model="sonnet", permission_mode="plan")

# Configure DSPy with JSONAdapter (RECOMMENDED)
# JSONAdapter uses Claude Code's native --json-schema for reliable structured output
dspy.configure(lm=lm, adapter=dspy.JSONAdapter())

# Use DSPy as normal
predict = dspy.Predict("question -> answer")
result = predict(question="What is 2+2?")
print(result.answer)

# Check cost and usage
print(f"Cost: ${lm.transcript.total_cost_usd():.4f}")

Recommended: Always use dspy.JSONAdapter() with ClaudeCodeLM. This enables native JSON schema output via Claude Code's --json-schema flag, providing more reliable structured output than string parsing.

Features

DSPy Adapters: JSONAdapter vs ChatAdapter

When using DSPy Signatures, you can choose between two adapters:

JSONAdapter (Recommended)

Uses Claude Code's native --json-schema flag for structured output. The response is pure JSON in the structured_output field.

import dspy
from dspy_coding_agent_lms import ClaudeCodeLM

lm = ClaudeCodeLM(model="sonnet")
dspy.configure(lm=lm, adapter=dspy.JSONAdapter())  # Recommended

class SentimentSignature(dspy.Signature):
    """Analyze sentiment of text."""
    text: str = dspy.InputField()
    sentiment: str = dspy.OutputField()
    confidence: float = dspy.OutputField()

result = dspy.Predict(SentimentSignature)(text="I love this!")
# Output format: {"sentiment": "positive", "confidence": 0.95}

ChatAdapter (Default)

Uses string parsing with [[ ## field ## ]] markers. This is DSPy's default but less reliable.

dspy.configure(lm=lm, adapter=dspy.ChatAdapter())  # Default, not recommended

# Output format:
# [[ ## sentiment ## ]]
# positive
# [[ ## confidence ## ]]
# 0.95

Why JSONAdapter is recommended:

  • More reliable parsing (no regex-based extraction)
  • Native JSON schema validation by Claude Code
  • Cleaner API responses with structured_output field
  • Better type safety for complex nested structures

Direct JSON Schema

Use Claude Code's built-in JSON schema validation for reliable structured outputs:

from dspy_coding_agent_lms import ClaudeCodeLM
import json

lm = ClaudeCodeLM(model="sonnet")

schema = {
    "type": "object",
    "properties": {
        "answer": {"type": "integer"},
        "explanation": {"type": "string"}
    },
    "required": ["answer"]
}

result = lm.forward(
    prompt="What is 15% of 200?",
    json_schema=schema
)

data = json.loads(result[0]["text"])
print(f"Answer: {data['answer']}")  # Answer: 30

Tool Restrictions

Control which tools Claude can access:

# Read-only code analysis
lm = ClaudeCodeLM(
    model="sonnet",
    permission_mode="plan",
    allowed_tools=["Read", "Glob", "Grep"],
    disallowed_tools=["Bash", "Edit", "Write"],
)

# Git operations only
lm = ClaudeCodeLM(
    allowed_tools=[
        "Bash(git status)",
        "Bash(git log*)",
        "Bash(git diff*)",
    ],
)

Async Support

Execute concurrent requests for better throughput:

import asyncio
from dspy_coding_agent_lms import ClaudeCodeLM

async def main():
    lm = ClaudeCodeLM(model="sonnet")

    prompts = ["Question 1", "Question 2", "Question 3"]
    tasks = [lm.aforward(prompt=p) for p in prompts]
    results = await asyncio.gather(*tasks)

asyncio.run(main())

Transcript and Cost Tracking

Access complete interaction history with usage metrics:

lm = ClaudeCodeLM(model="sonnet")

# Make some requests...
lm.forward(prompt="Hello")
lm.forward(prompt="World")

# Access transcript
for entry in lm.transcript:
    print(f"Prompt: {entry.prompt}")
    print(f"Duration: {entry.duration_ms}ms")
    print(f"Cost: ${entry.cost_usd:.4f}")

# Aggregated stats
print(f"Total cost: ${lm.transcript.total_cost_usd():.4f}")
print(f"Total tokens: {lm.transcript.total_tokens()}")

# Export transcript
lm.transcript.save_to_file("transcript.json")

Configuration Options

Constructor Parameters

Parameter Type Default Description
model str "sonnet" Model to use (sonnet, opus, haiku)
permission_mode str "plan" Permission mode (plan, dontAsk, acceptEdits, etc.)
system_prompt str None Override default system prompt
append_system_prompt str None Append to default system prompt
dangerously_skip_permissions bool False Bypass all permission checks
allowed_tools list None Tools that can execute without prompts
disallowed_tools list None Tools that are blocked
working_directory str None Working directory for execution
timeout_seconds float 300 Command timeout
max_budget_usd float None Maximum budget limit
cache bool True Enable response caching
capture_transcript bool True Capture interaction transcript
anthropic_api_key str None Explicit API key
oauth_token str None OAuth token for Pro/Max subscribers

Permission Modes

Mode Description
plan Allow planning but require approval for execution
dontAsk Don't ask for permissions (use with caution)
acceptEdits Auto-accept file edits
default Default Claude Code behavior
delegate Delegate permission decisions
bypassPermissions Bypass all permissions (dangerous)

Authentication

The library supports two authentication methods:

  1. API Key: Set ANTHROPIC_API_KEY environment variable or pass anthropic_api_key parameter
  2. OAuth Token: Set CLAUDE_CODE_OAUTH_TOKEN or pass oauth_token parameter (for Pro/Max subscribers)
# Using environment variables (recommended)
lm = ClaudeCodeLM(model="sonnet")  # Uses ANTHROPIC_API_KEY

# Explicit credentials
lm = ClaudeCodeLM(
    model="sonnet",
    anthropic_api_key="sk-...",
)

Examples

See the examples/ directory for complete examples:

  • basic_usage.py - Simple DSPy integration
  • structured_output.py - JSON schema structured outputs
  • with_tools.py - Tool restriction configurations
  • async_usage.py - Concurrent async requests

Testing

# Run unit tests
uv run pytest tests/unit/ -v

# Run with coverage
uv run pytest tests/unit/ -v --cov=dspy_coding_agent_lms

# Run integration tests (requires Claude Code CLI)
RUN_INTEGRATION_TESTS=1 uv run pytest tests/integration/ -v

API Reference

ClaudeCodeLM

Main class for DSPy integration.

class ClaudeCodeLM:
    def forward(
        self,
        prompt: str | None = None,
        messages: list[dict] | None = None,
        json_schema: dict | None = None,
        **kwargs
    ) -> list[dict]:
        """Execute synchronous request."""

    async def aforward(
        self,
        prompt: str | None = None,
        messages: list[dict] | None = None,
        json_schema: dict | None = None,
        **kwargs
    ) -> list[dict]:
        """Execute asynchronous request."""

    @property
    def transcript(self) -> Transcript:
        """Access interaction transcript."""

    def copy(self, **kwargs) -> ClaudeCodeLM:
        """Create copy with modified parameters."""

Transcript

Interaction history with aggregation methods.

class Transcript:
    def total_cost_usd(self) -> float: ...
    def total_tokens(self) -> dict[str, int]: ...
    def total_duration_ms(self) -> int: ...
    def to_json(self) -> str: ...
    def save_to_file(self, path: str) -> None: ...

License

MIT License - see LICENSE for details.

Contributing

Contributions are welcome! Please feel free to submit issues and pull requests.

Acknowledgments

  • DSPy - The declarative programming framework for LMs
  • Claude Code - Anthropic's agentic coding assistant

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

dspy_coding_agent_lms-0.1.3.tar.gz (220.7 kB view details)

Uploaded Source

Built Distribution

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

dspy_coding_agent_lms-0.1.3-py3-none-any.whl (26.4 kB view details)

Uploaded Python 3

File details

Details for the file dspy_coding_agent_lms-0.1.3.tar.gz.

File metadata

  • Download URL: dspy_coding_agent_lms-0.1.3.tar.gz
  • Upload date:
  • Size: 220.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.24 {"installer":{"name":"uv","version":"0.9.24","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for dspy_coding_agent_lms-0.1.3.tar.gz
Algorithm Hash digest
SHA256 e2713204251092b929fc49f02b1b79e40e068b3f164ff1e99d15624b1fa8e617
MD5 ec12e97fd8136473abee01137f8d2933
BLAKE2b-256 b3b76661997db88194bcfba26757ece8d83533cdb57f5e14480a1a8ad30f6970

See more details on using hashes here.

File details

Details for the file dspy_coding_agent_lms-0.1.3-py3-none-any.whl.

File metadata

  • Download URL: dspy_coding_agent_lms-0.1.3-py3-none-any.whl
  • Upload date:
  • Size: 26.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.24 {"installer":{"name":"uv","version":"0.9.24","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for dspy_coding_agent_lms-0.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 6dab30522461f950113a576e77153fe0a5e6f408b3168304045f1361a991acb8
MD5 9032d4b58f6c84311ac40e160662ebb9
BLAKE2b-256 298b659090b0475a958a114e29df8e110694427c9e299c821fb64209010e6a82

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