diff --git a/examples/python-sdk/README.md b/examples/python-sdk/README.md index 3c0b316..d87982c 100644 --- a/examples/python-sdk/README.md +++ b/examples/python-sdk/README.md @@ -1,7 +1,24 @@ -# Augment SDK Examples +# Augment Python SDK Examples This directory contains examples demonstrating how to use the Augment Python SDK. +## Quick Links + +- **[User Examples](user_examples/)** - Numbered tutorial examples (01-09) with a comprehensive [user guide](user_examples/user_guide.md) +- **[Documentation](docs/)** - Detailed guides on specific features +- **Basic Examples** - See below for standalone example scripts + +## Installation + +```bash +pip install auggie-sdk +``` + +Make sure you have the Auggie CLI installed: +```bash +auggie --version +``` + ## Basic Examples ### `basic_usage.py` @@ -16,7 +33,7 @@ Demonstrates fundamental SDK features: **Run it:** ```bash -python examples/basic_usage.py +python basic_usage.py ``` ### `session_usage.py` @@ -28,16 +45,15 @@ Shows how to use sessions for conversation continuity: **Run it:** ```bash -python examples/session_usage.py +python session_usage.py ``` -### `list_prs.py` / `list_prs_2.py` -Examples of working with GitHub PRs using the SDK. +### `list_prs.py` +Example of working with GitHub PRs using the SDK. -**Run them:** +**Run it:** ```bash -python examples/list_prs.py -python examples/list_prs_2.py +python list_prs.py ``` ## ACP Client Examples @@ -51,33 +67,13 @@ Demonstrates the AuggieACPClient for persistent sessions with the Augment CLI: **Run it:** ```bash -python examples/acp_example_usage.py -``` - -### Claude Code Client Tests - -For ClaudeCodeACPClient examples and testing, see the **real E2E tests**: - -**Location:** `tests/test_claude_code_client_e2e.py` - -**Prerequisites:** -```bash -npm install -g @zed-industries/claude-code-acp -export ANTHROPIC_API_KEY=... +python acp_example_usage.py ``` -**Run tests:** -```bash -# Quick tests (~30 seconds) -pytest tests/test_claude_code_client_e2e.py +### Claude Code Client -# All tests including slow ones (~5-10 minutes) -pytest tests/test_claude_code_client_e2e.py -m "" -``` - -**Documentation:** -- [Claude Code Client Guide](../docs/CLAUDE_CODE_CLIENT.md) -- [Testing Guide](../tests/README_CLAUDE_CODE_TESTS.md) +For ClaudeCodeACPClient documentation, see: +- [Claude Code Client Guide](docs/CLAUDE_CODE_CLIENT.md) ## Prompt-to-SDK Conversion @@ -104,22 +100,7 @@ Finally: - Create fix suggestions for top 3 ``` -**Convert it to SDK code:** -```bash -# In TUI mode -/prompt-to-sdk examples/example_complex_prompt.txt - -# From command line -auggie command prompt-to-sdk examples/example_complex_prompt.txt -``` - -### `demo_prompt_to_sdk.sh` -Interactive demo script that shows the prompt-to-sdk conversion process. - -**Run it:** -```bash -./examples/demo_prompt_to_sdk.sh -``` +See [README_PROMPT_TO_CODE.md](README_PROMPT_TO_CODE.md) for more details on prompt-to-code conversion. ## Workflow Patterns @@ -252,28 +233,35 @@ All examples can be run directly: ```bash # Run a specific example -python examples/basic_usage.py +python basic_usage.py # Run with custom workspace -python examples/basic_usage.py --workspace /path/to/workspace +python basic_usage.py --workspace /path/to/workspace # Run with different model -python examples/basic_usage.py --model gpt-4o +python basic_usage.py --model gpt-4o ``` ## Documentation -- **SDK README**: `../README.md` -- **Prompt-to-SDK Guide**: `../PROMPT_TO_SDK_GUIDE.md` -- **Slash Command Summary**: `../SLASH_COMMAND_SUMMARY.md` +For more comprehensive documentation, see the [docs](docs/) directory: +- [Agent Event Listener](docs/AGENT_EVENT_LISTENER.md) +- [Architecture](docs/ARCHITECTURE.md) +- [Automatic Type Inference](docs/AUTOMATIC_TYPE_INFERENCE.md) +- [Claude Code Client](docs/CLAUDE_CODE_CLIENT.md) +- [Function Calling](docs/FUNCTION_CALLING.md) +- [Prompt to Code](docs/PROMPT_TO_CODE.md) +- [Session Continuity](docs/SESSION_CONTINUITY.md) + +Also check out the [user_examples](user_examples/) directory for more examples and the [user guide](user_examples/user_guide.md). ## Getting Help If you encounter issues: -1. Check that the SDK is installed: `pip install -e .` +1. Check that the SDK is installed: `pip install auggie-sdk` 2. Verify auggie CLI is available: `auggie --version` 3. Check the workspace path is correct 4. Review error messages for specific issues -For more help, see the main SDK documentation. +For more help, see the documentation in the [docs](docs/) directory. diff --git a/examples/python-sdk/docs/AGENT_EVENT_LISTENER.md b/examples/python-sdk/docs/AGENT_EVENT_LISTENER.md new file mode 100644 index 0000000..cf2040e --- /dev/null +++ b/examples/python-sdk/docs/AGENT_EVENT_LISTENER.md @@ -0,0 +1,291 @@ +# AgentEventListener Interface Explained + +The `AgentEventListener` interface allows you to receive real-time notifications about what the agent is doing while it processes your request. This is useful for: +- Building UIs that show progress +- Logging agent activity +- Debugging agent behavior +- Providing user feedback during long-running operations + +## Overview + +When you send a message to the agent (e.g., "Create a function to add two numbers"), the agent goes through several steps: + +1. **Thinking** - The agent reasons about what to do +2. **Tool Calls** - The agent uses tools (read files, write files, run commands, etc.) +3. **Responding** - The agent sends back a message with the results + +The `AgentEventListener` lets you observe all of these steps in real-time as they happen. + +## The Five Event Types + +### 1. `on_agent_message_chunk(text: str)` + +**What it is:** Chunks of the agent's response message as it's being streamed. + +**When it's called:** Multiple times as the agent streams its response text to you. + +**Example scenario:** +``` +You ask: "What is 2 + 2?" + +The agent responds: "The answer is 4." + +Your listener receives: + on_agent_message_chunk("The ") + on_agent_message_chunk("answer ") + on_agent_message_chunk("is ") + on_agent_message_chunk("4.") + on_agent_message("The answer is 4.") # Complete message at the end +``` + +**Why it's chunked:** The agent streams its response in real-time rather than waiting to generate the entire message. This allows you to show progress to users immediately. + +**Use cases:** +- Display the agent's response as it's being typed (like ChatGPT) +- Show a progress indicator while waiting for the full response +- Build up the complete message incrementally + +### 2. `on_agent_thought(text: str)` + +**What it is:** The agent's internal reasoning/thinking process. + +**When it's called:** When the agent is thinking about what to do next, before it takes action. + +**Example scenario:** +``` +You ask: "Read the README file and summarize it" + +Your listener receives: + on_agent_thought("I need to first read the README.md file using the view tool") + on_agent_thought("Then I'll analyze the content and create a summary") +``` + +**Why it's useful:** Helps you understand the agent's decision-making process. This is like seeing the agent "think out loud." + +**Use cases:** +- Debug why the agent chose a particular approach +- Show users what the agent is planning to do +- Log the agent's reasoning for audit purposes + +### 3. `on_tool_call(tool_call_id, title, kind, status)` + +**What it is:** Notification that the agent is making a tool call. + +**When it's called:** When the agent begins executing a tool (reading a file, editing code, running a command, etc.) + +**Parameters:** +- `tool_call_id`: Unique ID for this specific tool call (e.g., "tc_123") +- `title`: Human-readable description (e.g., "view", "str-replace-editor", "launch-process") +- `kind`: Category of tool (e.g., "read", "edit", "delete", "execute") +- `status`: Current status (e.g., "pending", "in_progress") + +**Example scenario:** +``` +You ask: "Read the file test.py and fix any errors" + +Your listener receives: + on_tool_call( + tool_call_id="tc_001", + title="view", + kind="read", + status="pending" + ) +``` + +**Use cases:** +- Show "Reading file..." or "Editing file..." messages to users +- Track which tools the agent is using +- Display a progress indicator for long-running operations + +### 4. `on_tool_response(tool_call_id, status, content)` + +**What it is:** Response from a tool call. + +**When it's called:** When a tool responds with results. + +**Parameters:** +- `tool_call_id`: The ID from the corresponding `on_tool_call` +- `status`: Response status (e.g., "completed", "failed") +- `content`: Results or additional information from the tool + +**Example scenario:** +``` +Continuing from above... + +Your listener receives: + on_tool_response( + tool_call_id="tc_001", + status="completed", + content={"file_content": "def test():\n pass"} + ) +``` + +**Use cases:** +- Update progress indicators +- Show tool results to users +- Detect when operations complete or fail + +### 5. `on_agent_message(message: str)` + +**What it is:** The complete agent message after all chunks have been sent. + +**When it's called:** Once, after the agent finishes streaming its complete response. + +**Example scenario:** +``` +After receiving all the chunks: + on_agent_message_chunk("The ") + on_agent_message_chunk("answer ") + on_agent_message_chunk("is ") + on_agent_message_chunk("4.") + +You receive: + on_agent_message("The answer is 4.") +``` + +**Use cases:** +- Log the complete message +- Process the full response (e.g., parse it, save it) +- Update UI with final message +- Trigger actions based on complete response + +## Complete Example + +Here's a complete example showing all events in action: + +```python +from auggie_sdk import Auggie +from auggie_sdk.acp import AgentEventListener + +class MyListener(AgentEventListener): + def on_agent_message_chunk(self, text: str) -> None: + print(f"[CHUNK] {text}", end="", flush=True) + + def on_agent_message(self, message: str) -> None: + print(f"\n[COMPLETE MESSAGE] {message}") + + def on_tool_call(self, tool_call_id: str, title: str, + kind: str = None, status: str = None) -> None: + print(f"\n[TOOL CALL] {title} (kind={kind}, status={status})") + + def on_tool_response(self, tool_call_id: str, + status: str = None, content: Any = None) -> None: + print(f"[TOOL RESPONSE] status={status}") + + def on_agent_thought(self, text: str) -> None: + print(f"[THINKING] {text}") + +# Use the listener +listener = MyListener() +agent = Auggie(listener=listener) + +response = agent.run("Read the file test.py and count the lines") +``` + +**Output might look like:** +``` +[THINKING] I need to read the test.py file first +[TOOL CALL] view (kind=read, status=pending) +[TOOL RESPONSE] status=completed +[CHUNK] The file test.py has +[CHUNK] 42 lines +[CHUNK] . +[COMPLETE MESSAGE] The file test.py has 42 lines. +``` + +## Event Flow Diagram + +``` +User sends message: "Create a function" + ↓ +[THINKING] "I'll create a new file..." + ↓ +[TOOL CALL] save-file (kind=create) + ↓ +[TOOL RESPONSE] status=completed + ↓ +[AGENT MESSAGE CHUNK] "I've created " +[AGENT MESSAGE CHUNK] "the function " +[AGENT MESSAGE CHUNK] "for you." + ↓ +[AGENT MESSAGE] "I've created the function for you." +``` + +## Important Notes + +1. **Chunks vs Complete Messages**: `on_agent_message_chunk` is called multiple times with small chunks. `on_agent_message` is called once with the complete message at the end. + +2. **Optional Methods**: `on_agent_thought` and `on_agent_message` are NOT abstract, so you don't have to implement them if you don't need them. + +3. **Tool Call Pairing**: Each `on_tool_call` will have one or more corresponding `on_tool_response` calls with the same `tool_call_id`. + +4. **Thread Safety**: If you're updating a UI, make sure your listener methods are thread-safe. + +5. **Performance**: Keep your listener methods fast - they're called synchronously and will block the agent if they take too long. + +## Common Patterns + +### Pattern 1: Accumulate Full Message +```python +class MessageAccumulator(AgentEventListener): + def __init__(self): + self.message_chunks = [] + + def on_agent_message_chunk(self, text: str) -> None: + self.message_chunks.append(text) + + def on_agent_message(self, message: str) -> None: + # Or just use the complete message directly! + print(f"Complete message: {message}") + + # ... implement other required methods ... +``` + +### Pattern 2: Track Tool Usage +```python +class ToolTracker(AgentEventListener): + def __init__(self): + self.tools_used = [] + + def on_tool_call(self, tool_call_id, title, kind=None, status=None): + self.tools_used.append({ + "id": tool_call_id, + "name": title, + "kind": kind, + "start_time": time.time() + }) + + # ... implement other required methods ... +``` + +### Pattern 3: Progress UI +```python +class ProgressListener(AgentEventListener): + def on_tool_call(self, tool_call_id, title, kind=None, status=None): + print(f"⏳ {title}...") + + def on_tool_response(self, tool_call_id, status=None, content=None): + if status == "completed": + print(f"✅ Done!") + elif status == "failed": + print(f"❌ Failed!") + + # ... implement other required methods ... +``` + +## Quick Reference Table + +| Event | When Called | How Many Times | Purpose | +|-------|-------------|----------------|---------| +| `on_agent_message_chunk` | Agent is streaming response | Many (chunks) | Get response text in real-time | +| `on_agent_message` | Agent finishes response | Once | Get complete message | +| `on_agent_thought` | Agent is thinking | 0 or more | See agent's reasoning | +| `on_tool_call` | Agent makes a tool call | Once per tool | Know what tool is being used | +| `on_tool_response` | Tool responds | 1+ per tool | Get tool results | + +## See Also + +- `examples/event_listener_demo.py` - Interactive demo showing all events +- `examples/` directory for more working examples +- `augment/acp/test_client_e2e.py` for test examples using listeners + diff --git a/examples/python-sdk/docs/ARCHITECTURE.md b/examples/python-sdk/docs/ARCHITECTURE.md new file mode 100644 index 0000000..7a9d209 --- /dev/null +++ b/examples/python-sdk/docs/ARCHITECTURE.md @@ -0,0 +1,268 @@ +# Augment Python SDK Architecture + +## Overview + +The Augment Python SDK is organized into two distinct layers: + +1. **Protocol Layer** - Pure ACP (Agent Client Protocol) implementation +2. **SDK Layer** - Augment-specific convenience features and utilities + +This separation ensures the protocol client remains reusable and spec-compliant, while the SDK layer provides user-friendly features. + +## Architecture Diagram + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ User Code │ +└─────────────────────────────────────────────────────────────────┘ + │ + ├─────────────────┬─────────────────┐ + ↓ ↓ ↓ +┌──────────────────────────────────┐ ┌──────────────────────────────────┐ +│ SDK Layer (Optional) │ │ Protocol Layer (Core) │ +├──────────────────────────────────┤ ├──────────────────────────────────┤ +│ │ │ │ +│ Agent (subprocess-based) │ │ AuggieACPClient │ +│ - run(return_type=int) │ │ - start() │ +│ - Typed returns │ │ - send_message() → str │ +│ - Session management │ │ - stop() │ +│ - CLI subprocess spawning │ │ - Pure ACP protocol │ +│ │ │ - Event listeners │ +│ AgentACP (future) │ │ - Long-running process │ +│ - run(return_type=int) │ │ │ +│ - Typed returns │ │ Model/workspace config: │ +│ - Uses AuggieACPClient ────────────→│ - model parameter │ +│ - Agent-compatible API │ │ - workspace_root parameter │ +│ │ │ - Passed as CLI args │ +└──────────────────────────────────┘ └──────────────────────────────────┘ + │ + ↓ + ┌──────────────────────────────────┐ + │ Augment CLI (ACP mode) │ + │ - Runs with --acp flag │ + │ - Implements ACP protocol │ + │ - Long-running process │ + └──────────────────────────────────┘ +``` + +## Layer Responsibilities + +### Protocol Layer: `AuggieACPClient` + +**Purpose:** Pure implementation of the ACP (Agent Client Protocol) specification. + +**Responsibilities:** +- ✅ Spawn CLI in ACP mode +- ✅ Implement ACP protocol (initialize, newSession, prompt, etc.) +- ✅ Handle bidirectional JSON-RPC communication +- ✅ Stream responses via event listeners +- ✅ Manage process lifecycle +- ✅ Pass configuration via CLI arguments (model, workspace_root) + +**NOT Responsible For:** +- ❌ Typed return values (not in ACP spec) +- ❌ Augment-specific features +- ❌ Convenience wrappers +- ❌ Response parsing beyond protocol requirements + +**API:** +```python +from auggie_sdk.acp import AuggieACPClient + +client = AuggieACPClient( + model="claude-3-5-sonnet-latest", # CLI arg + workspace_root="/path/to/workspace", # CLI arg + listener=MyEventListener() # ACP feature +) + +client.start() # ACP: initialize + newSession +response = client.send_message("...") # ACP: prompt → returns str +client.clear_context() # ACP: clear +client.stop() # Cleanup +``` + +### SDK Layer: `Agent` and `AgentACP` + +**Purpose:** User-friendly API with Augment-specific features. + +**Responsibilities:** +- ✅ Typed return values (int, str, bool, list, dict, dataclass, enum) +- ✅ Convenient API for common use cases +- ✅ Session management (for subprocess-based `Agent`) +- ✅ Response parsing and validation +- ✅ Error handling and retries +- ✅ Augment-specific conventions + +**Current Implementation:** + +#### `Agent` (subprocess-based) +```python +from auggie_sdk import Auggie + +agent = Auggie( + workspace_root="/path/to/workspace", + model="claude-3-5-sonnet-latest" +) + +# Typed returns - SDK feature +result = agent.run("What is 2 + 2?", return_type=int) +print(result) # 4 (int, not str) + +# Session management - SDK feature +with agent.session() as session: + session.run("Create a function") + session.run("Test it") # Remembers context +``` + +#### `AgentACP` (future - ACP-based) +```python +from auggie_sdk import AgentACP + +agent = AgentACP( + workspace_root="/path/to/workspace", + model="claude-3-5-sonnet-latest" +) + +# Same API as Agent, but uses ACP client internally +result = agent.run("What is 2 + 2?", return_type=int) +print(result) # 4 (int, not str) + +# No session management needed - automatic! +agent.run("Create a function") +agent.run("Test it") # Automatically remembers context +``` + +## Feature Mapping + +| Feature | Protocol Layer | SDK Layer | Notes | +|---------|---------------|-----------|-------| +| **Model selection** | ✅ CLI arg | ✅ Constructor param | Passed to CLI via `--model` | +| **Workspace root** | ✅ CLI arg | ✅ Constructor param | Passed to CLI via `--workspace-root` | +| **Send message** | ✅ `send_message()` | ✅ `run()` | Protocol returns str, SDK can parse | +| **Typed returns** | ❌ Not in spec | ✅ `run(return_type=T)` | SDK-level feature | +| **Event streaming** | ✅ Event listeners | ⚠️ Optional | Protocol feature, SDK can expose | +| **Session continuity** | ✅ Automatic | ✅ Automatic (ACP) / Manual (subprocess) | Long-running vs subprocess | +| **Context clearing** | ✅ `clear_context()` | ✅ Exposed | Protocol feature | +| **Timeout** | ✅ Per-message | ✅ Per-request | Both support | + +## Design Principles + +### 1. Separation of Concerns + +**Protocol Layer:** +- Implements ACP specification exactly +- No Augment-specific features +- Reusable by other projects +- Minimal dependencies + +**SDK Layer:** +- Augment-specific conveniences +- User-friendly API +- Can use protocol layer or subprocess +- Rich feature set + +### 2. Configuration via CLI Arguments + +The ACP client passes configuration to the CLI via command-line arguments rather than protocol messages: + +```python +# Model and workspace are CLI arguments +cli_args = ["node", "augment.mjs", "--acp", "--model", "claude-3-5-sonnet-latest", "--workspace-root", "/path"] + +# NOT protocol parameters +# NewSessionRequest only has: cwd, mcpServers +``` + +**Why:** +- Model selection happens at CLI initialization +- Workspace root affects CLI behavior globally +- Keeps protocol simple and focused +- Matches how CLI is designed + +### 3. Long-Running Session Model + +The ACP client maintains a single persistent session: + +```python +client.start() # Creates ONE session +client.send_message("A") # Same session +client.send_message("B") # Same session +client.send_message("C") # Same session +client.stop() # Ends session +``` + +**Benefits:** +- Automatic context continuity +- No session resume needed +- Better performance (no subprocess overhead) +- Real-time streaming + +**Contrast with subprocess model:** +```python +agent.run("A") # New process, new session +agent.run("B") # New process, new session - NO CONTEXT + +with agent.session() as session: + session.run("A") # Process with session ID + session.run("B") # New process, resumes session - HAS CONTEXT +``` + +## Migration Path + +### Current State +- ✅ `Agent` - Subprocess-based, full-featured +- ✅ `AuggieACPClient` - Protocol layer, model/workspace support + +### Future State +- ✅ `Agent` - Keep for simple use cases +- ✅ `AuggieACPClient` - Protocol layer (stable) +- 🔄 `AgentACP` - SDK wrapper using ACP client (to be implemented) + +### When to Use Each + +**Use `Agent` (subprocess) when:** +- Simple one-off requests +- Don't need real-time streaming +- Want simplest API +- Explicit session control preferred + +**Use `AuggieACPClient` (protocol) when:** +- Building custom integrations +- Need full control over ACP protocol +- Want event-driven architecture +- Building tools/libraries + +**Use `AgentACP` (future) when:** +- Multiple related requests +- Want typed returns + automatic sessions +- Need better performance than subprocess +- Want Agent-compatible API with ACP benefits + +## Implementation Status + +### ✅ Complete +- [x] `AuggieACPClient` with model and workspace_root parameters +- [x] CLI argument passing for configuration +- [x] Automatic session continuity +- [x] Event listener support +- [x] Documentation and examples + +### 🔄 Future (Optional) +- [ ] `AgentACP` wrapper class +- [ ] Typed return values in SDK layer +- [ ] Migration guide from `Agent` to `AgentACP` +- [ ] Performance benchmarks + +### ❌ Not Planned +- Typed returns in `AuggieACPClient` (belongs in SDK layer) +- Session resume in ACP client (not needed - long-running) +- Protocol extensions for Augment-specific features (use CLI args) + +## Summary + +The architecture cleanly separates protocol implementation from SDK conveniences: + +- **`AuggieACPClient`** = Pure ACP protocol, reusable, spec-compliant +- **`Agent`/`AgentACP`** = User-friendly SDK with typed returns and conveniences + +This design keeps the protocol layer clean while providing rich features in the SDK layer. diff --git a/examples/python-sdk/docs/AUTOMATIC_TYPE_INFERENCE.md b/examples/python-sdk/docs/AUTOMATIC_TYPE_INFERENCE.md new file mode 100644 index 0000000..dd8bc1a --- /dev/null +++ b/examples/python-sdk/docs/AUTOMATIC_TYPE_INFERENCE.md @@ -0,0 +1,190 @@ +# Automatic Type Inference + +## Summary + +The Agent class now automatically infers the type of responses when no `return_type` is specified. This makes the SDK more intelligent and user-friendly by eliminating the need to explicitly specify types for common use cases. + +## Changes Made + +### 1. Removed `infer_type` Parameter + +**Before:** +```python +# Explicit type inference with infer_type parameter +result, chosen_type = agent.run("What is 2 + 2?", infer_type=[int, str, bool]) +``` + +**After:** +```python +# Automatic type inference (no parameter needed) +result, chosen_type = agent.run("What is 2 + 2?") +``` + +### 2. New Default Behavior + +When `return_type` is not specified, the agent automatically: +1. Infers the best type from a default set of common types +2. Returns a tuple of `(result, inferred_type)` + +**Default inference types:** +- `int` +- `float` +- `bool` +- `str` +- `list` +- `dict` + +### 3. Updated API + +**`Agent.run()` signature:** +```python +def run( + self, + instruction: str, + return_type: Type[T] = None, # infer_type parameter removed + timeout: Optional[int] = None, + max_retries: int = 3, +) -> Union[T, tuple[T, Type[T]]]: +``` + +**Return values:** +- If `return_type` is specified: Returns parsed result of that type +- If `return_type` is None: Returns tuple of `(result, inferred_type)` + +## Examples + +### Automatic Type Inference + +```python +from auggie_sdk import Auggie + +agent = Auggie() + +# Agent automatically determines the type +result, inferred_type = agent.run("What is 2 + 2?") +print(f"Result: {result}, Type: {inferred_type.__name__}") +# Output: Result: 4, Type: int + +result, inferred_type = agent.run("List the primary colors") +print(f"Result: {result}, Type: {inferred_type.__name__}") +# Output: Result: ['red', 'blue', 'yellow'], Type: list + +result, inferred_type = agent.run("Is Python statically typed?") +print(f"Result: {result}, Type: {inferred_type.__name__}") +# Output: Result: False, Type: bool +``` + +### Explicit Type Specification + +```python +# Still works as before - specify exact type +result = agent.run("What is 2 + 2?", return_type=int) +print(f"Result: {result}") # Result: 4 + +# Works with complex types +from dataclasses import dataclass + +@dataclass +class Task: + title: str + priority: str + +task = agent.run("Create a task", return_type=Task) +print(f"Task: {task.title}") +``` + +## Implementation Details + +### Core Changes + +1. **Added `DEFAULT_INFERENCE_TYPES` constant** in `augment/agent.py`: + ```python + DEFAULT_INFERENCE_TYPES = [int, float, bool, str, list, dict] + ``` + +2. **Modified `run()` method** to use automatic type inference when `return_type` is None: + ```python + if return_type is None: + # Type inference mode with default types + return self._run_with_type_inference( + client, + instruction, + DEFAULT_INFERENCE_TYPES, + effective_timeout, + max_retries, + ) + ``` + +3. **Removed `infer_type` parameter** from the `run()` method signature + +4. **Updated return type annotation** to reflect the new behavior: + ```python + -> Union[T, tuple[T, Type[T]]] + ``` + +### Test Updates + +- Updated all tests that call `agent.run()` without `return_type` to expect tuple returns +- Added helper function `make_type_inference_response()` for creating mock responses +- Renamed tests from `test_run_type_inference_*` to `test_run_automatic_type_inference_*` +- All 49 unit tests passing + +### Documentation Updates + +- Updated README.md with automatic type inference examples +- Added dedicated section explaining the feature +- Updated API reference +- Updated examples in `examples/basic_usage.py` + +## Benefits + +1. **Simpler API**: No need to specify `infer_type` parameter +2. **More Intelligent**: Agent automatically chooses the best type +3. **Backward Compatible**: Explicit `return_type` still works as before +4. **Better UX**: Natural behavior - when you don't specify a type, the agent figures it out + +## Migration Guide + +### If you were using `infer_type`: + +**Before:** +```python +result, chosen_type = agent.run("What is 2 + 2?", infer_type=[int, str, bool]) +``` + +**After:** +```python +# Just remove the infer_type parameter +result, chosen_type = agent.run("What is 2 + 2?") +``` + +### If you were calling `run()` without any type: + +**Before:** +```python +response = agent.run("Hello") # Returns string +``` + +**After:** +```python +result, inferred_type = agent.run("Hello") # Returns tuple +# Or unpack just the result if you don't need the type +result, _ = agent.run("Hello") +``` + +## Files Modified + +1. `augment/agent.py` - Core implementation +2. `tests/test_agent.py` - Test updates +3. `README.md` - Documentation +4. `examples/basic_usage.py` - Example updates +5. `pyproject.toml` - Fixed duplicate `[project.optional-dependencies]` section + +## Testing + +All tests pass: +```bash +cd experimental/guy/auggie_sdk +python3 -m pytest tests/test_agent.py -v +# 49 passed in 0.16s +``` diff --git a/examples/python-sdk/docs/CLAUDE_CODE_CLIENT.md b/examples/python-sdk/docs/CLAUDE_CODE_CLIENT.md new file mode 100644 index 0000000..393f084 --- /dev/null +++ b/examples/python-sdk/docs/CLAUDE_CODE_CLIENT.md @@ -0,0 +1,287 @@ +# Claude Code ACP Client + +The `ClaudeCodeACPClient` provides a Python interface to interact with [Claude Code](https://www.anthropic.com/claude-code) via the [Agent Client Protocol (ACP)](https://agentclientprotocol.com/). + +This client uses Zed's open-source ACP adapter ([`@zed-industries/claude-code-acp`](https://github.com/zed-industries/claude-code-acp)) to communicate with Claude Code through the standardized ACP protocol. + +## Prerequisites + +### 1. Install the ACP Adapter + +The adapter is a Node.js package that wraps the Claude Code SDK to speak ACP: + +```bash +npm install -g @zed-industries/claude-code-acp +``` + +### 2. Set Your API Key + +You need an Anthropic API key to use Claude Code: + +```bash +export ANTHROPIC_API_KEY=... +``` + +You can get an API key from [Anthropic's Console](https://console.anthropic.com/). + +## Basic Usage + +### Simple Example + +```python +from auggie_sdk.acp import ClaudeCodeACPClient + +# Create client (uses ANTHROPIC_API_KEY from environment) +client = ClaudeCodeACPClient() + +# Start the agent +client.start() + +# Send a message +response = client.send_message("What is 2 + 2?") +print(response) + +# Stop the agent +client.stop() +``` + +### With Context Manager + +```python +from auggie_sdk.acp import ClaudeCodeACPClient + +with ClaudeCodeACPClient() as client: + response = client.send_message("Write a hello world function in Python") + print(response) +``` + +### With Event Listener + +```python +from auggie_sdk.acp import ClaudeCodeACPClient, AgentEventListener + +class MyListener(AgentEventListener): + def on_agent_message_chunk(self, text: str) -> None: + print(text, end="", flush=True) + + def on_tool_call(self, tool_call_id: str, title: str, kind: str = None, status: str = None) -> None: + print(f"\n[Tool: {title}]") + +client = ClaudeCodeACPClient(listener=MyListener()) +client.start() +response = client.send_message("Create a Python function to calculate fibonacci numbers") +client.stop() +``` + +## Configuration Options + +### API Key + +You can provide the API key in three ways: + +1. **Environment variable** (recommended): + ```bash + export ANTHROPIC_API_KEY=... + ``` + +2. **Constructor argument**: + ```python + client = ClaudeCodeACPClient(api_key="...") + ``` + +3. **Programmatically**: + ```python + import os + os.environ["ANTHROPIC_API_KEY"] = "..." + client = ClaudeCodeACPClient() + ``` + +### Model Selection + +Specify which Claude model to use: + +```python +client = ClaudeCodeACPClient( + model="claude-3-5-sonnet-latest" +) +``` + +Available models: +- `claude-3-5-sonnet-latest` (default) +- `claude-3-opus-latest` +- `claude-3-haiku-latest` + +### Workspace Root + +Set the working directory for the agent: + +```python +client = ClaudeCodeACPClient( + workspace_root="/path/to/your/project" +) +``` + +### Custom Adapter Path + +If you installed the adapter locally or want to use a specific version: + +```python +client = ClaudeCodeACPClient( + adapter_path="/path/to/claude-code-acp" +) +``` + +## Session Continuity + +Messages sent to the same client instance share context automatically: + +```python +client = ClaudeCodeACPClient() +client.start() + +# First message +client.send_message("My favorite color is blue") + +# Second message - remembers the context +response = client.send_message("What is my favorite color?") +print(response) # Will mention "blue" + +client.stop() +``` + +To clear the context and start fresh: + +```python +client.clear_context() # Restarts the agent with a new session +``` + +## Features Supported + +The Claude Code ACP adapter supports all major Claude Code features: + +- ✅ **Context @-mentions** - Reference files and code +- ✅ **Images** - Send images for analysis +- ✅ **Tool calls** - File operations, terminal commands, etc. +- ✅ **Permission requests** - Interactive approval for sensitive operations +- ✅ **Edit review** - Review and approve code changes +- ✅ **TODO lists** - Track tasks and progress +- ✅ **Interactive terminals** - Run commands and see output +- ✅ **Slash commands** - Custom commands for specialized tasks +- ✅ **MCP servers** - Connect to Model Context Protocol servers + +## Error Handling + +```python +from auggie_sdk.acp import ClaudeCodeACPClient + +try: + client = ClaudeCodeACPClient() + client.start() + response = client.send_message("Hello!") + client.stop() +except ValueError as e: + print(f"Configuration error: {e}") +except RuntimeError as e: + print(f"Runtime error: {e}") +except TimeoutError as e: + print(f"Timeout: {e}") +``` + +Common errors: + +- **`ValueError: ANTHROPIC_API_KEY must be provided`** - Set your API key +- **`RuntimeError: npx not found`** - Install Node.js +- **`TimeoutError: Claude Code agent failed to start`** - Install the adapter with `npm install -g @zed-industries/claude-code-acp` + +## Testing + +Run the E2E tests to verify everything works: + +```bash +# Quick tests (~30 seconds) +pytest tests/test_claude_code_client_e2e.py + +# All tests including slow ones (~5-10 minutes) +pytest tests/test_claude_code_client_e2e.py -m "" + +# Specific test +pytest tests/test_claude_code_client_e2e.py::test_simple_math_query -v +``` + +See [Testing Guide](../tests/README_CLAUDE_CODE_TESTS.md) for details. + +## Comparison with AuggieACPClient + +| Feature | ClaudeCodeACPClient | AuggieACPClient | +|---------|---------------------|-----------------| +| Backend | Claude Code (Anthropic) | Augment CLI | +| API Key | Anthropic API key | Augment credentials | +| Installation | npm package | Built-in CLI | +| Models | Claude 3.x family | Multiple providers | +| Features | Full Claude Code features | Augment-specific features | + +## Architecture + +``` +Python Code + ↓ +ClaudeCodeACPClient (Python) + ↓ +agent-client-protocol (Python package) + ↓ +ACP Protocol (stdio) + ↓ +@zed-industries/claude-code-acp (Node.js adapter) + ↓ +Claude Agent SDK (TypeScript) + ↓ +Claude Code + ↓ +Anthropic API +``` + +## Troubleshooting + +### Adapter not found + +``` +RuntimeError: npx not found +``` + +**Solution**: Install Node.js from [nodejs.org](https://nodejs.org/) + +### Agent fails to start + +``` +TimeoutError: Claude Code agent failed to start within 30 seconds +``` + +**Solution**: Install the adapter: +```bash +npm install -g @zed-industries/claude-code-acp +``` + +### API key errors + +``` +ValueError: ANTHROPIC_API_KEY must be provided +``` + +**Solution**: Set your API key: +```bash +export ANTHROPIC_API_KEY=... +``` + +### Process exits immediately + +Check the error message for details. Common causes: +- Invalid API key +- Network issues +- Adapter version mismatch + +## References + +- [Claude Code Documentation](https://docs.anthropic.com/en/docs/claude-code) +- [Agent Client Protocol](https://agentclientprotocol.com/) +- [Zed's ACP Adapter](https://github.com/zed-industries/claude-code-acp) +- [Claude Agent SDK](https://github.com/anthropics/claude-agent-sdk-python) diff --git a/examples/python-sdk/docs/FUNCTION_CALLING.md b/examples/python-sdk/docs/FUNCTION_CALLING.md new file mode 100644 index 0000000..fc68ae1 --- /dev/null +++ b/examples/python-sdk/docs/FUNCTION_CALLING.md @@ -0,0 +1,263 @@ +# Function Calling Feature + +## Overview + +The Augment Python SDK now supports **function calling**, allowing you to provide Python functions that the agent can invoke during execution. This enables the agent to interact with external systems, perform calculations, fetch data, and more. + +## Implementation Summary + +### What Was Added + +1. **Function Schema Generation** (`augment/function_tools.py`) + - Converts Python functions with type hints to JSON schemas + - Extracts parameter descriptions from docstrings + - Supports Google-style and NumPy-style docstrings + - Handles optional parameters, default values, and various Python types + +2. **Agent Integration** (`augment/agent.py`) + - Added `functions` parameter to `Agent.run()` method + - Function schemas are added to the instruction prompt + - Integrated into normal code path (no separate function calling flow) + - Added `_handle_function_calls()` helper to process function calls in responses + - Added `_parse_function_calls()` to extract function calls from agent responses + - Automatic function execution and result passing back to agent + +3. **Documentation** + - Updated README.md with function calling examples + - Updated examples/README.md with proper usage + - Updated .augment/commands/prompt-to-sdk.md + +4. **Examples and Tests** + - Created `examples/function_calling_example.py` with comprehensive examples + - Created `examples/simple_function_test.py` for quick testing + - Created `test_function_calling.py` with unit tests + +## How It Works + +### 1. Function Definition + +Define Python functions with type hints and docstrings: + +```python +def get_weather(location: str, unit: str = "celsius") -> dict: + """ + Get the current weather for a location. + + Args: + location: City name or coordinates + unit: Temperature unit (celsius or fahrenheit) + """ + # Your implementation + return {"temp": 22, "condition": "sunny"} +``` + +### 2. Pass Functions to Agent + +```python +from auggie_sdk import Auggie + +agent = Auggie() + +result = agent.run( + "What's the weather in San Francisco?", + return_type=dict, + functions=[get_weather] +) +``` + +### 3. Agent Workflow + +1. Function schemas are added to the instruction prompt +2. Agent receives instruction and can call functions as needed +3. If agent calls a function, it returns: + ``` + + { + "name": "get_weather", + "arguments": {"location": "San Francisco", "unit": "celsius"} + } + + ``` +4. SDK parses the function call and executes the function +5. SDK sends results back to agent in the same conversation +6. Agent continues with the response (can call more functions if needed) +7. Final response is parsed according to `return_type` + +This all happens in the normal code path - function calling is integrated seamlessly. + +## Function Requirements + +For functions to work properly with the agent: + +1. **Type Hints**: All parameters should have type hints + ```python + def my_func(param1: str, param2: int) -> dict: + ``` + +2. **Docstrings**: Include parameter descriptions in docstring + ```python + """ + Function description. + + Args: + param1: Description of param1 + param2: Description of param2 + """ + ``` + +3. **Return Type**: Specify return type hint + ```python + def my_func(...) -> dict: + ``` + +## Supported Types + +The schema generator supports: +- Basic types: `str`, `int`, `float`, `bool` +- Collections: `list`, `dict`, `List[T]`, `Dict[K, V]` +- Optional types: `Optional[T]` +- Union types: `Union[T1, T2]` +- Any type: `Any` + +## Examples + +### Simple Calculation + +```python +def add_numbers(a: int, b: int) -> int: + """Add two numbers.""" + return a + b + +result = agent.run( + "What is 15 + 27?", + return_type=int, + functions=[add_numbers] +) +``` + +### Multiple Functions + +```python +def multiply(a: int, b: int) -> int: + """Multiply two numbers.""" + return a * b + +def divide(a: int, b: int) -> float: + """Divide two numbers.""" + return a / b + +result = agent.run( + "Calculate (10 * 5) / 2", + return_type=float, + functions=[multiply, divide] +) +``` + +### Complex Workflow + +```python +def fetch_data(source: str) -> list: + """Fetch data from a source.""" + return [{"id": 1, "value": 100}, {"id": 2, "value": 200}] + +def process_data(data: list) -> dict: + """Process the fetched data.""" + total = sum(item["value"] for item in data) + return {"count": len(data), "total": total} + +result = agent.run( + "Fetch data from 'api' and process it", + return_type=dict, + functions=[fetch_data, process_data] +) +``` + +## Error Handling + +If a function raises an exception, the error is caught and returned to the agent: + +```python +def failing_function(x: int) -> int: + """A function that might fail.""" + if x < 0: + raise ValueError("x must be positive") + return x * 2 + +# Agent will receive error message and can handle it +result = agent.run( + "Call failing_function with x=-5", + return_type=str, + functions=[failing_function] +) +``` + +## Limitations + +1. **Maximum Rounds**: Function calling is limited to 5 rounds to prevent infinite loops +2. **Function Signature**: Functions must be callable with keyword arguments +3. **Serialization**: Function arguments and return values must be JSON-serializable +4. **No Nested Calls**: Functions cannot call other provided functions directly + +## Testing + +Run the tests: + +```bash +# Unit tests +cd experimental/guy/auggie_sdk +python3 -m pytest test_function_calling.py + +# Simple integration test +python3 examples/simple_function_test.py + +# Full examples +python3 examples/function_calling_example.py +``` + +## Implementation Details + +### Function Schema Format + +Functions are converted to this schema format: + +```json +{ + "name": "function_name", + "description": "Function description from docstring", + "parameters": { + "type": "object", + "properties": { + "param1": { + "type": "string", + "description": "Parameter description" + } + }, + "required": ["param1"] + } +} +``` + +### Agent Protocol + +The agent uses XML tags to indicate function calls: + +```xml + +{ + "name": "function_name", + "arguments": {"param": "value"} +} + +``` + +Multiple function calls can be made in a single response. + +## Future Enhancements + +Potential improvements: +- Support for async functions +- Streaming function results +- Function call history/logging +- Better error recovery +- Function call validation before execution +- Support for more complex type hints (TypedDict, Literal, etc.) diff --git a/examples/python-sdk/docs/PROMPT_TO_CODE.md b/examples/python-sdk/docs/PROMPT_TO_CODE.md new file mode 100644 index 0000000..cb18947 --- /dev/null +++ b/examples/python-sdk/docs/PROMPT_TO_CODE.md @@ -0,0 +1,281 @@ +# Prompt to Code Converter + +The `prompt_to_code.py` tool converts complex, multi-stage prompts into well-structured Python programs using the Augment SDK. This allows you to express complex workflows with conditions, loops, and multiple stages as code rather than a single monolithic prompt. + +## Why Convert Prompts to SDK Programs? + +Complex prompts often involve: +- **Sequential stages** that build on each other +- **Conditional logic** based on intermediate results +- **Loops** over collections of items +- **Data dependencies** between steps +- **Validation** and error handling + +Converting these to SDK programs gives you: +- ✅ **Better control** over workflow execution +- ✅ **Type safety** with Python's type system +- ✅ **Debugging** capabilities with standard Python tools +- ✅ **Reusability** - run the same workflow multiple times +- ✅ **Maintainability** - easier to modify and extend +- ✅ **Visibility** - see exactly what's happening at each stage + +## Installation + +Make sure the Augment SDK is installed: + +```bash +cd experimental/guy/auggie_sdk +pip install -e . +``` + +## Usage + +### Basic Usage + +```bash +python prompt_to_code.py +``` + +This will: +1. Read your prompt from the file +2. Analyze its structure +3. Generate a Python program using the Augment SDK +4. Save it to `generated_sdk_program.py` (or a numbered variant) + +### With Custom Output File + +```bash +python prompt_to_code.py my_prompt.txt --output my_workflow.py +``` + +### With Custom Model + +```bash +python prompt_to_code.py my_prompt.txt --model claude-3-5-sonnet-latest +``` + +### With Custom Workspace + +```bash +python prompt_to_code.py my_prompt.txt --workspace /path/to/workspace +``` + +## Example + +Given a prompt file `example_prompt.txt`: + +``` +Analyze all Python files in the src/ directory, identify any security +vulnerabilities or code quality issues, create a detailed report in +markdown format, and if there are any critical security issues found, +generate fix suggestions for each one with specific code changes needed. +``` + +Run the converter: + +```bash +python prompt_to_code.py examples/example_prompt.txt +``` + +This generates a Python program like: + +```python +#!/usr/bin/env python3 +""" +Security analysis workflow for Python files. +""" + +from auggie_sdk import Auggie +from dataclasses import dataclass +from typing import List + +@dataclass +class SecurityIssue: + file: str + severity: str + description: str + line_number: int + +def main(): + agent = Auggie(workspace_root=".", model="claude-3-5-sonnet-latest") + + # Stage 1: Get list of Python files + files = agent("List all Python files in src/ directory", list[str]) + print(f"Found {len(files)} Python files to analyze") + + # Stage 2: Analyze each file for security issues + all_issues: List[SecurityIssue] = [] + with agent.session() as session: + for file in files: + issues = session( + f"Analyze {file} for security vulnerabilities. " + f"Return a list of issues found.", + list[SecurityIssue] + ) + all_issues.extend(issues) + + print(f"Found {len(all_issues)} total security issues") + + # Stage 3: Create report + agent(f"Create a security report in security_report.md summarizing " + f"the {len(all_issues)} issues found across {len(files)} files") + + # Stage 4: Generate fixes for critical issues + critical_issues = [i for i in all_issues if i.severity == "critical"] + + if critical_issues: + print(f"Found {len(critical_issues)} critical issues - generating fixes") + with agent.session() as session: + for issue in critical_issues: + session( + f"Create a fix for the critical security issue in {issue.file} " + f"at line {issue.line_number}: {issue.description}" + ) + else: + print("No critical issues found!") + +if __name__ == "__main__": + main() +``` + +## How It Works + +The converter analyzes your prompt and identifies patterns: + +### 1. Sequential Stages with Context + +When steps build on each other, it uses sessions: + +```python +with agent.session() as session: + session("Step 1: Create the base structure") + session("Step 2: Add features to what we just created") + session("Step 3: Test everything we built") +``` + +### 2. Conditional Logic + +When decisions are needed, it uses typed results: + +```python +result = agent("Check if the file exists", bool) +if result: + agent("Process the existing file") +else: + agent("Create a new file first") +``` + +### 3. Loops/Iterations + +When operating on collections: + +```python +files = agent("List all Python files in src/", list[str]) +for file in files: + agent(f"Review {file} for security issues") +``` + +### 4. Data Dependencies + +When steps need structured data from previous steps: + +```python +@dataclass +class FileInfo: + path: str + size: int + type: str + +files = agent("Analyze all config files", list[FileInfo]) +for file in files: + if file.size > 1000: + agent(f"Optimize {file.path} - it's {file.size} bytes") +``` + +### 5. Function Calling + +When the agent needs to interact with external systems: + +```python +def run_tests(test_file: str) -> dict: + """Run tests and return results.""" + # Your test execution logic + return {"passed": 10, "failed": 2, "file": test_file} + +result = agent.run( + "Run all tests and analyze failures", + return_type=dict, + functions=[run_tests] +) +``` + +## Command-Line Options + +``` +usage: prompt_to_code.py [-h] [-o OUTPUT] [-m MODEL] [-w WORKSPACE] prompt_file + +Convert a complex prompt into an Augment SDK program + +positional arguments: + prompt_file Path to the file containing the prompt to convert + +optional arguments: + -h, --help show this help message and exit + -o OUTPUT, --output OUTPUT + Output file path for the generated program (default: auto-generated) + -m MODEL, --model MODEL + Model to use for conversion (default: claude-3-5-sonnet-latest) + -w WORKSPACE, --workspace WORKSPACE + Workspace root directory (default: current directory) +``` + +## Tips for Writing Good Prompts + +To get the best SDK programs, write prompts that clearly describe: + +1. **The stages** - What are the distinct steps? +2. **The data flow** - What information passes between steps? +3. **The conditions** - When should different actions be taken? +4. **The iterations** - What collections need to be processed? +5. **The outputs** - What should be created or returned? + +### Good Prompt Example + +``` +First, scan all TypeScript files in the components/ directory and identify +which ones are missing unit tests. For each file without tests, create a +comprehensive test file with at least 80% coverage. Then run all the new +tests and if any fail, fix the issues. Finally, generate a summary report +of test coverage improvements. +``` + +This clearly shows: +- Sequential stages (scan → create tests → run tests → fix → report) +- Iteration (for each file without tests) +- Conditional logic (if any fail) +- Data dependencies (which files need tests) + +### Less Ideal Prompt Example + +``` +Make the codebase better with tests and stuff. +``` + +This is too vague and doesn't provide enough structure for conversion. + +## After Generation + +Once you have your generated SDK program: + +1. **Review the code** - Make sure it matches your intent +2. **Customize as needed** - Add error handling, logging, etc. +3. **Test it** - Run it on a small subset first +4. **Iterate** - Modify the program based on results +5. **Reuse it** - Run the same workflow whenever needed + +## See Also + +- [Agent Documentation](./AGENT_EVENT_LISTENER.md) +- [Session Continuity](./SESSION_CONTINUITY.md) +- [Function Calling](./FUNCTION_CALLING.md) +- [Type Inference](./AUTOMATIC_TYPE_INFERENCE.md) diff --git a/examples/python-sdk/docs/SESSION_CONTINUITY.md b/examples/python-sdk/docs/SESSION_CONTINUITY.md new file mode 100644 index 0000000..095913f --- /dev/null +++ b/examples/python-sdk/docs/SESSION_CONTINUITY.md @@ -0,0 +1,268 @@ +# Session Continuity: Agent vs ACP Client + +## Key Insight + +**The ACP client does NOT need session resume functionality** because it maintains a **long-running process with a single persistent session**. All messages automatically share context. + +## Architecture Comparison + +### Agent Library (Subprocess-Based) + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Agent Library │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ agent.run("Create a function") │ +│ ↓ │ +│ ┌──────────────────────────────────┐ │ +│ │ Spawn CLI Process │ │ +│ │ Session ID: abc123 │ │ +│ │ Execute → Return → Exit │ │ +│ └──────────────────────────────────┘ │ +│ │ +│ agent.run("Test it") │ +│ ↓ │ +│ ┌──────────────────────────────────┐ │ +│ │ Spawn NEW CLI Process │ │ +│ │ Session ID: xyz789 ← NEW! │ │ +│ │ Execute → Return → Exit │ │ +│ └──────────────────────────────────┘ │ +│ │ +│ ❌ NO CONTEXT - Different sessions! │ +│ │ +└─────────────────────────────────────────────────────────────┘ +``` + +**Solution:** Explicit session management + +```python +with agent.session() as session: + session.run("Create a function") # Session: abc123 + session.run("Test it") # Session: abc123 (resumed) + # ✅ HAS CONTEXT - Same session! +``` + +### ACP Client (Long-Running) + +``` +┌─────────────────────────────────────────────────────────────┐ +│ ACP Client │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ client.start() │ +│ ↓ │ +│ ┌──────────────────────────────────┐ │ +│ │ Spawn CLI Process (ACP mode) │ │ +│ │ Session ID: abc123 │ │ +│ │ │ │ +│ │ ┌──────────────────────────────┐ │ │ +│ │ │ RUNNING... │ │ │ +│ │ │ │ │ │ +│ │ │ client.send_message(...) │ │ Session: abc123 │ +│ │ │ → Response │ │ │ +│ │ │ │ │ │ +│ │ │ client.send_message(...) │ │ Session: abc123 │ +│ │ │ → Response │ │ │ +│ │ │ │ │ │ +│ │ │ client.send_message(...) │ │ Session: abc123 │ +│ │ │ → Response │ │ │ +│ │ │ │ │ │ +│ │ └──────────────────────────────┘ │ │ +│ │ │ │ +│ └──────────────────────────────────┘ │ +│ ↓ │ +│ client.stop() │ +│ │ +│ ✅ AUTOMATIC CONTEXT - Same session throughout! │ +│ │ +└─────────────────────────────────────────────────────────────┘ +``` + +**No session management needed!** All messages go to the same session automatically. + +## Code Examples + +### Agent Library - Requires Session Management + +```python +from auggie_sdk import Auggie + +agent = Auggie() + +# ❌ NO CONTEXT - Each call is independent +agent.run("Create a function called add_numbers") +agent.run("Test that function") # Doesn't know about add_numbers! + +# ✅ WITH CONTEXT - Explicit session +with agent.session() as session: + session.run("Create a function called add_numbers") + session.run("Test that function") # Knows about add_numbers! +``` + +### ACP Client - Automatic Session Continuity + +```python +from auggie_sdk.acp import AuggieACPClient + +client = AuggieACPClient() +client.start() + +# ✅ AUTOMATIC CONTEXT - All messages share session +client.send_message("Create a function called add_numbers") +client.send_message("Test that function") # Automatically knows about add_numbers! + +client.stop() +``` + +## Why This Matters + +### Performance + +**Agent Library:** +- Spawns new process per request: ~500ms overhead +- 10 requests = 10 processes = ~5 seconds overhead + +**ACP Client:** +- One process for all requests: ~500ms overhead once +- 10 requests = 1 process = ~500ms overhead total +- **10x faster for multiple requests!** + +### User Experience + +**Agent Library:** +```python +# User must remember to use session context +with agent.session() as session: # Easy to forget! + session.run("Step 1") + session.run("Step 2") +``` + +**ACP Client:** +```python +# Session continuity is automatic +client.start() +client.send_message("Step 1") +client.send_message("Step 2") # Just works! +client.stop() +``` + +### Real-Time Feedback + +**Agent Library:** +- Waits for entire response before returning +- No progress updates + +**ACP Client:** +- Streams responses in real-time +- Can show tool calls, thinking process, etc. +- Better UX for long-running operations + +```python +from auggie_sdk.acp import AuggieACPClient, AgentEventListener + +class MyListener(AgentEventListener): + def on_agent_message_chunk(self, text: str): + print(text, end="", flush=True) # Real-time output! + + def on_tool_call(self, tool_name: str, tool_input: dict): + print(f"\n[Using tool: {tool_name}]") + +client = AuggieACPClient(listener=MyListener()) +client.start() +client.send_message("Create a complex function") # See progress in real-time! +client.stop() +``` + +## When to Use Each + +### Use Agent Library When: + +- ✅ Simple one-off requests +- ✅ Don't need session continuity +- ✅ Want simplest possible API +- ✅ Don't need real-time streaming + +```python +from auggie_sdk import Auggie + +agent = Auggie() +result = agent.run("What is 2 + 2?", return_type=int) +print(result) # 4 +``` + +### Use ACP Client When: + +- ✅ Multiple related requests +- ✅ Need automatic session continuity +- ✅ Want real-time streaming +- ✅ Need better performance +- ✅ Want event-driven architecture + +```python +from auggie_sdk.acp import AuggieACPClient + +client = AuggieACPClient(model="claude-3-5-sonnet-latest") +client.start() + +# All these share context automatically +client.send_message("Create a function") +client.send_message("Test it") +client.send_message("Now optimize it") + +client.stop() +``` + +## Migration Path + +If you're using `Agent` with sessions: + +```python +# Before (Agent with session) +from auggie_sdk import Auggie + +agent = Auggie(workspace_root="/path/to/workspace", model="claude-3-5-sonnet-latest") + +with agent.session() as session: + session.run("Create a function") + session.run("Test it") + session.run("Optimize it") +``` + +Migrate to ACP client: + +```python +# After (ACP client - simpler!) +from auggie_sdk.acp import AuggieACPClient + +client = AuggieACPClient( + workspace_root="/path/to/workspace", + model="claude-3-5-sonnet-latest" +) + +client.start() +client.send_message("Create a function") +client.send_message("Test it") +client.send_message("Optimize it") +client.stop() + +# Or use context manager +with AuggieACPClient(workspace_root="/path/to/workspace") as client: + client.send_message("Create a function") + client.send_message("Test it") + client.send_message("Optimize it") +``` + +## Summary + +| Feature | Agent Library | ACP Client | +|---------|--------------|------------| +| **Session Continuity** | Manual (with `session()`) | Automatic | +| **Process Model** | New process per request | Long-running process | +| **Performance** | ~500ms overhead per request | ~500ms overhead once | +| **Real-time Streaming** | ❌ No | ✅ Yes | +| **Event Listeners** | ❌ No | ✅ Yes | +| **API Complexity** | Simple for one-off, complex for sessions | Consistent | +| **Best For** | One-off requests | Multiple related requests | + +**Key Takeaway:** The ACP client's long-running architecture makes session resume unnecessary - it's a feature, not a bug! 🎉 diff --git a/examples/python-sdk/user_examples/01_quick_start.py b/examples/python-sdk/user_examples/01_quick_start.py new file mode 100644 index 0000000..951f38f --- /dev/null +++ b/examples/python-sdk/user_examples/01_quick_start.py @@ -0,0 +1,11 @@ +"""Quick Start Example from user_guide.md""" + +from auggie_sdk import Auggie + +# Create an agent instance +agent = Auggie() + +# Run a simple instruction +response = agent.run("What is the capital of France?") +print(response) +# Output: Paris diff --git a/examples/python-sdk/user_examples/02_event_listener_builtin.py b/examples/python-sdk/user_examples/02_event_listener_builtin.py new file mode 100644 index 0000000..4c2fcaa --- /dev/null +++ b/examples/python-sdk/user_examples/02_event_listener_builtin.py @@ -0,0 +1,13 @@ +"""Built-in Logger Example from user_guide.md""" + +from auggie_sdk import Agent, LoggingAgentListener + +# Use the built-in logger for easy debugging +agent = Auggie(listener=LoggingAgentListener(verbose=True)) + +result = agent.run("What is 2 + 2?") +print(f"\nFinal result: {result}") +# Output will include: +# 🔧 Tool calls +# 💭 Thinking messages +# 💬 Agent messages diff --git a/examples/python-sdk/user_examples/03_event_listener_custom.py b/examples/python-sdk/user_examples/03_event_listener_custom.py new file mode 100644 index 0000000..11c68a3 --- /dev/null +++ b/examples/python-sdk/user_examples/03_event_listener_custom.py @@ -0,0 +1,34 @@ +"""Custom Listener Example from user_guide.md""" + +from auggie_sdk import Auggie, AgentListener +from typing import Any, Optional + + +class MyCustomListener(AgentListener): + def on_agent_thought(self, text: str): + """Called when the agent shares its internal reasoning.""" + print(f"[Thinking] {text[:100]}...") + + def on_tool_call( + self, + tool_call_id: str, + title: str, + kind: Optional[str] = None, + status: Optional[str] = None, + ): + """Called when the agent is about to execute a tool.""" + print(f"[Tool Call] {title} (kind={kind}, status={status})") + + def on_tool_response( + self, tool_call_id: str, status: Optional[str] = None, content: Any = None + ): + """Called when a tool execution completes.""" + print(f"[Tool Result] status={status}, content={str(content)[:50]}...") + + def on_agent_message(self, message: str): + """Called when the agent sends a complete message.""" + print(f"[Agent Message] {message}") + + +agent = Auggie(listener=MyCustomListener()) +agent.run("What is 5 * 5?") diff --git a/examples/python-sdk/user_examples/04_session_management.py b/examples/python-sdk/user_examples/04_session_management.py new file mode 100644 index 0000000..c040be7 --- /dev/null +++ b/examples/python-sdk/user_examples/04_session_management.py @@ -0,0 +1,16 @@ +"""Session Management Example from user_guide.md""" + +from auggie_sdk import Auggie + +agent = Auggie() + +# ❌ INCORRECT: These calls don't know about each other +agent.run("Remember that my favorite color is blue") +result1 = agent.run("What is my favorite color?") # Agent won't know +print(f"Without session: {result1}") + +# ✅ CORRECT: Using a session for shared context +with agent.session() as session: + session.run("Remember that my favorite color is blue") + response = session.run("What is my favorite color?") + print(f"With session: {response}") # Output: Blue diff --git a/examples/python-sdk/user_examples/05_type_inference.py b/examples/python-sdk/user_examples/05_type_inference.py new file mode 100644 index 0000000..24d2130 --- /dev/null +++ b/examples/python-sdk/user_examples/05_type_inference.py @@ -0,0 +1,15 @@ +"""Automatic Type Inference Example from user_guide.md""" + +from auggie_sdk import Auggie + +agent = Auggie() + +# Automatic type inference +result = agent.run("What is 2 + 2?") +print(f"Result: {result} (Type: {type(result).__name__})") +# Output: Result: 4 (Type: int) + +# It handles complex types too +result = agent.run("Return a list of the first 3 prime numbers") +print(result) +# Output: [2, 3, 5] diff --git a/examples/python-sdk/user_examples/06_typed_returns.py b/examples/python-sdk/user_examples/06_typed_returns.py new file mode 100644 index 0000000..a43427f --- /dev/null +++ b/examples/python-sdk/user_examples/06_typed_returns.py @@ -0,0 +1,34 @@ +"""Explicit Type Specification Example from user_guide.md""" + +from auggie_sdk import Auggie +from dataclasses import dataclass +from typing import List, Dict + + +@dataclass +class Task: + id: int + description: str + is_done: bool + + +agent = Auggie() + +# Get a strongly-typed object back +task = agent.run("Create a sample task for 'Buy groceries'", return_type=Task) +print(f"Task {task.id}: {task.description} (Done: {task.is_done})") + +# Get a list of objects +tasks = agent.run( + "Create 3 sample tasks for a weekend to-do list", return_type=List[Task] +) +for t in tasks: + print(f"- {t.description}") + +# Get a dictionary of simple types +counts = agent.run( + "Count the number of vowels in 'hello world' and return as a dict", + return_type=Dict[str, int], +) +print(counts) +# Output: {'e': 1, 'o': 2} diff --git a/examples/python-sdk/user_examples/07_success_criteria.py b/examples/python-sdk/user_examples/07_success_criteria.py new file mode 100644 index 0000000..7253b03 --- /dev/null +++ b/examples/python-sdk/user_examples/07_success_criteria.py @@ -0,0 +1,17 @@ +"""Success Criteria & Verification Example from user_guide.md""" + +from auggie_sdk import Auggie + +agent = Auggie() + +# The agent will verify its work meets the criteria +result = agent.run( + "Write a Python function to calculate fibonacci numbers", + success_criteria=[ + "Function has type hints", + "Function has a docstring", + "Function handles the base cases (0 and 1)", + ], + max_verification_rounds=3, # Optional: limit retry attempts (default: 3) +) +print(result) diff --git a/examples/python-sdk/user_examples/08_function_tools.py b/examples/python-sdk/user_examples/08_function_tools.py new file mode 100644 index 0000000..a522a40 --- /dev/null +++ b/examples/python-sdk/user_examples/08_function_tools.py @@ -0,0 +1,32 @@ +"""Function Tools Example from user_guide.md""" + +from auggie_sdk import Auggie +import datetime + + +def get_current_weather(location: str, unit: str = "celsius") -> dict: + """ + Gets the weather for a given location. + + Args: + location: The city and state, e.g. San Francisco, CA + unit: Temperature unit ('celsius' or 'fahrenheit') + """ + # In a real app, you'd call a weather API here + return {"temp": 72, "unit": unit, "forecast": "sunny"} + + +def get_time() -> str: + """Returns the current time.""" + return datetime.datetime.now().strftime("%H:%M") + + +agent = Auggie() + +# The agent will call the appropriate function(s) to answer the question +response = agent.run( + "What's the weather like in NYC right now, and what time is it there?", + functions=[get_current_weather, get_time], +) + +print(response) diff --git a/examples/python-sdk/user_examples/09_remove_tools.py b/examples/python-sdk/user_examples/09_remove_tools.py new file mode 100644 index 0000000..53edc4f --- /dev/null +++ b/examples/python-sdk/user_examples/09_remove_tools.py @@ -0,0 +1,20 @@ +from typing import List + +from auggie_sdk import Auggie + +INTEGRATION_TOOLS = [ + "github-api", + "linear", + "notion", + "glean", + "supabase", + "jira", + "confluence", + "slack", +] + +agent = Auggie(removed_tools=INTEGRATION_TOOLS) + +response = agent.run("What are the tools you have available?") + +print(response) diff --git a/examples/python-sdk/user_examples/README.md b/examples/python-sdk/user_examples/README.md new file mode 100644 index 0000000..d838c35 --- /dev/null +++ b/examples/python-sdk/user_examples/README.md @@ -0,0 +1,30 @@ +# User Examples + +This directory contains executable examples and the comprehensive user guide for the Augment Python SDK. + +## 📖 User Guide + +See **[user_guide.md](user_guide.md)** for the complete documentation with detailed explanations, API reference, and usage patterns. + +## 🚀 Quick Start Examples + +1. **01_quick_start.py** - Basic agent usage +2. **02_event_listener_builtin.py** - Using the built-in logging listener +3. **03_event_listener_custom.py** - Creating a custom event listener +4. **04_session_management.py** - Using sessions for conversational context +5. **05_type_inference.py** - Automatic type inference +6. **06_typed_returns.py** - Explicit type specification with dataclasses +7. **07_success_criteria.py** - Using success criteria for verification +8. **08_function_tools.py** - Giving the agent access to custom functions + +## Running Examples + +Run individual examples: +```bash +python 01_quick_start.py +``` + +Run all examples: +```bash +./run_all.sh +``` diff --git a/examples/python-sdk/user_examples/run_all.sh b/examples/python-sdk/user_examples/run_all.sh new file mode 100755 index 0000000..0094c5b --- /dev/null +++ b/examples/python-sdk/user_examples/run_all.sh @@ -0,0 +1,26 @@ +#!/bin/bash +set -e + +# Change to the directory where this script is located +cd "$(dirname "$0")" + +echo "==========================================" +echo "Running User Examples from USER_README.md" +echo "==========================================" +echo "" + +for script in *.py; do + if [ -f "$script" ]; then + echo "==========================================" + echo "Running: $script" + echo "==========================================" + python "$script" + echo "" + echo "✅ $script completed successfully" + echo "" + fi +done + +echo "==========================================" +echo "All examples completed successfully! ✅" +echo "==========================================" diff --git a/examples/python-sdk/user_examples/test_listener.py b/examples/python-sdk/user_examples/test_listener.py new file mode 100644 index 0000000..404dae9 --- /dev/null +++ b/examples/python-sdk/user_examples/test_listener.py @@ -0,0 +1,40 @@ +"""Test if listener is being called""" + +from auggie_sdk import Agent, AgentListener +from typing import Any, Optional + + +class TestListener(AgentListener): + def __init__(self): + self.called = [] + + def on_agent_message(self, message: str): + print(f"✅ on_agent_message called: {message[:50]}") + self.called.append("on_agent_message") + + def on_agent_thought(self, text: str): + print(f"✅ on_agent_thought called: {text[:50]}") + self.called.append("on_agent_thought") + + def on_tool_call( + self, + tool_call_id: str, + title: str, + kind: Optional[str] = None, + status: Optional[str] = None, + ): + print(f"✅ on_tool_call called: {title}") + self.called.append("on_tool_call") + + def on_tool_response( + self, tool_call_id: str, status: Optional[str] = None, content: Any = None + ): + print(f"✅ on_tool_response called: status={status}") + self.called.append("on_tool_response") + + +listener = TestListener() +agent = Auggie(listener=listener) +result = agent.run("What is 2 + 2?") +print(f"\nResult: {result}") +print(f"Listener methods called: {listener.called}") diff --git a/examples/python-sdk/user_examples/user_guide.md b/examples/python-sdk/user_examples/user_guide.md new file mode 100644 index 0000000..3991de4 --- /dev/null +++ b/examples/python-sdk/user_examples/user_guide.md @@ -0,0 +1,250 @@ +# Auggie Python SDK + +A Python SDK for interacting with the Augment CLI agent (`auggie`) programmatically. + +## Prerequisites + +1. **Python** +2. **Augment CLI (`auggie`)**: Must be installed and authenticated on your system. + * **Note:** The SDK currently requires the pre-release version of auggie + * Install: `npm install -g @augmentcode/auggie@prerelease` + * Login: `auggie auth login` + +## Installation + +Install the SDK from PyPI: + +```bash +pip install auggie-sdk +``` + +## Quick Start + +```python +from auggie_sdk import Auggie + +# Create an agent instance +agent = Auggie() + +# Run a simple instruction +response = agent.run("What is the capital of France?") +print(response) +# Output: Paris +``` + +--- + +## Listening to Events + +To monitor the agent's internal actions (tool calls, thoughts, function executions), you can attach a listener. + +### Built-in Logger + +Use the built-in `LoggingAgentListener` for easy debugging to stdout: + +```python +from auggie_sdk import Agent, LoggingAgentListener + +# Use the built-in logger for easy debugging +agent = Auggie(listener=LoggingAgentListener(verbose=True)) + +agent.run("Find the 'main' function in src/app.py and summarize it") +# Output will now include: +# 🔧 Tool: view (read) +# 💭 Thinking: I need to read the file first... +# 💬 Agent: The main function does... +``` + +### Custom Listener + +You can implement your own listener by subclassing `AgentListener`. This is useful for integrating with custom logging systems, UIs, or for programmatic reactions to agent events. + +```python +from auggie_sdk import Agent, AgentListener +from typing import Any, Optional + +class MyCustomListener(AgentListener): + def on_agent_thought(self, text: str): + """Called when the agent shares its internal reasoning.""" + print(f"[Thinking] {text[:100]}...") + + def on_tool_call( + self, + tool_call_id: str, + title: str, + kind: Optional[str] = None, + status: Optional[str] = None, + ): + """Called when the agent is about to execute a tool.""" + print(f"[Tool Call] {title} (kind={kind}, status={status})") + + def on_tool_response( + self, tool_call_id: str, status: Optional[str] = None, content: Any = None + ): + """Called when a tool execution completes.""" + print(f"[Tool Result] status={status}, content={str(content)[:50]}...") + + def on_agent_message(self, message: str): + """Called when the agent sends a complete message.""" + print(f"[Agent Message] {message}") + +agent = Auggie(listener=MyCustomListener()) +agent.run("What is 5 * 5?") +``` + +--- + +## Core Features + +### 1. Session Management + +By default, every `agent.run()` call is independent and has no memory of previous calls. Use a **session** to maintain conversational context. + +```python +from auggie_sdk import Auggie + +agent = Auggie() + +# ❌ INCORRECT: These calls don't know about each other +agent.run("Remember that my favorite color is blue") +agent.run("What is my favorite color?") # Agent won't know + +# ✅ CORRECT: Using a session for shared context +with agent.session() as session: + session.run("Remember that my favorite color is blue") + response = session.run("What is my favorite color?") + print(response) # Output: Blue +``` + +### 2. Typed Returns + +The SDK provides flexible type handling for agent responses. + +#### Automatic Type Inference + +By default, the agent returns a string. You can let it automatically infer the best type (int, float, bool, list, dict, str): + +```python +from auggie_sdk import Auggie + +agent = Auggie() + +# Automatic type inference +result = agent.run("What is 2 + 2?") +print(f"Result: {result} (Type: {type(result).__name__})") +# Output: Result: 4 (Type: int) + +# It handles complex types too +result = agent.run("Return a list of the first 3 prime numbers") +print(result) +# Output: [2, 3, 5] +``` + +#### Explicit Type Specification + +For strict control, specify the exact `return_type` you expect. The SDK ensures the agent returns data in this format, automatically retrying if it fails initially. + +Supported types: `int`, `float`, `str`, `bool`, `list`, `dict`, `dataclasses`, and `Enum`s. +Compound types are also supported, such as `List[int]`, `Dict[str, int]`, or `List[MyDataclass]`. + +```python +from auggie_sdk import Auggie +from dataclasses import dataclass +from typing import List, Dict + +@dataclass +class Task: + id: int + description: str + is_done: bool + +agent = Auggie() + +# Get a strongly-typed object back +task = agent.run( + "Create a sample task for 'Buy groceries'", + return_type=Task +) +print(f"Task {task.id}: {task.description} (Done: {task.is_done})") + +# Get a list of objects +tasks = agent.run( + "Create 3 sample tasks for a weekend to-do list", + return_type=List[Task] +) +for t in tasks: + print(f"- {t.description}") + +# Get a dictionary of simple types +counts = agent.run( + "Count the number of vowels in 'hello world' and return as a dict", + return_type=Dict[str, int] +) +print(counts) +# Output: {'e': 1, 'o': 2} +``` + +### 3. Success Criteria & Verification + +You can specify success criteria that the agent must meet. The agent will verify its work and automatically retry if the criteria aren't met. + +```python +from auggie_sdk import Auggie + +agent = Auggie() + +# The agent will verify its work meets the criteria +result = agent.run( + "Write a Python function to calculate fibonacci numbers", + success_criteria=[ + "Function has type hints", + "Function has a docstring", + "Function handles the base cases (0 and 1)", + ], + max_verification_rounds=3 # Optional: limit retry attempts (default: 3) +) +print(result) +``` + +The agent will: +1. Complete the task +2. Verify the result against each criterion +3. If any criterion fails, automatically fix the issues and retry +4. Repeat until all criteria pass or max rounds reached + +If verification fails after max rounds, an `AugmentVerificationError` is raised with details about which criteria failed. + +### 4. Function Tools + +You can give the agent access to your own Python functions. It will call them intelligently to complete tasks. +**Important**: Functions *must* have type hints and docstrings, as these are used to generate the tool definition for the agent. + +```python +from auggie_sdk import Auggie +import datetime + +def get_current_weather(location: str, unit: str = "celsius") -> dict: + """ + Gets the weather for a given location. + + Args: + location: The city and state, e.g. San Francisco, CA + unit: Temperature unit ('celsius' or 'fahrenheit') + """ + # In a real app, you'd call a weather API here + return {"temp": 72, "unit": unit, "forecast": "sunny"} + +def get_time() -> str: + """Returns the current time.""" + return datetime.datetime.now().strftime("%H:%M") + +agent = Auggie() + +# The agent will call the appropriate function(s) to answer the question +response = agent.run( + "What's the weather like in NYC right now, and what time is it there?", + functions=[get_current_weather, get_time] +) + +print(response) +```