Add more functionality to signature description parsing
This commit is contained in:
332
README.md
332
README.md
@@ -16,18 +16,21 @@ A DSPy module that wraps the Claude Code Python SDK with a signature-driven inte
|
|||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Install with uv
|
# install with uv
|
||||||
uv add claude-agent-sdk dspy modaic nest-asyncio
|
uv add claude-agent-sdk dspy modaic nest-asyncio
|
||||||
|
|
||||||
# Or with pip
|
# or with pip
|
||||||
pip install claude-agent-sdk dspy modaic nest-asyncio
|
pip install claude-agent-sdk dspy modaic nest-asyncio
|
||||||
```
|
```
|
||||||
|
|
||||||
**Prerequisites:**
|
**Prerequisites:**
|
||||||
- Python 3.10+
|
- Python 3.10+
|
||||||
- Claude Code CLI installed (get it from [code.claude.com](https://code.claude.com))
|
|
||||||
- Anthropic API key set in `ANTHROPIC_API_KEY` environment variable
|
- Anthropic API key set in `ANTHROPIC_API_KEY` environment variable
|
||||||
|
|
||||||
|
**Note:** The Claude Code CLI is automatically bundled with the `claude-agent-sdk` package - no separate installation required! The SDK uses the bundled CLI by default. If you prefer to use a system-wide installation or a specific version:
|
||||||
|
- Install separately: `curl -fsSL https://claude.ai/install.sh | bash`
|
||||||
|
- Specify custom path: `ClaudeAgentOptions(cli_path="/path/to/claude")`
|
||||||
|
|
||||||
## Quick Start with Modaic Hub (Recommended)
|
## Quick Start with Modaic Hub (Recommended)
|
||||||
|
|
||||||
The fastest way to use ClaudeCode is to pull a pre-configured agent from Modaic Hub.
|
The fastest way to use ClaudeCode is to pull a pre-configured agent from Modaic Hub.
|
||||||
@@ -35,10 +38,10 @@ The fastest way to use ClaudeCode is to pull a pre-configured agent from Modaic
|
|||||||
### 1. Set up environment
|
### 1. Set up environment
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Copy the example file
|
# copy the example file
|
||||||
cp .env.example .env
|
cp .env.example .env
|
||||||
|
|
||||||
# Edit .env with your keys
|
# edit .env with your keys
|
||||||
ANTHROPIC_API_KEY="<YOUR_ANTHROPIC_API_KEY>"
|
ANTHROPIC_API_KEY="<YOUR_ANTHROPIC_API_KEY>"
|
||||||
MODAIC_TOKEN="<YOUR_MODAIC_TOKEN>" # Optional, for pushing to hub
|
MODAIC_TOKEN="<YOUR_MODAIC_TOKEN>" # Optional, for pushing to hub
|
||||||
```
|
```
|
||||||
@@ -48,17 +51,21 @@ MODAIC_TOKEN="<YOUR_MODAIC_TOKEN>" # Optional, for pushing to hub
|
|||||||
```python
|
```python
|
||||||
from modaic import AutoProgram
|
from modaic import AutoProgram
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
import dspy
|
||||||
|
|
||||||
class FileList(BaseModel):
|
class FileList(BaseModel):
|
||||||
files: list[str]
|
files: list[str]
|
||||||
|
|
||||||
|
class FileSignature(dspy.Signature):
|
||||||
|
message: str = dspy.InputField(desc="Request to process")
|
||||||
|
output: FileList = dspy.OutputField(desc="List of files")
|
||||||
|
|
||||||
# Load pre-compiled agent from hub
|
# Load pre-compiled agent from hub
|
||||||
|
# Note: Only model is set via config, other params are kwargs
|
||||||
agent = AutoProgram.from_precompiled(
|
agent = AutoProgram.from_precompiled(
|
||||||
"farouk1/claude-code",
|
"farouk1/claude-code",
|
||||||
config={
|
signature=FileSignature,
|
||||||
"signature": "message:str -> output:FileList",
|
working_directory=".",
|
||||||
"working_directory": ".",
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Use it!
|
# Use it!
|
||||||
@@ -70,15 +77,26 @@ print(result.usage) # Token usage
|
|||||||
### 3. Override Config Options
|
### 3. Override Config Options
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
from modaic import AutoProgram
|
||||||
|
from claude_dspy import ClaudeCodeConfig
|
||||||
|
import dspy
|
||||||
|
|
||||||
|
class MySignature(dspy.Signature):
|
||||||
|
message: str = dspy.InputField(desc="Request to process")
|
||||||
|
answer: str = dspy.OutputField(desc="Response")
|
||||||
|
|
||||||
|
# Create config with custom model
|
||||||
|
config = ClaudeCodeConfig(model="claude-opus-4-5-20251101")
|
||||||
|
|
||||||
# Load with custom configuration
|
# Load with custom configuration
|
||||||
|
# Model comes from config, other params are kwargs
|
||||||
agent = AutoProgram.from_precompiled(
|
agent = AutoProgram.from_precompiled(
|
||||||
"farouk1/claude-code",
|
"farouk1/claude-code",
|
||||||
config={
|
config=config,
|
||||||
"signature": "message:str -> answer:str",
|
signature=MySignature,
|
||||||
"model": "claude-opus-4-5-20251101",
|
working_directory=".",
|
||||||
"permission_mode": "acceptEdits",
|
permission_mode="acceptEdits",
|
||||||
"allowed_tools": ["Read", "Write", "Bash"],
|
allowed_tools=["Read", "Write", "Bash"],
|
||||||
}
|
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -174,6 +192,8 @@ class ClaudeCodeConfig:
|
|||||||
|
|
||||||
- **`model`** - Claude model to use (default: `"claude-opus-4-5-20251101"`)
|
- **`model`** - Claude model to use (default: `"claude-opus-4-5-20251101"`)
|
||||||
|
|
||||||
|
**Important:** `ClaudeCodeConfig` only contains the model. All other parameters (`signature`, `working_directory`, `permission_mode`, `allowed_tools`, etc.) are passed as keyword arguments to `ClaudeCode.__init__()` or `AutoProgram.from_precompiled()`, not through the config object.
|
||||||
|
|
||||||
### ClaudeCode
|
### ClaudeCode
|
||||||
|
|
||||||
Main agent class.
|
Main agent class.
|
||||||
@@ -196,16 +216,49 @@ class ClaudeCode(PrecompiledProgram):
|
|||||||
|
|
||||||
**Parameters:**
|
**Parameters:**
|
||||||
|
|
||||||
|
**Core Configuration:**
|
||||||
- **`config`** - ClaudeCodeConfig instance with model configuration
|
- **`config`** - ClaudeCodeConfig instance with model configuration
|
||||||
- **`signature`** (required) - DSPy signature defining input/output fields (must have exactly 1 input and 1 output)
|
- **`signature`** (required) - DSPy signature defining input/output fields (must have exactly 1 input and 1 output)
|
||||||
- **`working_directory`** - Directory where Claude will execute commands (default: `"."`)
|
- **`working_directory`** - Directory where Claude will execute commands (default: `"."`)
|
||||||
- **`permission_mode`** - Permission mode: `"default"`, `"acceptEdits"`, `"plan"`, `"bypassPermissions"`
|
- **`permission_mode`** - Permission mode: `"default"`, `"acceptEdits"`, `"plan"`, `"bypassPermissions"`
|
||||||
- **`allowed_tools`** - List of allowed tool names (e.g., `["Read", "Write", "Bash"]`)
|
- **`allowed_tools`** - List of allowed tool names (e.g., `["Read", "Write", "Bash", "Glob", "Grep"]`). See [Available Tools](#available-tools) section for complete list.
|
||||||
- **`disallowed_tools`** - List of disallowed tool names
|
- **`disallowed_tools`** - List of disallowed tool names
|
||||||
- **`sandbox`** - Sandbox configuration dict
|
- **`sandbox`** - Sandbox configuration dict. See [SDK docs](https://platform.claude.com/docs/en/agent-sdk/python#sandboxsettings) for details.
|
||||||
- **`system_prompt`** - Custom system prompt or preset config
|
- **`system_prompt`** - Custom system prompt or preset config
|
||||||
- **`api_key`** - Anthropic API key (falls back to `ANTHROPIC_API_KEY` env var)
|
- **`api_key`** - Anthropic API key (falls back to `ANTHROPIC_API_KEY` env var)
|
||||||
|
|
||||||
|
**MCP Servers:**
|
||||||
|
- **`mcp_servers`** - MCP server configurations for custom tools. See [MCP section](#using-mcp-servers) below.
|
||||||
|
|
||||||
|
**Session Management:**
|
||||||
|
- **`continue_conversation`** - Continue the most recent conversation (default: `False`)
|
||||||
|
- **`resume`** - Session ID to resume from a previous session
|
||||||
|
- **`max_turns`** - Maximum number of conversation turns
|
||||||
|
- **`fork_session`** - Fork to a new session when resuming (default: `False`)
|
||||||
|
|
||||||
|
**Advanced Options:**
|
||||||
|
- **`permission_prompt_tool_name`** - MCP tool name for permission prompts
|
||||||
|
- **`settings`** - Path to custom settings file
|
||||||
|
- **`add_dirs`** - Additional directories Claude can access
|
||||||
|
- **`env`** - Environment variables to pass to Claude Code
|
||||||
|
- **`extra_args`** - Additional CLI arguments
|
||||||
|
- **`max_buffer_size`** - Maximum bytes when buffering CLI stdout
|
||||||
|
- **`cli_path`** - Custom path to Claude Code CLI executable
|
||||||
|
|
||||||
|
**Callbacks and Hooks:**
|
||||||
|
- **`stderr`** - Callback function for stderr output: `Callable[[str], None]`
|
||||||
|
- **`can_use_tool`** - Permission callback for tool usage control
|
||||||
|
- **`hooks`** - Hook configurations for intercepting events. See [SDK docs](https://platform.claude.com/docs/en/agent-sdk/python#hook-types) for details.
|
||||||
|
|
||||||
|
**User and Settings:**
|
||||||
|
- **`user`** - User identifier
|
||||||
|
- **`include_partial_messages`** - Include partial message streaming events (default: `False`)
|
||||||
|
- **`setting_sources`** - Which settings to load: `["user", "project", "local"]`
|
||||||
|
|
||||||
|
**Subagents and Plugins:**
|
||||||
|
- **`agents`** - Programmatically defined subagents
|
||||||
|
- **`plugins`** - Custom plugins to load
|
||||||
|
|
||||||
#### Methods
|
#### Methods
|
||||||
|
|
||||||
##### `__call__(**kwargs) -> Prediction` (or `forward`)
|
##### `__call__(**kwargs) -> Prediction` (or `forward`)
|
||||||
@@ -292,11 +345,234 @@ print(agent.session_id) # 'eb1b2f39-e04c-4506-9398-b50053b1fd83'
|
|||||||
|
|
||||||
##### `config: ClaudeCodeConfig`
|
##### `config: ClaudeCodeConfig`
|
||||||
|
|
||||||
Access to the agent's configuration.
|
Access to the agent's configuration (model only).
|
||||||
|
|
||||||
```python
|
```python
|
||||||
print(agent.config.model) # 'claude-opus-4-5-20251101'
|
print(agent.config.model) # 'claude-opus-4-5-20251101'
|
||||||
print(agent.config.working_directory) # '.'
|
```
|
||||||
|
|
||||||
|
**Note:** Only the `model` is stored in config. Other parameters like `working_directory`, `permission_mode`, and `allowed_tools` are instance attributes, not config properties.
|
||||||
|
|
||||||
|
## Key Implementation Details
|
||||||
|
|
||||||
|
### Config vs Kwargs
|
||||||
|
|
||||||
|
The `ClaudeCodeConfig` **only** contains the `model` parameter. All other configuration options are passed as keyword arguments:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from claude_dspy import ClaudeCode, ClaudeCodeConfig
|
||||||
|
|
||||||
|
# Config contains ONLY the model
|
||||||
|
config = ClaudeCodeConfig(model="claude-opus-4-5-20251101")
|
||||||
|
|
||||||
|
# All other params are kwargs
|
||||||
|
agent = ClaudeCode(
|
||||||
|
config, # Config with model
|
||||||
|
signature="message:str -> answer:str", # Kwarg
|
||||||
|
working_directory=".", # Kwarg
|
||||||
|
permission_mode="acceptEdits", # Kwarg
|
||||||
|
allowed_tools=["Read", "Write", "Bash"], # Kwarg
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### AutoProgram.from_precompiled
|
||||||
|
|
||||||
|
When loading from Modaic Hub, the same pattern applies:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from modaic import AutoProgram
|
||||||
|
from claude_dspy import ClaudeCodeConfig
|
||||||
|
import dspy
|
||||||
|
|
||||||
|
class MySignature(dspy.Signature):
|
||||||
|
message: str = dspy.InputField(desc="Request")
|
||||||
|
answer: str = dspy.OutputField(desc="Response")
|
||||||
|
|
||||||
|
# Option 1: Use default model (no config needed)
|
||||||
|
agent = AutoProgram.from_precompiled(
|
||||||
|
"farouk1/claude-code",
|
||||||
|
signature=MySignature, # Kwarg
|
||||||
|
working_directory=".", # Kwarg
|
||||||
|
permission_mode="acceptEdits", # Kwarg
|
||||||
|
allowed_tools=["Read", "Write", "Bash"], # Kwarg
|
||||||
|
)
|
||||||
|
|
||||||
|
# Option 2: Override model via config
|
||||||
|
config = ClaudeCodeConfig(model="claude-opus-4-5-20251101")
|
||||||
|
agent = AutoProgram.from_precompiled(
|
||||||
|
"farouk1/claude-code",
|
||||||
|
config=config, # Config with model
|
||||||
|
signature=MySignature, # Kwarg
|
||||||
|
working_directory=".", # Kwarg
|
||||||
|
permission_mode="acceptEdits", # Kwarg
|
||||||
|
allowed_tools=["Read", "Write", "Bash"], # Kwarg
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Available Tools
|
||||||
|
|
||||||
|
The `allowed_tools` parameter accepts any valid Claude Code tool name:
|
||||||
|
|
||||||
|
**File Operations:**
|
||||||
|
- `"Read"` - Read files and directories
|
||||||
|
- `"Write"` - Write and create files
|
||||||
|
- `"Edit"` - Edit existing files
|
||||||
|
|
||||||
|
**Command Execution:**
|
||||||
|
- `"Bash"` - Execute bash commands
|
||||||
|
|
||||||
|
**Code Search:**
|
||||||
|
- `"Glob"` - Search for files by pattern
|
||||||
|
- `"Grep"` - Search file contents
|
||||||
|
|
||||||
|
**Web Tools:**
|
||||||
|
- `"WebSearch"` - Search the web
|
||||||
|
- `"WebFetch"` - Fetch web content
|
||||||
|
|
||||||
|
**Other Tools:**
|
||||||
|
- `"NotebookEdit"` - Edit Jupyter notebooks
|
||||||
|
- And other Claude Code tools...
|
||||||
|
|
||||||
|
## Advanced Features
|
||||||
|
|
||||||
|
### Using MCP Servers
|
||||||
|
|
||||||
|
MCP (Model Context Protocol) servers allow you to add custom tools to Claude. The SDK supports creating in-process MCP servers with custom tools.
|
||||||
|
|
||||||
|
```python
|
||||||
|
from claude_dspy import ClaudeCode, ClaudeCodeConfig
|
||||||
|
from claude_agent_sdk import tool, create_sdk_mcp_server
|
||||||
|
from typing import Any
|
||||||
|
import dspy
|
||||||
|
|
||||||
|
# Define custom tools with @tool decorator
|
||||||
|
@tool("calculate", "Perform mathematical calculations", {"expression": str})
|
||||||
|
async def calculate(args: dict[str, Any]) -> dict[str, Any]:
|
||||||
|
try:
|
||||||
|
result = eval(args["expression"], {"__builtins__": {}})
|
||||||
|
return {
|
||||||
|
"content": [{"type": "text", "text": f"Result: {result}"}]
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
return {
|
||||||
|
"content": [{"type": "text", "text": f"Error: {str(e)}"}],
|
||||||
|
"is_error": True
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create MCP server
|
||||||
|
calculator_server = create_sdk_mcp_server(
|
||||||
|
name="calculator",
|
||||||
|
version="1.0.0",
|
||||||
|
tools=[calculate]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Use with ClaudeCode
|
||||||
|
config = ClaudeCodeConfig()
|
||||||
|
agent = ClaudeCode(
|
||||||
|
config,
|
||||||
|
signature="message:str -> answer:str",
|
||||||
|
working_directory=".",
|
||||||
|
mcp_servers={"calc": calculator_server},
|
||||||
|
allowed_tools=["mcp__calc__calculate"] # MCP tools are prefixed with "mcp__<server>__<tool>"
|
||||||
|
)
|
||||||
|
|
||||||
|
result = agent(message="Calculate 123 * 456")
|
||||||
|
print(result.answer)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Session Management
|
||||||
|
|
||||||
|
Resume and continue conversations from previous sessions:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from claude_dspy import ClaudeCode, ClaudeCodeConfig
|
||||||
|
|
||||||
|
config = ClaudeCodeConfig()
|
||||||
|
|
||||||
|
# First conversation
|
||||||
|
agent1 = ClaudeCode(
|
||||||
|
config,
|
||||||
|
signature="message:str -> answer:str",
|
||||||
|
working_directory="."
|
||||||
|
)
|
||||||
|
result1 = agent1(message="Create a file called notes.txt")
|
||||||
|
session_id = agent1.session_id
|
||||||
|
print(f"Session ID: {session_id}")
|
||||||
|
|
||||||
|
# Resume the same conversation later
|
||||||
|
agent2 = ClaudeCode(
|
||||||
|
config,
|
||||||
|
signature="message:str -> answer:str",
|
||||||
|
working_directory=".",
|
||||||
|
resume=session_id # Resume from session ID
|
||||||
|
)
|
||||||
|
result2 = agent2(message="What file did we just create?")
|
||||||
|
print(result2.answer) # Claude remembers the previous context!
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using Hooks
|
||||||
|
|
||||||
|
Intercept and modify tool execution with hooks:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from claude_dspy import ClaudeCode, ClaudeCodeConfig
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
async def pre_tool_logger(input_data: dict[str, Any], tool_use_id: str | None, context: Any) -> dict[str, Any]:
|
||||||
|
"""Log all tool usage before execution."""
|
||||||
|
tool_name = input_data.get('tool_name', 'unknown')
|
||||||
|
print(f"About to use tool: {tool_name}")
|
||||||
|
|
||||||
|
# Block dangerous commands
|
||||||
|
if tool_name == "Bash" and "rm -rf /" in str(input_data.get('tool_input', {})):
|
||||||
|
return {
|
||||||
|
'hookSpecificOutput': {
|
||||||
|
'hookEventName': 'PreToolUse',
|
||||||
|
'permissionDecision': 'deny',
|
||||||
|
'permissionDecisionReason': 'Dangerous command blocked'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {}
|
||||||
|
|
||||||
|
config = ClaudeCodeConfig()
|
||||||
|
agent = ClaudeCode(
|
||||||
|
config,
|
||||||
|
signature="message:str -> answer:str",
|
||||||
|
working_directory=".",
|
||||||
|
hooks={
|
||||||
|
'PreToolUse': [
|
||||||
|
{'matcher': 'Bash', 'hooks': [pre_tool_logger]}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
allowed_tools=["Read", "Write", "Bash"]
|
||||||
|
)
|
||||||
|
|
||||||
|
result = agent(message="List files in this directory")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Loading Project Settings
|
||||||
|
|
||||||
|
Control which filesystem settings to load:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from claude_dspy import ClaudeCode, ClaudeCodeConfig
|
||||||
|
|
||||||
|
config = ClaudeCodeConfig()
|
||||||
|
|
||||||
|
# Load only project settings (e.g., CLAUDE.md files)
|
||||||
|
agent = ClaudeCode(
|
||||||
|
config,
|
||||||
|
signature="message:str -> answer:str",
|
||||||
|
working_directory=".",
|
||||||
|
system_prompt={
|
||||||
|
"type": "preset",
|
||||||
|
"preset": "claude_code"
|
||||||
|
},
|
||||||
|
setting_sources=["project"], # Load .claude/settings.json and CLAUDE.md
|
||||||
|
allowed_tools=["Read", "Write"]
|
||||||
|
)
|
||||||
|
|
||||||
|
result = agent(message="Add a feature following project conventions")
|
||||||
```
|
```
|
||||||
|
|
||||||
## Usage Patterns
|
## Usage Patterns
|
||||||
@@ -446,7 +722,7 @@ agent = ClaudeCode(
|
|||||||
signature="message:str -> answer:str",
|
signature="message:str -> answer:str",
|
||||||
working_directory=".",
|
working_directory=".",
|
||||||
permission_mode="default",
|
permission_mode="default",
|
||||||
allowed_tools=["Read", "Glob", "Grep"],
|
allowed_tools=["Read"], # Only allow reading files
|
||||||
)
|
)
|
||||||
|
|
||||||
# Auto-accept file edits
|
# Auto-accept file edits
|
||||||
@@ -456,16 +732,17 @@ agent = ClaudeCode(
|
|||||||
signature="message:str -> answer:str",
|
signature="message:str -> answer:str",
|
||||||
working_directory=".",
|
working_directory=".",
|
||||||
permission_mode="acceptEdits",
|
permission_mode="acceptEdits",
|
||||||
allowed_tools=["Read", "Write", "Edit"],
|
allowed_tools=["Read", "Write"], # Allow reading and writing
|
||||||
)
|
)
|
||||||
|
|
||||||
# Sandbox mode for command execution
|
# Full permissions with command execution
|
||||||
config = ClaudeCodeConfig()
|
config = ClaudeCodeConfig()
|
||||||
agent = ClaudeCode(
|
agent = ClaudeCode(
|
||||||
config,
|
config,
|
||||||
signature="message:str -> answer:str",
|
signature="message:str -> answer:str",
|
||||||
working_directory=".",
|
working_directory=".",
|
||||||
sandbox={"enabled": True},
|
permission_mode="acceptEdits",
|
||||||
|
allowed_tools=["Read", "Write", "Bash"], # All tools enabled
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -489,7 +766,7 @@ agent = ClaudeCode(
|
|||||||
signature="message:str -> review:CodeReview",
|
signature="message:str -> review:CodeReview",
|
||||||
working_directory="/path/to/project",
|
working_directory="/path/to/project",
|
||||||
permission_mode="default",
|
permission_mode="default",
|
||||||
allowed_tools=["Read", "Glob", "Grep"],
|
allowed_tools=["Read"], # Read-only for code review
|
||||||
)
|
)
|
||||||
|
|
||||||
result = agent(message="Review the changes in src/main.py")
|
result = agent(message="Review the changes in src/main.py")
|
||||||
@@ -757,9 +1034,10 @@ See LICENSE file.
|
|||||||
|
|
||||||
## Related Documentation
|
## Related Documentation
|
||||||
|
|
||||||
- [Claude Code SDK API Reference](https://docs.claude.com/en/agent-sdk/python)
|
- [Claude Agent SDK - Python Reference](https://platform.claude.com/docs/en/agent-sdk/python) - Complete SDK API reference
|
||||||
- [DSPy Documentation](https://dspy-docs.vercel.app/)
|
- [Claude Agent SDK - Overview](https://platform.claude.com/docs/en/agent-sdk/overview) - SDK concepts and guides
|
||||||
- [Claude Code Documentation](https://code.claude.com/docs)
|
- [DSPy Documentation](https://dspy-docs.vercel.app/) - DSPy framework documentation
|
||||||
|
- [Claude Code CLI](https://code.claude.com) - Claude Code command-line interface
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -44,19 +44,61 @@ class ClaudeCodeConfig(PrecompiledConfig):
|
|||||||
|
|
||||||
model: str = "claude-opus-4-5-20251101"
|
model: str = "claude-opus-4-5-20251101"
|
||||||
|
|
||||||
|
|
||||||
class ClaudeCodeKwargs(BaseModel):
|
class ClaudeCodeKwargs(BaseModel):
|
||||||
model_config = {"arbitrary_types_allowed": True}
|
"""Arguments for ClaudeCode initialization.
|
||||||
|
|
||||||
signature: Any # str | dspy.Signature (validated manually)
|
Matches ClaudeAgentOptions from the SDK with additional DSPy-specific fields.
|
||||||
|
See: https://platform.claude.com/docs/en/agent-sdk/python#claudeagentoptions
|
||||||
|
"""
|
||||||
|
|
||||||
|
# DSPy-specific (required)
|
||||||
|
signature: Any # str | dspy.Signature - validated manually in __init__
|
||||||
|
|
||||||
|
# Authentication
|
||||||
api_key: str | None = None
|
api_key: str | None = None
|
||||||
|
|
||||||
|
# Basic configuration
|
||||||
working_directory: str = "."
|
working_directory: str = "."
|
||||||
permission_mode: str | None = None
|
permission_mode: str | None = None
|
||||||
allowed_tools: list[str] | None = None
|
allowed_tools: list[str] | None = None # Any Claude Code tools
|
||||||
disallowed_tools: list[str] | None = None
|
disallowed_tools: list[str] | None = None
|
||||||
sandbox: dict[str, Any] | None = None
|
sandbox: dict[str, Any] | None = None
|
||||||
system_prompt: str | dict[str, Any] | None = None
|
system_prompt: str | dict[str, Any] | None = None
|
||||||
|
|
||||||
|
# MCP servers
|
||||||
|
mcp_servers: dict[str, Any] | str | Path | None = None
|
||||||
|
|
||||||
|
# Session management
|
||||||
|
continue_conversation: bool = False
|
||||||
|
resume: str | None = None
|
||||||
|
max_turns: int | None = None
|
||||||
|
fork_session: bool = False
|
||||||
|
|
||||||
|
# Advanced options
|
||||||
|
permission_prompt_tool_name: str | None = None
|
||||||
|
settings: str | None = None
|
||||||
|
add_dirs: list[str | Path] | None = None
|
||||||
|
env: dict[str, str] | None = None
|
||||||
|
extra_args: dict[str, str | None] | None = None
|
||||||
|
max_buffer_size: int | None = None
|
||||||
|
|
||||||
|
# Callbacks and hooks
|
||||||
|
stderr: Any | None = None # Callable[[str], None] - can't type check callables in Pydantic easily
|
||||||
|
can_use_tool: Any | None = None # CanUseTool callback
|
||||||
|
hooks: dict[str, list[dict[str, Any]]] | None = None
|
||||||
|
|
||||||
|
# User and settings
|
||||||
|
user: str | None = None
|
||||||
|
include_partial_messages: bool = False
|
||||||
|
setting_sources: list[str] | None = None # List of "user" | "project" | "local"
|
||||||
|
|
||||||
|
# Subagents and plugins
|
||||||
|
agents: dict[str, dict[str, Any]] | None = None
|
||||||
|
plugins: list[dict[str, Any]] | None = None
|
||||||
|
|
||||||
|
# CLI configuration
|
||||||
|
cli_path: str | Path | None = None
|
||||||
|
|
||||||
|
|
||||||
class ClaudeCode(PrecompiledProgram):
|
class ClaudeCode(PrecompiledProgram):
|
||||||
"""DSPy module that wraps Claude Code SDK.
|
"""DSPy module that wraps Claude Code SDK.
|
||||||
@@ -88,22 +130,14 @@ class ClaudeCode(PrecompiledProgram):
|
|||||||
|
|
||||||
args = ClaudeCodeKwargs(**kwargs)
|
args = ClaudeCodeKwargs(**kwargs)
|
||||||
|
|
||||||
|
# Parse and validate signature
|
||||||
signature = args.signature
|
signature = args.signature
|
||||||
api_key = args.api_key
|
|
||||||
working_directory = args.working_directory
|
|
||||||
permission_mode = args.permission_mode
|
|
||||||
allowed_tools = args.allowed_tools
|
|
||||||
disallowed_tools = args.disallowed_tools
|
|
||||||
sandbox = args.sandbox
|
|
||||||
system_prompt = args.system_prompt
|
|
||||||
|
|
||||||
# parse and validate signature
|
|
||||||
if isinstance(signature, str):
|
if isinstance(signature, str):
|
||||||
self.signature = dspy.Signature(signature)
|
self.signature = dspy.Signature(signature)
|
||||||
else:
|
else:
|
||||||
self.signature = signature
|
self.signature = signature
|
||||||
|
|
||||||
# validate signature has exactly 1 input and 1 output
|
# Validate signature has exactly 1 input and 1 output
|
||||||
input_fields = list(self.signature.input_fields.keys())
|
input_fields = list(self.signature.input_fields.keys())
|
||||||
output_fields = list(self.signature.output_fields.keys())
|
output_fields = list(self.signature.output_fields.keys())
|
||||||
|
|
||||||
@@ -124,17 +158,51 @@ class ClaudeCode(PrecompiledProgram):
|
|||||||
self.input_field = self.signature.input_fields[self.input_field_name]
|
self.input_field = self.signature.input_fields[self.input_field_name]
|
||||||
self.output_field = self.signature.output_fields[self.output_field_name]
|
self.output_field = self.signature.output_fields[self.output_field_name]
|
||||||
|
|
||||||
# store config values
|
# Store all configuration values
|
||||||
self.working_directory = Path(working_directory).resolve()
|
self.api_key = args.api_key or os.getenv("ANTHROPIC_API_KEY")
|
||||||
|
self.working_directory = Path(args.working_directory).resolve()
|
||||||
self.model = config.model
|
self.model = config.model
|
||||||
self.permission_mode = permission_mode
|
|
||||||
self.allowed_tools = allowed_tools
|
# Basic options
|
||||||
self.disallowed_tools = disallowed_tools
|
self.permission_mode = args.permission_mode
|
||||||
self.sandbox = sandbox
|
self.allowed_tools = args.allowed_tools
|
||||||
self.system_prompt = system_prompt
|
self.disallowed_tools = args.disallowed_tools
|
||||||
self.api_key = api_key or os.getenv("ANTHROPIC_API_KEY")
|
self.sandbox = args.sandbox
|
||||||
# No extra options since all kwargs are parsed by ClaudeCodeKwargs
|
self.system_prompt = args.system_prompt
|
||||||
self.extra_options = {}
|
|
||||||
|
# MCP servers
|
||||||
|
self.mcp_servers = args.mcp_servers
|
||||||
|
|
||||||
|
# Session management
|
||||||
|
self.continue_conversation = args.continue_conversation
|
||||||
|
self.resume = args.resume
|
||||||
|
self.max_turns = args.max_turns
|
||||||
|
self.fork_session = args.fork_session
|
||||||
|
|
||||||
|
# Advanced options
|
||||||
|
self.permission_prompt_tool_name = args.permission_prompt_tool_name
|
||||||
|
self.settings = args.settings
|
||||||
|
self.add_dirs = args.add_dirs
|
||||||
|
self.env = args.env
|
||||||
|
self.extra_args = args.extra_args
|
||||||
|
self.max_buffer_size = args.max_buffer_size
|
||||||
|
|
||||||
|
# Callbacks and hooks
|
||||||
|
self.stderr = args.stderr
|
||||||
|
self.can_use_tool = args.can_use_tool
|
||||||
|
self.hooks = args.hooks
|
||||||
|
|
||||||
|
# User and settings
|
||||||
|
self.user = args.user
|
||||||
|
self.include_partial_messages = args.include_partial_messages
|
||||||
|
self.setting_sources = args.setting_sources
|
||||||
|
|
||||||
|
# Subagents and plugins
|
||||||
|
self.agents = args.agents
|
||||||
|
self.plugins = args.plugins
|
||||||
|
|
||||||
|
# CLI configuration
|
||||||
|
self.cli_path = args.cli_path
|
||||||
|
|
||||||
# determine output format upfront
|
# determine output format upfront
|
||||||
self.output_format = self._get_output_format()
|
self.output_format = self._get_output_format()
|
||||||
@@ -154,19 +222,68 @@ class ClaudeCode(PrecompiledProgram):
|
|||||||
|
|
||||||
def _create_client(self) -> ClaudeSDKClient:
|
def _create_client(self) -> ClaudeSDKClient:
|
||||||
"""Create ClaudeSDKClient with configured options."""
|
"""Create ClaudeSDKClient with configured options."""
|
||||||
options = ClaudeAgentOptions(
|
# Build options dict, only including non-None values
|
||||||
cwd=str(self.working_directory),
|
options_dict = {
|
||||||
model=self.model,
|
"cwd": str(self.working_directory),
|
||||||
permission_mode=self.permission_mode,
|
"model": self.model,
|
||||||
allowed_tools=self.allowed_tools or [],
|
"output_format": self.output_format,
|
||||||
disallowed_tools=self.disallowed_tools or [],
|
}
|
||||||
sandbox=self.sandbox,
|
|
||||||
system_prompt=self.system_prompt,
|
|
||||||
output_format=self.output_format, # include output format
|
|
||||||
**self.extra_options,
|
|
||||||
)
|
|
||||||
|
|
||||||
# set API key if provided
|
# Add optional fields only if they're not None
|
||||||
|
if self.permission_mode is not None:
|
||||||
|
options_dict["permission_mode"] = self.permission_mode
|
||||||
|
if self.allowed_tools is not None:
|
||||||
|
options_dict["allowed_tools"] = self.allowed_tools
|
||||||
|
if self.disallowed_tools is not None:
|
||||||
|
options_dict["disallowed_tools"] = self.disallowed_tools
|
||||||
|
if self.sandbox is not None:
|
||||||
|
options_dict["sandbox"] = self.sandbox
|
||||||
|
if self.system_prompt is not None:
|
||||||
|
options_dict["system_prompt"] = self.system_prompt
|
||||||
|
if self.mcp_servers is not None:
|
||||||
|
options_dict["mcp_servers"] = self.mcp_servers
|
||||||
|
if self.continue_conversation:
|
||||||
|
options_dict["continue_conversation"] = self.continue_conversation
|
||||||
|
if self.resume is not None:
|
||||||
|
options_dict["resume"] = self.resume
|
||||||
|
if self.max_turns is not None:
|
||||||
|
options_dict["max_turns"] = self.max_turns
|
||||||
|
if self.fork_session:
|
||||||
|
options_dict["fork_session"] = self.fork_session
|
||||||
|
if self.permission_prompt_tool_name is not None:
|
||||||
|
options_dict["permission_prompt_tool_name"] = self.permission_prompt_tool_name
|
||||||
|
if self.settings is not None:
|
||||||
|
options_dict["settings"] = self.settings
|
||||||
|
if self.add_dirs is not None:
|
||||||
|
options_dict["add_dirs"] = self.add_dirs
|
||||||
|
if self.env is not None:
|
||||||
|
options_dict["env"] = self.env
|
||||||
|
if self.extra_args is not None:
|
||||||
|
options_dict["extra_args"] = self.extra_args
|
||||||
|
if self.max_buffer_size is not None:
|
||||||
|
options_dict["max_buffer_size"] = self.max_buffer_size
|
||||||
|
if self.stderr is not None:
|
||||||
|
options_dict["stderr"] = self.stderr
|
||||||
|
if self.can_use_tool is not None:
|
||||||
|
options_dict["can_use_tool"] = self.can_use_tool
|
||||||
|
if self.hooks is not None:
|
||||||
|
options_dict["hooks"] = self.hooks
|
||||||
|
if self.user is not None:
|
||||||
|
options_dict["user"] = self.user
|
||||||
|
if self.include_partial_messages:
|
||||||
|
options_dict["include_partial_messages"] = self.include_partial_messages
|
||||||
|
if self.setting_sources is not None:
|
||||||
|
options_dict["setting_sources"] = self.setting_sources
|
||||||
|
if self.agents is not None:
|
||||||
|
options_dict["agents"] = self.agents
|
||||||
|
if self.plugins is not None:
|
||||||
|
options_dict["plugins"] = self.plugins
|
||||||
|
if self.cli_path is not None:
|
||||||
|
options_dict["cli_path"] = self.cli_path
|
||||||
|
|
||||||
|
options = ClaudeAgentOptions(**options_dict)
|
||||||
|
|
||||||
|
# Set API key if provided
|
||||||
if self.api_key:
|
if self.api_key:
|
||||||
os.environ["ANTHROPIC_API_KEY"] = self.api_key
|
os.environ["ANTHROPIC_API_KEY"] = self.api_key
|
||||||
|
|
||||||
|
|||||||
25
main.py
25
main.py
@@ -1,5 +1,6 @@
|
|||||||
from claude_dspy import ClaudeCode, ClaudeCodeConfig
|
from claude_dspy import ClaudeCode, ClaudeCodeConfig
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
from modaic import AutoProgram
|
||||||
import dspy
|
import dspy
|
||||||
|
|
||||||
|
|
||||||
@@ -9,7 +10,7 @@ class Output(BaseModel):
|
|||||||
|
|
||||||
class ClaudeCodeSignature(dspy.Signature):
|
class ClaudeCodeSignature(dspy.Signature):
|
||||||
message: str = dspy.InputField(desc="Request to process")
|
message: str = dspy.InputField(desc="Request to process")
|
||||||
output: list[str] = dspy.OutputField(desc="List of files modified or created")
|
output: Output = dspy.OutputField(desc="List of files modified or created")
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
@@ -22,24 +23,22 @@ def main():
|
|||||||
signature=ClaudeCodeSignature,
|
signature=ClaudeCodeSignature,
|
||||||
working_directory=".",
|
working_directory=".",
|
||||||
permission_mode="acceptEdits",
|
permission_mode="acceptEdits",
|
||||||
allowed_tools=["Read", "Glob", "Write"],
|
allowed_tools=["Read", "Bash", "Write"],
|
||||||
)
|
)
|
||||||
|
|
||||||
# use it
|
|
||||||
print("Running ClaudeCode...")
|
|
||||||
result = cc(
|
|
||||||
message="Create a new file called helloworld.txt with the alphabet backwards"
|
|
||||||
)
|
|
||||||
|
|
||||||
print(f"Success: {result.output}")
|
|
||||||
print(result.usage)
|
|
||||||
|
|
||||||
print(f"Session ID: {cc.session_id}")
|
|
||||||
cc.push_to_hub(
|
cc.push_to_hub(
|
||||||
"farouk1/claude-code",
|
"farouk1/claude-code",
|
||||||
with_code=True,
|
with_code=True,
|
||||||
commit_message="Add more functionality to signature description parsing",
|
commit_message="Add more functionality to signature description parsing",
|
||||||
)
|
)
|
||||||
|
"""
|
||||||
|
agent = AutoProgram.from_precompiled("farouk1/claude-code", signature=ClaudeCodeSignature, working_directory=".", permission_mode="acceptEdits", allowed_tools=["Read", "Write", "Bash"])
|
||||||
|
|
||||||
|
# Test the agent
|
||||||
|
result = agent(message="create a python program that prints 'Hello, World!' and save it to a file in this directory")
|
||||||
|
print(result.output.files)
|
||||||
|
print(result.output)
|
||||||
|
print(result.usage)
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
Reference in New Issue
Block a user