1. 为什么我要用 LangGraph 重测 DeepSeek 的 Agent 能力DeepSeek 推理模型在 Agent 场景里到底能不能打这个问题我在实际项目里被问过很多次。很多人看到 R1 的思维链输出很漂亮就默认它做多步任务规划也一定强。但真正把 LangGraph 的 ReAct 图跑起来之后结果和直觉差距不小。LangGraph 是 LangChain 团队推出的图结构 Agent 编排框架核心思路是把 Agent 的每一步推理和工具调用拆成节点用边来定义流转逻辑。它适合谁适合已经写过基础 Agent、想深入控制推理流程的开发者也适合需要对比不同模型在工具调用、任务规划、错误恢复上表现的评测场景。我这次评测的目标很明确用同一套 LangGraph 图结构、同一套提示词模板、同一套工具集只切换底层模型观察 DeepSeek 推理模型在三个难度递增的任务上的实际表现。任务从单步搜索到多条件分支加邮件发送覆盖了 ReAct Agent 最核心的三种能力工具选择、动态规划、错误恢复。先说结论方向DeepSeek 推理模型在“问题本身复杂”的任务上确实强但在“需要严格遵循结构化输出和动态多步规划”的 Agent 任务上反而容易出现不遵循逐步推理指令、基于假设推理、遗漏步骤等问题。下面我把完整复现路径和验证脚本拆开讲。2. TaoToken 统一 Key/API 通道的前置配置做多模型对比评测最烦的就是每个模型都要单独申请 Key、单独配 Base URL、单独处理不同的 SDK 兼容性。我这次用 TaoToken 作为统一调用通道一个 Key 就能切换 DeepSeek、GPT、Qwen 等模型省掉了大量环境切换成本。TaoToken 的定位是模型 API 聚合通道官网地址是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 入口是 https://taotoken.net/api 。它兼容 OpenAI 的接口格式所以 LangChain 的 ChatOpenAI 可以直接用只需要改 base_url 和 api_key。你需要先拿到 API Key。进入控制台创建密钥https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 在 API Keys 页面生成一个 Keyhttps://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。建议给这个 Key 起个名字叫“langgraph-eval”方便后续区分。拿到 Key 之后在项目根目录建一个.env文件写入TAOTOKEN_API_KEYsk-你的实际key TAOTOKEN_BASE_URLhttps://taotoken.net/api然后在 Python 里这样初始化模型import os from dotenv import load_dotenv from langchain_openai import ChatOpenAI load_dotenv() def build_llm(model_id: str, temperature: float 0.0): return ChatOpenAI( modelmodel_id, api_keyos.getenv(TAOTOKEN_API_KEY), base_urlos.getenv(TAOTOKEN_BASE_URL), temperaturetemperature, timeout120, max_retries2, )这里有个关键点base_url必须写成https://taotoken.net/api不要加多余的路径后缀。LangChain 内部会自动拼接/chat/completions。如果你写成https://taotoken.net/api/v1会报 404。模型 ID 的写法要和你实际调用的模型对齐。比如 DeepSeek 推理模型、DeepSeek 通用模型、GPT 系列、Qwen 系列在 TaoToken 的模型列表里都有对应的 ID。你可以在模型对话页面先手动测一下https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 确认模型能正常返回再写进代码。如果你打算长期跑 Agent 评测或做编码类 Agent可以考虑 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 它在高频调用场景下更划算。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 里面有各语言 SDK 的配置示例。3. LangGraph ReAct 图结构与可复制配置这一节是全文技术核心。我会给出完整的图结构配置、节点提示词模板、工具定义以及三类任务的验证脚本。你可以直接复制到本地跑。3.1 依赖安装与项目结构先装依赖pip install langgraph langchain-openai langchain-community python-dotenv tavily-python项目结构建议这样langgraph-deepseek-eval/ ├── .env ├── agent.py ├── tools.py ├── prompts.py └── run_eval.py3.2 工具定义tools.py我定义了四个模拟工具网络搜索、计算器、文本摘要、邮件发送。搜索用 Tavily 做真实调用其余三个用模拟逻辑方便观察 Agent 的调用决策。from langchain_core.tools import tool from langchain_community.tools.tavily_search import TavilySearchResults tool def network_search(query: str) - str: 用于执行网络搜索并返回搜索结果。 Args: query (str): 搜索关键词 print(f[TOOL] network_search - {query}) try: results TavilySearchResults(max_results2).invoke({query: query}) return f搜索结果: {results} except Exception as e: return f搜索错误: {str(e)} tool def calculator(query: str) - str: 用来执行加减乘除计算。 Args: query (str): 要计算的表达式如 12*3 print(f[TOOL] calculator - {query}) try: return str(eval(query)) except Exception as e: return f计算错误: {str(e)} tool def document_summarizer(text: str) - str: 用来提炼与总结文本的核心内容形成摘要。 Args: text (str): 要总结的文本内容 print(f[TOOL] document_summarizer - {text[:50]}...) return f摘要: {text[:200]} tool def email(recipient: str, subject: str, body: str) - str: 用于发送电子邮件。 Args: recipient (str): 收件人邮箱 subject (str): 邮件主题 body (str): 邮件正文 print(f[TOOL] email - {recipient} | {subject}) return f邮件已发送至 {recipient}主题{subject}3.3 ReAct 提示词模板prompts.py推理模型不支持 Function Calling所以必须用提示工程让模型输出结构化的 Action 和 Action Input。这个模板是整个 Agent 能否稳定运行的关键。REACT_PROMPT 你被设计用于帮助完成各种输入任务包括回答问题、内容创作、自动化处理等。 ## 工具 你可以使用各种工具并且需要自行决定使用工具的顺序以完成当前任务。 这可能需要将任务拆分为多个子任务并使用不同的工具来完成各个子任务。 你可以使用以下工具 {tools_desc} ## 输出格式 如果本次需要使用工具完成某个子任务请按照以下格式输出 Thought: 我需要使用一个工具来帮助回答这个问题。 Action: 工具名称 (从 {tool_names} 中选择一个工具) Action Input: 传递给工具的输入使用 JSON 格式表示参数例如{{query: 你好}} 注意 * 始终以 Thought 开头。 * 一次响应最多只能使用一个工具以完成一个子任务。不要在一次响应中出现多个 Action。 * 请使用有效的 JSON 格式作为 Action Input。 如果你已经获得足够的信息来输出最终回答则必须使用以下格式 Thought: 我可以在不使用更多工具的情况下回答问题。 Answer: [你的回答] ## 当前对话 以下是当前的对话历史由人类、AI 的消息交替组成。 3.4 图结构配置agent.py这是 LangGraph 的核心部分。我用StateGraph手动搭建 ReAct 循环而不是用 prebuilt 的create_react_agent因为后者强制要求模型支持 Function Calling。import json from dataclasses import dataclass, field from typing import Annotated, Sequence, Literal, Dict, List from langchain_core.messages import ( AnyMessage, AIMessage, HumanMessage, SystemMessage, ToolMessage ) from langgraph.graph import StateGraph, END from langgraph.graph.message import add_messages from langgraph.prebuilt import ToolNode from langchain_core.runnables import RunnableConfig from tools import network_search, calculator, document_summarizer, email from prompts import REACT_PROMPT tools [network_search, calculator, document_summarizer, email] dataclass class InputState: messages: Annotated[Sequence[AnyMessage], add_messages] field(default_factorylist) dataclass class State(InputState): is_last_step: bool field(defaultFalse) def build_agent(llm, max_steps: int 8): async def call_model(state: State, config: RunnableConfig) - Dict[str, List[AIMessage]]: tools_desc \n.join([f- {t.name}: {t.description} for t in tools]) tool_names [t.name for t in tools] system_prompt REACT_PROMPT.format(tools_desctools_desc, tool_namestool_names) # 将 ToolMessage 转成 HumanMessage避免部分模型对 tool role 不兼容 normalized [] for msg in state.messages: if isinstance(msg, ToolMessage): normalized.append(HumanMessage(contentf工具返回{msg.content})) else: normalized.append(msg) response await llm.ainvoke( [SystemMessage(contentsystem_prompt)] normalized ) content response.content print(\n Reasoning ) print(content) print( End \n) if Action: in content and Action Input: in content: lines [l for l in content.split(\n) if l.startswith(Action:) or l.startswith(Action Input:)] if len(lines) 2: tool_name lines[0].replace(Action:, ).strip() tool_input lines[1].replace(Action Input:, ).strip() tool_input tool_input.replace(, ).replace(\\, \\\\) try: args json.loads(tool_input) response.tool_calls [{ id: call_1, type: function, name: tool_name, args: args, }] except json.JSONDecodeError: print(f[WARN] JSON 解析失败: {tool_input}) if state.is_last_step and response.tool_calls: return {messages: [AIMessage(content对不起我在指定步骤数内无法完成任务。)]} return {messages: [response]} def route_model_output(state: State) - Literal[__end__, tools]: last state.messages[-1] if not isinstance(last, AIMessage): raise ValueError(fExpected AIMessage, got {type(last).__name__}) if not last.tool_calls: return __end__ return tools builder StateGraph(State, inputInputState) builder.add_node(call_model, call_model) builder.add_node(tools, ToolNode(tools)) builder.add_edge(__start__, call_model) builder.add_conditional_edges(call_model, route_model_output) builder.add_edge(tools, call_model) graph builder.compile() graph.name ReAct Agent return graph3.5 三类任务验证脚本run_eval.py三个任务从简单到复杂覆盖单步工具调用、多步串联、条件分支加错误恢复。import asyncio from dotenv import load_dotenv from langchain_core.messages import HumanMessage from agent import build_agent from langchain_openai import ChatOpenAI import os load_dotenv() TASKS [ 搜索《哪吒2》的最新票房数据, 搜索《哪吒2》的详细剧情介绍并发送到 testexample.com, 搜索《哪吒2》的最新票房如果超过50亿再帮我搜索《哪吒3》的上映时间如果没有超过50亿则创作一段加油的文字。最后把结果发邮件到 testexample.com, ] async def run_one(model_id: str, task: str): llm ChatOpenAI( modelmodel_id, api_keyos.getenv(TAOTOKEN_API_KEY), base_urlos.getenv(TAOTOKEN_BASE_URL), temperature0.0, timeout180, ) graph build_agent(llm) result await graph.ainvoke( {messages: [HumanMessage(contenttask)]}, config{configurable: {thread_id: eval-1}}, ) final result[messages][-1].content print(f\n[FINAL] {final}\n) return final async def main(): models [deepseek-reasoner, deepseek-chat, gpt-4o-mini] for m in models: for i, task in enumerate(TASKS, 1): print(f\n{*60}) print(fModel: {m} | Task {i}) print(f{*60}) try: await run_one(m, task) except Exception as e: print(f[ERROR] {m} task{i}: {e}) if __name__ __main__: asyncio.run(main())跑之前记得在.env里补上TAVILY_API_KEY否则搜索工具会报错。如果你不想用 Tavily可以把network_search改成返回固定字符串的模拟函数不影响 Agent 流程验证。4. 验证请求与成功结果对照配置写完之后先做一次最小验证确认 TaoToken 通道和 LangGraph 图都能跑通。4.1 最小连通性测试先用一个最简单的请求确认 Key 和 Base URL 没问题import os from dotenv import load_dotenv from langchain_openai import ChatOpenAI load_dotenv() llm ChatOpenAI( modeldeepseek-chat, api_keyos.getenv(TAOTOKEN_API_KEY), base_urlos.getenv(TAOTOKEN_BASE_URL), ) print(llm.invoke(用一句话说明什么是 ReAct Agent).content)如果返回正常文本说明通道没问题。如果报 401检查 Key 是否复制完整如果报连接错误检查base_url是否写成了https://taotoken.net/api。4.2 任务一单步搜索输入“搜索《哪吒2》的最新票房数据”预期 Agent 输出一轮 Thought-Action调用network_search拿到结果后输出 Answer。成功时的推理日志大致是这样Thought: 我需要使用网络搜索工具来获取《哪吒2》的最新票房数据。 Action: network_search Action Input: {query: 哪吒2 最新票房}工具返回后第二轮输出Thought: 我已经获得了票房数据可以回答问题了。 Answer: 根据搜索结果《哪吒2》的最新票房为...这个任务对大多数模型都不难。实测下来DeepSeek 推理模型、DeepSeek 通用模型、GPT-4o-mini 都能通过。4.3 任务二搜索加邮件发送输入“搜索《哪吒2》的详细剧情介绍并发送到 testexample.com”预期 Agent 先调搜索再调邮件工具。成功时的两轮工具调用第一轮 Thought: 我需要先搜索剧情介绍。 Action: network_search Action Input: {query: 哪吒2 剧情介绍} 第二轮 Thought: 我已经获得剧情介绍现在需要发送邮件。 Action: email Action Input: {recipient: testexample.com, subject: 哪吒2剧情介绍, body: ...}这个任务开始出现分化。DeepSeek 推理模型大部分时候能完成但偶尔会把两个 Action 写在同一轮响应里导致解析失败。DeepSeek 通用模型和 GPT-4o-mini 表现更稳定。4.4 任务三条件分支加邮件这是最复杂的任务需要 Agent 先搜索票房判断是否超过 50 亿再决定走搜索上映时间还是创作加油文字最后发邮件。成功时的推理链应该包含至少四轮第一轮搜索票房 第二轮判断票房数值决定分支 第三轮执行分支任务搜索上映时间 或 创作文字 第四轮发送邮件实测结果对照模型任务一任务二任务三gpt-4o-mini通过通过通过deepseek-chat通过通过通过deepseek-reasoner通过大部分通过大部分失败qwen-2.5:7b通过通过失败DeepSeek 推理模型在任务三上的典型失败现象是一次性输出包含多个 Thought-Action 的完整过程或者遗漏“发送邮件”步骤却给出看似完整的最终答案。5. 本篇常见错误排查这一节对照真实报错给出排查路径。5.1 401 Unauthorized报错信息openai.AuthenticationError: Error code: 401 - {error: {message: Invalid API key}}原因通常是 Key 没读到或复制不完整。检查.env文件里TAOTOKEN_API_KEY是否有多余空格load_dotenv()是否在读取环境变量之前调用。如果你在 Jupyter 里跑重启 kernel 后再试。5.2 local proxy failed / Connection error报错信息openai.APIConnectionError: Connection error.先确认base_url写的是https://taotoken.net/api不要带/v1或/chat/completions。然后检查本机网络是否能正常访问该地址。如果你在公司内网确认没有拦截外部 HTTPS 请求。5.3 reading choices 报错报错信息KeyError: choices这通常说明返回体不是标准的 OpenAI 格式可能是模型 ID 写错了或者请求被重定向到了错误端点。去模型对话页面确认你用的模型 ID 是否存在然后检查base_url是否完整。5.4 OAuth / 鉴权失败如果你用的是某些需要 OAuth 的客户端报错可能长这样OAuth token exchange failed: invalid_grantTaoToken 的 API 调用走的是 API Key 鉴权不需要 OAuth。如果你在 Claude Code 或 Codex 类工具里配置确保填的是 Base URL、API Key、Model ID 三件套{ base_url: https://taotoken.net/api, api_key: sk-你的key, model: deepseek-chat }Codex 的auth.json里对应字段是OPENAI_BASE_URL和OPENAI_API_KEYModel ID 写在配置的model字段。Cline MCP 的配置类似在 settings 里填 Base URL 和 Key模型名单独指定。CC Switch 切换配置时确认三件套都跟着切换不要只换 Key 不换 Base URL。5.5 JSON 解析失败导致工具调用中断报错信息[WARN] JSON 解析失败: {query: 哪吒2 票房,}推理模型有时会在 Action Input 末尾多写逗号或者用单引号。我在call_model里做了replace(, )的容错但更稳妥的做法是在提示词里强调“使用有效 JSON不要有尾随逗号”。如果某个模型频繁出错可以在解析失败时追加一轮修正提示让模型重新输出。5.6 达到最大步数仍未完成报错信息对不起我在指定步骤数内无法完成任务。这说明 Agent 在max_steps内没有收敛。先看推理日志判断是模型一直在重复调用同一个工具还是陷入了无效循环。如果是推理模型很可能是它把多个步骤合并输出导致图只走了一轮就结束了。解决办法是降低 temperature、强化提示词里的“一次只输出一个 Action”或者换用通用模型做任务规划。6. 评测结论与接入入口把三个任务跑完我对 DeepSeek 推理模型在 LangGraph Agent 里的表现有了比较清晰的判断。推理模型擅长的是“问题本身复杂”的任务比如根据搜索结果写一份有深度的报告、解决复杂数学问题、生成有逻辑的长文本。但 ReAct Agent 需要的是“基于复杂上下文和对话历史做动态多步规划”这恰好是当前推理模型的弱项。它容易不遵循逐步推理指令、基于假设推理、遗漏步骤、输出格式不稳定。所以我的实际建议是Agent 的任务规划节点用通用模型比如 DeepSeek 通用模型或 GPT-4o-mini推理模型可以作为其中一个 Tool专门负责需要深度思考的子任务。这样既发挥了推理模型的长处又避开了它在结构化输出和动态规划上的短板。如果你要复现这套评测统一走 TaoToken 的 API 通道最省事。API Key 在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 创建接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 有各语言示例。想先手动验证模型输出可以去模型对话页面https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。长期跑 Agent 或编码类任务Coding Plan 更合适https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。最后留一个实用技巧在call_model里把每轮推理的原始输出打到日志里不要只看最终 Answer。推理模型的失败往往藏在中间步骤的格式偏差里只看最终结果很容易误判它“成功了”。
阅读完成 · 觉得有帮助?