DeepAgents(三):Quick Start:装完到跑通,只定 4 个决定
Quick Start:装完到跑通,只定 4 个决定
装上 deepagents 之后有 2 件事值得先弄清:一条 pip install 到底往环境里放了什么,create_deep_agent 那 18 个参数里又有哪几个是第一次装配必须定的。这篇按「装、定、验」3 步走,共 7 节,读完约 5 分钟。
要准备的只有 2 样:装好 deepagents,以及一个可用的模型凭据。走完你会得到一个能跑通的 Agent,和 3 个随时能自查装配结果的手法。依赖树读自包元数据,装配结果用一个假模型捕获,全程零 API 费用;环境是 deepagents 0.7.13 与 langchain 1.4.0,数字都是本机实测,引用官方原话的地方标了出处。
一、先把三步的结论摆出来
| 步 | 做什么 | 结论 |
|---|---|---|
| 装 | pip install deepagents |
11 条依赖,7 条硬拉,捎带 3 个包 |
| 定 | 给 create_deep_agent 传参 |
18 个参数,0 个必需,要定 4 个 |
| 验 | 确认装配结果 | 模型侧 8 个工具 / 11 个中间件实例 / 图里 5 个节点 |
二、装:一条命令,捎带 3 个包
用标准库 importlib.metadata 读 deepagents 的元数据,Requires-Dist 一共 11 条。判断标准很硬:条目里出现 extra == 就是可选,没有就是硬依赖。
| 类型 | 条数 | 内容 |
|---|---|---|
| 硬依赖 | 7 | langchain / langchain-core / langchain-anthropic / langchain-google-genai / langsmith / packaging / wcmatch |
| extra | 4 | 对应官方声明的 3 组:aws / quickjs / video |
所以这条命令会把 langchain-anthropic、langchain-google-genai、langsmith 一起装进来,3 个都是完整集成包。
from importlib import metadata
name = "deepagents"
d = metadata.distribution(name)
for r in d.requires or []:
if "extra ==" not in r:
print(r)
硬依赖只说明 pip 会装它,不说明程序会用到它。在一个干净子进程里先 import langchain 与 import langgraph(harness 的前置),模块数只有 802;再 import deepagents 并建一次 Agent,涨到 3591。
这 3 个模块都在里面:langchain_anthropic、langchain_google_genai、langsmith。它们卸不掉,harness 要开箱支持 Anthropic 与 Gemini 的 prompt caching,索性把集成包硬拉进来。想卸只剩一条路:不用官方那份 create_deep_agent,自己按顺序装配中间件。
三、定:18 个参数里只定 4 个
用 inspect.signature 数出来是 18 个参数,没有一个是必需的。第一次装配要定的是下面 4 个。
| 决定 | 落在哪个参数 | 不传时会怎样 | 一句话 |
|---|---|---|---|
| 模型怎么给 | model(第 1 位) |
只告警,仍能跑 | 1.0 起从「可省略」改成「必须给」 |
| 工具从哪来 | tools(第 2 位) |
8 个内置工具 | 只能加,减不掉 |
| 提示词给多少 | system_prompt(第 3 位) |
0 字符 | 给多少就是多少,逐字还原 |
| backend | backend(第 9 位) |
StateBackend |
文件在图的 state 里,不落磁盘 |
剩下 14 个分 2 类:扩展面有 subagents / skills / memory / middleware / permissions / interrupt_on / response_format,运行时钩子有 state_schema / context_schema / checkpointer / store / cache / debug / name。它们每一个都能单独撑起一篇,但都不是「不决定就跑不起来」的东西。
模型有 2 种给法。给名字形如 openai:gpt-5.5,走的是 init_chat_model,要真的解析 provider 并找凭据;本机没有 OpenAI 凭据,实测报 OpenAIError: Missing credentials。给实例更省事,但必须是 BaseChatModel 子类,包装一层就会踩到第五节那个报错。
完全不传 model 现在只抛一条弃用告警,告警里有这两句:
Passing
model=Nonetocreate_deep_agentis deprecated and will be removed indeepagents==1.0.0. Themodelparameter type will change fromBaseChatModel | str | NonetoBaseChatModel | str.
现在不传能跑,1.0 起变成必填,今天就显式指定等于给将来省一次改版。
backend 不传时用的哪个,源码给得很直接:
# create_deep_agent 源码第 367 行
backend = (
backend if backend is not None
else StateBackend())
deepagents.backends 一共导出 6 个 backend 类。默认的 StateBackend 是内存态,文件存在图的 state 里,不落磁盘;它也不实现沙箱协议,所以 execute 工具在默认装配下不会出现在模型侧。
提示词这一条最反直觉:给多少,模型侧就是多少。
system_prompt 传入 |
模型侧 system 消息 | 模型侧工具数 |
|---|---|---|
| 不传 | 0 字符 | 8 |
| 8 字符 | 8 字符 | 8 |
| 27 字符 | 27 字符 | 8 |
一个字符都不多。官方 docstring 的原话是:「With system_prompt=None and no profile base_system_prompt or system_prompt_suffix, the model receives an empty authored system prompt.」
工具这一项实测很干脆:不传 tools,模型侧 8 个内置工具;传 1 个自己的工具,模型侧 9 个。内置的一个没少,差集里「消失」那一半是空的。官方原话是:「Passing tools here is additive — it never removes a built-in.」
要减掉内置工具得换口子,官方给的路径有 2 条:在 harness profile 里配 excluded_tools,或者自己传一个 FilesystemMiddleware 顶掉默认那份。知道「加的入口不等于减的入口」能省一次试错。
4 个决定一次配好:
import deepagents as da
def my_tool(q: str) -> str:
"""查库存。"""
return "ok"
agent = da.create_deep_agent(
model="openai:gpt-5.5",
system_prompt="先给结论。",
tools=[my_tool],
)
换任意 BaseChatModel 实例、把 tools 去掉,装配结果的数字都还是上面那些。
四、验:3 个手法看清装了什么
跑通之后还要能确认装配结果。下面 3 个手法各看一个面,都不发真实请求。
手法一,放个假模型进去:继承 BaseChatModel,覆写 bind_tools 记下工具清单,在 _generate 里量 system 消息长度。实测模型侧 8 个工具:ls / read_file / write_file / edit_file / delete / glob / grep / task,system 消息 0 字符。它看的是模型侧,不看源码也不看文档。
手法二,给中间件打补丁数实例:默认装配创建 11 个实例,主 Agent 的 5 个,默认 general-purpose 子 Agent 的 5 个,加 1 个 SubAgentMiddleware 桥接。也就是说默认装配里躺着 2 套完整中间件。补丁留着还能做因果对照,加一个参数看装配表多什么:
| 加的参数 | 中间件实例数 | 新增 |
|---|---|---|
| 什么都不加 | 11 | — |
skills=["/skills/"] |
13 | SkillsMiddleware 2 个 |
memory=["/memory/AGENTS.md"] |
12 | MemoryMiddleware 1 个 |
interrupt_on={"edit_file": True} |
14 | HumanInTheLoopMiddleware 3 个 |
同一个参数在不同位置的加法不一样:skills 同时装进主 Agent 与子 Agent,memory 只装进主 Agent。这种差异凭文档推不出来,补丁跑一遍就出来了。
手法三,读编译后的图:agent.get_graph() 实测 5 个节点 6 条边,节点是 __start__ / model / tools / PatchToolCallsMiddleware.before_agent / __end__。去掉首尾 2 个标记节点,可执行 3 个。11 个中间件实例只落成 1 个节点,因为绝大多数中间件是钩子式的,挂在 before_agent 与 after_agent 上,不产生独立节点。
三者互不替代:spy 看不到装配过程,补丁看不到钩子有没有被真的调用,读图看不见钩子式中间件。问「谁收到了什么」用 spy,问「装了几个」用补丁,问「流程长什么样」用读图。
五、一次真实排查:报错跟真实原因差很远
有个报错值得单独讲。场景是把模型包一层,加个日志或计费之类的东西。包装对象对外看起来完全一样:invoke 在,bind_tools 在,直接调 invoke 也能跑,但 isinstance(包装对象, BaseChatModel) 是 False。
交给 create_deep_agent,报出来的是:
AttributeError: 'Spy' object has no attribute 'count'
根因是 resolve_model 用 isinstance 分流。是子类就走实例分支,不是子类就被当成模型名字符串,送进 apply_provider_profile,在那里读 model.count。排查时的一句话判据:isinstance(你的模型, BaseChatModel) 是不是 True。
六、这套默认装配适合什么,不适合什么
默认装配最大的优点是零配置可跑:不给模型名、不选 backend、不写提示词,8 个工具已经在手,循环就能转起来。适合学习和验证 harness 的行为,也适合任务边界清楚、跑完即弃的一次性脚本。
不适合的也很明确:
| 不适合的场景 | 原因 | 要改哪个决定 |
|---|---|---|
| 文件要跨进程留存 | StateBackend 是内存态 |
backend |
| 要跑 shell 命令 | 默认 backend 不实现沙箱协议 | backend |
| 说话风格要固定 | 提示词默认 0 字符 | 提示词 |
| 要收窄工具权限 | tools= 只能加,减不掉 |
工具 |
| 要长期跑、要能恢复 | 不传 checkpointer 就没有持久状态 |
那 14 个参数里的钩子 |
这 4 个决定覆盖的是「第一次跑通」,不覆盖「长期运行」。要断点续跑、要做人工审批,都属于那 14 个参数的地盘。
七、结论
4 个决定里,3 个的默认值属于「安全但需要你知道」,1 个是「现在不报错,将来会报错」。
| 决定 | 建议的起点 | 什么时候改 |
|---|---|---|
| 模型 | 显式给,不省略 | 换模型时只改 1 处 |
| 工具 | 先不传,够用 | 要减内置时换口子 |
| 提示词 | 至少给 1 句 | 说话方式出问题时补 |
| backend | 先不传 | 要落盘或跑 shell 时换 |
装配这件事的反直觉之处在这里:18 个参数看着很多,第一次要做的决定只有 4 个。而且每一个的判据都不在参数说明里,在实测结果里。