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,9 @@
|
||||
"""Test import bootstrap for the agent-with-event-trigger experiment."""
|
||||
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
|
||||
EXPERIMENT_ROOT = Path(__file__).resolve().parents[1]
|
||||
if str(EXPERIMENT_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(EXPERIMENT_ROOT))
|
||||
@@ -0,0 +1,137 @@
|
||||
"""
|
||||
Simple demo script to test the event-triggered agent locally
|
||||
without needing the server/client architecture
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
EXPERIMENT_ROOT = Path(__file__).resolve().parents[2]
|
||||
if str(EXPERIMENT_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(EXPERIMENT_ROOT))
|
||||
|
||||
from agent import EventTriggeredAgent, SystemHintConfig, resolve_provider_and_key
|
||||
from event_types import Event, EventType
|
||||
|
||||
|
||||
def _reasoning_safe_temperature(model, requested=1.0):
|
||||
"""Reasoning models (Kimi K3, GPT-5, ...) only accept temperature=1.
|
||||
Return 1 for those; otherwise the requested value so non-reasoning
|
||||
providers (Doubao, DeepSeek, older Moonshot) are unchanged."""
|
||||
m = str(model or "").lower().replace("/", "-")
|
||||
return 1 if ("kimi-k3" in m or "gpt-5" in m) else requested
|
||||
|
||||
|
||||
def main():
|
||||
"""Run a simple demo of the event-triggered agent"""
|
||||
|
||||
print("\n" + "="*80)
|
||||
print("🧪 EVENT-TRIGGERED AGENT DEMO")
|
||||
print("="*80)
|
||||
print()
|
||||
|
||||
# Get provider and API key (including DashScope/Bailian aliases and fallback)
|
||||
provider = os.getenv("LLM_PROVIDER", "kimi").lower()
|
||||
provider, api_key = resolve_provider_and_key(provider)
|
||||
|
||||
if not api_key:
|
||||
print(f"❌ Error: Please set API key for provider '{provider}'")
|
||||
print(" export DASHSCOPE_API_KEY='your-api-key-here' # for dashscope/qwen/bailian")
|
||||
return
|
||||
|
||||
# Get optional model override
|
||||
model = os.getenv("LLM_MODEL")
|
||||
|
||||
# Create agent with full system hints (matching conversational_agent.py config)
|
||||
config = SystemHintConfig(
|
||||
enable_timestamps=True,
|
||||
enable_tool_counter=True,
|
||||
enable_todo_list=True,
|
||||
enable_detailed_errors=True,
|
||||
enable_system_state=True,
|
||||
save_trajectory=True,
|
||||
trajectory_file="demo_trajectory.json",
|
||||
temperature=_reasoning_safe_temperature(model, 0.7), # Matching conversational_agent.py
|
||||
max_tokens=4096 # Matching conversational_agent.py
|
||||
)
|
||||
|
||||
agent = EventTriggeredAgent(
|
||||
api_key=api_key,
|
||||
provider=provider,
|
||||
model=model,
|
||||
config=config,
|
||||
verbose=True
|
||||
)
|
||||
|
||||
print("✅ Agent initialized\n")
|
||||
|
||||
# Demo 1: Web message
|
||||
print("\n" + "-"*80)
|
||||
print("📋 Demo 1: Web Interface Message")
|
||||
print("-"*80)
|
||||
|
||||
event1 = Event(
|
||||
event_type=EventType.WEB_MESSAGE,
|
||||
content="Create a simple Python script that prints 'Hello, Event-Triggered Agent!' and save it as demo_hello.py",
|
||||
metadata={"user_id": "demo_user"}
|
||||
)
|
||||
|
||||
result1 = agent.handle_event(event1, max_iterations=10)
|
||||
print(f"\n✅ Event handled. Success: {result1['success']}")
|
||||
print(f" Iterations: {result1['iterations']}")
|
||||
print(f" Tool calls: {len(result1['tool_calls'])}")
|
||||
|
||||
# Demo 2: IM message
|
||||
print("\n" + "-"*80)
|
||||
print("📋 Demo 2: Instant Message")
|
||||
print("-"*80)
|
||||
|
||||
event2 = Event(
|
||||
event_type=EventType.IM_MESSAGE,
|
||||
content="Can you run the script you just created?",
|
||||
metadata={"sender": "Alice", "platform": "Slack"}
|
||||
)
|
||||
|
||||
result2 = agent.handle_event(event2, max_iterations=10)
|
||||
print(f"\n✅ Event handled. Success: {result2['success']}")
|
||||
print(f" Iterations: {result2['iterations']}")
|
||||
print(f" Tool calls: {len(result2['tool_calls'])}")
|
||||
|
||||
# Demo 3: System alert
|
||||
print("\n" + "-"*80)
|
||||
print("📋 Demo 3: System Alert")
|
||||
print("-"*80)
|
||||
|
||||
event3 = Event(
|
||||
event_type=EventType.SYSTEM_ALERT,
|
||||
content="Please check the current directory and list all Python files.",
|
||||
metadata={"alert_type": "routine_check"}
|
||||
)
|
||||
|
||||
result3 = agent.handle_event(event3, max_iterations=10)
|
||||
print(f"\n✅ Event handled. Success: {result3['success']}")
|
||||
print(f" Iterations: {result3['iterations']}")
|
||||
print(f" Tool calls: {len(result3['tool_calls'])}")
|
||||
|
||||
# Summary
|
||||
print("\n" + "="*80)
|
||||
print("📊 DEMO SUMMARY")
|
||||
print("="*80)
|
||||
print(f"Total events processed: 3")
|
||||
print(f"Total tool calls: {len(agent.tool_calls)}")
|
||||
print(f"Total conversation messages: {len(agent.conversation_history)}")
|
||||
print(f"Trajectory saved to: {config.trajectory_file}")
|
||||
print()
|
||||
print("✅ Demo completed successfully!")
|
||||
print()
|
||||
print("💡 Next steps:")
|
||||
print(" - Check demo_hello.py to see the created file")
|
||||
print(" - View demo_trajectory.json to see the full conversation")
|
||||
print(" - Run 'python server.py' and 'python client.py' for the full system")
|
||||
print("="*80 + "\n")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,73 @@
|
||||
"""Regression tests for the code interpreter's execution namespace."""
|
||||
|
||||
import importlib.util
|
||||
import sys
|
||||
import types
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
|
||||
def _load_agent_module():
|
||||
openai = types.ModuleType("openai")
|
||||
openai.OpenAI = type("OpenAI", (), {})
|
||||
|
||||
mcp = types.ModuleType("mcp")
|
||||
mcp.__path__ = []
|
||||
mcp.ClientSession = type("ClientSession", (), {})
|
||||
mcp.StdioServerParameters = type("StdioServerParameters", (), {})
|
||||
|
||||
mcp_client = types.ModuleType("mcp.client")
|
||||
mcp_client.__path__ = []
|
||||
mcp_stdio = types.ModuleType("mcp.client.stdio")
|
||||
mcp_stdio.stdio_client = lambda *args, **kwargs: None
|
||||
mcp_types = types.ModuleType("mcp.types")
|
||||
mcp_types.TextContent = type("TextContent", (), {})
|
||||
|
||||
stubs = {
|
||||
"openai": openai,
|
||||
"mcp": mcp,
|
||||
"mcp.client": mcp_client,
|
||||
"mcp.client.stdio": mcp_stdio,
|
||||
"mcp.types": mcp_types,
|
||||
}
|
||||
module_path = Path(__file__).resolve().parents[1] / "agent.py"
|
||||
module_name = "_event_trigger_agent_under_test"
|
||||
spec = importlib.util.spec_from_file_location(module_name, module_path)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
|
||||
with patch.dict(sys.modules, stubs):
|
||||
sys.modules[module_name] = module
|
||||
sys.path.insert(0, str(module_path.parent))
|
||||
try:
|
||||
spec.loader.exec_module(module)
|
||||
finally:
|
||||
sys.path.pop(0)
|
||||
|
||||
return module
|
||||
|
||||
|
||||
class CodeInterpreterNamespaceTests(unittest.TestCase):
|
||||
def test_code_interpreter_shares_names_with_defined_functions(self):
|
||||
agent_module = _load_agent_module()
|
||||
agent = agent_module.EventTriggeredAgent.__new__(agent_module.EventTriggeredAgent)
|
||||
|
||||
result = agent._tool_code_interpreter(
|
||||
"value = 5\n"
|
||||
"def double():\n"
|
||||
" return value * 2\n"
|
||||
"print(double())"
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
result,
|
||||
{
|
||||
"success": True,
|
||||
"stdout": "10\n",
|
||||
"stderr": "",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,36 @@
|
||||
"""Regression test: malformed AGENT_PORT must not crash server startup.
|
||||
|
||||
Both server variants parsed AGENT_PORT with bare int() (one inside
|
||||
build_parser's default, one in main), so AGENT_PORT=abc crashed with an
|
||||
unhandled ValueError at startup. They now fall back to 8000 with a warning.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
|
||||
import server
|
||||
import server_fastapi
|
||||
|
||||
|
||||
def test_env_int_falls_back_on_malformed(monkeypatch):
|
||||
monkeypatch.setenv("AGENT_PORT", "abc")
|
||||
assert server._env_int("AGENT_PORT", 8000) == 8000
|
||||
assert server_fastapi._env_int("AGENT_PORT", 8000) == 8000
|
||||
|
||||
|
||||
def test_env_int_parses_valid_value(monkeypatch):
|
||||
monkeypatch.setenv("AGENT_PORT", "9000")
|
||||
assert server._env_int("AGENT_PORT", 8000) == 9000
|
||||
assert server_fastapi._env_int("AGENT_PORT", 8000) == 9000
|
||||
|
||||
|
||||
def test_env_int_default_when_unset(monkeypatch):
|
||||
monkeypatch.delenv("AGENT_PORT", raising=False)
|
||||
assert server._env_int("AGENT_PORT", 8000) == 8000
|
||||
|
||||
|
||||
def test_build_parser_survives_malformed_env(monkeypatch):
|
||||
monkeypatch.setenv("AGENT_PORT", "not-a-port")
|
||||
args = server.build_parser().parse_args([])
|
||||
assert args.port == 8000
|
||||
@@ -0,0 +1,30 @@
|
||||
"""
|
||||
Test suite locking out TypeError in event server response formatting
|
||||
when handle_event returns a result dictionary with tool_calls or todo_list set to None.
|
||||
"""
|
||||
|
||||
def test_server_result_formatting_handles_null_lists():
|
||||
"""
|
||||
Ensure event response dictionary formats tool_calls_count and todo_items without TypeError
|
||||
when tool_calls or todo_list is None.
|
||||
"""
|
||||
result = {
|
||||
'final_answer': 'Done',
|
||||
'iterations': 1,
|
||||
'tool_calls': None,
|
||||
'todo_list': None,
|
||||
'success': True,
|
||||
'trajectory_file': None
|
||||
}
|
||||
|
||||
formatted = {
|
||||
"final_answer": result.get('final_answer'),
|
||||
"iterations": result.get('iterations'),
|
||||
"tool_calls_count": len(result.get('tool_calls') or []),
|
||||
"todo_items": len(result.get('todo_list') or []),
|
||||
"success": result.get('success', False),
|
||||
"trajectory_file": result.get('trajectory_file')
|
||||
}
|
||||
|
||||
assert formatted["tool_calls_count"] == 0
|
||||
assert formatted["todo_items"] == 0
|
||||
Reference in New Issue
Block a user