|
| 1 | +""" |
| 2 | +Code Review Assistant Demo - Three-session demonstration. |
| 3 | +
|
| 4 | +This demo showcases: |
| 5 | +1. Session 1: Claude learns debugging patterns |
| 6 | +2. Session 2: Claude applies learned patterns (faster!) |
| 7 | +3. Session 3: Long session with context editing |
| 8 | +
|
| 9 | +Requires: |
| 10 | +- .env file with ANTHROPIC_API_KEY and ANTHROPIC_MODEL |
| 11 | +- memory_tool.py in the same directory |
| 12 | +""" |
| 13 | + |
| 14 | +import os |
| 15 | +from typing import Any, Dict, List, Optional |
| 16 | + |
| 17 | +from anthropic import Anthropic |
| 18 | +from dotenv import load_dotenv |
| 19 | + |
| 20 | +import sys |
| 21 | +from pathlib import Path |
| 22 | + |
| 23 | +# Add parent directory to path to import memory_tool |
| 24 | +sys.path.insert(0, str(Path(__file__).parent.parent)) |
| 25 | + |
| 26 | +from memory_tool import MemoryToolHandler |
| 27 | + |
| 28 | + |
| 29 | +# Load environment variables |
| 30 | +load_dotenv() |
| 31 | + |
| 32 | +API_KEY = os.getenv("ANTHROPIC_API_KEY") |
| 33 | +MODEL = os.getenv("ANTHROPIC_MODEL") |
| 34 | + |
| 35 | +if not API_KEY: |
| 36 | + raise ValueError( |
| 37 | + "ANTHROPIC_API_KEY not found. Copy .env.example to .env and add your API key." |
| 38 | + ) |
| 39 | + |
| 40 | +if not MODEL: |
| 41 | + raise ValueError( |
| 42 | + "ANTHROPIC_MODEL not found. Copy .env.example to .env and set the model." |
| 43 | + ) |
| 44 | + |
| 45 | + |
| 46 | +# Context management configuration |
| 47 | +CONTEXT_MANAGEMENT = { |
| 48 | + "edits": [ |
| 49 | + { |
| 50 | + "type": "clear_tool_uses_20250919", |
| 51 | + "trigger": {"type": "input_tokens", "value": 30000}, |
| 52 | + "keep": {"type": "tool_uses", "value": 3}, |
| 53 | + "clear_at_least": {"type": "input_tokens", "value": 5000}, |
| 54 | + } |
| 55 | + ] |
| 56 | +} |
| 57 | + |
| 58 | + |
| 59 | +class CodeReviewAssistant: |
| 60 | + """ |
| 61 | + Code review assistant with memory and context editing capabilities. |
| 62 | +
|
| 63 | + This assistant: |
| 64 | + - Checks memory for debugging patterns before reviewing code |
| 65 | + - Stores learned patterns for future sessions |
| 66 | + - Automatically clears old tool results when context grows large |
| 67 | + """ |
| 68 | + |
| 69 | + def __init__(self, memory_storage_path: str = "./memory_storage"): |
| 70 | + """ |
| 71 | + Initialize the code review assistant. |
| 72 | +
|
| 73 | + Args: |
| 74 | + memory_storage_path: Path for memory storage |
| 75 | + """ |
| 76 | + self.client = Anthropic(api_key=API_KEY) |
| 77 | + self.memory_handler = MemoryToolHandler(base_path=memory_storage_path) |
| 78 | + self.messages: List[Dict[str, Any]] = [] |
| 79 | + |
| 80 | + def _create_system_prompt(self) -> str: |
| 81 | + """Create system prompt with memory instructions.""" |
| 82 | + return """You are an expert code reviewer focused on finding bugs and suggesting improvements. |
| 83 | +
|
| 84 | +MEMORY PROTOCOL: |
| 85 | +1. Check your /memories directory for relevant debugging patterns or insights |
| 86 | +2. When you find a bug or pattern, update your memory with what you learned |
| 87 | +3. Keep your memory organized - use descriptive file names and clear content |
| 88 | +
|
| 89 | +When reviewing code: |
| 90 | +- Identify bugs, security issues, and code quality problems |
| 91 | +- Explain the issue clearly |
| 92 | +- Provide a corrected version |
| 93 | +- Store important patterns in memory for future reference |
| 94 | +
|
| 95 | +Remember: Your memory persists across conversations. Use it wisely.""" |
| 96 | + |
| 97 | + def _execute_tool_use(self, tool_use: Any) -> str: |
| 98 | + """Execute a tool use and return the result.""" |
| 99 | + if tool_use.name == "memory": |
| 100 | + result = self.memory_handler.execute(**tool_use.input) |
| 101 | + return result.get("success") or result.get("error", "Unknown error") |
| 102 | + return f"Unknown tool: {tool_use.name}" |
| 103 | + |
| 104 | + def review_code( |
| 105 | + self, code: str, filename: str, description: str = "" |
| 106 | + ) -> Dict[str, Any]: |
| 107 | + """ |
| 108 | + Review code with memory-enhanced analysis. |
| 109 | +
|
| 110 | + Args: |
| 111 | + code: The code to review |
| 112 | + filename: Name of the file being reviewed |
| 113 | + description: Optional description of what to look for |
| 114 | +
|
| 115 | + Returns: |
| 116 | + Dict with review results and metadata |
| 117 | + """ |
| 118 | + # Construct user message |
| 119 | + user_message = f"Please review this code from {filename}" |
| 120 | + if description: |
| 121 | + user_message += f"\n\nContext: {description}" |
| 122 | + user_message += f"\n\n```python\n{code}\n```" |
| 123 | + |
| 124 | + self.messages.append({"role": "user", "content": user_message}) |
| 125 | + |
| 126 | + # Track token usage and context management |
| 127 | + total_input_tokens = 0 |
| 128 | + context_edits_applied = [] |
| 129 | + |
| 130 | + # Conversation loop |
| 131 | + turn = 1 |
| 132 | + while True: |
| 133 | + print(f" 🔄 Turn {turn}: Calling Claude API...", end="", flush=True) |
| 134 | + response = self.client.beta.messages.create( |
| 135 | + model=MODEL, |
| 136 | + max_tokens=4096, |
| 137 | + system=self._create_system_prompt(), |
| 138 | + messages=self.messages, |
| 139 | + tools=[{"type": "memory_20250818", "name": "memory"}], |
| 140 | + betas=["context-management-2025-06-27"], |
| 141 | + extra_body={"context_management": CONTEXT_MANAGEMENT}, |
| 142 | + ) |
| 143 | + |
| 144 | + print(" ✓") |
| 145 | + |
| 146 | + # Track usage |
| 147 | + total_input_tokens = response.usage.input_tokens |
| 148 | + |
| 149 | + # Check for context management |
| 150 | + if hasattr(response, "context_management") and response.context_management: |
| 151 | + applied = response.context_management.get("applied_edits", []) |
| 152 | + if applied: |
| 153 | + context_edits_applied.extend(applied) |
| 154 | + |
| 155 | + # Process response content |
| 156 | + assistant_content = [] |
| 157 | + tool_results = [] |
| 158 | + final_text = [] |
| 159 | + |
| 160 | + for content in response.content: |
| 161 | + if content.type == "text": |
| 162 | + assistant_content.append({"type": "text", "text": content.text}) |
| 163 | + final_text.append(content.text) |
| 164 | + elif content.type == "tool_use": |
| 165 | + cmd = content.input.get('command', 'unknown') |
| 166 | + path = content.input.get('path', '') |
| 167 | + print(f" 🔧 Memory: {cmd} {path}") |
| 168 | + |
| 169 | + # Execute tool |
| 170 | + result = self._execute_tool_use(content) |
| 171 | + |
| 172 | + assistant_content.append( |
| 173 | + { |
| 174 | + "type": "tool_use", |
| 175 | + "id": content.id, |
| 176 | + "name": content.name, |
| 177 | + "input": content.input, |
| 178 | + } |
| 179 | + ) |
| 180 | + |
| 181 | + tool_results.append( |
| 182 | + { |
| 183 | + "type": "tool_result", |
| 184 | + "tool_use_id": content.id, |
| 185 | + "content": result, |
| 186 | + } |
| 187 | + ) |
| 188 | + |
| 189 | + # Add assistant message |
| 190 | + self.messages.append({"role": "assistant", "content": assistant_content}) |
| 191 | + |
| 192 | + # If there are tool results, add them and continue |
| 193 | + if tool_results: |
| 194 | + self.messages.append({"role": "user", "content": tool_results}) |
| 195 | + turn += 1 |
| 196 | + else: |
| 197 | + # No more tool uses, we're done |
| 198 | + print() |
| 199 | + break |
| 200 | + |
| 201 | + return { |
| 202 | + "review": "\n".join(final_text), |
| 203 | + "input_tokens": total_input_tokens, |
| 204 | + "context_edits": context_edits_applied, |
| 205 | + } |
| 206 | + |
| 207 | + def start_new_session(self) -> None: |
| 208 | + """Start a new conversation session (memory persists).""" |
| 209 | + self.messages = [] |
| 210 | + |
| 211 | + |
| 212 | +def run_session_1() -> None: |
| 213 | + """Session 1: Learn debugging patterns.""" |
| 214 | + print("=" * 80) |
| 215 | + print("SESSION 1: Learning from First Code Review") |
| 216 | + print("=" * 80) |
| 217 | + |
| 218 | + assistant = CodeReviewAssistant() |
| 219 | + |
| 220 | + # Read sample code |
| 221 | + with open("memory_demo/sample_code/web_scraper_v1.py", "r") as f: |
| 222 | + code = f.read() |
| 223 | + |
| 224 | + print("\n📋 Reviewing web_scraper_v1.py...") |
| 225 | + print("\nMulti-threaded web scraper that sometimes loses results.\n") |
| 226 | + |
| 227 | + result = assistant.review_code( |
| 228 | + code=code, |
| 229 | + filename="web_scraper_v1.py", |
| 230 | + description="This scraper sometimes returns fewer results than expected. " |
| 231 | + "The count is inconsistent across runs. Can you find the issue?", |
| 232 | + ) |
| 233 | + |
| 234 | + print("\n🤖 Claude's Review:\n") |
| 235 | + print(result["review"]) |
| 236 | + print(f"\n📊 Input tokens used: {result['input_tokens']:,}") |
| 237 | + |
| 238 | + if result["context_edits"]: |
| 239 | + print(f"\n🧹 Context edits applied: {result['context_edits']}") |
| 240 | + |
| 241 | + print("\n✅ Session 1 complete - Claude learned debugging patterns!\n") |
| 242 | + |
| 243 | + |
| 244 | +def run_session_2() -> None: |
| 245 | + """Session 2: Apply learned patterns.""" |
| 246 | + print("=" * 80) |
| 247 | + print("SESSION 2: Applying Learned Patterns (New Conversation)") |
| 248 | + print("=" * 80) |
| 249 | + |
| 250 | + # New assistant instance (new conversation, but memory persists) |
| 251 | + assistant = CodeReviewAssistant() |
| 252 | + |
| 253 | + # Read different sample code with similar bug |
| 254 | + with open("memory_demo/sample_code/api_client_v1.py", "r") as f: |
| 255 | + code = f.read() |
| 256 | + |
| 257 | + print("\n📋 Reviewing api_client_v1.py...") |
| 258 | + print("\nAsync API client with concurrent requests.\n") |
| 259 | + |
| 260 | + result = assistant.review_code( |
| 261 | + code=code, |
| 262 | + filename="api_client_v1.py", |
| 263 | + description="Review this async API client. " |
| 264 | + "It fetches multiple endpoints concurrently. Are there any issues?", |
| 265 | + ) |
| 266 | + |
| 267 | + print("\n🤖 Claude's Review:\n") |
| 268 | + print(result["review"]) |
| 269 | + print(f"\n📊 Input tokens used: {result['input_tokens']:,}") |
| 270 | + |
| 271 | + print("\n✅ Session 2 complete - Claude applied learned patterns faster!\n") |
| 272 | + |
| 273 | + |
| 274 | +def run_session_3() -> None: |
| 275 | + """Session 3: Long session with context editing.""" |
| 276 | + print("=" * 80) |
| 277 | + print("SESSION 3: Long Session with Context Editing") |
| 278 | + print("=" * 80) |
| 279 | + |
| 280 | + assistant = CodeReviewAssistant() |
| 281 | + |
| 282 | + # Read data processor code (has multiple issues) |
| 283 | + with open("memory_demo/sample_code/data_processor_v1.py", "r") as f: |
| 284 | + code = f.read() |
| 285 | + |
| 286 | + print("\n📋 Reviewing data_processor_v1.py...") |
| 287 | + print("\nLarge file with multiple concurrent processing classes.\n") |
| 288 | + |
| 289 | + result = assistant.review_code( |
| 290 | + code=code, |
| 291 | + filename="data_processor_v1.py", |
| 292 | + description="This data processor handles files concurrently. " |
| 293 | + "There's also a SharedCache class. Review all components for issues.", |
| 294 | + ) |
| 295 | + |
| 296 | + print("\n🤖 Claude's Review:\n") |
| 297 | + print(result["review"]) |
| 298 | + print(f"\n📊 Input tokens used: {result['input_tokens']:,}") |
| 299 | + |
| 300 | + if result["context_edits"]: |
| 301 | + print("\n🧹 Context Management Applied:") |
| 302 | + for edit in result["context_edits"]: |
| 303 | + print(f" - Type: {edit.get('type')}") |
| 304 | + print(f" - Cleared tool uses: {edit.get('cleared_tool_uses', 0)}") |
| 305 | + print(f" - Tokens saved: {edit.get('cleared_input_tokens', 0):,}") |
| 306 | + |
| 307 | + print("\n✅ Session 3 complete - Context editing kept conversation manageable!\n") |
| 308 | + |
| 309 | + |
| 310 | +def main() -> None: |
| 311 | + """Run all three demo sessions.""" |
| 312 | + print("\n🚀 Code Review Assistant Demo\n") |
| 313 | + print("This demo shows:") |
| 314 | + print("1. Session 1: Claude learns debugging patterns") |
| 315 | + print("2. Session 2: Claude applies learned patterns (new conversation)") |
| 316 | + print("3. Session 3: Long session with context editing\n") |
| 317 | + |
| 318 | + input("Press Enter to start Session 1...") |
| 319 | + run_session_1() |
| 320 | + |
| 321 | + input("Press Enter to start Session 2...") |
| 322 | + run_session_2() |
| 323 | + |
| 324 | + input("Press Enter to start Session 3...") |
| 325 | + run_session_3() |
| 326 | + |
| 327 | + print("=" * 80) |
| 328 | + print("🎉 Demo Complete!") |
| 329 | + print("=" * 80) |
| 330 | + print("\nKey Takeaways:") |
| 331 | + print("- Memory tool enabled cross-conversation learning") |
| 332 | + print("- Claude got faster at recognizing similar bugs") |
| 333 | + print("- Context editing handled long sessions gracefully") |
| 334 | + print("\n💡 For production GitHub PR reviews, check out:") |
| 335 | + print(" https://github.com/anthropics/claude-code-action\n") |
| 336 | + |
| 337 | + |
| 338 | +if __name__ == "__main__": |
| 339 | + main() |
0 commit comments