Typography

学习笔记

Deep Agents 学习02 Customization

发布于 # Deep Agents

真正决定一个 Deep Agent 上限的,通常不是“能不能运行”,而是“你能把它改造成什么样子”。当模型、工具、提示词、后端、审批和记忆开始组合时,它就不再只是一个示例里的 Agent,而会逐渐变成贴合你自己业务流程的执行系统。

为什么值得关注

create_deep_agent(...) 看起来只是一个构造函数,但它其实是整个能力装配口。你在这里做的每一个选择,都会直接影响 Agent 的推理方式、执行边界、知识来源和输出形态。也因此,这一层最值得系统地理解,而不是把参数当成零散开关去背。

可自定义的核心能力包括:

一个可配置的 Deep Agent,本质上是把“推理能力、执行能力、上下文管理、安全控制、知识加载方式”组合在一起。


create_deep_agent(...) 的核心入口

create_deep_agent(
    model: str | BaseChatModel | None = None,
    tools: Sequence[BaseTool | Callable | dict[str, Any]] | None = None,
    *,
    system_prompt: str | SystemMessage | None = None,
    middleware: Sequence[AgentMiddleware] = (),
    subagents: Sequence[SubAgent | CompiledSubAgent | AsyncSubAgent] | None = None,
    skills: list[str] | None = None,
    memory: list[str] | None = None,
    response_format: ResponseFormat[ResponseT] | type[ResponseT] | dict[str, Any] | None = None,
    backend: BackendProtocol | BackendFactory | None = None,
    interrupt_on: dict[str, bool | InterruptOnConfig] | None = None,
    ...
)

理解这个函数时,可以先把参数分成 5 组:


模型

先看模型这一层,是因为它决定了整个 Agent 的“思考方式”。同样一套工具和提示词,换一个模型,任务拆解方式、工具调用积极性、以及长流程里的稳定性都可能明显不同。所以这里不是单纯在配 provider,而是在决定 Agent 的基础推理能力。

模型是 Agent 的推理核心,负责:

最常用的传入方式

方式 1:直接传模型字符串

from deepagents import create_deep_agent

agent = create_deep_agent(model="openai:gpt-5.4")

这种方式最适合快速切换模型。

模型字符串通常使用:

provider:model

例如:

方式 2:先初始化模型,再传给 Agent

from langchain.chat_models import init_chat_model
from deepagents import create_deep_agent

model = init_chat_model(model="openai:gpt-5.4")
agent = create_deep_agent(model=model)

这种方式适合你需要自定义模型参数时使用,例如重试次数、超时、温度等。

方式 3:直接使用模型类

from langchain_openai import ChatOpenAI
from deepagents import create_deep_agent

model = ChatOpenAI(model="gpt-5.4")
agent = create_deep_agent(model=model)

这种方式适合你已经熟悉具体模型 SDK,并希望使用更细粒度的初始化参数。

模型选择建议

选择模型时优先考虑:

连接稳定性与重试

LangChain chat model 默认会对以下失败场景自动重试:

默认最大重试次数是 6。像 401404 这种客户端错误通常不会重试。

如果网络不稳定或任务运行时间较长,可以提高重试次数和超时时间:

from langchain.chat_models import init_chat_model
from deepagents import create_deep_agent

agent = create_deep_agent(
    model=init_chat_model(
        model="google_genai:gemini-3.1-pro-preview",
        max_retries=10,
        timeout=120,
    ),
)

什么时候提高 max_retries

适合提高到 10~15 的场景:

如果任务本身比较长,最好再配合持久化检查点,这样失败后还能保留进度。


工具

工具是 Agent 与外部世界交互的入口。

内置能力已经覆盖了:

自定义工具则用来补充业务动作,例如:

自定义工具示例

import os
from typing import Literal
from tavily import TavilyClient
from deepagents import create_deep_agent

tavily_client = TavilyClient(api_key=os.environ["TAVILY_API_KEY"])

def internet_search(
    query: str,
    max_results: int = 5,
    topic: Literal["general", "news", "finance"] = "general",
    include_raw_content: bool = False,
):
    """Run a web search"""
    return tavily_client.search(
        query,
        max_results=max_results,
        include_raw_content=include_raw_content,
        topic=topic,
    )

agent = create_deep_agent(
    model="google_genai:gemini-3.1-pro-preview",
    tools=[internet_search]
)

工具设计原则

设计工具时,重点关注 4 件事:

为什么 docstring 很重要

模型并不是“看代码逻辑后推断用途”,它更依赖工具描述来决定:

因此工具说明越清楚,调用效果越稳定。


系统提示词

系统提示词决定 Agent 的工作身份、目标和行为风格。

Deep Agents 自带一套基础系统提示词,用于指导内置能力,例如:

在此基础上,仍然应该补充一段与业务目标相关的系统提示词。

