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

This commit is contained in:
2026-08-20 13:12:50 +00:00
commit b119135836
10275 changed files with 3284984 additions and 0 deletions
+17
View File
@@ -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()
+104
View File
@@ -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
+142
View File
@@ -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!")