Skip to main content

Pydantic AI model adapter for Claude Code CLI

Project description

claude-code-model

Pydantic AI model adapter for Claude Code CLI — type-safe agents on your Max subscription.

Use Pydantic AI's powerful agent framework with Claude Code CLI instead of API calls. Get structured outputs, tool calling, and full type safety without per-token costs.

Why Use This?

Approach Cost Type Safety Structured Output Tool Calling
Raw claude -p $0
Pydantic AI + API $$$ per token
claude-code-model $0

If you have a Claude Max subscription ($100-200/month), you get unlimited Claude access via CLI. This adapter lets you use that access with Pydantic AI's excellent developer experience.

Installation

# 1. Install Claude Code CLI (if not already installed)
npm install -g @anthropic-ai/claude-code

# 2. Authenticate
claude auth

# 3. Install this package
pip install claude-code-model

Quick Start

Structured Output

Get type-safe responses with automatic validation:

from pydantic import BaseModel
from pydantic_ai import Agent
from claude_code_model import ClaudeCodeModel

class ReviewResult(BaseModel):
    verdict: str  # APPROVE, REQUEST_CHANGES, COMMENT
    issues: list[str]
    suggestions: list[str]

agent = Agent(
    ClaudeCodeModel(),
    result_type=ReviewResult,
    system_prompt="You are a code reviewer. Return structured JSON."
)

result = agent.run_sync("Review this code: def add(a,b): return a+b")

# Result is fully typed!
print(result.data.verdict)  # IDE autocomplete works
print(result.data.issues)   # Type checking works

Tool Calling

Give your agent functions it can call:

from pydantic_ai import Agent
from claude_code_model import ClaudeCodeModel

agent = Agent(ClaudeCodeModel())

@agent.tool_plain
def read_file(path: str) -> str:
    """Read a file from disk."""
    return Path(path).read_text()

@agent.tool_plain
def list_files(directory: str = ".") -> list[str]:
    """List files in a directory."""
    return [f.name for f in Path(directory).iterdir()]

result = agent.run_sync("What Python files are in the current directory?")
# Agent automatically calls list_files() and uses the results

Multi-Agent Systems

Different models for different tasks:

from claude_code_model import ClaudeCodeModel

# Fast agent for quick tasks
researcher = Agent(
    ClaudeCodeModel(model="haiku"),
    system_prompt="Quick research"
)

# Powerful agent for analysis
analyst = Agent(
    ClaudeCodeModel(model="sonnet"),
    system_prompt="Deep analysis"
)

# Analyst can delegate to researcher
@analyst.tool_plain
async def research(topic: str) -> str:
    result = await researcher.run(f"Research {topic}")
    return result.data

Features

  • Zero API Costs: Uses your Claude Max subscription
  • Full Pydantic AI Compatibility: Structured outputs, tools, deps, async
  • Type Safety: Full IDE autocomplete and type checking
  • Multiple Models: sonnet (default), opus, haiku
  • Tool Calling: Give agents functions to call
  • Async Support: Full async/await support
  • Simple: <500 lines of code, easy to audit

Configuration

from claude_code_model import ClaudeCodeModel
from pathlib import Path

model = ClaudeCodeModel(
    model="sonnet",      # "sonnet" | "opus" | "haiku"
    timeout=30,          # seconds per CLI request (default: 30)
    cwd=Path("/path"),   # working directory for CLI
    verbose=False,       # enable debug logging
)

Debug Mode

Enable verbose logging to see exactly what's happening:

model = ClaudeCodeModel(verbose=True)

This shows:

  • Request/response timing (e.g., CLAUDE CLI RESPONSE (took 3.7s))
  • Full prompts being sent
  • Tool calls detected
  • JSON extraction status
  • Request count and total time

Examples

The examples/ directory contains working examples:

  • simple.py: Structured output for code review
  • with_tools.py: File system tools with interactive chat
  • multi_agent.py: Multi-agent delegation pattern

Run them:

uv run python examples/simple.py
uv run python examples/with_tools.py

How It Works

┌─────────────────────────────────────────┐
│  Your Code                              │
│  agent = Agent(ClaudeCodeModel())       │
│  result = agent.run_sync("prompt")      │
└──────────────┬──────────────────────────┘
               │
               ▼
┌─────────────────────────────────────────┐
│  Pydantic AI                            │
│  - Manages conversation state           │
│  - Handles tool calls and retries       │
│  - Validates output against schema      │
└──────────────┬──────────────────────────┘
               │
               ▼
┌─────────────────────────────────────────┐
│  claude-code-model                      │
│  - Converts messages → prompt string    │
│  - Adds JSON schema instructions        │
│  - Calls CLI wrapper                    │
│  - Parses response → ModelResponse      │
└──────────────┬──────────────────────────┘
               │
               ▼
┌─────────────────────────────────────────┐
│  Claude Code CLI                        │
│  $ claude -p "prompt" --model sonnet    │
│  > {"verdict": "APPROVE", "issues": []} │
└─────────────────────────────────────────┘

