与一般程序不同:LangChain的提示模板template中的{tool_names}和{agent_scratchpad}变量并不需显示指定,它们会自动被填充。
LangChain的提示模板template如下示例:
python
from langchain.prompts import PromptTemplate
template = ('''
'尽你所能用中文回答以下问题。如果能力不够你可以使用以下工具:\n\n'
'{tools}\n\n
Use the following format:\n\n'
'Question: the input question you must answer\n'
'Thought: you should always think about what to do\n'
'Action: the action to take, should be one of [{tool_names}]\n'
'Action Input: the input to the action\n'
'Observation: the result of the action\n'
'... (this Thought/Action/Action Input/Observation can repeat N times)\n'
'Thought: I now know the final answer\n'
'Final Answer: the final answer to the original input question\n\n'
'Begin!\n\n'
'Question: {input}\n'
'Thought:{agent_scratchpad}'
'''
)
其中的{tool_names}和{agent_scratchpad}变量在程序中并没有被取代,而其它变量(如,{tools})则被明确替换(如,tools = load_tools(["serpapi", "llm-math"], llm=llm))。
本文分析这种情况。
一、template中{tool_names}
template中{tool_names} 是一个占位符,用于在提示模板中动态插入工具名称列表。在 ReAct 模式中,{tool_names} 用于指示大模型在执行过程中可以选择的工具列表。
在 LangChain 中,{tool_names} 的自动填充逻辑如下:
{tool_names} 不是你手动赋值的,而是 LangChain 的 ReAct Agent(create_react_agent + AgentExecutor)在执行阶段(invoke 调用时),自动从传入的 tools 列表中提取工具名称并动态代入 ------ 这是 LangChain 为 ReAct 范式 Agent 内置的变量绑定逻辑。
步骤1:加载工具 → 确定工具名称列表(数据源)
python
tools = load_tools(["serpapi", "llm-math"], llm=llm)
-
这一步是
{tool_names}变量值的数据源 :load_tools加载了两个工具,对应的核心信息如下(LangChain 内部存储):传入的工具标识 LangChain 内部对应的工具名 工具功能 "serpapi" "Search"(或保留"serpapi") 联网搜索 "llm-math" "Calculator"(或保留"llm-math") 数学计算 -
这些工具名称会被 LangChain 提取,作为
{tool_names}的最终代入值。
步骤2:创建 ReAct Agent → 绑定工具与模板变量的映射(关键铺垫)
python
agent = create_react_agent(llm, tools, prompt)
create_react_agent是 LangChain 为 ReAct 范式(思考-行动-观察)封装的核心函数,它内置了对 ReAct 模板变量的处理逻辑 :- 会自动识别模板中的
{tools}、{tool_names}、{agent_scratchpad}等 ReAct 范式的固定变量; - 会建立「模板变量 ↔ 工具信息」的映射:
{tools}→ 映射为工具的详细描述(名称+功能+使用方式);{tool_names}→ 映射为工具名称的列表(如["Search", "Calculator"]或["serpapi", "llm-math"]);{agent_scratchpad}→ 映射为 Agent 思考过程的中间结果(后续填充)。
- 会自动识别模板中的
- 这一步只是"绑定映射关系",还没实际代入值(因为还没有具体的执行上下文)。
步骤3:AgentExecutor 执行(invoke)→ 动态代入所有变量(最终代入)
python
agent_executor.invoke({"input": "你的问题..."})
这是 {tool_names} 被实际代入的核心时机:
- 当调用
invoke时,AgentExecutor 会先收集所有需要填充的变量:{input}:填充为你传入的用户问题("目前市场上玫瑰花的一般进货价格是多少?...");{tool_names}:从tools列表中提取工具名称,填充为类似["serpapi", "llm-math"]或["Search", "Calculator"]的字符串(格式为逗号分隔,比如"serpapi, llm-math");{tools}:填充为工具的详细描述(比如"Search:用于联网搜索最新信息;Calculator:用于数学计算");{agent_scratchpad}:初始为空,后续Agent思考过程中会逐步填充(Thought/Action/Observation 等步骤)。
- AgentExecutor 会将这些变量值动态填充到 PromptTemplate 中,生成完整的提示词发送给大模型;
- 大模型基于填充后的完整提示词,按照 ReAct 格式输出思考和行动步骤(比如选择
Search工具查玫瑰进货价,再用Calculator工具计算加价5%的定价)。
验证:打印填充后的 Prompt(直观看到 {tool_names} 的代入值)
你可以在代码中添加以下验证代码,直接查看 {tool_names} 被代入后的实际值:
python
# 新增:手动模拟 Agent 填充变量的过程,打印完整 Prompt
from langchain.agents.format_scratchpad import format_log_to_str
from langchain.schema import AgentFinish, AgentAction
# 1. 提取工具名称(模拟 LangChain 内部逻辑)
tool_names = [tool.name for tool in tools] # 提取工具名,比如 ["Search", "Calculator"]
print("=== 提取的 tool_names 变量值 ===")
print(tool_names) # 输出:['Search', 'Calculator'](或 ['serpapi', 'llm-math'],取决于工具定义)
# 2. 填充模板变量(模拟 AgentExecutor 执行时的填充逻辑)
filled_prompt = prompt.format(
tools="\n".join([f"{tool.name}: {tool.description}" for tool in tools]), # 工具详细描述
tool_names=", ".join(tool_names), # 工具名称列表(代入 {tool_names})
input="目前市场上玫瑰花的一般进货价格是多少?如果我在此基础上加价5%,应该如何定价?", # 用户输入
agent_scratchpad="" # 初始为空的思考过程
)
print("\n=== 填充 {tool_names} 后的完整 Prompt ===")
print(filled_prompt)
验证输出示例:
=== 提取的 tool_names 变量值 ===
['Search', 'Calculator']
=== 填充 {tool_names} 后的完整 Prompt ===
尽你所能用中文回答以下问题。如果能力不够你可以使用以下工具:
Search: A search engine. Useful for when you need to answer questions about current events...
Calculator: Useful for when you need to do math calculations...
Use the following format:
Question: the input question you must answer
Thought: you should always think about what to do
Action: the action to take, should be one of [Search, Calculator] # {tool_names} 被代入为 Search, Calculator
Action Input: the input to the action
Observation: the result of the action
...
Thought: I now know the final answer
Final Answer: the final answer to the original input question
Begin!
Question: 目前市场上玫瑰花的一般进货价格是多少?如果我在此基础上加价5%,应该如何定价?
Thought:
思考
-
为什么无需手动赋值?
LangChain 的
create_react_agent是为 ReAct 范式定制的,它知道 ReAct 模板必须包含{tool_names}、{tools}、{agent_scratchpad}这些变量,因此会自动从传入的tools参数中提取对应值,无需开发者手动绑定。 -
代入时机?
PromptTemplate.from_template(template)只是解析模板结构(识别哪些是变量),并未赋值;- 真正的代入发生在
agent_executor.invoke(...)执行时,每次调用都会动态填充 (比如更换 tools 列表,{tool_names}会自动更新)。
总结
{tool_names}的代入主体:不是手动赋值,而是 LangChain 的 ReAct Agent(create_react_agent+AgentExecutor)自动处理;- 代入数据源:从
load_tools加载的tools列表中提取工具名称; - 代入时机:在
agent_executor.invoke()执行时动态填充(而非创建 PromptTemplate 时); - 核心逻辑:LangChain 为 ReAct 范式内置了模板变量与工具信息的映射,简化了开发者的编码工作。
简单说:你只需要告诉 LangChain "用哪些工具(tools)",它就会自动把工具名称填到 {tool_names} 里,无需手动干预。
二、template中{scratchpad}
scratchpad 本意是草稿纸、便签本 (也译作"暂存区"),在 LangChain 的 Agent 提示模板中,{agent_scratchpad} 是 Agent 的「思考过程草稿区」------ 专门用来记录 Agent 每一轮的"思考(Thought)-行动(Action)-行动输入(Action Input)-观察(Observation)"中间过程,相当于 Agent 推理时用的"草稿纸"。
生活化类比
你解一道数学题(比如"玫瑰进价加价5%定价"):
- 草稿纸(scratchpad):你会在纸上写下"第一步:查玫瑰进价→约2元/枝;第二步:计算2×1.05=2.1元"------ 这些中间步骤就是草稿;
- 最终答案:草稿纸写满后,你总结出"定价2.1元/枝"。
Agent 的 scratchpad 就是这个"草稿纸":它不存储最终答案,只存储 Agent 推理过程中的每一步思考和行动结果,支撑 Agent 完成多轮推理。
scratchpad 的核心作用(结合 ReAct 范式)
你的代码用的是 ReAct 范式 Agent(思考-行动-观察循环),scratchpad 是这个循环能跑起来的核心载体:
- 累积多轮推理过程 :ReAct Agent 不是一步得出答案,而是多轮循环(比如先搜玫瑰进价→再计算加价),
scratchpad会把每一轮的"Thought+Action+Action Input+Observation"都记录下来; - 让 Agent 有"记忆" :每一轮循环时,
scratchpad会被代入到提示模板中,Agent 能基于"上一轮的草稿"继续推理,而不是每次从零开始; - 透明化推理逻辑 :通过
scratchpad能清晰看到 Agent"为什么选这个工具""行动结果是什么",方便调试(比如代码中verbose=True会打印出这个过程)。
scratchpad 在代码中的执行过程(玫瑰定价例子)
设代码里的问题"查玫瑰进价+加价5%定价",则{agent_scratchpad} 的填充和使用过程如下:
阶段1:初始状态 → scratchpad 为空
调用 agent_executor.invoke() 时,{agent_scratchpad} 初始值为空,模板填充后这部分是空白:
Thought:
阶段2:第一轮推理 → scratchpad 首次填充
Agent 第一次思考后生成:
Thought: 我需要先查目前玫瑰花的一般进货价格,应该用Search工具。
Action: Search
Action Input: 2026年玫瑰花一般进货价格
Observation: 2026年玫瑰花批发进货价约为2元/枝(单头玫瑰),多头玫瑰约5元/枝。
这些内容会被 LangChain 自动写入 scratchpad,此时 {agent_scratchpad} 被替换为上述文本,模板中对应的部分变成:
Thought: 我需要先查目前玫瑰花的一般进货价格,应该用Search工具。
Action: Search
Action Input: 2026年玫瑰花一般进货价格
Observation: 2026年玫瑰花批发进货价约为2元/枝(单头玫瑰),多头玫瑰约5元/枝。
Thought:
阶段3:第二轮推理 → scratchpad 累积内容
Agent 基于上一轮的 scratchpad 继续思考,生成新的步骤:
Thought: 现在我有了进货价,需要计算加价5%后的定价,应该用Calculator工具。
Action: Calculator
Action Input: 2×1.05, 5×1.05
Observation: 2×1.05=2.1,5×1.05=5.25
这些内容被追加到 scratchpad 中,模板里的 {agent_scratchpad} 变成:
Thought: 我需要先查目前玫瑰花的一般进货价格,应该用Search工具。
Action: Search
Action Input: 2026年玫瑰花一般进货价格
Observation: 2026年玫瑰花批发进货价约为2元/枝(单头玫瑰),多头玫瑰约5元/枝。
Thought: 现在我有了进货价,需要计算加价5%后的定价,应该用Calculator工具。
Action: Calculator
Action Input: 2×1.05, 5×1.05
Observation: 2×1.05=2.1,5×1.05=5.25
Thought:
阶段4:最终推理 → scratchpad 停止累积
Agent 认为已有足够信息,生成最终答案,scratchpad 不再追加:
Thought: I now know the final answer
Final Answer: 目前单头玫瑰花一般进货价约2元/枝,加价5%后定价2.1元/枝;多头玫瑰花进货价约5元/枝,加价5%后定价5.25元/枝。
思考
-
谁来填充 scratchpad?
不是你手动赋值,而是 LangChain 的
AgentExecutor在每一轮"思考-行动-观察"循环中,自动收集并追加内容到agent_scratchpad,然后代入到提示模板中------和你之前问的{tool_names}类似,是 LangChain 为 ReAct Agent 内置的逻辑。 -
scratchpad 为什么是空的初始状态?
Agent 第一次推理时还没有任何思考/行动记录,所以初始
{agent_scratchpad}为空;每一轮循环后,LangChain 会把新的中间步骤追加进去,形成"累积式草稿"。 -
和普通变量的区别?
{input}:固定的用户问题(一次性赋值);{tool_names}:固定的工具名称列表(一次性赋值);{agent_scratchpad}:动态累积的中间过程(多轮循环中持续更新)。
体现智能体的"记忆能力"
- 核心含义 :
scratchpad(agent_scratchpad)是 Agent 的「思考草稿纸」,存储多轮"思考-行动-观察"的中间过程; - 核心作用:支撑 ReAct Agent 的多轮推理,让 Agent 能基于历史思考继续推导,而非从零开始;
- 填充逻辑:由 LangChain 的 AgentExecutor 自动累积填充,初始为空,每轮循环追加新的中间步骤;
- 本质价值:透明化 Agent 的推理过程,同时让 Agent 具备"记忆思考过程"的能力,是 ReAct 范式的核心变量之一。
简单说:没有 scratchpad,Agent 就只能"一步式思考",无法完成"先搜价格→再算加价"的多步骤任务------它是 Agent 实现复杂推理的"记忆载体"。