1. 晨会与导师的亲切指导
今天一早,导师 Alex 拿着咖啡笑眯开心地走到我的工位前。他不仅技术极其扎实,而且特别亲切耐心,完全没有架构师的架子。
"昨天弄懂了 Tool 和 SQL 查库,感觉怎么样?" Alex 笑着问。
我有点不好意思地回答:"工具是写好了,但我还是好奇,模型是怎么知道'先去查数据库,拿到结果后再决定接下来干嘛'的?它难道懂逻辑顺序吗?"
Alex 拍了拍我的肩膀:"问到点子上了!今天我们就来啃核心------ReAct 推理引擎 。别被这个英文名词吓到,它其实非常符合我们人类解决问题的直觉。今天我手把手带你把 core/react_engine.py 的底层原理给拆明白!"
2. 今日补课:Alex 导师拆解 ReAct 机制
Alex 拿过马克笔,在白板上耐心地画出了流程图,给我详细讲解了 ReAct(Reasoning + Acting) 框架。
"传统的代码是线性执行的,而 ReAct 是让大模型像人类思考一样,形成一个闭环循环(Loop)。" Alex 边画边解释,"它核心就分为三步,一直循环到得出最终答案为止:"
┌────────────────────────┐
│ 用户提出复杂问题 │
└───────────┬────────────┘
│
┌────────────────────▼────────────────────┐
│ 1. Thought (思考) │
│ 大模型分析当前上下文,决定下一步做什么 │
└────────────────────┬────────────────────┘
│
┌────────────────────▼────────────────────┐
│ 2. Action (行动) │
│ 大模型决定调用哪个 Tool,并输出 JSON 参数│
└────────────────────┬────────────────────┘
│
┌────────────────────▼────────────────────┐
│ 3. Observation (观察) │
│ 代码执行 Tool,将真实结果反馈给大模型 │
└────────────────────┬────────────────────┘
│
├─► [如果问题未解决] ──► 回到 Step 1 继续循环
│
└─► [如果问题已解决] ──► 输出 Final Answer 给用户
-
Thought(思考):大模型结合当前的用户输入和历史记录,在心里默默推演:"我现在知道了什么?我还缺少什么信息?我下一步应该调用什么工具?"
-
Action(行动) :根据思考结果,决定具体的动作(例如调用
get_order_status工具,并给出对应参数)。 -
Observation(观察):我们的 Python 代码拿到模型的 Action,真正去跑函数(如查询数据库),然后把运行结果以自然语言或 JSON 的形式喂回(Feed Back)给大模型。
"看明白了么?" Alex 笑着问,"重点就在于 Observation 会被追加到 Prompt 的上下文里,模型拿到新信息后,再发起下一轮 Thought。这就是'思考-行动-观察'循环。"
3. 今日动手实操:从零手写一个极简 ReAct 引擎
听完 Alex 的讲解,我恍然大悟。Alex 鼓励我:"来,抛开 LangChain 这种重型框架,我们今天直接用原生 Python 手写一个简易的 react_engine.py!"
在 Alex 的指导下,我写下了如下代码,并加入了极简的思维链循环:
import json
import os
from openai import OpenAI
from tools.order_tools import get_order_status # 昨天写的真实查库工具
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
# 1. 注册可用工具映射表
AVAILABLE_TOOLS = {
"get_order_status": get_order_status
}
# 2. 系统 Prompt:强制要求模型按照 ReAct 格式输出
SYSTEM_PROMPT = """
你是一个严谨的 AI 助手。你必须严格按照以下格式进行思考和行动:
Question: 用户提出的问题
Thought: 你对当前状况的思考,分析下一步该做什么
Action: 需要调用的工具名称,必须是 [{tool_names}] 中的一个
Action Input: 传递给工具的 JSON 格式参数
Observation: 工具执行后返回的结果(这部分由系统自动填入,你不需要生成)
当你获得了足够的信息并能够回答用户问题时,请输出:
Thought: 我知道了最终答案
Final Answer: 给用户的最终回答
开始!
""".format(tool_names=", ".join(AVAILABLE_TOOLS.keys()))
def run_react_agent(user_query: str, max_turns: int = 5):
"""手写 ReAct 循环引擎"""
# 维持上下文的历史消息队列
messages = [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": f"Question: {user_query}"}
]
print(f"🤖 Agent 开始处理问题: '{user_query}'\n" + "="*40)
for turn in range(max_turns):
print(f"\n--- 🔄 第 {turn + 1} 轮循环 ---")
# 请求大模型
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
stop=["Observation:"] # 遇到 Observation: 停止生成,等待代码执行工具
)
llm_output = response.choices[0].message.content
print(llm_output)
# 将模型的输出追加进上下文
messages.append({"role": "assistant", "content": llm_output})
# 如果模型输出了 Final Answer,说明流程结束,直接退出循环
if "Final Answer:" in llm_output:
print("\n✅ 任务完成!")
break
# 解析 Action 和 Action Input
if "Action:" in llm_output and "Action Input:" in llm_output:
try:
action_line = [line for line in llm_output.split("\n") if line.startswith("Action:")][0]
action_input_line = [line for line in llm_output.split("\n") if line.startswith("Action Input:")][0]
tool_name = action_line.replace("Action:", "").strip()
tool_args = json.loads(action_input_line.replace("Action Input:", "").strip())
# 执行真正工具代码 (Observation)
if tool_name in AVAILABLE_TOOLS:
observation_result = AVAILABLE_TOOLS[tool_name](**tool_args)
else:
observation_result = f"错误:未找到名称为 {tool_name} 的工具"
observation_text = f"Observation: {json.dumps(observation_result, ensure_ascii=False)}"
print(f"⚙️ 执行工具结果 -> {observation_text}")
# 把 Observation 结果追加回消息列表中,供下一轮思考使用
messages.append({"role": "user", "content": observation_text})
except Exception as e:
error_msg = f"Observation: 工具解析或执行失败 - {str(e)}"
messages.append({"role": "user", "content": error_msg})
4. 运行测试与踩坑复盘
代码写完后,我输入测试问题:"请帮我看看订单 ORD-12345678 到哪了,顺便告诉我快递单号。"
控制台清晰地打印出了 Agent 的完整"思考脉络":
🤖 Agent 开始处理问题
--- 🔄 第 1 轮循环 ---
Thought: 用户想查询订单 ORD-12345678 的状态和快递单号,我需要调用订单查询工具获取数据。
Action :
get_order_statusAction Input :
{"order_id": "ORD-12345678"}⚙️ 执行工具结果 \\rightarrow
Observation: {"order_id": "ORD-12345678", "status": "已发货", "carrier": "顺丰速运", "tracking_number": "SF100123"}--- 🔄 第 2 轮循环 ---
Thought: 我已经收到了数据库返回的信息,订单状态是已发货,承运商是顺丰速运,运单号是 SF100123。我拥有足够的信息回答用户了。
Final Answer: 您的订单(ORD-12345678)目前状态为【已发货】,由顺丰速运承运,快递单号为 SF100123。
今日踩坑点:
初试时我忘记加 stop=["Observation:"] 截断参数,导致大模型在输出 Action Input 之后,竟然自己凭空编造了一个假的 Observation 结果往下自嗨!
Alex 看到后笑着解释:"这就是典型的幻觉 ,因为 LLM 本质是文本接龙。你必须用 stop 参数在 Observation 处强制切断,让 Python 运行真正的 SQL 函数把客观事实填进来。"
5. 今日复盘与收获
-
原理通透 :终于明白所谓的 ReAct Agent 并不是黑盒魔法,本质上就是 While 循环 + 上下文追加 + 格式化 Prompt 约束。
-
导师指导感悟 :Alex 导师真非常耐心地帮我排查了
stop参数的细节,强调了不能盲目相信 LangChain 等封装好的框架,动手写底层循环才能真正打牢 Agent 开发的基本功。