The adapter translates between Pydantic AI's message format and Claude CLI's prompt-response model. It handles:

  • System prompts, user messages, tool calls, tool results
  • JSON extraction from various response formats
  • Tool call detection and parsing
  • Structured output validation

API Reference

ClaudeCodeModel

Main model class implementing Pydantic AI's Model interface.

@dataclass
class ClaudeCodeModel(Model):
    model: Literal["sonnet", "opus", "haiku"] = "sonnet"
    timeout: int = 30       # seconds per request
    cwd: Path | None = None
    verbose: bool = False   # enable debug logging

Properties (read-only, for monitoring):

  • request_count: int - Number of CLI requests made
  • total_time: float - Total seconds spent in CLI requests

Methods:

  • reset_stats() - Reset request_count and total_time to zero

Exceptions

from claude_code_model import (
    ClaudeCodeError,           # Base exception
    ClaudeCodeNotFoundError,   # CLI not installed
    ClaudeCodeTimeoutError,    # Command timed out
    ClaudeCodeExecutionError,  # Non-zero exit code
)

Limitations

  • No streaming: CLI doesn't support streaming well
  • No token counting: CLI doesn't report usage (returns 0)
  • No concurrent calls: Run one request at a time
  • Text-only: No image inputs (CLI limitation)
  • Rate limits: Subject to Claude Max rate limits

Known Behaviors

Tool Calling is Prompt-Based

Unlike the API, Claude CLI doesn't have native tool calling. This adapter simulates tools by:

  1. Including tool definitions in the prompt with format instructions
  2. Asking Claude to output TOOL_CALL: name({"arg": "value"})
  3. Parsing the response for this pattern

This works well but Claude occasionally:

  • Ignores tools and answers directly
  • Outputs the tool call but continues talking
  • Returns placeholder JSON instead of real content

Mitigation: Use retries=5 on your Agent to handle occasional misbehavior:

agent = Agent(
    ClaudeCodeModel(),
    output_type=MyResult,
    retries=5,  # Allow retries for output validation
)

Response Timing

Each CLI invocation takes 2-10 seconds depending on:

  • Model (haiku is fastest, opus slowest)
  • Prompt length
  • Claude's current load

Multi-agent workflows with tool calls require multiple CLI invocations, so expect 15-30+ seconds for complex tasks.

IDE Compatibility

The adapter works in both terminal and IDE environments. It uses stdin=DEVNULL to prevent hangs when running without a TTY (common in IDEs like PyCharm).

Development

# Setup
git clone https://github.com/yourusername/claude-code-model.git
cd claude-code-model
uv sync

# Run tests
uv run pytest

# Run tests with coverage
uv run pytest --cov=claude_code_model

# Type check
uv run mypy src/

# Lint
uv run ruff check src/ tests/
uv run ruff format src/ tests/

# Run example
uv run python examples/simple.py

Requirements

  • Python 3.11+
  • Claude Code CLI installed and authenticated
  • Claude Max subscription (for unlimited CLI access)
  • pydantic-ai >= 0.1.0
  • pydantic >= 2.0.0

Contributing

Contributions welcome! Please:

  1. Keep changes focused and small (<200 lines)
  2. Add tests for new functionality
  3. Ensure pytest, mypy, and ruff all pass
  4. Update README if adding features

See CLAUDE.md for development guide.

License

MIT License - see LICENSE file for details.

Credits

Built with:

Disclaimer

This is an unofficial adapter, not affiliated with Anthropic. Use in accordance with Claude's Terms of Service. You are responsible for ensuring your usage complies with Anthropic's policies.

Support

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

claude_code_model-0.3.0.tar.gz (222.4 kB view details)

Uploaded Source

Built Distribution

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

claude_code_model-0.3.0-py3-none-any.whl (13.2 kB view details)

Uploaded Python 3

File details

Details for the file claude_code_model-0.3.0.tar.gz.

File metadata

  • Download URL: claude_code_model-0.3.0.tar.gz
  • Upload date:
  • Size: 222.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for claude_code_model-0.3.0.tar.gz
Algorithm Hash digest
SHA256 82aa1657242f618c881edd413233af3e95cc16884c48e3765fc12a99c9af3f26
MD5 0aed25827c9001774c9ffc3e75e7d479
BLAKE2b-256 7fd5c7d25c3ddac9798ecfd2b0db5a907cf358fbb4fb3d88735cb2870c225f5a

See more details on using hashes here.

Provenance

The following attestation bundles were made for claude_code_model-0.3.0.tar.gz:

Publisher: publish.yml on aleksandr-bogdanov/claude-code-model

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file claude_code_model-0.3.0-py3-none-any.whl.

File metadata

File hashes

Hashes for claude_code_model-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 3efb10645f68a5b855234e4b70eb294f6a7099dae3d944b0b5d6c6381b09c6e8
MD5 f5f3ae95f4c94f1a11ee5db1ff02ea52
BLAKE2b-256 46644e20d0fc52148118cc505b6862cee9523ef022e99aca21158f2267b4898a

See more details on using hashes here.

Provenance

The following attestation bundles were made for claude_code_model-0.3.0-py3-none-any.whl:

Publisher: publish.yml on aleksandr-bogdanov/claude-code-model

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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