本文聚焦 LangChain Agent 系统的扩展能力:如何自定义工具(Tool)、编写插件,以及如何灵活管理 Agent 的中间推理状态(
AgentAction
/AgentFinish
)。目标是帮助你搭建高可控、可复用的 Agent 工具体系。
一、自定义 Tool 的标准方式:继承 BaseTool
默认装饰器封装适合简单场景,但复杂业务时推荐继承 BaseTool
:
python
from langchain_core.tools import BaseTool
class ReverseTool(BaseTool):
name: str = "reverse"
description: str = "反转输入字符串,例如 'hello' → 'olleh'"
def _run(self, query: str) -> str:
return query[::-1]
async def _arun(self, query: str) -> str:
raise NotImplementedError("Async not implemented")
reverse_tool = ReverseTool()
print(reverse_tool.invoke("我是一个正向的字符串。"))
# 输出:。串符字的向正个一是我
✅ 使用
invoke()
即可获取同步结果。
二、AgentPlugin 插件:插入 Agent 执行流程
先创建一个 ReAct Agent
python
import os
from langchain_core.prompts import PromptTemplate
from langchain.agents import create_react_agent
from langchain_openai import ChatOpenAI
# 初始化模型
llm = ChatOpenAI(
temperature=0,
model='deepseek-chat',
openai_api_key=os.getenv('DEEPSEEK_API_KEY'),
openai_api_base='https://api.deepseek.com',
max_tokens=1024
)
template = """你是一个可以通过工具来完成任务的智能 AI 助手,擅长推理、规划和执行。
你的目标是尽可能准确地解决用户的问题。可能的情况下请优先使用以下工具来完成任务:
{tools}
严格地使用以下格式进行思考与行动:
Question: 用户的输入内容
Thought: 你对当前问题的理解与推理
Action: 选择调用的工具名称,应为 [{tool_names}] 之一
Action Input: 务必准确的工具所需的输入
Observation: 调用工具返回的结果
...(Thought/Action/Action Input/Observation 可以重复多次)
Thought: 根据观察得出的最终结论
Final Answer: 给用户的直接答复
开始!
Question: {input}
Thought: {agent_scratchpad}"""
prompt = PromptTemplate.from_template(template)
# 构造 ReAct 类型的 Agent
agent = create_react_agent(llm=llm, tools=[ReverseTool()], prompt=prompt)
LangChain 支持插件机制:插件可以拦截、修改行为或日志。例如插入日志追踪插件:
python
from langchain.agents import AgentExecutor
from langchain.callbacks.base import BaseCallbackHandler
class LoggingPlugin(BaseCallbackHandler):
def on_agent_action(self, action, **kwargs):
print(f"Agent 决策:调用工具 {action.tool},输入 {action.tool_input}")
def on_tool_end(self, output, **kwargs):
print("工具返回:", output)
agent_executor = AgentExecutor.from_agent_and_tools(
agent=agent,
tools=[ReverseTool(callbacks=[LoggingPlugin()])],
callbacks=[LoggingPlugin()],
verbose=False,
handle_parsing_errors=True
)
agent_executor.invoke({"input": "请你优先使用我提供的工具反转字符串:LangChain"})
插件会在每轮 AgentAction
与工具执行后打印日志。

三、AgentAction
与 AgentFinish
:控制 Agent 行为流程
在 LangChain 的 Agent 执行过程中,核心控制流依赖两个数据结构:
AgentAction
:表示 下一步调用哪个工具、用什么参数、以及对应的思考日志。AgentFinish
:表示 Agent 推理结束,输出最终结果。
🔹1. AgentAction
:代理调用工具的一步行为
每一轮推理中,Agent 可能会产生一个 AgentAction
,结构如下:
python
AgentAction(
tool="reverse", # 工具名称(必须与 tools 列表中的名称匹配)
tool_input="LangChain", # 传给工具的输入
log="我认为需要调用 reverse 工具将字符串反转。" # Scratchpad 中的中间推理日志
)
✅ 注意:这个
log
不只是为了输出,它将被用于下一轮 prompt 的 scratchpad 填充,指导 LLM 的进一步思考。
🔹2. AgentFinish
:代理任务完成的信号
当 Agent 认为任务完成时,会返回一个 AgentFinish
对象:
python
AgentFinish(
return_values={"output": "niahCgnaL"}, # 最终输出(dict)
log="工具调用完毕,任务完成。" # 可用于展示最终思考过程
)
AgentExecutor
一旦收到 AgentFinish
,就会 终止循环并返回结果。
🔹3. 在 Agent 控制循环中的作用
LangChain 中 AgentExecutor 的主循环本质上是这样的伪代码:
python
while True:
action_or_finish = agent.plan(...)
if isinstance(action_or_finish, AgentFinish):
return action_or_finish.return_values
elif isinstance(action_or_finish, AgentAction):
observation = tool.run(action_or_finish.tool_input)
intermediate_steps.append((action_or_finish, observation))
其中 intermediate_steps
会自动构造 scratchpad
,供下一轮推理参考。
源码:
langchain.agents.agent.AgentExecutor._call()

