🐍 Day06 Practice Template
📄 文件:
⬇ 下载 .py 文件day06_practice_template.py"""
Day 6 练习:不依赖框架,纯 Python 手写一个 ReAct Agent 循环
验收标准:
1. 必须实现 ReAct 循环(Reasoning + Acting 交替)
2. 循环必须有 max_steps 上限(防止无限循环)
3. 必须支持工具调用(至少 2 个工具)
4. 必须使用 async/await(异步 LLM 调用)
5. 必须正确解析 LLM 的 Thought/Action/Action Input/Observation 格式
6. 最终输出 Final Answer
"""
import asyncio
import json
import re
import functools
from typing import List, Dict, Any, Optional, Callable, Tuple
from dataclasses import dataclass, field
# ==================== 数据结构 ====================
@dataclass
class ToolResult:
"""工具调用结果"""
tool_name: str
tool_input: str
output: str
success: bool
@dataclass
class AgentStep:
"""Agent 单步执行记录"""
thought: str = ""
action: str = ""
action_input: str = ""
observation: str = ""
full_response: str = ""
# ==================== 任务 1:实现工具注册和调用 ====================
# 全局工具注册表
TOOLS_REGISTRY: Dict[str, Dict[str, Any]] = {}
def tool(name: str, description: str):
"""工具注册装饰器"""
def decorator(func: Callable):
@functools.wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
TOOLS_REGISTRY[name] = {
"function": wrapper,
"description": description,
"parameters": func.__code__.co_varnames[:func.__code__.co_argcount]
}
return wrapper
return decorator
# TODO 1.1:注册至少 2 个工具
# 提示:参考 Day 2 的写法,定义计算器工具和天气查询工具
@tool("Calculator", "执行四则运算,例如:Calculator[3 + 5 * 2]")
def calculator(expression: str) -> str:
"""计算器工具"""
try:
result = eval(expression, {"__builtins__": {}}, {})
return str(result)
except Exception as e:
return f"错误: {e}"
@tool("Weather", "查询城市天气,例如:Weather[北京]")
def weather(city: str) -> str:
"""天气查询工具(模拟)"""
# 模拟天气数据
天气数据 = {
"北京": "晴,25°C",
"上海": "多云,22°C",
"深圳": "雨,28°C",
}
return 天气数据.get(city, f"{city} 的天气数据未找到")
def get_tools_description() -> str:
"""生成工具描述文本(放入 System Prompt)"""
# TODO 1.2:实现工具描述生成
# 格式:
# Calculator[参数]: 描述
# Weather[参数]: 描述
pass
def execute_tool(tool_name: str, tool_input: str) -> ToolResult:
"""执行工具调用"""
# TODO 1.3:实现工具执行逻辑
# 1. 从 TOOLS_REGISTRY 查找工具
# 2. 调用工具的 "function"
# 3. 返回 ToolResult
pass
# ==================== 任务 2:实现 ReAct Prompt 构建 ====================
def build_react_prompt(user_input: str, steps: List[AgentStep]) -> str:
"""
构建 ReAct 格式的 Prompt
ReAct 格式:
Question: [用户问题]
Thought: [推理过程]
Action: [工具名]
Action Input: [工具输入]
Observation: [工具返回结果]
...(可重复多轮)
Thought: [最终推理]
Final Answer: [最终答案]
"""
# TODO 2.1:实现 ReAct Prompt 构建
# 提示:
# 1. System Prompt 包含工具描述和 ReAct 格式说明
# 2. 把历史 steps 格式化为 Thought/Action/Observation 序列
# 3. 最后加上当前问题
pass
# ==================== 任务 3:实现 ReAct 格式解析 ====================
def parse_react_response(response: str) -> Tuple[str, str, str]:
"""
解析 LLM 返回的 ReAct 格式文本
返回:(thought, action, action_input)
- 如果包含 "Final Answer:",说明是最终回答,action 为空
- 否则解析 Action 和 Action Input
示例响应:
Thought: 我需要计算 3 + 5 的值
Action: Calculator
Action Input: 3 + 5 * 2
"""
# TODO 3.1:实现 ReAct 响应解析
# 提示:
# 1. 用正则或字符串查找提取 Thought、Action、Action Input
# 2. 检查是否包含 "Final Answer"(区分大小写)
# 3. 返回 (thought, action, action_input)
# 如果 action 为空,说明是最终回答,此时 action_input 存最终答案
pass
# ==================== 任务 4:实现 ReAct Agent 主循环 ====================
async def react_agent(
user_input: str,
max_steps: int = 10,
verbose: bool = True,
) -> str:
"""
纯 Python 实现的 ReAct Agent 主循环
流程:
1. 初始化 steps = []
2. for step in range(max_steps):
3. 构建 prompt(build_react_prompt)
4. 调用 LLM(模拟或真实 API)
5. 解析响应(parse_react_response)
6. 如果是 Final Answer → 返回结果,结束
7. 否则执行工具(execute_tool)
8. 把结果加入 steps,继续循环
9. 如果超过 max_steps → 返回错误信息
"""
steps: List[AgentStep] = []
# TODO 4.1:实现 ReAct Agent 主循环
# 提示:这里是整个 Agent 的核心!
# 注意:LLM 调用可以用模拟函数(见下方 mock_llm_call)
pass
# ==================== 模拟 LLM(用于测试,不依赖真实 API) ====================
async def mock_llm_call(prompt: str) -> str:
"""
模拟 LLM 调用(用于本地测试,不消耗 API 额度)
根据 prompt 的内容,返回一个符合 ReAct 格式的模拟响应
用于测试 ReAct 循环逻辑是否正确
"""
# 检测是否包含计算相关问题
if "计算" in prompt or "加" in prompt or "减" in prompt or "+" in prompt:
return """Thought: 我需要计算这个表达式的结果
Action: Calculator
Action Input: 3 + 5 * 2"""
if "天气" in prompt:
return """Thought: 我需要查询北京的天气
Action: Weather
Action Input: 北京"""
# 默认最终回答
return """Thought: 我已经通过工具调用获得了所需信息
Final Answer: 根据查询结果,答案是 13。"""
# ==================== 任务 5:用真实 LLM API 替换 mock ====================
# (可选,有 API key 再做;先用 mock 把逻辑跑通)
async def real_llm_call(prompt: str, api_key: str = "") -> str:
"""
真实 LLM API 调用(OpenAI 兼容格式)
支持:DeepSeek、Qwen、Moonshot 等
"""
# TODO 5.1:实现真实 LLM 调用
# 提示:
# 1. 用 aiohttp 或 httpx 发送异步 HTTP 请求
# 2. API 格式兼容 OpenAI:
# POST https://api.deepseek.com/chat/completions
# Headers: {"Authorization": f"Bearer {api_key}"}
# Body: {"model": "...", "messages": [{"role": "user", "content": prompt}]}
# 3. 返回 response.json()["choices"][0]["message"]["content"]
pass
# ==================== 主函数:测试 Agent ====================
async def main():
print("=" * 60)
print("Day 6 练习:纯 Python 手写 ReAct Agent")
print("=" * 60)
# 测试用例 1:计算问题
print("\n📝 测试 1:计算问题")
print("用户:帮我计算 3 + 5 * 2 等于多少?")
答案 = await react_agent(
user_input="帮我计算 3 + 5 * 2 等于多少?",
max_steps=5,
verbose=True,
)
print(f"\n✅ Agent 最终回答:{答案}")
# 测试用例 2:天气问题
print("\n\n📝 测试 2:天气查询")
print("用户:北京今天天气怎么样?")
答案 = await react_agent(
user_input="北京今天天气怎么样?",
max_steps=5,
verbose=True,
)
print(f"\n✅ Agent 最终回答:{答案}")
print("\n" + "=" * 60)
print("✅ 所有测试完成!现在运行 python day06_practice_validator.py 验收")
print("=" * 60)
if __name__ == "__main__":
asyncio.run(main())