ai-agent-book 精选快照(<2MB 代码与文档,来自 github.com/bojieli/ai-agent-book)
Build latest book artifacts / build (push) Canceled after 0s
dependency resolution / resolve (3.11) (push) Canceled after 0s
dependency resolution / resolve (3.13) (push) Canceled after 0s
deploy-pages / build (push) Canceled after 0s
deploy-pages / deploy (push) Canceled after 0s
i18n consistency check / check (push) Canceled after 0s
provider adoption tests / test (chapter2/context-compression) (push) Canceled after 0s
provider adoption tests / test (chapter2/prompt-injection) (push) Canceled after 0s
provider adoption tests / test (chapter2/system-hint) (push) Canceled after 0s
provider adoption tests / test (chapter3/log-sanitization) (push) Canceled after 0s
web-search-agent tests / test (push) Canceled after 0s
web-search-agent tests / agentbook (push) Canceled after 0s
Build latest book artifacts / build (push) Canceled after 0s
dependency resolution / resolve (3.11) (push) Canceled after 0s
dependency resolution / resolve (3.13) (push) Canceled after 0s
deploy-pages / build (push) Canceled after 0s
deploy-pages / deploy (push) Canceled after 0s
i18n consistency check / check (push) Canceled after 0s
provider adoption tests / test (chapter2/context-compression) (push) Canceled after 0s
provider adoption tests / test (chapter2/prompt-injection) (push) Canceled after 0s
provider adoption tests / test (chapter2/system-hint) (push) Canceled after 0s
provider adoption tests / test (chapter3/log-sanitization) (push) Canceled after 0s
web-search-agent tests / test (push) Canceled after 0s
web-search-agent tests / agentbook (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
"""Pytest bootstrap for the kv-cache experiment tests."""
|
||||
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import types
|
||||
|
||||
|
||||
EXPERIMENT_ROOT = Path(__file__).resolve().parents[1]
|
||||
if str(EXPERIMENT_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(EXPERIMENT_ROOT))
|
||||
|
||||
try:
|
||||
import openai # noqa: F401
|
||||
except ImportError:
|
||||
openai_stub = types.ModuleType("openai")
|
||||
openai_stub.OpenAI = object
|
||||
sys.modules.setdefault("openai", openai_stub)
|
||||
@@ -0,0 +1,11 @@
|
||||
"""Helpers for running kv-cache manual smoke scripts from tests/manual."""
|
||||
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
|
||||
def add_project_root() -> Path:
|
||||
project_root = Path(__file__).resolve().parents[2]
|
||||
if str(project_root) not in sys.path:
|
||||
sys.path.insert(0, str(project_root))
|
||||
return project_root
|
||||
@@ -0,0 +1,60 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Manual live check for agent recovery after tool errors."""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
from _bootstrap import add_project_root
|
||||
|
||||
add_project_root()
|
||||
|
||||
from agent import KVCacheAgent, KVCacheMode
|
||||
|
||||
|
||||
def check_agent_error_recovery():
|
||||
"""Run the live agent against an intentionally failing tool path."""
|
||||
api_key = os.getenv("MOONSHOT_API_KEY")
|
||||
if not api_key:
|
||||
print("❌ Please set MOONSHOT_API_KEY environment variable")
|
||||
sys.exit(1)
|
||||
|
||||
print("🧪 Testing agent error recovery")
|
||||
print("=" * 60)
|
||||
|
||||
agent = KVCacheAgent(
|
||||
api_key=api_key,
|
||||
mode=KVCacheMode.CORRECT,
|
||||
root_dir="../..",
|
||||
verbose=True,
|
||||
)
|
||||
|
||||
task = """Please do the following:
|
||||
1. Try to read a file that doesn't exist: 'non_existent_file.txt'
|
||||
2. Then find Python files in chapter1/context directory
|
||||
3. Tell me what you found"""
|
||||
|
||||
print(f"Task: {task[:100]}...")
|
||||
result = agent.execute_task(task, max_iterations=10)
|
||||
|
||||
print(f"\n✓ Completed in {result['iterations']} iterations")
|
||||
print(f"✓ Tool calls made: {len(result['tool_calls'])}")
|
||||
|
||||
error_count = 0
|
||||
for tool_call in result["tool_calls"]:
|
||||
if tool_call.result and not tool_call.result.get("success", True):
|
||||
error_count += 1
|
||||
print(
|
||||
f"• Tool error in {tool_call.name}: "
|
||||
f"{tool_call.result.get('error', 'Unknown')[:50]}..."
|
||||
)
|
||||
|
||||
print(f"✓ Errors encountered and handled: {error_count}")
|
||||
print(f"✓ Agent continued despite errors: {result['success']}")
|
||||
|
||||
if result["final_answer"]:
|
||||
print("\nFinal answer provided despite errors:")
|
||||
print(f"{result['final_answer'][:200]}...")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
check_agent_error_recovery()
|
||||
@@ -0,0 +1,126 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test script to verify KV cache is properly invalidated in incorrect modes
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
from _bootstrap import add_project_root
|
||||
|
||||
add_project_root()
|
||||
|
||||
from agent import KVCacheAgent, KVCacheMode
|
||||
|
||||
# Set up logging to see details
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def test_cache_invalidation():
|
||||
"""Test that incorrect modes properly invalidate KV cache each iteration"""
|
||||
|
||||
# Get API key
|
||||
api_key = os.getenv("MOONSHOT_API_KEY")
|
||||
if not api_key:
|
||||
print("❌ Please set MOONSHOT_API_KEY environment variable")
|
||||
sys.exit(1)
|
||||
|
||||
print("🔬 Testing KV Cache Invalidation")
|
||||
print("="*60)
|
||||
|
||||
# Simple task that requires multiple iterations
|
||||
task = "Find Python files in chapter1/context and tell me how many there are."
|
||||
|
||||
print(f"Task: {task}")
|
||||
print("-"*40)
|
||||
|
||||
# Test 1: CORRECT mode (should use cache)
|
||||
print("\n1️⃣ Testing CORRECT mode (should use cache):")
|
||||
agent_correct = KVCacheAgent(
|
||||
api_key=api_key,
|
||||
mode=KVCacheMode.CORRECT,
|
||||
root_dir="../..",
|
||||
verbose=True
|
||||
)
|
||||
|
||||
result_correct = agent_correct.execute_task(task, max_iterations=5)
|
||||
metrics_correct = result_correct["metrics"]
|
||||
|
||||
print(f"\n Results for CORRECT mode:")
|
||||
print(f" • Iterations: {result_correct['iterations']}")
|
||||
print(f" • TTFT per iteration: {[f'{t:.2f}s' for t in metrics_correct.ttft_per_iteration]}")
|
||||
print(f" • Cached tokens: {metrics_correct.cached_tokens}")
|
||||
print(f" • Cache hits: {metrics_correct.cache_hits}")
|
||||
|
||||
# Test 2: DYNAMIC_SYSTEM mode (should NOT use cache)
|
||||
print("\n2️⃣ Testing DYNAMIC_SYSTEM mode (should NOT use cache):")
|
||||
agent_dynamic = KVCacheAgent(
|
||||
api_key=api_key,
|
||||
mode=KVCacheMode.DYNAMIC_SYSTEM,
|
||||
root_dir="../..",
|
||||
verbose=True
|
||||
)
|
||||
|
||||
result_dynamic = agent_dynamic.execute_task(task, max_iterations=5)
|
||||
metrics_dynamic = result_dynamic["metrics"]
|
||||
|
||||
print(f"\n Results for DYNAMIC_SYSTEM mode:")
|
||||
print(f" • Iterations: {result_dynamic['iterations']}")
|
||||
print(f" • TTFT per iteration: {[f'{t:.2f}s' for t in metrics_dynamic.ttft_per_iteration]}")
|
||||
print(f" • Cached tokens: {metrics_dynamic.cached_tokens}")
|
||||
print(f" • Cache hits: {metrics_dynamic.cache_hits}")
|
||||
|
||||
# Analysis
|
||||
print("\n" + "="*60)
|
||||
print("📊 ANALYSIS:")
|
||||
print("-"*40)
|
||||
|
||||
# Check TTFT improvement
|
||||
if len(metrics_correct.ttft_per_iteration) > 1:
|
||||
correct_improvement = (metrics_correct.ttft_per_iteration[0] - metrics_correct.ttft_per_iteration[-1]) / metrics_correct.ttft_per_iteration[0] * 100
|
||||
print(f"CORRECT mode TTFT improvement: {correct_improvement:.1f}%")
|
||||
|
||||
if len(metrics_dynamic.ttft_per_iteration) > 1:
|
||||
dynamic_improvement = (metrics_dynamic.ttft_per_iteration[0] - metrics_dynamic.ttft_per_iteration[-1]) / metrics_dynamic.ttft_per_iteration[0] * 100
|
||||
print(f"DYNAMIC mode TTFT improvement: {dynamic_improvement:.1f}%")
|
||||
|
||||
# Verify cache behavior
|
||||
print("\n✅ Verification:")
|
||||
if metrics_correct.cached_tokens > 0:
|
||||
print(f" ✓ CORRECT mode used cache: {metrics_correct.cached_tokens} tokens")
|
||||
else:
|
||||
print(f" ✗ CORRECT mode did NOT use cache (unexpected!)")
|
||||
|
||||
if metrics_dynamic.cached_tokens == 0:
|
||||
print(f" ✓ DYNAMIC mode did NOT use cache (expected)")
|
||||
else:
|
||||
print(f" ✗ DYNAMIC mode used cache: {metrics_dynamic.cached_tokens} tokens (unexpected!)")
|
||||
|
||||
# Check TTFT consistency
|
||||
print("\n🔍 TTFT Consistency Check:")
|
||||
if len(metrics_correct.ttft_per_iteration) > 2:
|
||||
# CORRECT mode should show improvement after first iteration
|
||||
first_ttft = metrics_correct.ttft_per_iteration[0]
|
||||
avg_rest = sum(metrics_correct.ttft_per_iteration[1:]) / len(metrics_correct.ttft_per_iteration[1:])
|
||||
if avg_rest < first_ttft * 0.7: # At least 30% improvement
|
||||
print(f" ✓ CORRECT mode shows cache benefit (first: {first_ttft:.2f}s, avg rest: {avg_rest:.2f}s)")
|
||||
else:
|
||||
print(f" ⚠️ CORRECT mode improvement less than expected")
|
||||
|
||||
if len(metrics_dynamic.ttft_per_iteration) > 2:
|
||||
# DYNAMIC mode should NOT show significant improvement
|
||||
all_ttfts = metrics_dynamic.ttft_per_iteration
|
||||
min_ttft = min(all_ttfts)
|
||||
max_ttft = max(all_ttfts)
|
||||
if (max_ttft - min_ttft) / max_ttft < 0.3: # Less than 30% variation
|
||||
print(f" ✓ DYNAMIC mode shows consistent TTFT (no cache benefit)")
|
||||
else:
|
||||
print(f" ⚠️ DYNAMIC mode shows unexpected TTFT variation")
|
||||
|
||||
print("\n💡 Key Finding:")
|
||||
print("The CORRECT mode should show significant TTFT improvement after the first")
|
||||
print("iteration due to KV cache, while incorrect modes should maintain")
|
||||
print("consistently high TTFT because the cache is invalidated on each iteration.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_cache_invalidation()
|
||||
@@ -0,0 +1,77 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test script to verify cached tokens are being parsed correctly from Kimi API
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
from _bootstrap import add_project_root
|
||||
|
||||
add_project_root()
|
||||
|
||||
from agent import KVCacheAgent, KVCacheMode
|
||||
|
||||
def test_cached_tokens():
|
||||
"""Test that cached tokens are correctly parsed from API response"""
|
||||
|
||||
# Get API key
|
||||
api_key = os.getenv("MOONSHOT_API_KEY")
|
||||
if not api_key:
|
||||
print("❌ Please set MOONSHOT_API_KEY environment variable")
|
||||
sys.exit(1)
|
||||
|
||||
print("🔍 Testing Cached Tokens Parsing")
|
||||
print("="*60)
|
||||
|
||||
# Simple task that requires a few iterations
|
||||
task = "Find Python files in chapter1/context directory and tell me how many there are."
|
||||
|
||||
print(f"Task: {task}")
|
||||
print("-"*40)
|
||||
|
||||
# Run with correct implementation (should use cache)
|
||||
print("\nRunning agent with CORRECT implementation...")
|
||||
agent = KVCacheAgent(
|
||||
api_key=api_key,
|
||||
mode=KVCacheMode.CORRECT,
|
||||
root_dir="../..",
|
||||
verbose=True # Enable verbose to see token logging
|
||||
)
|
||||
|
||||
result = agent.execute_task(task, max_iterations=5)
|
||||
metrics = result["metrics"]
|
||||
|
||||
print("\n" + "="*60)
|
||||
print("📊 Cache Token Results:")
|
||||
print(f" • Total iterations: {result['iterations']}")
|
||||
print(f" • Cached tokens accumulated: {metrics.cached_tokens}")
|
||||
print(f" • Cache hits: {metrics.cache_hits}")
|
||||
print(f" • Cache misses: {metrics.cache_misses}")
|
||||
|
||||
# Check each iteration's TTFT
|
||||
if metrics.ttft_per_iteration:
|
||||
print(f"\n • TTFT per iteration:")
|
||||
for i, ttft in enumerate(metrics.ttft_per_iteration, 1):
|
||||
status = "🔴 No cache" if i == 1 else "🟢 With cache"
|
||||
print(f" Iteration {i}: {ttft:.3f}s {status}")
|
||||
|
||||
# Verify cache is working
|
||||
print("\n✅ Verification:")
|
||||
if metrics.cached_tokens > 0:
|
||||
print(f" ✓ Cached tokens detected: {metrics.cached_tokens}")
|
||||
else:
|
||||
print(f" ⚠️ No cached tokens detected - cache may not be working")
|
||||
|
||||
if len(metrics.ttft_per_iteration) > 1:
|
||||
first_ttft = metrics.ttft_per_iteration[0]
|
||||
second_ttft = metrics.ttft_per_iteration[1]
|
||||
if second_ttft < first_ttft * 0.8: # At least 20% improvement
|
||||
print(f" ✓ TTFT improved from {first_ttft:.3f}s to {second_ttft:.3f}s")
|
||||
else:
|
||||
print(f" ⚠️ TTFT did not improve significantly")
|
||||
|
||||
print("\n💡 Note: Kimi API should return cached_tokens in the usage object")
|
||||
print(" starting from the second iteration when context is stable.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_cached_tokens()
|
||||
@@ -0,0 +1,99 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test script to verify the agent correctly identifies final answers
|
||||
when no tool calls are made
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
from _bootstrap import add_project_root
|
||||
|
||||
add_project_root()
|
||||
|
||||
from agent import KVCacheAgent, KVCacheMode
|
||||
|
||||
def test_completion_logic():
|
||||
"""Test that the agent correctly handles responses without tool calls as final answers"""
|
||||
|
||||
# Get API key
|
||||
api_key = os.getenv("MOONSHOT_API_KEY")
|
||||
if not api_key:
|
||||
print("❌ Please set MOONSHOT_API_KEY environment variable")
|
||||
sys.exit(1)
|
||||
|
||||
print("🧪 Testing Final Answer Detection")
|
||||
print("="*60)
|
||||
|
||||
# Test 1: Simple question that doesn't require tools
|
||||
print("\n1️⃣ Test: Simple question without tools")
|
||||
task1 = "What is 2 + 2? Just tell me the answer, no need to use any tools."
|
||||
|
||||
agent = KVCacheAgent(
|
||||
api_key=api_key,
|
||||
mode=KVCacheMode.CORRECT,
|
||||
root_dir="../..",
|
||||
verbose=False
|
||||
)
|
||||
|
||||
result = agent.execute_task(task1, max_iterations=5)
|
||||
print(f" Task: {task1}")
|
||||
print(f" ✓ Completed in {result['iterations']} iteration(s)")
|
||||
print(f" ✓ Tool calls: {len(result['tool_calls'])}")
|
||||
print(f" ✓ Has final answer: {result['success']}")
|
||||
if result['final_answer']:
|
||||
print(f" Answer: {result['final_answer'][:100]}")
|
||||
|
||||
# Test 2: Question that requires tools
|
||||
print("\n2️⃣ Test: Question requiring tools")
|
||||
task2 = "How many Python files are in the chapter1/context directory?"
|
||||
|
||||
agent2 = KVCacheAgent(
|
||||
api_key=api_key,
|
||||
mode=KVCacheMode.CORRECT,
|
||||
root_dir="../..",
|
||||
verbose=False
|
||||
)
|
||||
|
||||
result2 = agent2.execute_task(task2, max_iterations=5)
|
||||
print(f" Task: {task2}")
|
||||
print(f" ✓ Completed in {result2['iterations']} iteration(s)")
|
||||
print(f" ✓ Tool calls: {len(result2['tool_calls'])}")
|
||||
print(f" ✓ Has final answer: {result2['success']}")
|
||||
|
||||
if result2['tool_calls']:
|
||||
print(" Tools used:")
|
||||
for tc in result2['tool_calls']:
|
||||
print(f" • {tc.name}")
|
||||
|
||||
# Test 3: Multi-step task
|
||||
print("\n3️⃣ Test: Multi-step task")
|
||||
task3 = "Find Python files in chapter1/context, then tell me if there's a file named 'agent.py'"
|
||||
|
||||
agent3 = KVCacheAgent(
|
||||
api_key=api_key,
|
||||
mode=KVCacheMode.CORRECT,
|
||||
root_dir="../..",
|
||||
verbose=False
|
||||
)
|
||||
|
||||
result3 = agent3.execute_task(task3, max_iterations=10)
|
||||
print(f" Task: {task3}")
|
||||
print(f" ✓ Completed in {result3['iterations']} iteration(s)")
|
||||
print(f" ✓ Tool calls: {len(result3['tool_calls'])}")
|
||||
print(f" ✓ Has final answer: {result3['success']}")
|
||||
|
||||
# Summary
|
||||
print("\n" + "="*60)
|
||||
print("📊 Summary:")
|
||||
print(f" • Test 1 (no tools): {result['iterations']} iterations, {len(result['tool_calls'])} tools")
|
||||
print(f" • Test 2 (with tools): {result2['iterations']} iterations, {len(result2['tool_calls'])} tools")
|
||||
print(f" • Test 3 (multi-step): {result3['iterations']} iterations, {len(result3['tool_calls'])} tools")
|
||||
|
||||
print("\n✅ The agent correctly:")
|
||||
print(" 1. Identifies final answers when no tools are needed")
|
||||
print(" 2. Uses tools when necessary to gather information")
|
||||
print(" 3. Provides final answer after tool execution")
|
||||
print("\nNo explicit 'final answer' keyword needed!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_completion_logic()
|
||||
@@ -0,0 +1,73 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test script to verify the updated agent works with standard OpenAI tool calling
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
from _bootstrap import add_project_root
|
||||
|
||||
add_project_root()
|
||||
|
||||
from agent import KVCacheAgent, KVCacheMode
|
||||
|
||||
def test_tool_calling():
|
||||
"""Test that the agent correctly uses OpenAI tool calling format"""
|
||||
|
||||
# Get API key
|
||||
api_key = os.getenv("MOONSHOT_API_KEY")
|
||||
if not api_key:
|
||||
print("❌ Please set MOONSHOT_API_KEY environment variable")
|
||||
sys.exit(1)
|
||||
|
||||
print("🧪 Testing Standard OpenAI Tool Calling Format")
|
||||
print("="*60)
|
||||
|
||||
# Simple task that requires tool calls
|
||||
task = "Find all Python files in the chapter1/context directory and tell me how many there are."
|
||||
|
||||
print(f"📝 Task: {task}")
|
||||
print("-"*60)
|
||||
|
||||
# Create agent with correct implementation
|
||||
agent = KVCacheAgent(
|
||||
api_key=api_key,
|
||||
mode=KVCacheMode.CORRECT,
|
||||
root_dir="../..",
|
||||
verbose=True # Enable verbose to see tool calls
|
||||
)
|
||||
|
||||
# Execute task
|
||||
result = agent.execute_task(task, max_iterations=5)
|
||||
|
||||
# Check results
|
||||
print("\n" + "="*60)
|
||||
print("📊 Results:")
|
||||
print(f"✓ Success: {result['success']}")
|
||||
print(f"✓ Iterations: {result['iterations']}")
|
||||
print(f"✓ Tool Calls Made: {len(result['tool_calls'])}")
|
||||
|
||||
if result['tool_calls']:
|
||||
print("\n🔧 Tool Calls:")
|
||||
for tc in result['tool_calls']:
|
||||
print(f" • {tc.name}({tc.arguments})")
|
||||
if tc.result and tc.result.get('success'):
|
||||
if tc.name == 'find':
|
||||
print(f" → Found {tc.result.get('count', 0)} files")
|
||||
|
||||
if result['final_answer']:
|
||||
print(f"\n💬 Final Answer:")
|
||||
print(f" {result['final_answer'][:200]}...")
|
||||
|
||||
# Test metrics
|
||||
metrics = result['metrics']
|
||||
print(f"\n📈 Performance Metrics:")
|
||||
print(f" • TTFT: {metrics.ttft:.3f}s")
|
||||
print(f" • Total Time: {metrics.total_time:.3f}s")
|
||||
print(f" • Cached Tokens: {metrics.cached_tokens}")
|
||||
|
||||
print("\n✅ Tool calling test completed successfully!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_tool_calling()
|
||||
@@ -0,0 +1,137 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test script to demonstrate TTFT tracking across iterations
|
||||
Shows how cache usage improves response times
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
from _bootstrap import add_project_root
|
||||
|
||||
add_project_root()
|
||||
|
||||
from agent import KVCacheAgent, KVCacheMode
|
||||
|
||||
def test_ttft_tracking():
|
||||
"""Test and display TTFT tracking across iterations"""
|
||||
|
||||
# Get API key
|
||||
api_key = os.getenv("MOONSHOT_API_KEY")
|
||||
if not api_key:
|
||||
print("❌ Please set MOONSHOT_API_KEY environment variable")
|
||||
sys.exit(1)
|
||||
|
||||
print("📊 TTFT Tracking Demonstration")
|
||||
print("="*60)
|
||||
|
||||
# Task that requires multiple iterations
|
||||
task = """Analyze the chapter1/context directory:
|
||||
1. Find all Python files
|
||||
2. Read the agent.py file (first 100 lines)
|
||||
3. Search for classes in the code
|
||||
4. Provide a summary of what you found"""
|
||||
|
||||
print(f"Task: {task[:100]}...")
|
||||
print("="*60)
|
||||
|
||||
# Test with correct implementation (should show cache benefits)
|
||||
print("\n✅ CORRECT Implementation (with KV cache):")
|
||||
print("-"*40)
|
||||
|
||||
agent = KVCacheAgent(
|
||||
api_key=api_key,
|
||||
mode=KVCacheMode.CORRECT,
|
||||
root_dir="../..",
|
||||
verbose=False # Set to True to see detailed logs
|
||||
)
|
||||
|
||||
result = agent.execute_task(task, max_iterations=10)
|
||||
metrics = result["metrics"]
|
||||
|
||||
# Display TTFT progression
|
||||
print(f"Iterations completed: {result['iterations']}")
|
||||
print(f"Tool calls made: {len(result['tool_calls'])}")
|
||||
print(f"\nTTFT per iteration:")
|
||||
|
||||
for i, ttft in enumerate(metrics.ttft_per_iteration, 1):
|
||||
bar_length = int(ttft * 10) # Visual bar representation
|
||||
bar = "█" * min(bar_length, 50)
|
||||
print(f" Iter {i:2d}: {ttft:6.3f}s {bar}")
|
||||
|
||||
# Calculate statistics
|
||||
if len(metrics.ttft_per_iteration) > 1:
|
||||
first = metrics.ttft_per_iteration[0]
|
||||
last = metrics.ttft_per_iteration[-1]
|
||||
avg_all = sum(metrics.ttft_per_iteration) / len(metrics.ttft_per_iteration)
|
||||
avg_after_first = sum(metrics.ttft_per_iteration[1:]) / len(metrics.ttft_per_iteration[1:])
|
||||
|
||||
print(f"\n📈 Performance Analysis:")
|
||||
print(f" • First iteration: {first:.3f}s (cold start)")
|
||||
print(f" • Last iteration: {last:.3f}s")
|
||||
print(f" • Average (all): {avg_all:.3f}s")
|
||||
print(f" • Average (cached): {avg_after_first:.3f}s")
|
||||
print(f" • Speed improvement: {(first - last) / first * 100:.1f}%")
|
||||
print(f" • Cached tokens: {metrics.cached_tokens:,}")
|
||||
|
||||
# Compare with dynamic system prompt (no cache benefits)
|
||||
print("\n" + "="*60)
|
||||
print("❌ DYNAMIC SYSTEM Implementation (breaks KV cache):")
|
||||
print("-"*40)
|
||||
|
||||
agent2 = KVCacheAgent(
|
||||
api_key=api_key,
|
||||
mode=KVCacheMode.DYNAMIC_SYSTEM,
|
||||
root_dir="../..",
|
||||
verbose=False
|
||||
)
|
||||
|
||||
result2 = agent2.execute_task(task, max_iterations=10)
|
||||
metrics2 = result2["metrics"]
|
||||
|
||||
print(f"Iterations completed: {result2['iterations']}")
|
||||
print(f"Tool calls made: {len(result2['tool_calls'])}")
|
||||
print(f"\nTTFT per iteration:")
|
||||
|
||||
for i, ttft in enumerate(metrics2.ttft_per_iteration, 1):
|
||||
bar_length = int(ttft * 10)
|
||||
bar = "█" * min(bar_length, 50)
|
||||
print(f" Iter {i:2d}: {ttft:6.3f}s {bar}")
|
||||
|
||||
if len(metrics2.ttft_per_iteration) > 1:
|
||||
first2 = metrics2.ttft_per_iteration[0]
|
||||
last2 = metrics2.ttft_per_iteration[-1]
|
||||
avg_all2 = sum(metrics2.ttft_per_iteration) / len(metrics2.ttft_per_iteration)
|
||||
|
||||
print(f"\n📉 Performance Analysis:")
|
||||
print(f" • First iteration: {first2:.3f}s")
|
||||
print(f" • Last iteration: {last2:.3f}s")
|
||||
print(f" • Average (all): {avg_all2:.3f}s")
|
||||
print(f" • Speed improvement: {(first2 - last2) / first2 * 100:.1f}% (minimal)")
|
||||
print(f" • Cached tokens: {metrics2.cached_tokens:,} (should be 0)")
|
||||
|
||||
# Comparison
|
||||
print("\n" + "="*60)
|
||||
print("🔬 COMPARISON:")
|
||||
print("-"*40)
|
||||
|
||||
if metrics.ttft_per_iteration and metrics2.ttft_per_iteration:
|
||||
avg1 = sum(metrics.ttft_per_iteration) / len(metrics.ttft_per_iteration)
|
||||
avg2 = sum(metrics2.ttft_per_iteration) / len(metrics2.ttft_per_iteration)
|
||||
|
||||
print(f"Average TTFT:")
|
||||
print(f" • Correct (with cache): {avg1:.3f}s")
|
||||
print(f" • Dynamic (no cache): {avg2:.3f}s")
|
||||
print(f" • Difference: {avg2 - avg1:.3f}s slower without cache")
|
||||
print(f" • Performance penalty: {(avg2 - avg1) / avg1 * 100:.1f}% slower")
|
||||
|
||||
print(f"\nCache Usage:")
|
||||
print(f" • Correct: {metrics.cached_tokens:,} tokens cached")
|
||||
print(f" • Dynamic: {metrics2.cached_tokens:,} tokens cached")
|
||||
|
||||
print("\n💡 Key Observation:")
|
||||
print("The correct implementation shows significant TTFT improvement after the")
|
||||
print("first iteration due to KV cache, while dynamic system prompt maintains")
|
||||
print("consistently high TTFT because the cache is invalidated on each request.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_ttft_tracking()
|
||||
@@ -0,0 +1,108 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Quick demonstration of KV cache impact
|
||||
Shows the difference between correct and incorrect implementations
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
try:
|
||||
from dotenv import load_dotenv
|
||||
load_dotenv()
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
from _bootstrap import add_project_root
|
||||
|
||||
add_project_root()
|
||||
|
||||
from agent import KVCacheAgent, KVCacheMode
|
||||
|
||||
from agentbook.providers import PROVIDERS
|
||||
|
||||
def main():
|
||||
"""Run a quick demo comparing correct vs incorrect implementation"""
|
||||
|
||||
# Get API key. 优先 Moonshot/Kimi;缺失时回退 OPENROUTER_API_KEY
|
||||
# (KVCacheAgent 会自动切换到 OpenRouter 端点并映射模型名)。
|
||||
# 接受哪些环境变量由 agentbook 的 provider 注册表定义。
|
||||
api_key = PROVIDERS["kimi"].api_key() or os.getenv("OPENROUTER_API_KEY")
|
||||
if not api_key:
|
||||
print("❌ Please set MOONSHOT_API_KEY (or KIMI_API_KEY / OPENROUTER_API_KEY)")
|
||||
print(" export MOONSHOT_API_KEY='your-api-key-here'")
|
||||
sys.exit(1)
|
||||
|
||||
print("🚀 KV Cache Quick Demo")
|
||||
print("="*60)
|
||||
|
||||
# Simple task that requires multiple tool calls
|
||||
task = """Please do the following:
|
||||
1. Find all Python files in the chapter1 directory
|
||||
2. Read the main.py file from the context project
|
||||
3. Search for the word 'agent' in chapter1 files
|
||||
4. Provide a brief summary of what you found"""
|
||||
|
||||
print(f"📝 Task: {task}")
|
||||
print("="*60)
|
||||
|
||||
# Test 1: Correct implementation
|
||||
print("\n✅ Testing CORRECT implementation (with KV cache)...")
|
||||
print("-"*60)
|
||||
agent_correct = KVCacheAgent(
|
||||
api_key=api_key,
|
||||
mode=KVCacheMode.CORRECT,
|
||||
root_dir="../..",
|
||||
verbose=False # Set to True for detailed logs
|
||||
)
|
||||
|
||||
result_correct = agent_correct.execute_task(task, max_iterations=10)
|
||||
metrics_correct = result_correct["metrics"]
|
||||
|
||||
print(f"✓ TTFT: {metrics_correct.ttft:.3f}s")
|
||||
print(f"✓ Total Time: {metrics_correct.total_time:.3f}s")
|
||||
print(f"✓ Cached Tokens: {metrics_correct.cached_tokens:,}")
|
||||
print(f"✓ Cache Hits: {metrics_correct.cache_hits}")
|
||||
print(f"✓ Total Tokens Used: {metrics_correct.prompt_tokens + metrics_correct.completion_tokens:,}")
|
||||
|
||||
# Test 2: Incorrect implementation (dynamic system prompt)
|
||||
print("\n❌ Testing INCORRECT implementation (dynamic system prompt)...")
|
||||
print("-"*60)
|
||||
agent_incorrect = KVCacheAgent(
|
||||
api_key=api_key,
|
||||
mode=KVCacheMode.DYNAMIC_SYSTEM,
|
||||
root_dir="../..",
|
||||
verbose=False
|
||||
)
|
||||
|
||||
result_incorrect = agent_incorrect.execute_task(task, max_iterations=10)
|
||||
metrics_incorrect = result_incorrect["metrics"]
|
||||
|
||||
print(f"✗ TTFT: {metrics_incorrect.ttft:.3f}s")
|
||||
print(f"✗ Total Time: {metrics_incorrect.total_time:.3f}s")
|
||||
print(f"✗ Cached Tokens: {metrics_incorrect.cached_tokens:,}")
|
||||
print(f"✗ Cache Hits: {metrics_incorrect.cache_hits}")
|
||||
print(f"✗ Total Tokens Used: {metrics_incorrect.prompt_tokens + metrics_incorrect.completion_tokens:,}")
|
||||
|
||||
# Comparison
|
||||
print("\n📊 Performance Impact:")
|
||||
print("="*60)
|
||||
|
||||
ttft_diff = ((metrics_incorrect.ttft - metrics_correct.ttft) / metrics_correct.ttft) * 100
|
||||
time_diff = ((metrics_incorrect.total_time - metrics_correct.total_time) / metrics_correct.total_time) * 100
|
||||
cache_lost = metrics_correct.cached_tokens - metrics_incorrect.cached_tokens
|
||||
|
||||
print(f"⚡ TTFT increased by: {ttft_diff:.1f}%")
|
||||
print(f"⏱️ Total time increased by: {time_diff:.1f}%")
|
||||
print(f"💾 Cache tokens lost: {cache_lost:,}")
|
||||
|
||||
if ttft_diff > 50:
|
||||
print("\n⚠️ Dynamic system prompts severely impact performance!")
|
||||
print(" Even small context changes can invalidate the entire KV cache.")
|
||||
|
||||
print("\n💡 Key Takeaway:")
|
||||
print(" Maintaining stable context is crucial for LLM performance.")
|
||||
print(" Small implementation details can have major performance impacts!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Offline regressions for local tool error handling."""
|
||||
|
||||
from agent import LocalFileTools
|
||||
|
||||
def test_error_handling():
|
||||
"""Test that local tools return structured errors instead of raising."""
|
||||
|
||||
print("🧪 Testing Error Handling in Tool Execution")
|
||||
print("="*60)
|
||||
|
||||
# Test local tools directly first
|
||||
print("\n1️⃣ Testing direct tool error handling:")
|
||||
tools = LocalFileTools(root_dir="../..")
|
||||
|
||||
# Test with invalid arguments
|
||||
print(" Testing read_file with extra 'limit' parameter...")
|
||||
# The tool should ignore the extra parameter
|
||||
result = tools.read_file("chapter1/context/README.md")
|
||||
print(f" Result: {'✓ Success' if result.get('success') else '✗ Error'}")
|
||||
assert result.get("success") is True
|
||||
|
||||
# Test with non-existent file
|
||||
print(" Testing read_file with non-existent file...")
|
||||
result = tools.read_file("non_existent_file.txt")
|
||||
print(f" Result: {'✓ Error handled' if not result.get('success') else '✗ Unexpected success'}")
|
||||
print(f" Error message: {result.get('error', 'N/A')}")
|
||||
assert result.get("success") is False
|
||||
assert "File not found" in result.get("error", "")
|
||||
|
||||
# Test security boundary
|
||||
print(" Testing security boundary...")
|
||||
result = tools.read_file("../../../../etc/passwd")
|
||||
print(f" Result: {'✓ Access denied' if 'Access denied' in result.get('error', '') else '✗ Security issue'}")
|
||||
assert result.get("success") is False
|
||||
assert "Access denied" in result.get("error", "")
|
||||
|
||||
print("\n" + "="*60)
|
||||
print("✅ Error handling test complete!")
|
||||
print("\nKey findings:")
|
||||
print(" • Tools return errors as results instead of throwing exceptions")
|
||||
print(" • Unexpected arguments are filtered out safely")
|
||||
print(" • Security boundaries are enforced")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_error_handling()
|
||||
@@ -0,0 +1,104 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test script for the read_file tool with offset and size parameters
|
||||
"""
|
||||
|
||||
from agent import LocalFileTools
|
||||
|
||||
def test_file_range_reading():
|
||||
"""Test reading files with offset and size parameters"""
|
||||
|
||||
print("🧪 Testing File Range Reading")
|
||||
print("="*60)
|
||||
|
||||
# Initialize tools
|
||||
tools = LocalFileTools(root_dir="../..")
|
||||
|
||||
# Test file
|
||||
test_file = "chapter1/context/agent.py"
|
||||
|
||||
# Test 1: Read first 10 lines
|
||||
print("\n1️⃣ Reading first 10 lines:")
|
||||
result = tools.read_file(test_file, offset=0, size=10)
|
||||
if result["success"]:
|
||||
print(f" ✓ Read {result['lines_read']} lines from total {result['total_lines']}")
|
||||
print(f" Range: lines {result['offset']}-{result['end_line']}")
|
||||
print(f" First line: {result['content'].split(chr(10))[0][:50]}...")
|
||||
else:
|
||||
print(f" ✗ Error: {result['error']}")
|
||||
|
||||
# Test 2: Read lines 100-110
|
||||
print("\n2️⃣ Reading lines 100-110:")
|
||||
result = tools.read_file(test_file, offset=100, size=10)
|
||||
if result["success"]:
|
||||
print(f" ✓ Read {result['lines_read']} lines")
|
||||
print(f" Range: lines {result['offset']}-{result['end_line']}")
|
||||
lines = result['content'].split('\n')
|
||||
if lines:
|
||||
print(f" Sample: {lines[0][:60]}...")
|
||||
else:
|
||||
print(f" ✗ Error: {result['error']}")
|
||||
|
||||
# Test 3: Read from offset 250 with size 500 (as specified)
|
||||
print("\n3️⃣ Reading from offset 250, size 500:")
|
||||
result = tools.read_file(test_file, offset=250, size=500)
|
||||
if result["success"]:
|
||||
print(f" ✓ Read {result['lines_read']} lines")
|
||||
print(f" Range: lines {result['offset']}-{result['end_line']}")
|
||||
print(f" Total file has {result['total_lines']} lines")
|
||||
else:
|
||||
print(f" ✗ Error: {result['error']}")
|
||||
|
||||
# Test 4: Read without size (from offset to end)
|
||||
print("\n4️⃣ Reading from offset 700 to end:")
|
||||
result = tools.read_file(test_file, offset=700)
|
||||
if result["success"]:
|
||||
print(f" ✓ Read {result['lines_read']} lines")
|
||||
print(f" Range: lines {result['offset']}-{result['end_line']}")
|
||||
else:
|
||||
print(f" ✗ Error: {result['error']}")
|
||||
|
||||
# Test 5: Offset beyond file length
|
||||
print("\n5️⃣ Testing offset beyond file length:")
|
||||
result = tools.read_file(test_file, offset=10000, size=10)
|
||||
if result["success"]:
|
||||
print(f" ✓ Handled gracefully: {result.get('message', 'No error')}")
|
||||
print(f" Lines read: {result['lines_read']}")
|
||||
else:
|
||||
print(f" Result: {result}")
|
||||
|
||||
# Test 6: Read entire file (no offset, no size)
|
||||
print("\n6️⃣ Reading entire file (default behavior):")
|
||||
result = tools.read_file("chapter1/context/README.md")
|
||||
if result["success"]:
|
||||
print(f" ✓ Read entire file")
|
||||
print(f" Total lines: {result['total_lines']}")
|
||||
print(f" Lines read: {result['lines_read']}")
|
||||
print(f" Truncated: {result.get('truncated', False)}")
|
||||
else:
|
||||
print(f" ✗ Error: {result['error']}")
|
||||
|
||||
# Test 7: Compare with limit parameter (the user's original request)
|
||||
print("\n7️⃣ API-style usage (offset=250, size=500):")
|
||||
result = tools.read_file("chapter2/local_llm_serving/main.py", offset=250, size=500)
|
||||
if result["success"]:
|
||||
print(f" ✓ Successfully read lines {result['offset']}-{result['end_line']}")
|
||||
print(f" Lines read: {result['lines_read']}")
|
||||
print(f" File has {result['total_lines']} total lines")
|
||||
|
||||
# Show a sample of the content
|
||||
lines = result['content'].split('\n')[:3]
|
||||
print("\n First 3 lines of content:")
|
||||
for i, line in enumerate(lines):
|
||||
print(f" Line {250+i}: {line[:60]}..." if len(line) > 60 else f" Line {250+i}: {line}")
|
||||
|
||||
print("\n" + "="*60)
|
||||
print("✅ File range reading tests complete!")
|
||||
print("\nThe read_file tool now supports:")
|
||||
print(" • offset: Starting line number (0-based)")
|
||||
print(" • size: Number of lines to read")
|
||||
print(" • Handles edge cases gracefully")
|
||||
print(" • Maintains security boundaries")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_file_range_reading()
|
||||
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test script for interactive mode selection
|
||||
"""
|
||||
|
||||
from main import select_mode_interactive
|
||||
|
||||
def test_mode_selection(monkeypatch):
|
||||
"""Test the interactive mode selection without running the agent"""
|
||||
|
||||
print("🧪 Testing Interactive Mode Selection")
|
||||
print("(This is a test - no agent will actually run)")
|
||||
|
||||
# Test the selection menu
|
||||
monkeypatch.setattr("builtins.input", lambda _prompt: "7")
|
||||
selected = select_mode_interactive()
|
||||
assert selected == "compare"
|
||||
|
||||
print("\n" + "="*60)
|
||||
if selected == "compare":
|
||||
print("✅ You selected: Compare all modes")
|
||||
print("In real usage, this would run all 6 implementations and compare them.")
|
||||
else:
|
||||
print(f"✅ You selected: {selected}")
|
||||
print(f"In real usage, this would run the '{selected}' implementation.")
|
||||
|
||||
print("\nTest complete!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_mode_selection()
|
||||
@@ -0,0 +1,86 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test script to verify message flow in correct vs incorrect modes
|
||||
"""
|
||||
|
||||
def test_message_flow_logic():
|
||||
"""Simulate how messages are handled in different modes"""
|
||||
|
||||
print("🔍 Testing Message Flow Logic")
|
||||
print("="*60)
|
||||
|
||||
# Simulate CORRECT mode
|
||||
print("\n✅ CORRECT Mode:")
|
||||
print("-"*40)
|
||||
|
||||
messages_correct = None
|
||||
history_correct = []
|
||||
|
||||
for iteration in range(1, 4):
|
||||
print(f"\nIteration {iteration}:")
|
||||
|
||||
if iteration == 1:
|
||||
# First iteration: create messages
|
||||
messages_correct = ["system", "task"]
|
||||
print(f" • Created messages: {messages_correct}")
|
||||
else:
|
||||
print(f" • Using existing messages: {messages_correct}")
|
||||
|
||||
# Simulate tool call
|
||||
print(f" • API returns tool call")
|
||||
messages_correct.append(f"assistant_iter{iteration}")
|
||||
history_correct.append(f"assistant_iter{iteration}")
|
||||
|
||||
# Simulate tool result
|
||||
print(f" • Tool executed")
|
||||
messages_correct.append(f"tool_result_iter{iteration}")
|
||||
history_correct.append(f"tool_result_iter{iteration}")
|
||||
|
||||
print(f" • Messages now: {messages_correct}")
|
||||
print(f" • History now: {history_correct}")
|
||||
|
||||
# Simulate INCORRECT mode
|
||||
print("\n\n❌ INCORRECT Mode (e.g., DYNAMIC_SYSTEM):")
|
||||
print("-"*40)
|
||||
|
||||
history_incorrect = []
|
||||
|
||||
for iteration in range(1, 4):
|
||||
print(f"\nIteration {iteration}:")
|
||||
|
||||
# Always recreate messages from history
|
||||
messages_incorrect = ["system_with_timestamp", "task"] + history_incorrect
|
||||
print(f" • Recreated messages: {messages_incorrect}")
|
||||
|
||||
# Simulate tool call
|
||||
print(f" • API returns tool call")
|
||||
messages_incorrect.append(f"assistant_iter{iteration}")
|
||||
history_incorrect.append(f"assistant_iter{iteration}")
|
||||
|
||||
# Simulate tool result
|
||||
print(f" • Tool executed")
|
||||
messages_incorrect.append(f"tool_result_iter{iteration}")
|
||||
history_incorrect.append(f"tool_result_iter{iteration}")
|
||||
|
||||
print(f" • Messages now: {messages_incorrect}")
|
||||
print(f" • History now: {history_incorrect}")
|
||||
|
||||
print("\n\n📊 Key Observations:")
|
||||
print("="*60)
|
||||
print("\n1. CORRECT Mode:")
|
||||
print(" • Messages list persists across iterations")
|
||||
print(" • Each iteration adds to the same list")
|
||||
print(" • Context remains stable → KV cache works")
|
||||
|
||||
print("\n2. INCORRECT Mode:")
|
||||
print(" • Messages list recreated each iteration")
|
||||
print(" • System prompt changes (timestamp)")
|
||||
print(" • Context changes → KV cache invalidated")
|
||||
|
||||
print("\n3. Both Modes:")
|
||||
print(" • Within an iteration, tool results are appended")
|
||||
print(" • This ensures the API sees complete conversation")
|
||||
print(" • History is maintained for reconstruction")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_message_flow_logic()
|
||||
@@ -0,0 +1,26 @@
|
||||
"""Regression: negative size must read to EOF, not drop a suffix."""
|
||||
import sys
|
||||
import types
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def _stub():
|
||||
try:
|
||||
import openai # noqa: F401
|
||||
except ImportError:
|
||||
sys.modules.setdefault("openai", types.ModuleType("openai"))
|
||||
sys.modules["openai"].OpenAI = object
|
||||
|
||||
|
||||
_stub()
|
||||
|
||||
from agent import LocalFileTools # noqa: E402
|
||||
|
||||
|
||||
def test_negative_size_reads_all(tmp_path: Path):
|
||||
(tmp_path / "a.txt").write_text("a\nb\nc\n", encoding="utf-8")
|
||||
tools = LocalFileTools(str(tmp_path))
|
||||
out = tools.read_file("a.txt", offset=0, size=-1)
|
||||
assert out["success"] is True
|
||||
assert out["content"] == "a\nb\nc\n"
|
||||
assert out["lines_read"] == 3
|
||||
@@ -0,0 +1,142 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test script for local file system tools
|
||||
Validates that read_file, find, and grep work correctly
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
from agent import LocalFileTools
|
||||
|
||||
|
||||
def test_file_tools():
|
||||
"""Test the local file system tools"""
|
||||
|
||||
print("🧪 Testing Local File System Tools")
|
||||
print("="*60)
|
||||
|
||||
# Initialize tools with project root
|
||||
tools = LocalFileTools(root_dir="../..")
|
||||
|
||||
# Test 1: Find Python files
|
||||
print("\n1️⃣ Testing 'find' command...")
|
||||
print(" Finding *.py files in chapter1/context directory...")
|
||||
result = tools.find("*.py", "chapter1/context")
|
||||
|
||||
if result["success"]:
|
||||
print(f" ✓ Found {result['count']} Python files")
|
||||
if result["matches"]:
|
||||
print(f" Sample files: {result['matches'][:3]}")
|
||||
else:
|
||||
print(f" ✗ Error: {result['error']}")
|
||||
|
||||
# Test 2: Read a file
|
||||
print("\n2️⃣ Testing 'read_file' command...")
|
||||
test_file = "chapter1/context/README.md"
|
||||
print(f" Reading {test_file}...")
|
||||
result = tools.read_file(test_file)
|
||||
|
||||
if result["success"]:
|
||||
print(f" ✓ Read file successfully ({len(result['content'])} bytes)")
|
||||
print(f" First 100 chars: {result['content'][:100]}...")
|
||||
else:
|
||||
print(f" ✗ Error: {result['error']}")
|
||||
|
||||
# Test 3: Grep for a pattern
|
||||
print("\n3️⃣ Testing 'grep' command...")
|
||||
print(" Searching for 'agent' in chapter1 directory...")
|
||||
result = tools.grep("agent", directory="chapter1")
|
||||
|
||||
if result["success"]:
|
||||
print(f" ✓ Found {result['match_count']} matches in {result['files_searched']} files")
|
||||
if result["matches"]:
|
||||
sample = result["matches"][0]
|
||||
print(f" Sample match: {sample['file']}:{sample['line_num']} - {sample['line'][:50]}...")
|
||||
else:
|
||||
print(f" ✗ Error: {result['error']}")
|
||||
|
||||
# Test 4: Security check - try to access outside root
|
||||
print("\n4️⃣ Testing security boundaries...")
|
||||
print(" Attempting to read file outside root directory...")
|
||||
result = tools.read_file("../../../../../../etc/passwd")
|
||||
|
||||
if not result["success"] and "Access denied" in result.get("error", ""):
|
||||
print(" ✓ Security check passed - access denied as expected")
|
||||
else:
|
||||
print(" ⚠️ Security check result:", result.get("error", "Unexpected result"))
|
||||
|
||||
# Test 5: Grep in specific file
|
||||
print("\n5️⃣ Testing 'grep' in specific file...")
|
||||
print(" Searching for 'class' in chapter1/context/agent.py...")
|
||||
result = tools.grep("class", file_path="chapter1/context/agent.py")
|
||||
|
||||
if result["success"]:
|
||||
print(f" ✓ Found {result['match_count']} matches")
|
||||
if result["matches"]:
|
||||
for match in result["matches"][:3]:
|
||||
print(f" Line {match['line_num']}: {match['line'][:60]}...")
|
||||
else:
|
||||
print(f" ✗ Error: {result['error']}")
|
||||
|
||||
print("\n" + "="*60)
|
||||
print("✅ Tool testing complete!")
|
||||
print("\nAll tools are working correctly and can be used by the ReAct agent.")
|
||||
print("Security boundaries are properly enforced.")
|
||||
|
||||
|
||||
def test_pattern_matching():
|
||||
"""Test various pattern matching scenarios"""
|
||||
|
||||
print("\n🔍 Testing Pattern Matching Capabilities")
|
||||
print("="*60)
|
||||
|
||||
tools = LocalFileTools(root_dir="../..")
|
||||
|
||||
# Test different file patterns
|
||||
patterns = [
|
||||
("*.md", "chapter1", "Markdown files"),
|
||||
("*.py", "chapter2", "Python files"),
|
||||
("README*", ".", "README files"),
|
||||
("test_*.py", "chapter1", "Test files"),
|
||||
]
|
||||
|
||||
for pattern, directory, description in patterns:
|
||||
print(f"\n• Finding {description}: {pattern} in {directory}")
|
||||
result = tools.find(pattern, directory)
|
||||
if result["success"]:
|
||||
print(f" Found {result['count']} files")
|
||||
else:
|
||||
print(f" Error: {result['error']}")
|
||||
|
||||
# Test different grep patterns
|
||||
print("\n📝 Testing Grep Patterns")
|
||||
print("-"*40)
|
||||
|
||||
grep_tests = [
|
||||
(r"def \w+\(", "chapter1/context/agent.py", "Function definitions"),
|
||||
(r"import \w+", "chapter1/context/main.py", "Import statements"),
|
||||
(r"TODO|FIXME", "chapter1", "TODO/FIXME comments"),
|
||||
(r"^\s*class", "chapter1/context/agent.py", "Class definitions"),
|
||||
]
|
||||
|
||||
for pattern, target, description in grep_tests:
|
||||
print(f"\n• Searching for {description}: {pattern}")
|
||||
if "/" in target:
|
||||
result = tools.grep(pattern, file_path=target)
|
||||
else:
|
||||
result = tools.grep(pattern, directory=target)
|
||||
|
||||
if result["success"]:
|
||||
print(f" Found {result['match_count']} matches")
|
||||
else:
|
||||
print(f" Error: {result['error']}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Run basic tests
|
||||
test_file_tools()
|
||||
|
||||
# Run pattern matching tests
|
||||
test_pattern_matching()
|
||||
|
||||
print("\n🎉 All tests completed successfully!")
|
||||
Reference in New Issue
Block a user