用 LangChain 零微调构建 AI Agent:从 Self Ask 到 ReAct
前面聊过思维链(Chain of Thought)的用法和原理,现在该进正题了——把思维链和工具结合起来,让模型不只是动嘴,还能动手。这就是 AI Agent。
AI Agent 能干吗?换个角度看:
- 把 LLM 当成大脑,Agent 就是它的手脚和感官。感官,用来获取真实世界信息:实时数据(天气、股价)、私有数据(用户资料)、多模态输入(图像、声音);手脚,用来和外部交互:跑段 Python、调搜索引擎、订个机票。
- 反过来,有了 Agent,大模型成了更高效的中介,帮我们处理各种非结构化数据。以前重度依赖结构化查询,现在很多场景可以靠模型自己消化了。
OpenAI 的 Lilian Weng 把 AI Agent 拆成三块:规划、工具、记忆。规划负责拆解问题形成执行路径;工具决定能调什么、怎么调;记忆分短期(工具返回值、已完成的推理)和长期(外部知识库)。
接下来,我结合 LangChain 聊聊两种零微调方案:Self Ask 和 ReAct。不涉及模型训练,仅靠 few-shot 或 zero-shot prompt 驱动推理和工具调用。
Self Ask
Self Ask 的思路很直接:让模型自己问自己。每次把问题拆成更小的子问题,调用搜索工具拿到中间答案,然后继续追问,直到得到最终结果。重点不在'提问'本身,而是引导模型拆分问题——也就是规划能力。
原理
论文里提到了 Compositionality Gap:模型能正确回答'贾斯汀·比伯哪年出生?'也能回答'94 年大师赛冠军是谁?',但被问到'比伯出生那年的大师赛冠军是谁?'就歇菜。通过引入拆解式推理,这个问题就解决了。
应用
LangChain 的 Self Ask 实现,我用了 Google 搜索和 GPT-3.5。关键代码片段:
import os
from langchain.agents.loading import AGENT_TO_CLASS
from langchain.agents.agent import AgentExecutor
from langchain.agents import AgentType, Tool
from langchain import OpenAI, SerpAPIWrapper
# 定义大模型和搜索工具
llm = OpenAI(temperature=0, openai_api_key="你的 Key")
search = SerpAPIWrapper(params={
"engine": "google",
"gl": "us",
"hl": "zh-cn",
}, serpapi_api_key="你的 Key")
tools = [
Tool(
name="Intermediate Answer",
func=search.run,
description="useful for when you need to ask with search"
)
]
agent_cls = AGENT_TO_CLASS[AgentType.SELF_ask_with_search]
agent = agent_cls.from_llm_and_tools(llm, tools)
chain = AgentExecutor.from_agent_and_tools(agent, tools, return_intermediate_steps=True)
不同 Agent 类型的差异主要在 few-shot prompt 和对应的解析器。Self Ask 的 few-shot 长这样,既定义了输出格式,也示范了拆解套路:
Question: Who lived longer, Muhammad Ali or Alan Turing? Are follow up questions needed here: Yes. Follow up: How old was Muhammad Ali when he died? Intermediate answer: Muhammad Ali was 74 years old when he died. Follow up: How old was Alan Turing when he died? Intermediate answer: Alan Turing was 41 years old when he died. So the final answer is: Muhammad Ali
Question: When was the founder of craigslist born? Are follow up questions needed here: Yes. Follow up: Who was the founder of craigslist? Intermediate answer: Craigslist was founded by Craig Newmark. Follow up: When was Craig Newmark born? Intermediate answer: Craig Newmark was born on December 6, 1952. So the final answer is: December 6, 1952
...
Question: {input} Are followup questions needed here:{agent_scratchpad}
调用后可以看到中间过程:模型确实把问题拆成多个子问题,逐个搜索,最后得出结论。
Self Ask 结果不好的原因主要有两个:
- 搜索返回结果不给力。搜索引擎的排序和摘要往往不是针对问答优化的,返回的 top 1 可能答非所问,或者时效性出错。
- 模型拆解方向跑偏。Self Ask 预设的是组合型问题,如果你的场景拆法不同,需要调整 few-shot prompt,甚至通过指令微调注入新的拆解逻辑。
整体而言,Self Ask 只支持单一搜索工具,是最简单的调用模板。
ReAct
ReAct 把推理分成两步:Reason(分析)和 Action(执行)。Reason 产出分析步骤,Action 生成具体的工具调用请求,二者交替直到结束。和 Self Ask 相比,ReAct 把推理与工具调用解耦了。在 Self Ask 里,自我提问同时充当了推理和搜索 query;ReAct 则先推理后调用,能更好地适配搜索之外的工具。
应用
LangChain 里有基于 ReAct 的 ReActDocstoreAgent 和 ZeroShotAgent。为了跟 Self Ask 对比,我用同样的方式初始化。
ZeroShotAgent
ZeroShotAgent 完全靠上下文和工具描述进行工具选择,没有固定的推理模板,适合灵活的场景。除了 Google 搜索,我还加入了 Wolfram Alpha。
import os
from langchain.agents.loading import AGENT_TO_CLASS
from langchain.agents.agent import AgentExecutor
from langchain.agents import AgentType, Tool
from langchain import OpenAI, SerpAPIWrapper
from langchain.utilities.wolfram_alpha import WolframAlphaAPIWrapper
os.environ["WOLFRAM_ALPHA_APPID"] = "你的 key"
llm = OpenAI(temperature=0, openai_api_key="你的 key")
search = SerpAPIWrapper(params={
"engine": "google",
"gl": "us",
"hl": "zh-cn",
}, serpapi_api_key="你的 key")
wolfram = WolframAlphaAPIWrapper()
tools = [
Tool(
name="搜索",
description="搜索引擎,当你需要回答当前问题的时候调用,输入是检索 query",
func=search.run
),
Tool(
name="Wolfram",
description="Wolfram Alpha,当你需要回答和数学,科学,科技,文化,社会,日常生活相关的问题时调用,输入是检索 query",
func=wolfram.run
),
]
agent_cls = AGENT_TO_CLASS[AgentType.ZERO_SHOT_REACT_DESCRIPTION]
agent = agent_cls.from_llm_and_tools(llm, tools)
chain = AgentExecutor.from_agent_and_tools(agent, tools, return_intermediate_steps=True)
output = chain("昨日 A 股市场涨幅最高的板块成交量多少")
zero-shot prompt 里交代了工具描述和 Action 可用的工具列表。因为没有 few-shot 拆解指引,模型在面对'A 股涨幅最高板块成交量'这种组合问题时未必会先拆解再搜索,但它正确选择了搜索工具。当问题明显属于 Wolfram 能解决的领域(比如求几何面积),模型会调 Wolfram。
ReActDocstoreAgent
这个 Agent 针对文档问答,推理模板和工具使用都是固定的。论文定义了两种工具:Search(检索文档)和 Lookup(在文档中查找关键词所在句子)。因为模板固化,适用场景很窄。
ReAct 虽然支持直接 zero-shot 使用,但论文也指出,加上指令微调后效果会更好。
小结与选型建议
Self Ask 和 ReAct 都有明显的局限,也各有适用阵地。
- 适合比较'自然语言化'的工具输入,比如搜索。碰到高度结构化的调用(比如 API 参数齐备),纯 prompt 的准确率有限。
- 工具数量不宜过多,通常 3~5 个。prompt 长度有限,塞太多工具描述会拖垮效果。
- 规划能力上,few-shot 比 zero-shot 强。但如果拆解逻辑复杂多变,固定 prompt 很难覆盖所有情况。
- 两种都是串行推理,每一步都要等上一步,延迟高。这个问题 ReWOO 的方案可以缓解(并行推理+槽位填充)。
选型时可以参考:
- 组合型查询(如'A 的 B 属性'),主力靠搜索 → 优先 Self Ask。
- 需要组合多种工具(搜索+计算器+数据库),拆解逻辑不复杂 → 试试 ReAct (ZeroShot)。
- 文档内部检索 → 上 ReActDocstoreAgent。
- 更复杂的工具调用和规划,后面走指令微调的路子。
碰到更复杂的场景,下一章我们聊聊基于指令微调的工具调用方案。


