Add more functionality to signature description parsing

This commit is contained in:
2025-12-06 01:41:32 -05:00
parent b5284d344b
commit e7b1ead56e
3 changed files with 470 additions and 76 deletions

332
README.md
View File

@@ -16,18 +16,21 @@ A DSPy module that wraps the Claude Code Python SDK with a signature-driven inte
## Installation
```bash
# Install with uv
# install with uv
uv add claude-agent-sdk dspy modaic nest-asyncio
# Or with pip
# or with pip
pip install claude-agent-sdk dspy modaic nest-asyncio
```
**Prerequisites:**
- 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
**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)
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
```bash
# Copy the example file
# copy the example file
cp .env.example .env
# Edit .env with your keys
# edit .env with your keys
ANTHROPIC_API_KEY="<YOUR_ANTHROPIC_API_KEY>"
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
from modaic import AutoProgram
from pydantic import BaseModel
import dspy
class FileList(BaseModel):
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
# Note: Only model is set via config, other params are kwargs
agent = AutoProgram.from_precompiled(
"farouk1/claude-code",
config={
"signature": "message:str -> output:FileList",
"working_directory": ".",
}
signature=FileSignature,
working_directory=".",
)
# Use it!
@@ -70,15 +77,26 @@ print(result.usage) # Token usage
### 3. Override Config Options
```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
# Model comes from config, other params are kwargs
agent = AutoProgram.from_precompiled(
"farouk1/claude-code",
config={
"signature": "message:str -> answer:str",
"model": "claude-opus-4-5-20251101",
"permission_mode": "acceptEdits",
"allowed_tools": ["Read", "Write", "Bash"],
}
config=config,
signature=MySignature,
working_directory=".",
permission_mode="acceptEdits",
allowed_tools=["Read", "Write", "Bash"],
)
```
@@ -174,6 +192,8 @@ class ClaudeCodeConfig:
- **`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
Main agent class.
@@ -196,16 +216,49 @@ class ClaudeCode(PrecompiledProgram):
**Parameters:**
**Core Configuration:**
- **`config`** - ClaudeCodeConfig instance with model configuration
- **`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: `"."`)
- **`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
- **`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
- **`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
##### `__call__(**kwargs) -> Prediction` (or `forward`)
@@ -292,11 +345,234 @@ print(agent.session_id) # 'eb1b2f39-e04c-4506-9398-b50053b1fd83'
##### `config: ClaudeCodeConfig`
Access to the agent's configuration.
Access to the agent's configuration (model only).
```python
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
@@ -446,7 +722,7 @@ agent = ClaudeCode(
signature="message:str -> answer:str",
working_directory=".",
permission_mode="default",
allowed_tools=["Read", "Glob", "Grep"],
allowed_tools=["Read"], # Only allow reading files
)
# Auto-accept file edits
@@ -456,16 +732,17 @@ agent = ClaudeCode(
signature="message:str -> answer:str",
working_directory=".",
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()
agent = ClaudeCode(
config,
signature="message:str -> answer:str",
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",
working_directory="/path/to/project",
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")
@@ -757,9 +1034,10 @@ See LICENSE file.
## Related Documentation
- [Claude Code SDK API Reference](https://docs.claude.com/en/agent-sdk/python)
- [DSPy Documentation](https://dspy-docs.vercel.app/)
- [Claude Code Documentation](https://code.claude.com/docs)
- [Claude Agent SDK - Python Reference](https://platform.claude.com/docs/en/agent-sdk/python) - Complete SDK API reference
- [Claude Agent SDK - Overview](https://platform.claude.com/docs/en/agent-sdk/overview) - SDK concepts and guides
- [DSPy Documentation](https://dspy-docs.vercel.app/) - DSPy framework documentation
- [Claude Code CLI](https://code.claude.com) - Claude Code command-line interface
---