1. 从一次跑不通的 deep agent 说起
langchain v1.0 的 deep agent 把 Multimodal、backend、subagent 三块能力拆得比较清楚,但真到本地跑最小闭环时,坑往往不在模型本身,而在三件事:多模态消息格式对不上、backend 的 root_dir 写错、subagent 的 tools 没注册进去。这篇就按我实际调试的顺序,把这三块配置和验证动作串一遍,目标是让你在本地 AI 工具链里跑通一个能读图、能落盘、能派子智能体的 deep agent。
适合谁看:已经装好 Python 环境、手里有至少一个支持多模态的模型 Key、想用 deep agent 做研究型或编码型 Agent 的开发者。如果你还没配过统一 Key 通道,建议先把 TaoToken 的 API Key 拿到手,后面所有模型调用都走同一个 base_url,省得在多个厂商之间来回切。
核心检索词先摆出来:langchain deep agent 的 Multimodal 负责让 Agent 接收图片输入,backend 决定 Agent 的文件系统落在哪,subagent 负责把重上下文任务隔离出去。三者组合起来,才是一个能长期跑的 Agent 骨架。
2. TaoToken 前置:统一 Key 与 API 通道
deep agent 默认走 OpenAI 兼容协议,所以只要有一个兼容 OpenAI 的 base_url 和 Key,就能把模型接进来。TaoToken 在这里的角色是统一通道:你不需要为每个模型单独记一套地址和 Key,改base_url和api_key两个字段即可。
官网入口:https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=
API 地址(不带 UTM):https://taotoken.net/api
拿 Key 的路径是 console 里的 API Keys 页面,创建后复制一次,后面写进.env。如果你后面要长期跑编码类 Agent,可以顺带看下 Coding Plan,它更适合高频调用场景;只是验证模型连通性的话,模型对话页面就够用。
注意:Key 只写进本地
.env,不要硬编码进代码,也不要提交到 git。
3. 可复制配置:config.toml 与 settings.json 骨架
先把配置文件立起来,后面代码只读环境变量。我习惯在项目根目录放一个config.toml管模型通道,再放一个settings.json管 Agent 行为。
# config.toml [llm] provider = "openai-compatible" base_url = "https://taotoken.net/api" api_key_env = "TAOTOKEN_API_KEY" model = "glm-4.5v" timeout = 60 [agent] name = "deep-agent-local" max_iterations = 12 verbose = true [backend] type = "filesystem" root_dir = "./workspace" virtual_mode = true{ "settings": { "multimodal": { "enabled": true, "image_max_bytes": 5242880, "supported_types": ["image/jpeg", "image/png"] }, "subagents": { "enabled": true, "max_concurrent": 2, "inherit_tools": false }, "logging": { "level": "INFO", "save_trace": true } } }.env里只放三行:
TAOTOKEN_API_KEY=你的Key TAVILY_API_KEY=你的搜索Key LANGSMITH_API_KEY=可选读配置的代码可以这样写,避免路径写死:
import os, tomllib from pathlib import Path ROOT = Path(__file__).resolve().parent.parent with open(ROOT / "config.toml", "rb") as f: cfg = tomllib.load(f) base_url = cfg["llm"]["base_url"] api_key = os.environ[cfg["llm"]["api_key_env"]] model_name = cfg["llm"]["model"] workspace = (ROOT / cfg["backend"]["root_dir"]).resolve() workspace.mkdir(parents=True, exist_ok=True)这里有个容易忽略的点:root_dir用相对路径时,基准是当前工作目录而不是脚本目录,所以上面用ROOT拼绝对路径,能避免 backend 把文件写到意想不到的地方。
4. Multimodal 接入:让 deep agent 读图
deep agent 本身不限制输入类型,只要底层 LLM 支持多模态,消息里就能带image_url。用 OpenAI 兼容通道时,消息体长这样:
from langchain_openai import ChatOpenAI from deepagents import create_deep_agent model = ChatOpenAI( api_key=api_key, base_url=base_url, model=model_name, ) agent = create_deep_agent( model=model, system_prompt="你是一个会读图的研究助手,先描述图片内容,再给出结论。", ) result = agent.invoke({ "messages": [{ "role": "user", "content": [ {"type": "text", "text": "这张图里是什么?用三句话描述。"}, {"type": "image_url", "image_url": {"url": "https://example.com/demo.jpg"}}, ], }] }) print(result["messages"][-1].content)如果你用的是本地图片,先转 base64 再塞进url字段,格式是data:image/jpeg;base64,xxxx。实测下来,图片超过 5MB 时部分通道会直接拒掉,所以settings.json里那个image_max_bytes不是摆设,超限时提前压缩比等报错强。
验证动作:跑完后看返回内容里有没有对图片的具体描述。如果只回了一句“我无法查看图片”,八成是模型本身不支持多模态,换glm-4.5v这类视觉模型再试。
5. backend 选型:文件到底落在哪
deep agent 的 backend 决定 Agent 的文件操作工具(ls、read_file、write_file、edit_file、glob、grep)作用在哪个存储上。四种 built-in backend 的差别,用一张表说清:
| backend | 存储位置 | 是否持久 | 适用场景 |
|---|---|---|---|
| StateBackend | Agent 状态内 | 否 | 临时文件、一次性任务 |
| FilesystemBackend | 本地磁盘 | 是 | 需要落盘的工作区 |
| LocalShellBackend | 本地磁盘 + shell | 是 | 需要执行命令的任务 |
| StoreBackend | LangGraph store | 取决于 store | 多会话共享、数据库后端 |
StateBackend 是默认值,不传backend参数就是它,适合只想让 Agent 在内存里倒腾文件的场景。FilesystemBackend 要显式指定root_dir:
from deepagents.backends import FilesystemBackend backend = FilesystemBackend(root_dir=str(workspace), virtual_mode=True) agent = create_deep_agent(model=model, backend=backend)virtual_mode=True会把文件操作限制在root_dir内,防止 Agent 写到工作区外面。LocalShellBackend 在此基础上多了执行 shell 的能力,配置时注意env和inherit_env:inherit_env=True会继承当前进程环境变量,方便但也要留意别把敏感变量带进去。
StoreBackend 的落盘位置由 store 决定,用InMemoryStore就是内存,用 Redis 或 Postgres 就是数据库。示例:
from deepagents.backends import StoreBackend from langgraph.store.memory import InMemoryStore agent = create_deep_agent( model=model, backend=StoreBackend(namespace=lambda rt: ("default",)), store=InMemoryStore(), )验证动作:让 Agent 写一个test.txt再读出来,然后去workspace目录看文件是否真的存在。FilesystemBackend 下能看到实体文件,StateBackend 下看不到,这就是最直接的区分方式。
6. subagent 配置:把重上下文任务隔离出去
subagent 解决的是 context bloat 问题:主 Agent 只拿子 Agent 的总结,不把中间过程全塞进上下文。什么时候该用,什么时候不该用,对照下面这张表:
| 场景 | 是否用 subagent |
|---|---|
| 多步骤、上下文容易爆 | 用 |
| 需要专用工具或领域指令 | 用 |
| 主 Agent 只做高层协调 | 用 |
| 简单单步任务 | 不用 |
| 依赖中间状态 | 不用 |
| 创建成本大于收益 | 不用 |
普通 subagent 用字典声明即可:
research_subagent = { "name": "research-agent", "description": "用于深入检索资料", "system_prompt": "你是一个严谨的研究员,输出带来源的结论。", "tools": [internet_search], } agent = create_deep_agent( model=model, subagents=[research_subagent], )如果子 Agent 本身就是一个编译好的 graph,用CompiledSubAgent包一层:
from deepagents import CompiledSubAgent custom_graph = create_deep_agent( model=model, tools=[internet_search], system_prompt="你是专门的检索 Agent。", ) custom_subagent = CompiledSubAgent( name="internet-search", description="专门做网络检索", runnable=custom_graph, ) agent = create_deep_agent(model=model, subagents=[custom_subagent])验证动作:给主 Agent 一个需要检索的问题,观察返回里有没有出现子 Agent 的调用痕迹。如果主 Agent 自己把活干了,说明description写得不够明确,主 Agent 没意识到该派子 Agent。
7. 本篇常见错排查
报错一:KeyError: 'TAOTOKEN_API_KEY'.env没加载或变量名拼错。确认load_dotenv的路径指向项目根目录,变量名和config.toml里的api_key_env一致。
报错二:图片输入返回“不支持”模型不是多模态模型。换glm-4.5v这类视觉模型,或检查image_url字段是否写成了image。
报错三:backend 写文件写到项目根目录root_dir用了相对路径且基准不对。改成绝对路径,或确认virtual_mode=True已开启。
报错四:subagent 没被调用description太模糊,主 Agent 判断不需要派发。把子 Agent 的职责写具体,比如“用于检索最新新闻并返回带链接的摘要”。
报错五:LocalShellBackend 执行命令失败env里的 PATH 不完整。补上/usr/bin:/bin,或设inherit_env=True继承当前环境。
8. 继续往下走
Multimodal、backend、subagent 三块跑通后,deep agent 的最小闭环就成立了:能读图、能落盘、能派子 Agent。下一步可以接 Async subagents,把并发检索和长任务拆开跑。
如果你还没配好统一 Key 通道,建议先去 API Keys 页面把 Key 建好,再对照接入文档把base_url换成https://taotoken.net/api。验证模型连通性用模型对话页面最快,长期跑编码类 Agent 的话,Coding Plan 的额度模型更划算。配置这东西,跑通一次之后就是复制粘贴的事,卡住的地方多半在路径和字段名上,对着上面的排查表过一遍,基本能定位。