最小示例

from deepagents import create_deep_agent

research_instructions = """\
You are an expert researcher. Your job is to conduct \
thorough research, and then write a polished report. \
"""

agent = create_deep_agent(
    model="google_genai:gemini-3.1-pro-preview",
    system_prompt=research_instructions,
)

系统提示词负责什么

系统提示词主要控制:

编写建议

好的系统提示词通常要覆盖:

例如可以明确要求:


中间件

如果说模型、工具、提示词决定的是 Agent“会做什么”,那中间件决定的更像是“这些动作在执行过程中要不要被额外加工”。很多工程里真正难处理的不是单个功能点,而是日志、审计、压缩、过滤、重试这类横切问题,中间件就是放这些逻辑的位置。

中间件用于在 Agent 的执行链路中插入额外逻辑。

它适合处理横切关注点,例如:

默认可用的中间件能力

默认情况下,Deep Agents 已经具备以下类型的能力:

如果启用了记忆、技能或人工审批,还会额外加入相应的中间件。

自定义中间件示例

from langchain.tools import tool
from langchain.agents.middleware import wrap_tool_call
from deepagents import create_deep_agent


@tool
def get_weather(city: str) -> str:
    """Get the weather in a city."""
    return f"The weather in {city} is sunny."


call_count = [0]

@wrap_tool_call
def log_tool_calls(request, handler):
    """Intercept and log every tool call."""
    call_count[0] += 1
    tool_name = request.name if hasattr(request, 'name') else str(request)

    print(f"[Middleware] Tool call #{call_count[0]}: {tool_name}")
    print(f"[Middleware] Arguments: {request.args if hasattr(request, 'args') else 'N/A'}")

    result = handler(request)

    print(f"[Middleware] Tool call #{call_count[0]} completed")

    return result


agent = create_deep_agent(
    model="google_genai:gemini-3.1-pro-preview",
    tools=[get_weather],
    middleware=[log_tool_calls],
)

这个例子说明了什么

中间件可以:

不要在中间件实例上做可变共享状态

错误方式是把计数器、缓存、临时变量直接挂在中间件实例属性上,然后在钩子中不断修改。

这样做的问题是:

正确做法是把这类状态放到 graph state 里。

正确示例

class CustomMiddleware(AgentMiddleware):
    def __init__(self):
        pass

    def before_agent(self, state, runtime):
        return {"x": state.get("x", 0) + 1}

错误示例

class CustomMiddleware(AgentMiddleware):
    def __init__(self):
        self.x = 1

    def before_agent(self, state, runtime):
        self.x += 1

记住一个原则:


子代理

当主 Agent 开始同时承担研究、分析、写作、执行这些不同性质的工作时,把所有东西都塞进同一个上下文里往往会越来越笨重。子代理的意义,不只是“多开一个 Agent”,而是把某一类任务单独隔离出来,让它用更适合自己的角色、工具和模型去处理。

子代理用于隔离复杂子任务,避免主 Agent 上下文膨胀。

适合子代理的任务类型包括:

子代理示例

import os
from typing import Literal
from tavily import TavilyClient
from deepagents import create_deep_agent

tavily_client = TavilyClient(api_key=os.environ["TAVILY_API_KEY"])

def internet_search(
    query: str,
    max_results: int = 5,
    topic: Literal["general", "news", "finance"] = "general",
    include_raw_content: bool = False,
):
    """Run a web search"""
    return tavily_client.search(
        query,
        max_results=max_results,
        include_raw_content=include_raw_content,
        topic=topic,
    )

research_subagent = {
    "name": "research-agent",
    "description": "Used to research more in depth questions",
    "system_prompt": "You are a great researcher",
    "tools": [internet_search],
    "model": "openai:gpt-5.2",
}

agent = create_deep_agent(
    model="claude-sonnet-4-6",
    subagents=[research_subagent]
)

子代理的关键字段

什么时候适合用子代理

适合引入子代理的场景:

子代理的价值

子代理最大的价值有两个:


后端与虚拟文件系统

一旦 Agent 开始把中间结果写出去、把材料分段读取、或者在多轮任务里复用文件,你就不能再把文件系统理解成一个无关紧要的附属能力。backend 决定的是这些文件到底落在哪、能存多久、会不会碰到真实磁盘,以及执行边界是不是可控。

Deep Agent 会使用文件系统工具来存储和读取中间结果,因此需要一个 backend 来提供文件能力。

后端决定了这些问题:

1. StateBackend

默认后端。文件系统存在 graph state 中。

特点:

from deepagents.backends import StateBackend
from deepagents import create_deep_agent

agent = create_deep_agent(
    model="google_genai:gemini-3.1-pro-preview",
    backend=StateBackend()
)

2. FilesystemBackend

直接使用本地文件系统。

