DeepSeek + LangGraph: 探讨并评测推理模型在Agent应用上的能力与表现
既然DeepSeek-R1擅长深度“思考”,那么在更需要自主规划能力的Agent应用中,它是否有更好的表现呢?本文将以最常见的ReAct范式的Agent来探讨这个问题,不过,结果可能会颠覆你的预期。
- 回顾:什么是ReAct Agent?
- ReAct Agent需要哪些推理能力?
- 手搓一个支持DeepSeek-R1的ReAct Agent(基于LangGraph)
- 不同模型的ReAct Agent评测
- 思考与总结
什么是ReAct Agent?
ReAct(Reasoning + Acting)是一种结合推理(Reasoning)和行动(Acting)的 AI 智能体范式。其核心理念是让 AI 先进行思考推理,再据此采取行动,如调用外部工具;经过多次这样循环,直到任务完成。
简单的说就是:
给Agent安上手脚(Tools)和大脑(LLM),让它自己思考,并借助手脚完成任务。
所以,ReAct Agent的特点是:
-
自主规划能力:自行推理多子任务步骤,并在过程中动态调整
-
工具使用:支持与API、数据库、搜索引擎等交互,以完成任务
-
更高的灵活性:相对于有固定流程的Workflows,任务更灵活
-
存在不可控性:LLM输出的不确定性可能会带来一定不可控性