🔹4. 你可以自定义这两个输出:用于测试 / 调试 / 插件式控制
python
# 手动构造一个完整的"伪 Agent"行为流程
steps = [
AgentAction(tool="reverse", tool_input="LangChain", log="尝试反转字符串"),
AgentFinish(return_values={"output": "niahCgnaL"}, log="得到最终反转结果")
]
for step in steps:
if isinstance(step, AgentAction):
print(f"调用工具:{step.tool},输入:{step.tool_input}")
elif isinstance(step, AgentFinish):
print(f"完成推理,输出:{step.return_values['output']}")
也可以在自定义 Agent 类中显式控制输出:
python
from langchain.agents import BaseSingleActionAgent
class MyManualAgent(BaseSingleActionAgent):
def plan(self, intermediate_steps, **kwargs):
return AgentAction(
tool="my_tool",
tool_input="my_input",
log="我决定调用 my_tool"
)
🔹5. AgentAction
与 Scratchpad 的关系
LangChain 会将 intermediate_steps: List[Tuple[AgentAction, str]]
变为 scratchpad:
text
Thought: 我需要调用 reverse 工具来处理输入
Action: reverse
Action Input: LangChain
Observation: niahCgnaL
这个会拼接到下一轮 prompt 中,引导大模型思考。
🔹6. 实战用途场景
场景 | 使用方式 |
---|---|
✅ 单元测试 | 手动构造 AgentAction / AgentFinish 检查工具行为与响应 |
✅ 多 Agent 协同 | 每个 Agent 按返回 AgentAction 协调工具调用 |
✅ 控制工具调用 | 你可以中途插入自定义 AgentAction 实现特定路径控制 |
✅ 可解释性分析 | AgentAction.log 和 AgentFinish.log 记录了完整推理过程 |
✅ 自定义插件 | 插件可基于这两个数据结构实现工具路由、后处理等能力 |
python
import os
from typing import List, Tuple, Any, Sequence
from langchain.agents import BaseSingleActionAgent, AgentExecutor
from langchain_core.agents import AgentAction, AgentFinish
from langchain.schema import SystemMessage, HumanMessage
from langchain.tools import Tool
from langchain_openai import ChatOpenAI
# 1️⃣ 工具定义
def reverse_string(s: str) -> str:
return s[::-1]
tools = [
Tool(
name="reverse",
func=reverse_string,
description="反转字符串"
)
]
# 2️⃣ LLM 实例
llm = ChatOpenAI(
temperature=0.1,
model='deepseek-chat',
openai_api_key=os.getenv('DEEPSEEK_API_KEY'),
openai_api_base='https://api.deepseek.com',
max_tokens=1024
)
# 3️⃣ 自定义 Agent:支持 AgentFinish 输出
class SmartLLMAgent(BaseSingleActionAgent):
llm: ChatOpenAI
tools: Sequence[Tool]
@property
def input_keys(self) -> List[str]:
return ["input"]
def plan(
self,
intermediate_steps: List[Tuple[AgentAction, str]],
**kwargs: Any,
) -> AgentAction | AgentFinish:
user_input = kwargs["input"]
# 构造 scratchpad
scratchpad = ""
for i, (action, obs) in enumerate(intermediate_steps):
scratchpad += f"第{i + 1}步:我调用了 {action.tool},输入是「{action.tool_input}」,得到结果「{obs}」。\n"
print("中间步骤记事本:", scratchpad)
prompt = [
SystemMessage(content=(
"你是一个智能代理。你可以使用以下工具:"
f"{', '.join([tool.name for tool in self.tools])}。\n"
"你的目标是根据用户输入合理地调用工具,或者在觉得任务完成时输出最终答案。\n"
"请根据格式返回:\n"
"调用工具:TOOL_NAME | TOOL_INPUT\n"
"给出最终答案:FINAL_ANSWER | RESULT\n"
)),
HumanMessage(content=(
f"用户的问题是:{user_input}\n\n"
f"当前中间步骤如下:\n{scratchpad if scratchpad else '(尚未调用工具)'}"
))
]
output = self.llm.invoke(prompt).content.strip()
print("[LLM 输出]:", output)
if "给出最终答案" in output:
output = output.split(":")[1]
final_answer = output.split("|", 1)[1].strip()
return AgentFinish(return_values={"output": final_answer}, log=output)
elif "调用工具" in output:
output = output.split(":")[1]
tool, tool_input = [x.strip() for x in output.split("|", 1)]
return AgentAction(tool=tool, tool_input=tool_input, log=output)
else:
raise ValueError("无法解析 LLM 输出格式")
async def aplan(
self,
intermediate_steps: list[tuple[AgentAction, str]],
**kwargs: Any,
) -> AgentAction | AgentFinish:
pass
# 4️⃣ 构建 AgentExecutor
agent = SmartLLMAgent(llm=llm, tools=tools)
executor = AgentExecutor(
agent=agent,
tools=tools,
verbose=False,
handle_parsing_errors=True
)
# 5️⃣ 测试运行
result = executor.invoke({"input": "请把LangChain倒过来并告诉我结果"})
print("\n最终结果:", result["output"])

四、调整策略与中间态管理建议
- 可自定义插件延迟缓存工具输出、过滤结果、整合多个工具
- 可读取
agent_executor.intermediate_steps
来保存中间态日志或批量存储 - 当工具较多时,建议对
tools
列表进行命名和分类管理
python
agent_executor.return_intermediate_steps = True
result = agent_executor.invoke(
{"input": "请你优先使用我提供的工具反转字符串:LangChain"})
from pprint import pprint
pprint(result)

小结
- 自定义
BaseTool
提供了灵活的工具功能扩展能力; - 插件机制(callbacks)让你可以插入日志、缓存、限制等额外逻辑;
AgentAction
与AgentFinish
是控制 Agent 推理循环的标准结构,使你可以更可控地管理中间态与终止。
接下来我们将深入拆解 RetrievalQA 的内部架构,探究如何实现检索增强生成(RAG)。