1. DeepAgents 深度分析任务为什么总在第三步跑偏
很多人第一次跑 DeepAgents 的时候,都会经历一个相似的曲线:单表查询、简单统计这类任务表现很稳,一旦问题变成「按国家统计收入并生成一份带图表的 HTML 报告」,智能体就开始反复列目录、重复取 schema、SQL 写一半又推翻重来。这不是模型不够聪明,而是长周期任务缺少两样东西:可复用的能力模块和稳定的思维框架。
DeepAgents 是 LangChain 团队开源的一个智能体工具集,它构建在 LangChain 的智能体抽象层之上,底层跑在 LangGraph 的运行时环境里。你可以把它理解成一个已经搭好的「高级机器人模型」:内置了write_todos/read_todos规划工具、可插拔的文件系统(read_file/write_file/edit_file)、子智能体委托机制,以及自动上下文摘要。而它真正解决深度分析痛点的两个核心设计,就是skills 机制和系统提示词工程。
skills 是智能体的「能力插件」,每个 skill 是一个目录下的SKILL.md文件,用 Markdown 描述「什么时候用这个技能」「工作流程是什么」「质量红线在哪」。系统提示词则是智能体的「思维框架」,它不直接调用 skill,而是通过定义执行步骤,让大模型在推理过程中隐式地把用户问题路由到对应的 skill 上。整个项目里你找不到一行call_skill("query-writing")这样的代码,调用链路全部藏在提示词和框架的自动加载逻辑里。
这篇文章面向的是已经能在本地跑通 DeepAgents、但想把模型入口统一到一个 Key 上的开发者。我会先拆解 skills 编排和系统提示词的设计要点,给出可复制的SKILL.md片段和AGENTS.md模板,然后重点讲怎么把 endpoint 改到 TaoToken,并用一次真实请求验证回显、排查 401 和reading choices这类报错。适合谁:手上有 text-to-SQL 或数据分析类 Agent、正在被多模型 Key 管理折磨的人。
2. 拆解 skills 编排与系统提示词的隐式调用链路
2.1 一个 skill 目录长什么样
在 DeepAgents 里,skills 不是函数,是文档。框架会把整个skills/目录传给create_deep_agent,然后由模型根据系统提示词里的步骤描述,自己决定读哪个SKILL.md。一个典型的深度分析项目,skills 目录大概是这样:
skills/ ├── query-writing/SKILL.md ├── report-generation/SKILL.md ├── schema-exploration/SKILL.md └── ui-ux-pro-max/SKILL.md每个SKILL.md用 YAML front matter 声明名称和描述,正文写工作流。以query-writing为例,它的触发词是「查询」「统计」「多少」,工作流是「识别表 → 获取架构 → 编写 SQL → 执行 → 格式化答案」。而report-generation的触发词是「报告」「报表」「可视化」,工作流是「理解需求 → 查询数据 → 深度分析 → 生成 HTML」。
这里有个容易被忽略的点:skill 的 description 字段就是路由依据。模型在规划阶段会扫描所有 skill 的 name 和 description,判断当前子任务该用哪个。所以 description 写得越具体,路由越准。我见过有人把 description 写成「用于数据处理」,结果模型在「生成报告」和「查询数据」之间反复横跳,就是因为描述太模糊。
2.2 系统提示词如何「隐式」调用 skill
关键机制在于:系统提示词里定义的任务步骤,恰好和 skill 的工作流对齐。比如AGENTS.md里写「第一步:思考与规划,输出分析计划;第二步:严格按计划执行;第三步:总结与回答」,而query-writing的正文写「复杂查询先用write_todos分解任务」。模型在执行第二步时,读到「分解任务」这个动作,就会去加载query-writing/SKILL.md,然后照着里面的步骤走。
整个链路是这样的:
用户问题 → 模型读 AGENTS.md 的执行流程 → 规划阶段扫描 skills 的 description → 匹配到 query-writing / report-generation → 加载对应 SKILL.md 正文 → 按 SKILL.md 的工作流调用 tools(sql_db_schema / sql_db_query) → 汇总结果,按 AGENTS.md 的报告规范输出所以 skill 的触发不是代码级的 if-else,而是语义级的提示词路由。这也解释了为什么改 skill 的 description 比改代码更有效——你改的是模型的判断依据。
2.3 系统提示词模板:把「防跑偏」写进去
长任务最大的敌人是循环调用和重复操作。AGENTS.md里必须显式禁止这些行为。下面是我实测下来比较稳的一份模板,你可以直接改:
# 角色定位 你是 SQL 数据库交互专家,负责把自然语言问题转化为分析结论。 # 核心执行流程(强制三步走) ## 第一步:思考与规划 在执行任何工具前,先输出分析计划,包含需求理解和执行步骤。 复杂问题使用 write_todos 分解为最多 5 个步骤。 ## 第二步:严格按计划执行 逐步推进,不跳过、不重复。禁止重复调用同一工具 (如多次获取表列表、多次获取同一表架构)。 禁止重复执行相同的 SQL 语句。任务完成立即停止。 ## 第三步:总结与回答 汇总结果。若触发词为「报告/报表/可视化」, 直接输出完整 HTML,用 <!-- REPORT_HTML_START --> 和 <!-- REPORT_HTML_END --> 包裹,必须包含 Chart.js 图表, 禁止用 Markdown 生成报告,禁止调用上传工具。 # 数据库操作指南 - 默认 LIMIT 100,只查必要列,禁止 SELECT * - 仅限只读 SELECT,禁止 INSERT/UPDATE/DELETE/DROP/ALTER - 失败重试最多 2 次,超限后向用户说明 # 技能说明 技能是文档,不是工具调用。你无需等待调用, 直接按 SKILL.md 描述的工作流执行即可。注意最后一段「技能是文档,不是工具调用」。这句话是很多人的坑:模型会误以为 skill 是一个需要invoke的工具,然后卡在「等待技能返回」的状态。明确告诉它 skill 是文档,它才会直接照着执行。
2.4 创建 agent 时的加载点
回到代码层,create_deep_agent的调用把这几样东西串起来:
from deepagents import create_deep_agent from langchain_community.utilities import SQLDatabase from langchain_community.agent_toolkits import SQLDatabaseToolkit db = SQLDatabase.from_uri(uri, sample_rows_in_table_info=3) toolkit = SQLDatabaseToolkit(db=db, llm=model) sql_tools = toolkit.get_tools() agent = create_deep_agent( model=model, memory=[os.path.join(current_dir, "AGENTS.md")], # 系统提示词 skills=[os.path.join(current_dir, "skills/")], # 技能目录 tools=sql_tools, # SQL 工具集 backend=FilesystemBackend(root_dir=current_dir), )memory加载系统提示词,skills加载整个技能目录,tools提供 SQL 执行能力,backend决定文件系统落在哪。四者缺一不可:没有memory,模型没有执行框架;没有skills,模型不知道复杂任务怎么拆;没有tools,skill 里的sql_db_query就是空谈。
3. 把 DeepAgents 的 endpoint 改到 TaoToken 统一 Key
3.1 为什么要统一模型入口
本地跑通之后,最烦的往往不是 Agent 逻辑,而是 Key 管理。text-to-SQL 用一个模型,报告生成想换一个,子智能体又想用第三个,于是环境变量里堆了五六个*_API_KEY,换个环境就漏配一个。把 endpoint 统一到 TaoToken,好处是一个 Key 走天下,模型切换只改model字段,Base URL 和 Key 不动。
TaoToken 的 API 地址是https://taotoken.net/api,官网在https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=。下面所有配置都围绕这个 Base URL 展开。
3.2 可复制的配置片段
DeepAgents 底层用的是 LangChain 的模型抽象,所以配置方式和 LangChain 一致。推荐用环境变量 + 代码读取的方式,避免 Key 硬编码。
先建一个.env:
TAOTOKEN_API_KEY=sk-你的Key TAOTOKEN_BASE_URL=https://taotoken.net/api然后在代码里构造模型。如果你用的是 OpenAI 兼容接口:
import os from dotenv import load_dotenv from langchain_openai import ChatOpenAI load_dotenv() model = ChatOpenAI( model="claude-sonnet-4-5", # 按需替换 Model ID api_key=os.getenv("TAOTOKEN_API_KEY"), base_url=os.getenv("TAOTOKEN_BASE_URL"), timeout=120, max_retries=2, )如果你更习惯用配置文件管理,可以写一个config.toml:
[llm] base_url = "https://taotoken.net/api" api_key_env = "TAOTOKEN_API_KEY" model = "claude-sonnet-4-5" timeout = 120 max_retries = 2读取时:
import os, tomllib from langchain_openai import ChatOpenAI with open("config.toml", "rb") as f: cfg = tomllib.load(f)["llm"] model = ChatOpenAI( model=cfg["model"], api_key=os.environ[cfg["api_key_env"]], base_url=cfg["base_url"], timeout=cfg["timeout"], max_retries=cfg["max_retries"], )这里的三件套必须写全:Base URL是https://taotoken.net/api,Key从环境变量读,Model ID按你实际要用的模型填。少任何一个,请求都会在鉴权或路由阶段失败。
3.3 如果你用 Claude Code 或 Cline
有些人是通过 Claude Code 或 Cline 这类客户端来调 DeepAgents 的模型。这类工具通常支持自定义 Base URL。以 Claude Code 为例,在 settings 里配置:
{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_API_KEY": "sk-你的Key" } }Cline 的 MCP 配置里,同样是 Base URL + Key + Model ID 三件套。Codex 的auth.json则把 Key 写在OPENAI_API_KEY字段,Base URL 走OPENAI_BASE_URL。不管哪个客户端,核心都是把默认的官方地址替换成 TaoToken 的 API 地址,Key 换成 TaoToken 的 Key。
3.4 改完之后先别急着跑全流程
配置改完,不要直接上完整的深度分析任务。先用一个最小请求验证连通性,确认 Base URL 和 Key 生效,再跑 Agent。下一节给验证方法。
4. 验证请求与成功回显:一次最小连通性测试
4.1 用 curl 打一发
最直接的验证方式是绕过 Agent,直接打模型接口:
curl -s https://taotoken.net/api/v1/chat/completions \ -H "Authorization: Bearer $TAOTOKEN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "claude-sonnet-4-5", "messages": [{"role": "user", "content": "只回复两个字:连通"}], "max_tokens": 16 }'成功的回显长这样:
{ "id": "chatcmpl-xxx", "object": "chat.completion", "choices": [ { "index": 0, "message": {"role": "assistant", "content": "连通"}, "finish_reason": "stop" } ], "usage": {"prompt_tokens": 12, "completion_tokens": 2, "total_tokens": 14} }看到choices[0].message.content有内容,说明 Base URL、Key、Model ID 三件套都对。如果choices是空数组,或者报reading choices错误,往下看排障部分。
4.2 在 LangChain 层验证
curl 通了之后,再验证 LangChain 封装层:
from langchain_openai import ChatOpenAI import os model = ChatOpenAI( model="claude-sonnet-4-5", api_key=os.getenv("TAOTOKEN_API_KEY"), base_url=os.getenv("TAOTOKEN_BASE_URL"), ) resp = model.invoke("只回复两个字:连通") print(resp.content)如果这里报错但 curl 正常,多半是base_url少了/api后缀,或者环境变量没加载进来。LangChain 的ChatOpenAI会自动在base_url后面拼/chat/completions,所以base_url应该是https://taotoken.net/api,而不是https://taotoken.net/api/v1。
4.3 跑一次带 skill 的最小任务
连通性没问题后,跑一个只触发schema-exploration的简单任务:
result = agent.invoke({ "messages": [{"role": "user", "content": "这个数据库里有哪些表?"}] }) print(result["messages"][-1].content)预期行为:模型先输出一段简短计划,然后调用sql_db_list_tables,再对每张表调sql_db_schema,最后汇总成表清单。如果你在日志里看到它读了skills/schema-exploration/SKILL.md,说明 skill 路由生效了。这一步跑通,再上「生成 HTML 报告」这种复杂任务就稳了。
5. 常见报错排查:401、local proxy failed、reading choices
5.1 401 Unauthorized
最常见的 401 是 Key 没传对。检查三处:环境变量名是否和代码里读的一致;Key 是否带了多余空格或换行(从网页复制时经常带上);请求头是否是Authorization: Bearer sk-xxx格式。如果 curl 能通但代码报 401,八成是load_dotenv()没执行,或者.env文件不在当前工作目录。
还有一种隐蔽情况:Key 是对的,但 Base URL 写成了https://taotoken.net(少了/api),请求打到了官网而不是 API 网关,返回的也是 401 或 404。记住 API 地址是https://taotoken.net/api。
5.2 local proxy failed
这个报错通常出现在客户端类工具(Claude Code、Cline)里,意思是本地代理层连不上上游。排查顺序:先确认 Base URL 没有多余路径;再确认本机网络能访问taotoken.net;然后检查客户端是否配置了额外的代理设置,如果有,先关掉再试。这个错误和模型本身无关,纯粹是网络链路问题。
5.3 reading choices 报错
reading choices或list index out of range这类错误,本质是响应体里choices字段为空或结构不符。原因通常有三个:Model ID 写错了,上游返回了错误信息而不是正常 completion;max_tokens设得太小,模型还没输出就被截断;请求体格式不对,比如messages里 role 写成了system但上游不支持。
排查方法:把max_tokens调到 256 以上,用 curl 直接打,看原始响应。如果原始响应里choices是空的但error字段有内容,那就是 Model ID 或参数问题。对照一下你用的 Model ID 是否在 TaoToken 支持的列表里。
5.4 OAuth 相关报错
如果你用的是 Claude Code 这类带 OAuth 流程的客户端,可能会遇到OAuth token expired或invalid_grant。这类报错和 API Key 模式是两套鉴权。解决办法是切到 API Key 模式:在客户端设置里把鉴权方式从 OAuth 改成 API Key,填入 TaoToken 的 Key,Base URL 填https://taotoken.net/api。改完重启客户端,让它重新读取配置。
5.5 skill 没被触发
如果任务跑完了但行为不对,比如该生成 HTML 却输出了 Markdown,先检查AGENTS.md里有没有写清楚触发词和输出格式。再检查对应SKILL.md的 description 是否足够具体。最后看日志里模型有没有读那个 skill 文件。如果没读,说明路由没匹配上,把 description 改得更贴近用户可能的问法。
6. 把统一 Key 用顺之后的几个实操建议
配置跑通只是开始,真正让 DeepAgents 稳定输出深度分析结果,还有几个细节值得注意。
第一,AGENTS.md里的「禁止重复调用」规则要写得足够硬。我试过把「禁止重复获取同一表架构」单独拎出来加粗,循环调用的情况明显减少。模型对否定指令的敏感度,取决于指令的位置和措辞强度。
第二,skill 的粒度别太细。一个 skill 对应一类任务就够了,拆得太碎会导致模型在多个 skill 之间反复切换,反而增加 token 消耗和跑偏概率。query-writing和report-generation这种粒度是比较合适的。
第三,报告生成的 HTML 输出,一定要在AGENTS.md里明确「直接输出到对话,禁止调用上传工具」。否则模型会尝试调 MinIO 之类的上传工具,然后卡在工具不可用的状态。
第四,统一 Key 之后,模型切换成本极低。你可以准备两套配置:复杂分析用能力强的模型,简单查询用响应快的模型,在create_deep_agent时按任务类型传不同的model实例。Base URL 和 Key 都不用动。
如果你还没拿到 Key,可以去 TaoToken 的 API Keys 页面创建一个,接入文档里有各客户端的详细配置示例。验证模型是否可用,用模型对话页面直接测最快。长期跑编码类或 Agent 类任务,Coding Plan 的额度更划算。把 endpoint 统一之后,你会发现 DeepAgents 的 skills 编排和系统提示词设计才是真正值得花时间打磨的部分,Key 管理这种杂事,一次配好就不用再管了。