02
ReAct Agent需要哪些推理能力
ReAct Agent是一种高度依赖于LLM的智能体,其需要的推理能力包括:
* 多任务步骤的规划与推理
- 根据输入任务与工具等信息推理出子任务,并合理安排顺序
- 理解推理历史与观察工具结果,动态规划,调整决策,避免冗余等
比如这样的任务:
“了解公司新品的网络评价,如果偏负面,则发送邮件到产品经理。”
这个任务需要LLM最终能够规划出类似下面的任务步骤,注意这个规划是要在过程中动态完成的,因为你无法事先得知网络评价的结果:
* 工具使用的推理
能够推理子任务需要使用的工具,及其结构化的输入参数。这通常借助LLM的Function Calling特性或者提示工程(Prompt)来实现。
* 协助子任务的执行
除了任务步骤的推理,当然LLM也可以用来协助完成某个具体任务。比如在一个研究智能体中,辅助生成论文提纲等。
我们重点关注前两种推理能力,这是体现Agent自主能力的核心。
手搓支持DeepSeek-R1的ReAct Agent
一个典型的ReAct Agent的Graph图表示如下:
在LangGraph中构建ReAct Agent最快捷的方法是使用官方提供的prebuilt函数:
但这个方法对我们并不适用。因为这个方法有一个强制要求:
大模型必须支持Function Calling(函数调用),因为其内部实现需要借助LLM的Function Calling功能来推理下一个步骤与工具!而目前的推理模型都不支持函数调用。
所以,我们必须要使用LangGraph来自行实现这个Agent,这需要使用提示工程来要求LLM进行任务步骤推理。这里仅展示核心过程:
1. 提示词
设计ReAct范式的Prompt如下:
...
# ReAct 提示格式
REACT_PROMPT = '''
你被设计用于帮助完成各种输入任务,包括回答问题、内容创作、自动化处理等。
## 工具
你可以使用各种工具,并且需要自行决定使用工具的顺序,以完成当前任务。
这可能需要将任务拆分为多个子任务,并使用不同的工具来完成各个子任务。
你可以使用以下工具:
{tools_desc}
## 输出格式
如果本次需要使用工具完成某个子任务,请按照以下格式输出:
```
Thought: 我需要使用一个工具来帮助回答这个问题。
Action: 工具名称 (从 {tool_names} 中选择一个工具),如果需要使用工具的话。
Action Input: 传递给工具的输入,使用 JSON 格式表示参数(例如:{{"query": "你好"}})。
```
注意:
* 始终以“Thought”开头。
* 绝对不要在你的响应外部使用 Markdown 代码标记,但如果在你的回答中需要,你可以在适当的位置使用代码标记。
* 请使用有效的JSON 格式作为Action Input。不要使用类似 {{'input': '你好世界', 'num_beams': 5}} 这种错误格式。
* 一次响应最多只能使用一个工具以完成一个子任务。不要在一次响应中出现多个Action。
如果你已经获得足够的信息而来输出最终回答,则必须使用以下两种格式之一来输出最后答案:
```
Thought: 我可以在不使用更多工具的情况下回答问题。
Answer: [你的回答 (与用户问题的语言相同)]
```
```
Thought: 我无法使用提供的工具回答该问题。
Answer: [你的回答 (与用户问题的语言相同)]
```
注意:
* 确保所有子任务都完成后才输出最终答案
* 最终回答尽量涵盖用户任务的所有方面
## 当前对话
以下是当前的对话历史, 由人类、AI的消息交替组成。
'''
2. 准备Tools
准备几个简单的模拟Tool,交给Agent使用:
- 网络搜索(network_search)
- 计算器(caculator)
- 文本摘要与总结(document_summarizer)
- 电子邮件发送(Email)
代码大致如下(省略了内部逻辑):
...
# 定义4个模拟工具
@tool
def network_search(query: str) -> str:
"""
用于执行网络搜索并返回搜索结果
Args:
query (str): 要进行网络搜索的关键词
"""
print_colored(f'调用tool: network_search, 输入: {query}', 'green')
try:
# 调用 TavilySearchResults 来执行搜索,模拟返回结果
results = TavilySearchResults(max_results=2).invoke({"query": query})
return f"搜索结果: {results}"
except Exception as e:
return f"搜索错误: {str(e)}"
@tool
def calculator(query: str) -> str:
"""
用来执行加减乘除计算
Args:
query (str): 要计算的表达式,如1+2*3
"""
...
@tool
def document_summarizer(text: str) -> str:
"""
用来提炼与总结文本的核心内容,形成摘要
Args:
text (str): 要总结的文本内容
"""
...
@tool
def email(recipient, subject, body) -> str:
"""
用于发送电子邮件
Args:
recipient (str): 收件人的电子邮件地址
subject (str): 电子邮件的主题
body (str): 电子邮件的正文
"""
...
3. 实现核心推理逻辑(借助LLM)
这个是整个流程的核心部分,主要职责是调用LLM进行任务推理:
- 通过提示词与对话历史让LLM推理下一步行动
- 如果需要调用工具,则给出结构化的调用信息
- 如果无需再调用工具,则输出LLM给出的答案
- 如果达到最大迭代次数仍未完成任务,则报错
实现如下(代码中的llm将在后面测试中使用不同模型如Deepseek-R1来替换):
...
llm = ChatOpenAI(model='gpt-4o-mini')
tools = [network_search,calculator,document_summarizer,email]
async def call_model(
state: State, config: RunnableConfig
) -> Dict[str, List[AIMessage]]:
# 生成工具描述
tools_desc = "\n".join([
f"- {tool.name}: {tool.description}\n"
for tool in tools
])
tool_names = [tool.name for tool in tools]
# 生成 ReAct 提示
system_prompt = REACT_PROMPT.format(
tools_desc=tools_desc,
tool_names=tool_names
)
# 这一步需要根据不同模型增加或注释
state.messages = [HumanMessage(content=msg.content) if "ToolMessage" in msg.__class__.__name__ else msg for msg in state.messages]
# 调用 LLM,输入提示词(system_prompt)和对话历史(messages)
response = await llm.ainvoke([SystemMessage(content=system_prompt)] + state.messages)
content = response.content
print_colored("\n===========================Reasoning================================", 'magenta')
print_colored(f'{content}', 'magenta')
print_colored("=========================Reasoning End================================\n", 'magenta')
if "Action:" in content and "Action Input:" in content:
# 提取工具调用信息
action_lines = [line for line in content.split('\n') if line.startswith('Action:') or line.startswith('Action Input:')]
tool_name = action_lines[0].replace('Action:', '').strip()
tool_input = action_lines[1].replace('Action Input:', '').replace("'",'"').strip()
tool_input = tool_input.replace('\\', '\\\\')
response.tool_calls = [{
"id": "call_1",
"type": "function",
"name": tool_name,
"args": json.loads(tool_input)
}
]
if state.is_last_step and response.tool_calls:
return {
"messages": [
AIMessage(
content="对不起,我在指定的步骤数内无法找到问题的答案。"
)
]
}
return {"messages": [response]}
4. 创建Graph
完成了核心的推理节点,就可以创建这个Graph(Tools调用借助官方ToolNode组件即可):
@dataclass
class InputState:
messages: Annotated[Sequence[AnyMessage], add_messages] = field(
default_factory=list
)
@dataclass
class State(InputState):
is_last_step: IsLastStep = field(default=False)
builder = StateGraph(State, input=InputState)
builder.add_node(call_model)
builder.add_node("tools", ToolNode(tools))
builder.add_edge("__start__", "call_model")
# 定义一个函数,根据模型输出的内容来决定下一个节点;如果有工具调用,则转向tools
def route_model_output(state: State) -> Literal["__end__", "tools"]:
last_message = state.messages[-1]
if not isinstance(last_message, AIMessage):
raise ValueError(
f"Expected AIMessage in output edges, but got {type(last_message).__name__}"
)
if not last_message.tool_calls:
return "__end__"
return "tools"
# 添加条件边
builder.add_conditional_edges(
"call_model",
route_model_output,
)
builder.add_edge("tools", "call_model")
# 编译
graph = builder.compile()
graph.name = "ReAct Agent"
5. 验证可用性
现在你可以使用graph.invoke方法来输入任务,以测试这个ReAct Agent。按照预期,它应该能够理解你的自然语言输入任务,并使用已有的四个工具(搜索、计算器、摘要、邮件发送)来完成。为了更好的观察,我们加入一些跟踪信息,以展示LLM的推理过程。
这里先用一个简单任务看下效果(模型gpt-4o-mini):
可以看到,第一次推理,LLM认为需要使用搜索工具;第二次推理,因为观察到工具返回(这里未打印),LLM已经可以正确回答问题了!
不同模型的ReAct Agent评测
现在我们使用不同难度的任务来考察类似DeepSeek- R1这样的推理模型,在ReAct Agent中的多任务步骤推理时的表现,并同通用模型做对比。
测试任务(从简单到复杂):
- "搜索《哪吒2》的最新票房数据",
- "搜索《哪吒2》的详细剧情介绍,并发送到test@gmail.com",
- "搜索《哪吒2》的最新票房,如果超过50亿,再帮我搜索《哪吒3》的上映时间; 如果没有超过50亿,则创作一段加油的文字。最后把结果发邮件到test@gmail.com"
对比模型:
- gpt-4o-mini
- doubao-1.5-32k
- deepseek-v3(官方)
- deepseek-r1(官方)
- deepseek-r1:7b(开源)
- qwen-2.5:7b(开源)
测试方法:
- 在不改变其他代码与提示的情况下,切换不同的模型
- 依次输入3个测试任务,每个任务测试多次,观察结果
- 确保每次任务是独立的,不记忆之前的任务历史
测试结果:
任务一 | 任务二 | 任务三 | |
gpt-4o-mini |
Yes |
Yes | Yes |
doubao-1.5-32k | yes | yes | yes |
deepseek-v3 |
yes | yes | yes |
deepseek-r1 |
yes | yes * | no |
deepseek-r1:7b | yes | no * |
no |
qwen-2.5:7b | yes | yes |
no |
- yes: 全部成功,且推理过程输出符合预期
- no:全部失败,未获得期望结果甚至异常
- yes*: 代表大部分成功,偶尔失败;或者结果成功,但推理过程不符预期
- no*: 代表大部分失败,偶尔成功
DeepSeek-R1失败的主要现象:
1. 不遵循逐步推理的指令。即一次性返回包含多个Thought-Action行动步骤的完整任务过程(尽管这个过程可能是正确的)。如这次:
2. 基于假设(幻觉)推理。在第一步的子任务推理后,有时候会自作主张的“假设”一个工具调用结果,并据此做后续步骤推理(r1:7b模型)。
3. 遗漏某个任务步骤。比如某一次任务3的测试中,未推理与执行“发送邮件”这一步骤,但最终却有如下“貌似完美”的输出:
4. 输出格式不能稳定遵循指令要求。比如下图中,自行添加了'json'标记,当然格式的问题有时候可以通过代码来容错,但也可能会直接异常:
我们来看一下最复杂的任务3的成功输出,推理过程应该类似下图:
思考与总结
尽管从直觉上你可能会认为“自带Cot(思维链)”的推理模型Deepseek- R1应该更擅长自主的多步骤任务规划与推理,但是事实恰恰相反:
目前的推理模型(DeepSeek-R1、OpenAI的o1等)在Agent需要的自主、多步骤、动态的任务规划能力上还缺乏足够的可用性。
下面是我们的一些具体思考与总结:
1. 目前o1,r1这样的推理模型的确擅长step by step的思考问题,但输入任务应该更直接。即复杂性体现在问题本身,而不能是提示词与上下文。比如:
- 根据需求描述生成复杂的代码
- 解决一道复杂的数学问题
- 进行一项科学计算与研究
- 根据主题生成有深度的论文
而以下类型的任务它目前并不擅长:
- 基于复杂上下文与对话历史的推理任务
- 有严格的指令遵循与结构化输出要求的任务
- 依赖于Function Calling功能的智能体任务
这里的结构化输出可以借助辅助LLM来完善;Function Calling也可以借助提示词来获得,但是第一个问题则需要等待模型进化。
2. 具体到AI Agent应用,特别是ReAct Agent,由于其更依赖于LLM的多任务步骤推理能力。我们建议:
- 在任务步骤的动态规划与推理上,通用模型目前更适合,比如GPT-4o,DeepSeek-v3;且参数越大的模型越稳定
- 推理模型,包括满血的DeepSeek-R1,以及开源的1.5b等蒸馏版本,都不建议用作Agent的任务步骤推理;但是可以作为一个Tool来完成某个子任务,比如根据搜索结果编写一份有深度的报告
3. 推理模型在Agent中应用的另一个问题是响应性能,由于其深度思考的特点,其响应时间普遍较长,这是制约其在企业级应用的另一个问题。
以上的一些结论刚好也验证了DeepSeek-R1官方技术报告揭示的自身“能力缺陷”,包括不支持function calling、不擅长多轮对话、结构化输出较弱、提示词与复杂上下文敏感等,我们也期待下一个版本的R1在这些能力上取得突破!
来源:AI大模型应用实践