特点:

from deepagents.backends import FilesystemBackend
from deepagents import create_deep_agent

agent = create_deep_agent(
    model="google_genai:gemini-3.1-pro-preview",
    backend=FilesystemBackend(root_dir=".", virtual_mode=True)
)

3. LocalShellBackend

除了文件能力,还提供 execute 工具来执行宿主机 shell 命令。

特点:

from deepagents.backends import LocalShellBackend
from deepagents import create_deep_agent

agent = create_deep_agent(
    model="google_genai:gemini-3.1-pro-preview",
    backend=LocalShellBackend(root_dir=".", env={"PATH": "/usr/bin:/bin"})
)

4. StoreBackend

可跨线程持久化的后端,适合长期存储。

特点:

from langgraph.store.memory import InMemoryStore
from deepagents.backends import StoreBackend
from deepagents import create_deep_agent

agent = create_deep_agent(
    model="google_genai:gemini-3.1-pro-preview",
    backend=StoreBackend(
        namespace=lambda ctx: (ctx.runtime.context.user_id,),
    ),
    store=InMemoryStore()
)

为什么 namespace 很重要

多用户场景下,如果不隔离 namespace,可能出现:

因此多用户部署必须设计命名空间隔离策略。

5. CompositeBackend

允许把不同路径路由到不同后端。

适合场景:

from deepagents import create_deep_agent
from deepagents.backends import CompositeBackend, StateBackend, StoreBackend
from langgraph.store.memory import InMemoryStore

agent = create_deep_agent(
    model="google_genai:gemini-3.1-pro-preview",
    backend=CompositeBackend(
        default=StateBackend(),
        routes={
            "/memories/": StoreBackend(),
        }
    ),
    store=InMemoryStore()
)

沙箱

沙箱本质上是一类特殊后端。

作用是:

适合场景:

使用沙箱的思路

  1. 创建沙箱实例
  2. 把沙箱封装成 backend
  3. 传给 create_deep_agent(...)
  4. 任务执行完成后关闭沙箱
import modal
from deepagents import create_deep_agent
from langchain_anthropic import ChatAnthropic
from langchain_modal import ModalSandbox

app = modal.App.lookup("your-app")
modal_sandbox = modal.Sandbox.create(app=app)
backend = ModalSandbox(sandbox=modal_sandbox)

agent = create_deep_agent(
    model=ChatAnthropic(model="claude-sonnet-4-6"),
    system_prompt="You are a Python coding assistant with sandbox access.",
    backend=backend,
)

try:
    result = agent.invoke(
        {
            "messages": [
                {
                    "role": "user",
                    "content": "Create a small Python package and run pytest",
                }
            ]
        }
    )
finally:
    modal_sandbox.terminate()

沙箱选择原则


人工审批

有些工具调用存在明显风险,应该在执行前等待人工批准。

典型例子:

配置方式

from langchain.tools import tool
from deepagents import create_deep_agent
from langgraph.checkpoint.memory import MemorySaver

@tool
def delete_file(path: str) -> str:
    """Delete a file from the filesystem."""
    return f"Deleted {path}"

@tool
def read_file(path: str) -> str:
    """Read a file from the filesystem."""
    return f"Contents of {path}"

@tool
def send_email(to: str, subject: str, body: str) -> str:
    """Send an email."""
    return f"Sent email to {to}"

checkpointer = MemorySaver()

agent = create_deep_agent(
    model="google_genai:gemini-3.1-pro-preview",
    tools=[delete_file, read_file, send_email],
    interrupt_on={
        "delete_file": True,
        "read_file": False,
        "send_email": {"allowed_decisions": ["approve", "reject"]},
    },
    checkpointer=checkpointer
)

interrupt_on 的含义

它用于指定哪些工具在调用前需要中断等待人工处理。

常见配置方式:

为什么必须有 checkpointer

人工审批本质上意味着:

没有检查点,就无法可靠地恢复执行状态。

所以启用人工审批时,checkpointer 是必需项。


技能

技能可以理解为“按需加载的高层能力包”。

和工具的区别是:

技能的价值在于:

适合做成技能的内容

使用技能的关键点

给 Agent 配置 skills 之前,必须先把对应技能文件放入 backend 中可访问的位置。

StateBackend 示例

from urllib.request import urlopen
from deepagents import create_deep_agent
from deepagents.backends.utils import create_file_data
from langgraph.checkpoint.memory import MemorySaver

checkpointer = MemorySaver()

skill_url = "https://raw.githubusercontent.com/langchain-ai/deepagents/refs/heads/main/libs/cli/examples/skills/langgraph-docs/SKILL.md"
with urlopen(skill_url) as response:
    skill_content = response.read().decode('utf-8')

skills_files = {
    "/skills/langgraph-docs/SKILL.md": create_file_data(skill_content)
}

