fix: 修复 NameError/状态污染/类型标注/统计; 补全练习与 main; 新增 config/.gitignore/requirements; 文档统一

This commit is contained in:
agent
2026-06-02 13:44:46 +08:00
parent ef876a22d1
commit 908431e25f
23 changed files with 919 additions and 77 deletions
+5 -9
View File
@@ -100,15 +100,6 @@ class SimpleAgentState(TypedDict, total=False):
# 第三部分:定义 Tool 接口
# ═══════════════════════════════════════════════════════════════════════════════
@dataclass
class ToolCall:
"""工具调用的数据结构"""
name: str # 工具名称
arguments: dict # 传递给工具的参数
result: Any = None # 工具执行结果
error: str = None # 错误信息
class BaseTool(ABC):
"""
工具基类(简化版,来自 Step 01)
@@ -390,6 +381,9 @@ class SimpleAgent:
status="input"
)
# 对话轮次计数器
self.round_count = 0
def reset(self):
"""重置 Agent 状态"""
self.state = SimpleAgentState(
@@ -454,6 +448,8 @@ class SimpleAgent:
"content": user_input
})
self.state["status"] = "thinking"
self.state["tool_result"] = None
self.round_count += 1
# 2. 大脑决定行动
decision = self.brain.decide(self.state)
+87
View File
@@ -0,0 +1,87 @@
"""
Step 03 练习题:扩展 SimpleAgent
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🎯 练习目标:
1. 巩固 Agent 循环的运行机制
2. 增强 Brain 的决策能力
3. 体验 Tool 的注册流程
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
"""
# ═══════════════════════════════════════════════════════════════════════════════
# 练习 1:实现 DateTimeTool 并注册到 Agent
# ═══════════════════════════════════════════════════════════════════════════════
"""
任务:
在 SimpleAgent 中添加一个 DateTimeTool,提供「现在几点 / 今天日期」能力。
要求:
1. 继承 BaseTool
2. name = "datetime"description 描述清楚能做什么
3. execute(**kwargs) 接收 operation,支持:
- "now" -> 返回当前时间字符串("%Y-%m-%d %H:%M:%S"
- "today" -> 返回当前日期字符串("%Y-%m-%d"
- "weekday" -> 返回今天是星期几(中文,如 "星期一"
4. 注册到 SimpleAgent.tools 字典中
5. 测试:用户输入「现在几点」时 Brain 能正确选择 datetime 工具
"""
# ═══════════════════════════════════════════════════════════════════════════════
# 练习 2:改进 Brain 的工具匹配
# ═══════════════════════════════════════════════════════════════════════════════
"""
任务:
当前 AgentBrain.decide() 用「关键词 + 表达式正则」匹配 calculator
对「iOS / Android 兼容性」「产品 A+」这种文本会误判。
要求:
1. 在 AgentBrain.decide() 中加入你新加的 DateTimeTool 的路由
2. 修复 calculator 匹配的脆弱性(例如优先匹配明确的算式语法)
3. 让 Brain 在没有工具可调时返回 {"action": "respond", "response": "..."}
提示:
- 可用正则在 user_input 中提取首个形如「数字 运算符 数字」的子串
- 用 keyword in user_input 检测「现在」「今天」「星期」触发 datetime
"""
# ═══════════════════════════════════════════════════════════════════════════════
# 练习 3:让 Agent 暴露对话快照
# ═══════════════════════════════════════════════════════════════════════════════
"""
任务:
给 SimpleAgent 增加 snapshot()/restore(snap) 方法,用于保存和恢复会话。
要求:
1. snapshot() 返回 dict,包含 messages、tool_result、current_action
2. restore(snap) 用 snap 覆盖对应字段
3. 验证:snapshot -> 多轮对话 -> restore -> 状态回到 snapshot 时刻
提示:
- copy.deepcopy() 避免引用共享
- 只恢复可序列化的字段,不要把 self.brain / self.tools 一起覆盖
"""
# ═══════════════════════════════════════════════════════════════════════════════
# 测试
# ═══════════════════════════════════════════════════════════════════════════════
def test_exercises():
from step_03_simple_agent.concept import SimpleAgent
agent = SimpleAgent()
print("当前已注册工具:", list(agent.tools.keys()))
# TODO: 你的测试
if __name__ == "__main__":
test_exercises()
+160
View File
@@ -0,0 +1,160 @@
"""
Step 03 练习题答案
⚠️ 先自己思考,再看答案!
⚠️ 答案不是唯一的,这里只是其中一种实现
"""
import copy
import re
from datetime import datetime
from step_03_simple_agent.concept import (
BaseTool,
SimpleAgent,
ToolResult,
)
# ═══════════════════════════════════════════════════════════════════════════════
# 练习 1 答案:DateTimeTool
# ═══════════════════════════════════════════════════════════════════════════════
class DateTimeTool(BaseTool):
"""日期时间工具"""
@property
def name(self) -> str:
return "datetime"
@property
def description(self) -> str:
return "日期时间工具,支持 now(当前时间)/ today(今天日期)/ weekday(星期几)"
def execute(self, **kwargs) -> ToolResult:
operation = kwargs.get("operation", "now")
now = datetime.now()
if operation == "now":
return ToolResult(success=True, result=now.strftime("%Y-%m-%d %H:%M:%S"))
if operation == "today":
return ToolResult(success=True, result=now.strftime("%Y-%m-%d"))
if operation == "weekday":
names = ["星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"]
return ToolResult(success=True, result=names[now.weekday()])
return ToolResult(success=False, error=f"不支持的操作: {operation}")
# ═══════════════════════════════════════════════════════════════════════════════
# 练习 2 答案:稳健的 Brain 路由
# ═══════════════════════════════════════════════════════════════════════════════
EXPR_PATTERN = re.compile(r"-?\d+(?:\.\d+)?\s*[+\-*/]\s*-?\d+(?:\.\d+)?")
def improved_brain_decide(self, state):
"""把 AgentBrain.decide 替换为更稳健的版本。"""
user_input = state.get("user_input", "")
tool_result = state.get("tool_result")
# 工具结果回灌:直接产出回应
if tool_result is not None:
if isinstance(tool_result, dict) and not tool_result.get("success", True):
return {
"action": "respond",
"response": f"工具执行失败: {tool_result.get('error', '未知错误')}",
}
return {"action": "respond", "response": f"工具执行完成,结果:{tool_result}"}
text = user_input.lower()
# 明确的算式(如 "1 + 2" / "10*3")才走 calculator
expr_match = EXPR_PATTERN.search(user_input)
if expr_match and re.fullmatch(r"[\d\s+\-*/().]+", expr_match.group()):
return {
"action": "use_tool",
"tool_name": "calculator",
"tool_args": {"expression": expr_match.group().strip()},
}
# 日期时间路由
if any(kw in text for kw in ["现在几点", "现在时间", "今天", "日期", "星期"]):
op = "weekday" if "星期" in text else ("today" if "今天" in text or "日期" in text else "now")
return {"action": "use_tool", "tool_name": "datetime", "tool_args": {"operation": op}}
# 其他交给模板搜索
if any(kw in user_input for kw in ["报表", "模板", "jrxml", "jasper"]):
return {
"action": "use_tool",
"tool_name": "template_search",
"tool_args": {"keyword": user_input},
}
return {
"action": "respond",
"response": f"我收到了你的输入:{user_input}(暂无可用工具直接处理)",
}
# ═══════════════════════════════════════════════════════════════════════════════
# 练习 3 答案:snapshot / restore
# ═══════════════════════════════════════════════════════════════════════════════
def install_snapshot_methods(agent: SimpleAgent) -> None:
"""给 SimpleAgent 实例挂上 snapshot/restore 方法。"""
def snapshot(self):
return {
"messages": copy.deepcopy(self.state["messages"]),
"tool_result": copy.deepcopy(self.state.get("tool_result")),
"current_action": self.state.get("current_action"),
"round_count": self.round_count,
}
def restore(self, snap):
self.state["messages"] = copy.deepcopy(snap["messages"])
self.state["tool_result"] = copy.deepcopy(snap.get("tool_result"))
self.state["current_action"] = snap.get("current_action")
self.round_count = snap.get("round_count", 0)
SimpleAgent.snapshot = snapshot
SimpleAgent.restore = restore
# ═══════════════════════════════════════════════════════════════════════════════
# 测试
# ═══════════════════════════════════════════════════════════════════════════════
def test_answers():
print("\n" + "=" * 60)
print("Step 03 练习答案测试")
print("=" * 60)
agent = SimpleAgent()
# 注册新工具 + 替换 brain
agent.tools["datetime"] = DateTimeTool()
agent.brain.decide = improved_brain_decide.__get__(agent.brain)
print("\n📝 练习 1: DateTimeTool")
print(" 工具列表:", list(agent.tools.keys()))
print(" now ->", agent.tools["datetime"].execute(operation="now").result)
print(" weekday ->", agent.tools["datetime"].execute(operation="weekday").result)
print("\n📝 练习 2: 改进的 Brain 路由")
for q in ["1 + 2", "iOS / Android 兼容性", "现在几点", "今天星期几"]:
decision = agent.brain.decide({"user_input": q, "tool_result": None})
print(f" '{q}' -> {decision['action']} / {decision.get('tool_name', decision.get('response'))}")
print("\n📝 练习 3: snapshot / restore")
install_snapshot_methods(agent)
agent.process("1 + 2")
snap = agent.snapshot()
print(" snapshot round_count =", snap["round_count"])
agent.process("3 * 4")
print(" after 2 rounds, round_count =", agent.round_count)
agent.restore(snap)
print(" after restore, round_count =", agent.round_count)
if __name__ == "__main__":
test_answers()
+1 -1
View File
@@ -67,7 +67,7 @@ def main():
print("\n\n" + "=" * 70)
print("📊 会话统计")
print("=" * 70)
print(f" 对话轮次: {len(agent.get_history()) // 2}")
print(f" 对话轮次: {agent.round_count}")
print(f" 工具调用: {len(agent.state.get('tool_calls', []))}")
print("\n💡 继续学习:")
print(" Step 04: 添加 Memory - 记忆系统")