agent = create_deep_agent(
    model="google_genai:gemini-3.1-pro-preview",
    skills=["/skills/"],
    checkpointer=checkpointer,
)

result = agent.invoke(
    {
        "messages": [
            {
                "role": "user",
                "content": "What is langgraph?",
            }
        ],
        "files": skills_files
    },
    config={"configurable": {"thread_id": "12345"}},
)

技能的核心理解

技能本质上不是“多一个函数”,而是“多一份按需加载的专业工作说明书”。


记忆

记忆用于给 Agent 提供额外上下文,使其在不同轮次或不同任务中保留重要信息。

这里的记忆通过 AGENTS.md 文件提供。

可以把它理解为:

使用方式

创建 Agent 时,通过 memory=[...] 指定一个或多个记忆文件路径。

StateBackend 示例

from urllib.request import urlopen

from deepagents import create_deep_agent
from deepagents.backends.utils import create_file_data
from langgraph.checkpoint.memory import MemorySaver

with urlopen("https://raw.githubusercontent.com/langchain-ai/deepagents/refs/heads/main/examples/text-to-sql-agent/AGENTS.md") as response:
    agents_md = response.read().decode("utf-8")
checkpointer = MemorySaver()

agent = create_deep_agent(
    model="google_genai:gemini-3.1-pro-preview",
    memory=[
        "/AGENTS.md"
    ],
    checkpointer=checkpointer,
)

result = agent.invoke(
    {
        "messages": [
            {
                "role": "user",
                "content": "Please tell me what's in your memory files.",
            }
        ],
        "files": {"/AGENTS.md": create_file_data(agents_md)},
    },
    config={"configurable": {"thread_id": "123456"}},
)

记忆和技能的区别

可以这样理解:

什么时候用记忆

适合记忆的内容:


结构化输出

如果希望 Agent 最终返回的不是普通文本,而是一个结构化对象,可以配置 response_format

这适合以下场景:

示例

import os
from typing import Literal
from pydantic import BaseModel, Field
from tavily import TavilyClient
from deepagents import create_deep_agent

tavily_client = TavilyClient(api_key=os.environ["TAVILY_API_KEY"])

def internet_search(
    query: str,
    max_results: int = 5,
    topic: Literal["general", "news", "finance"] = "general",
    include_raw_content: bool = False,
):
    """Run a web search"""
    return tavily_client.search(
        query,
        max_results=max_results,
        include_raw_content=include_raw_content,
        topic=topic,
    )

class WeatherReport(BaseModel):
    """A structured weather report with current conditions and forecast."""
    location: str = Field(description="The location for this weather report")
    temperature: float = Field(description="Current temperature in Celsius")
    condition: str = Field(description="Current weather condition")
    humidity: int = Field(description="Humidity percentage")
    wind_speed: float = Field(description="Wind speed in km/h")
    forecast: str = Field(description="Brief forecast for the next 24 hours")

agent = create_deep_agent(
    model="google_genai:gemini-3.1-pro-preview",
    response_format=WeatherReport,
    tools=[internet_search]
)

result = agent.invoke({
    "messages": [{
        "role": "user",
        "content": "What's the weather like in San Francisco?"
    }]
})

print(result["structured_response"])

这里发生了什么

结构化输出的好处


配置组合思路

实际项目里,不建议把所有功能一次性堆上去,而是按需求逐步增加。

最小可用组合

适合快速验证:

增强型组合

适合复杂任务:

安全型组合

适合高风险操作:

长期运行组合

适合多用户、多轮任务:


常见错误与排查

模型能回答,但不会调用工具

常见原因:

排查方向:

使用人工审批时报错

常见原因:

解决方法:

技能或记忆配置了,但 Agent 读不到

常见原因:

解决方法:

多用户数据串了

常见原因:

解决方法:

中间件出现奇怪并发问题

常见原因:

解决方法:

Agent 写坏本地文件或执行危险命令

常见原因:

解决方法:


验收标准

可以用下面的标准判断自定义能力是否真正生效:


推荐实操顺序

建议按下面顺序逐层扩展:

  1. 先确认基础 Agent 可运行
  2. 添加一个自定义工具
  3. 补充系统提示词
  4. 再考虑子代理
  5. 再加入中间件和日志
  6. 再决定用哪种 backend
  7. 对高风险动作增加人工审批
  8. 再引入技能和记忆
  9. 最后加结构化输出

这样做的好处是:


关键要点


写在最后

自定义 Deep Agent 的核心,不是“把所有选项都打开”,而是围绕真实任务场景做能力组合。

可以把整个配置过程理解为三层:

当你把模型、工具、流程控制、存储和输出方式组合好之后,Deep Agent 才真正从一个演示型 Agent 变成一个能够进入实际系统的 Agent。