Deep Agents 实战课程

第二章 安装与快速上手

2.1 安装

python 复制代码
# uv 方式
uv init deep-agents-tutorial
cd deep-agents-tutorial
uv add deepagents langchain langchain-openai python-dotenv
python 复制代码
# pyproject.toml
[project]
name = "deep-agents-tutorial"
version = "0.1.0"
description = "Deep Agents 学习项目"
requires-python = ">=3.11"
dependencies = [
    "deepagents>=0.6.0",
    "langchain>=1.2.0",
    "langchain-openai>=0.3.0",
    "langgraph>=1.1.0",
    "python-dotenv>=1.0.0",
]

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
python 复制代码
# requirements.txt
deepagents>=0.5.1
langchain>=1.2.0
langchain-openai>=0.3.0
langgraph>=1.1.0
python-dotenv>=1.0.0

2.2 配置 API 密钥

python 复制代码
# .env
DASHSCOPE_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx
DASHSCOPE_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1

2.3 第一个 Deep Agent

python 复制代码
"""
你的第一个 Deep Agent
功能:一个能查询天气的小助手(其实只是个假数据 demo)
"""
import os
from dotenv import load_dotenv
from langchain.chat_models import init_chat_model
from langchain_openai import ChatOpenAI
from deepagents import create_deep_agent

# 1. 加载环境变量
load_dotenv()

# 2. 定义模型
model = init_chat_model(
    model="qwen-max",
    model_provider="openai",
    api_key=os.getenv("DASHSCOPE_API_KEY"),
    base_url=os.getenv("DASHSCOPE_BASE_URL"),
    temperature=0,
)

# 3. 定义自定义工具
def get_weather(city: str) -> str:
    """获取指定城市的天气信息。

    Args:
        city: 城市名称,例如 "北京"、"上海"
    Returns:
        该城市的天气描述
    """
    return f"{city} 今天天气晴朗,气温 22℃,适合出门。"

# 4. 创建 Deep Agent
agent = create_deep_agent(
    model=model,
    tools=[get_weather],
    system_prompt="你是一个友好的助手,擅长用中文回答用户问题。",
)

# 5. 调用 agent
result = agent.invoke({
    "messages": [
        {"role": "user", "content": "上海今天天气怎么样?"}
    ]
})

# 6. 打印最终回答
print("=" * 50)
print("Agent 最终回答:")
print(result["messages"][-1].content)
print("=" * 50)

2.5 模型字符串简写 vs 显式构造

python 复制代码
# 单一字符串简写
from deepagents import create_deep_agent
agent = create_deep_agent(
    model="anthropic:claude-sonnet-4-6",
    tools=[get_weather],
)

# init_chat_model 传更多参数
from langchain.chat_models import init_chat_model
from deepagents import create_deep_agent
agent = create_deep_agent(
    model=init_chat_model("openai:gpt-5.4", temperature=0),
    tools=[get_weather],
)

2.6 长任务的连接弹性配置

python 复制代码
from langchain.chat_models import init_chat_model
from langgraph.checkpoint.memory import InMemorySaver
from deepagents import create_deep_agent
import os

def get_weather(city: str) -> str:
    """获取指定城市的天气信息。"""
    return f"{city} 今天天气晴朗,气温 22℃,适合出门。"

model = init_chat_model(
    "qwen-max",
    model_provider="openai",
    api_key=os.getenv("DASHSCOPE_API_KEY"),
    base_url=os.getenv("DASHSCOPE_BASE_URL"),
    temperature=0,
    max_retries=12,
    timeout=60,
)

agent = create_deep_agent(
    model=model,
    tools=[get_weather],
    system_prompt="你是一个友好的助手。",
    checkpointer=InMemorySaver(),
)

config = {"configurable": {"thread_id": "user-123"}}
result = agent.invoke(
    {"messages": [{"role": "user", "content": "上海天气怎么样?"}]},
    config=config,
)

2.7 返回值

python 复制代码
agent = create_deep_agent(...)
print(type(agent))
# <class 'langgraph.graph.state.CompiledStateGraph'>
python 复制代码
# 流式输出小例子
for chunk in agent.stream(
    {"messages": [{"role": "user", "content": "上海天气怎么样?"}]},
    stream_mode=["updates", "custom"],
    version="v2",
):
    print(chunk["data"])

第三章 create_deep_agent 参数详解

3.2 model 参数:三种传入方式

python 复制代码
# 方式 A:字符串简写
agent = create_deep_agent(model="openai:gpt-5.4", tools=[...])

# 方式 B:init_chat_model 工厂函数
from langchain.chat_models import init_chat_model
agent = create_deep_agent(
    model=init_chat_model("anthropic:claude-sonnet-4-6", temperature=0),
    tools=[...],
)

# 方式 C:传入已构造的模型对象
from langchain_openai import ChatOpenAI
model = ChatOpenAI(
    model="qwen-max",
    api_key=os.getenv("DASHSCOPE_API_KEY"),
    base_url=os.getenv("DASHSCOPE_BASE_URL"),
    temperature=0,
)
agent = create_deep_agent(model=model, tools=[...])

3.3 tools 参数

python 复制代码
# 写法 A:普通 Python 函数
def get_weather(city: str) -> str:
    """查询城市天气。"""
    return f"{city} 晴朗 22℃"

# 写法 B:用 @tool 装饰器
from langchain_core.tools import tool

@tool
def get_weather(city: str) -> str:
    """查询城市天气。

    Args:
        city: 城市名称
    """
    return f"{city} 晴朗 22℃"

agent = create_deep_agent(model=model, tools=[get_weather])
python 复制代码
# 工具最佳实践:docstring + 类型注解
def search(query: str, top_k: int = 5) -> list[dict]:
    """在公司知识库中搜索相关文档。

    Args:
        query: 搜索关键词,建议 3-10 个字
        top_k: 返回的文档数量,默认 5

    Returns:
        文档列表,每个文档包含 title / url / snippet 字段
    """
python 复制代码
# 工具内部捕获异常
def fetch_url(url: str) -> str:
    """抓取网页内容。"""
    try:
        return requests.get(url, timeout=10).text[:5000]
    except Exception as e:
        return f"抓取失败:{e}。建议换一个 URL 或稍后重试。"

3.4 system_prompt 拼接

python 复制代码
# 调试时查看完整 prompt
for event in agent.stream(
    {"messages": [{"role": "user", "content": "你好"}]},
    stream_mode="debug",
):
    print(event)

3.5 middleware 中间件机制

python 复制代码
# ❌ 错误示范:不要在 hook 里改 self 属性
class BadMiddleware:
    def before_model(self, state):
        self.counter += 1
        return state

# ✅ 正确示范:通过 state 更新
class GoodMiddleware:
    def before_model(self, state):
        new_counter = state.get("counter", 0) + 1
        return {"counter": new_counter}

3.6 subagents / skills / memory

python 复制代码
# subagents
agent = create_deep_agent(
    model=model,
    tools=[...],
    subagents=[
        {
            "name": "researcher",
            "description": "专门负责网络检索与信息汇总",
            "system_prompt": "你是检索专家,只输出事实,不要主观判断。",
            "tools": [web_search, read_url],
        },
    ],
)
python 复制代码
# skills
agent = create_deep_agent(
    model=model,
    tools=[...],
    skills=["./skills/contract-review", "./skills/financial-analysis"],
)
python 复制代码
# memory
agent = create_deep_agent(
    model=model,
    tools=[...],
    memory=["~/.deepagents/researcher/AGENTS.md", "./project-AGENTS.md"],
)

3.7 backend / store / interrupt_on / response_format

python 复制代码
# backend:StateBackend(默认)
agent = create_deep_agent(model=model, tools=[...])

# backend:StoreBackend
from langgraph.store.memory import InMemoryStore
from deepagents.backends import StoreBackend

store = InMemoryStore()
agent = create_deep_agent(
    model=model,
    tools=[...],
    store=store,
    backend=lambda rt: StoreBackend(rt),
)

# backend:CompositeBackend
from deepagents.backends import CompositeBackend, StateBackend, StoreBackend

agent = create_deep_agent(
    model=model,
    tools=[...],
    store=store,
    backend=lambda rt: CompositeBackend(
        default=StateBackend(rt),
        routes={"/memories/": StoreBackend(rt)},
    ),
)
python 复制代码
# interrupt_on
from langchain_core.tools import tool
from langgraph.checkpoint.memory import InMemorySaver

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

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

agent = create_deep_agent(
    model=model,
    tools=[delete_file, send_email],
    interrupt_on={
        "delete_file": True,
        "send_email": {"allowed_decisions": ["approve", "reject"]},
    },
    checkpointer=InMemorySaver(),
)
python 复制代码
# response_format
from pydantic import BaseModel, Field

class WeatherReport(BaseModel):
    """天气情报。"""
    city: str = Field(description="城市名称")
    temperature: float = Field(description="气温(摄氏度)")
    condition: str = Field(description="天气状况,如'晴'、'多云'")
    suggestion: str = Field(description="出行建议")

agent = create_deep_agent(
    model=model,
    tools=[get_weather],
    system_prompt="你是天气助手,最终用 WeatherReport 格式输出。",
    response_format=WeatherReport,
)

result = agent.invoke({
    "messages": [{"role": "user", "content": "上海今天怎么样?"}]
})

report: WeatherReport = result["structured_response"]
print(report.city)
print(report.temperature)
print(report.suggestion)

第四章 核心能力(Harness Capabilities)

4.1 规划能力(Planning)

python 复制代码
"""
让 agent 主动规划:分析三家公司
"""
import os
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from deepagents import create_deep_agent

load_dotenv()

model = ChatOpenAI(
    model="qwen-max",
    api_key=os.getenv("DASHSCOPE_API_KEY"),
    base_url=os.getenv("DASHSCOPE_BASE_URL"),
    temperature=0,
)

def get_company_info(name: str) -> str:
    """查询公司基本信息(演示数据)。"""
    fake_db = {
        "阿里巴巴": "电商与云计算巨头,2024 年营收 9411 亿元",
        "腾讯": "社交与游戏巨头,2024 年营收 6602 亿元",
        "字节跳动": "短视频与广告巨头,2024 年营收约 1.55 万亿元",
    }
    return fake_db.get(name, f"未找到 {name} 的信息")

agent = create_deep_agent(
    model=model,
    tools=[get_company_info],
    system_prompt=(
        "你是一个商业分析师。"
        "对于多步骤任务,**必须先用 write_todos 列出计划**,再逐项执行。"
    ),
)

result = agent.invoke(
    {"messages": [{
        "role": "user",
        "content": "对比分析阿里巴巴、腾讯、字节跳动三家公司的规模,给我一份简报。"
    }]},
    version="v2",
)

state = result.value

print("📋 任务清单:")
for todo in state.get("todos", []):
    icon = {"completed": "✅", "in_progress": "🔄", "pending": "⏳"}[todo["status"]]
    print(f"  {icon} {todo['content']}")

print("\n📊 最终简报:")
print(state["messages"][-1].content)

4.2 虚拟文件系统

python 复制代码
"""
研究助手:先把多个来源的资料分别保存为文件,再综合写报告
"""
import os
from dotenv import load_dotenv
from langchain.chat_models import init_chat_model
from deepagents import create_deep_agent

load_dotenv()

model = init_chat_model(
    model="qwen-max",
    model_provider="openai",
    api_key=os.getenv("DASHSCOPE_API_KEY"),
    base_url=os.getenv("DASHSCOPE_BASE_URL"),
    temperature=0,
)

def search_news(keyword: str) -> str:
    """搜索新闻(演示数据,返回一段较长文本)。"""
    return f"关于「{keyword}」的最新新闻:\n" + "(这里假设是一段 2000 字的新闻内容...)"

agent = create_deep_agent(
    model=model,
    tools=[search_news],
    system_prompt=(
        "你是一个新闻分析师。处理用户请求时,请按以下流程:\n"
        "1. 用 write_todos 列出计划\n"
        "2. 对每个关键词,调用 search_news 获取资料,并用 write_file 保存到 /notes/<关键词>.md\n"
        "3. 用 ls 和 read_file 复查所有保存的资料\n"
        "4. 综合写出最终报告,保存到 /report.md\n"
        "5. 把 /report.md 的内容作为最终回答返回给用户"
    ),
)

result = agent.invoke(
    {"messages": [{
        "role": "user",
        "content": "帮我分析「AI Agent」和「Multi-Agent System」这两个话题的最新动态,写一份简报。"
    }]},
    version="v2",
)

state = result.value

print("📁 虚拟文件系统中的文件:")
for filename in state.get("files", {}).keys():
    print(f"  📄 {filename}")

print("\n📰 最终简报:")
print(state["messages"][-1].content)

4.3 子智能体配置示例

python 复制代码
agent = create_deep_agent(
    model=main_model,
    tools=[search_news, fetch_url],
    subagents=[
        {
            "name": "researcher",
            "description": (
                "深度调研专家。"
                "适合:'去网上查 XXX,给我个总结'、'对比几家公司的财报'。"
                "返回:5 条以内关键事实,每条不超过 100 字,附来源。"
                "不适合:主观判断、创意写作。"
            ),
            "system_prompt": (
                "你是检索专家。请大量搜索、阅读、对比,"
                "最终只输出 5 条关键事实,每条不超过 100 字。"
            ),
            "tools": [search_news, fetch_url],
        },
        {
            "name": "fact_checker",
            "description": "对一段陈述进行事实核查,返回 True / False 与证据",
            "system_prompt": "你是严格的事实核查员,只看证据,不做主观判断。",
            "tools": [search_news],
        },
    ],
    system_prompt=(
        "你是研究主管。复杂调研任务请用 task 工具委派给 researcher,"
        "需要核查事实时委派给 fact_checker。最后由你综合写报告。"
    ),
)
python 复制代码
# 主强子弱模型搭配
strong_model = ChatOpenAI(model="qwen-max", ...)
cheap_model  = ChatOpenAI(model="qwen-turbo", ...)

agent = create_deep_agent(
    model=strong_model,
    subagents=[
        {
            "name": "researcher",
            "description": "调研",
            "system_prompt": "...",
            "tools": [...],
            "model": cheap_model,
        },
    ],
)
python 复制代码
# 子代理结构化输出(v0.5.3+)
from pydantic import BaseModel, Field

class ResearchFindings(BaseModel):
    """研究结果。"""
    summary: str = Field(description="发现摘要")
    confidence: float = Field(description="置信度 0~1")
    sources: list[str] = Field(description="来源 URL 列表")

research_subagent = {
    "name": "researcher",
    "description": "调研话题并返回结构化结果",
    "system_prompt": "对给定话题做深度调研,返回结构化结果。",
    "tools": [web_search],
    "response_format": ResearchFindings,
}

agent = create_deep_agent(
    model="claude-sonnet-4-6",
    subagents=[research_subagent],
)
python 复制代码
# 异步子代理(v0.5 预览)
from deepagents import AsyncSubAgent, create_deep_agent

async_subagents = [
    AsyncSubAgent(
        name="researcher",
        description="后台研究 agent,适合长跑的检索任务",
        graph_id="researcher",
    ),
    AsyncSubAgent(
        name="coder",
        description="后台编码 agent",
        graph_id="coder",
        url="https://coder-deployment.langsmith.dev",
    ),
]

agent = create_deep_agent(
    model=model,
    subagents=async_subagents,
)

4.4 上下文管理:自定义阈值

python 复制代码
from deepagents.middleware import SummarizationMiddleware

agent = create_deep_agent(
    model=model,
    tools=[...],
    middleware=[
        SummarizationMiddleware(
            tool_token_limit_before_evict=10_000,
            max_input_tokens=100_000,
        ),
    ],
)
python 复制代码
# 手动触发摘要的 SummarizationToolMiddleware
from deepagents import create_deep_agent
from deepagents.backends import StateBackend
from deepagents.middleware.summarization import (
    create_summarization_tool_middleware,
)

agent = create_deep_agent(
    model=model,
    middleware=[
        create_summarization_tool_middleware(
            model,
            StateBackend,
        ),
    ],
)

4.6 人机协同(HITL)

python 复制代码
import os
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain.tools import tool
from deepagents import create_deep_agent
from langgraph.checkpoint.memory import MemorySaver

load_dotenv()

model = ChatOpenAI(
    model="qwen-max",
    api_key=os.getenv("DASHSCOPE_API_KEY"),
    base_url=os.getenv("DASHSCOPE_BASE_URL"),
    temperature=0,
)

@tool
def delete_file(path: str) -> str:
    """删除文件。"""
    return f"Deleted {path}"

@tool
def send_email(to: str, subject: str, body: str) -> str:
    """发邮件。"""
    return f"Sent email to {to}"

checkpointer = MemorySaver()

agent = create_deep_agent(
    model=model,
    tools=[delete_file, send_email],
    interrupt_on={
        "delete_file": True,
        "send_email": {"allowed_decisions": ["approve", "reject"]},
    },
    checkpointer=checkpointer,
)
python 复制代码
# 处理中断(v2 invoke)
from langchain_core.utils.uuid import uuid7
from langgraph.types import Command

config = {"configurable": {"thread_id": str(uuid7())}}

result = agent.invoke(
    {"messages": [{"role": "user", "content": "删除 temp.txt 文件"}]},
    config=config,
    version="v2",
)

if result.interrupts:
    interrupt_value = result.interrupts[0].value
    action_requests = interrupt_value["action_requests"]
    review_configs = interrupt_value["review_configs"]

    config_map = {cfg["action_name"]: cfg for cfg in review_configs}

    for action in action_requests:
        review_config = config_map[action["name"]]
        print(f"工具: {action['name']}")
        print(f"参数: {action['args']}")
        print(f"允许的决策: {review_config['allowed_decisions']}")

    decisions = [
        {"type": "approve"}
    ]

    result = agent.invoke(
        Command(resume={"decisions": decisions}),
        config=config,
        version="v2",
    )

print(result.value["messages"][-1].content)
python 复制代码
# 多个工具调用的批量审批
decisions = [
    {"type": "approve"},   # 第一个:delete_file 批准
    {"type": "reject"},    # 第二个:send_email 拒绝
]

result = agent.invoke(
    Command(resume={"decisions": decisions}),
    config=config,
    version="v2",
)
python 复制代码
# 编辑参数后批准
if result.interrupts:
    action_request = result.interrupts[0].value["action_requests"][0]

    decisions = [{
        "type": "edit",
        "edited_action": {
            "name": action_request["name"],
            "args": {
                "to": "team@company.com",
                "subject": "...",
                "body": "...",
            },
        },
    }]

    result = agent.invoke(
        Command(resume={"decisions": decisions}),
        config=config,
        version="v2",
    )

4.7 Skills 配置

python 复制代码
# StateBackend + 远程 SKILL.md
import os
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
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

load_dotenv()

model = ChatOpenAI(
    model="qwen-max",
    api_key=os.getenv("DASHSCOPE_API_KEY"),
    base_url=os.getenv("DASHSCOPE_BASE_URL"),
    temperature=0,
)

skill_url = "https://raw.githubusercontent.com/langchain-ai/deepagents/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=model,
    skills=["/skills/"],
    checkpointer=MemorySaver(),
)

result = agent.invoke(
    {
        "messages": [{"role": "user", "content": "什么是 langgraph?"}],
        "files": skills_files,
    },
    config={"configurable": {"thread_id": "12345"}},
    version="v2",
)

state = result.value

print("=== Files in state ===")
for path in state.get("files", {}):
    print(" -", path)

print("\n=== Final answer ===")
print(state["messages"][-1].content)
python 复制代码
# StoreBackend 方式
from langgraph.store.memory import InMemoryStore
from deepagents.backends import StoreBackend
from deepagents.backends.utils import create_file_data

store = InMemoryStore()
store.put(
    namespace=("filesystem",),
    key="/skills/langgraph-docs/SKILL.md",
    value=create_file_data(skill_content),
)

agent = create_deep_agent(
    model=model,
    backend=StoreBackend(),
    store=store,
    skills=["/skills/"],
)
python 复制代码
# FilesystemBackend 方式
from deepagents.backends.filesystem import FilesystemBackend

agent = create_deep_agent(
    model=model,
    backend=FilesystemBackend(root_dir="/Users/me/myproject"),
    skills=["/Users/me/myproject/skills/"],
)

4.8 综合实战:研究助手 v1

python 复制代码
"""
研究助手 v1
集成:规划、文件系统、子智能体、上下文管理、AGENTS.md、v2 invoke
"""
import os
from dotenv import load_dotenv
from langchain.chat_models import init_chat_model
from langgraph.checkpoint.memory import MemorySaver
from deepagents import create_deep_agent

load_dotenv()

main_model = init_chat_model(
    model="qwen-max",
    model_provider="openai",
    api_key=os.getenv("DASHSCOPE_API_KEY"),
    base_url=os.getenv("DASHSCOPE_BASE_URL"),
    temperature=0,
    max_retries=10,
)

sub_model = init_chat_model(
    model="qwen-turbo",
    model_provider="openai",
    api_key=os.getenv("DASHSCOPE_API_KEY"),
    base_url=os.getenv("DASHSCOPE_BASE_URL"),
    temperature=0,
    max_retries=10,
)

def search_web(keyword: str) -> str:
    """假装搜索网页(演示数据)。"""
    return f"关于「{keyword}」的搜索结果:\n(这里是一段 3000 字的检索结果...)"

def fetch_url(url: str) -> str:
    """假装抓取网页(演示数据)。"""
    return f"页面 {url} 的内容:\n(这里是一段 5000 字的网页正文...)"

agent = create_deep_agent(
    model=main_model,
    tools=[search_web, fetch_url],
    subagents=[
        {
            "name": "researcher",
            "description": (
                "深度调研某个具体话题。"
                "适合:'调研 XXX 的最新动态'、'对比几家公司'。"
                "返回:5 条以内关键事实+来源,每条不超过 100 字。"
            ),
            "system_prompt": (
                "你是检索专家。请调用 search_web、fetch_url 大量搜集资料,"
                "把详细内容写到 /notes/<话题>.md,"
                "最后只返回 5 条关键事实,每条不超过 100 字,附来源。"
            ),
            "tools": [search_web, fetch_url],
            "model": sub_model,
        },
    ],
    system_prompt=(
        "你是研究主管。处理用户任务的标准流程:\n"
        "1. 用 write_todos 列出计划\n"
        "2. 把每个子话题委派给 researcher 子代理(用 task 工具)\n"
        "3. 收到所有调研结果后,综合写报告,保存到 /report.md\n"
        "4. 把 /report.md 内容作为最终回答返回"
    ),
    memory=["/project/AGENTS.md"],
    checkpointer=MemorySaver(),
)

config = {"configurable": {"thread_id": "research-001"}}
result = agent.invoke(
    {"messages": [{
        "role": "user",
        "content": "调研「AI Agent」和「Multi-Agent System」的最新进展,写一份对比报告。"
    }]},
    config=config,
    version="v2",
)

state = result.value

print("📋 任务清单:")
for todo in state.get("todos", []):
    icon = {"completed": "✅", "in_progress": "🔄", "pending": "⏳"}[todo["status"]]
    print(f"  {icon} {todo['content']}")

print("\n📁 产出文件:")
for filename in state.get("files", {}).keys():
    print(f"  📄 {filename}")

print("\n📰 最终报告:")
print(state["messages"][-1].content)

第五章 可插拔后端与沙箱

5.2 内置后端

python 复制代码
# StateBackend(默认)
from deepagents.backends import StateBackend

agent = create_deep_agent(
    model=model,
    tools=[...],
    backend=StateBackend(),
)
python 复制代码
# FilesystemBackend
from deepagents.backends import FilesystemBackend

agent = create_deep_agent(
    model=model,
    tools=[...],
    backend=FilesystemBackend(
        root_dir="/Users/me/myproject",
        virtual_mode=True,
    ),
)
python 复制代码
# LocalShellBackend
from deepagents.backends import LocalShellBackend

agent = create_deep_agent(
    model=model,
    tools=[...],
    backend=LocalShellBackend(
        root_dir=".",
        env={"PATH": "/usr/bin:/bin"},
    ),
)
python 复制代码
# StoreBackend
from langgraph.store.memory import InMemoryStore
from deepagents.backends import StoreBackend

store = InMemoryStore()

agent = create_deep_agent(
    model=model,
    tools=[...],
    store=store,
    backend=StoreBackend(
        namespace=lambda rt: (rt.server_info.user.identity,),
    ),
)
python 复制代码
# 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=model,
    tools=[...],
    store=InMemoryStore(),
    backend=CompositeBackend(
        default=StateBackend(),
        routes={
            "/memories/": StoreBackend(
                namespace=lambda rt: (rt.server_info.user.identity,),
            ),
        },
    ),
)
python 复制代码
uv sync
modal token new
modal profile current
cat ~/.modal.toml

5.5 Sandbox-as-Tool 模式实战

python 复制代码
"""
Sandbox-as-Tool 模式 · 数据分析示例
"""
import os
import modal
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from deepagents import create_deep_agent
from langchain_modal import ModalSandbox

load_dotenv()

model = ChatOpenAI(
    model="qwen-max",
    api_key=os.getenv("DASHSCOPE_API_KEY"),
    base_url=os.getenv("DASHSCOPE_BASE_URL"),
    temperature=0,
    max_retries=10,
)

print("⏳ 正在准备 Modal 镜像 + 沙箱...")
app = modal.App.lookup("my-deep-agent", create_if_missing=True)
image = modal.Image.debian_slim(python_version="3.11")
modal_sandbox = modal.Sandbox.create(
    app=app,
    image=image,
    timeout=60 * 10,
)
print(f"✅ 沙箱已就绪:{modal_sandbox.object_id}")

backend = ModalSandbox(sandbox=modal_sandbox)

SYSTEM_PROMPT = """你是数据分析助手。

【固定工作流,必须严格按顺序执行】
步骤 1. write_file:把用户给的 CSV 原样存到 /workspace/data.csv
步骤 2. write_file:把分析逻辑写成完整的 Python 脚本 /workspace/analyze.py
步骤 3. execute:运行 `python /workspace/analyze.py`,让脚本把结论写到 /workspace/report.md
步骤 4. read_file:读取 /workspace/report.md
步骤 5. 用自然语言把分析结果完整地告诉用户

【硬性规则】
- 禁止使用 `python -c "..."` 内联运行代码
- 只用 Python 标准库
- 最后一步必须把报告内容用文字复述给用户
"""

agent = create_deep_agent(
    model=model,
    tools=[],
    system_prompt=SYSTEM_PROMPT,
    backend=backend,
)

try:
    print("🤖 agent 开始工作...")
    result = agent.invoke(
        {"messages": [{
            "role": "user",
            "content": (
                "这是某连锁店上周的销售数据,请做基本统计并指出哪个城市卖得最好:\n"
                "city,sales\n"
                "Shanghai,100\n"
                "Beijing,80\n"
                "Shenzhen,120\n"
                "Guangzhou,90\n"
            )
        }]},
        version="v2",
    )

    def _to_text(msg) -> str:
        c = msg.content
        if isinstance(c, str):
            return c
        if isinstance(c, list):
            return "\n".join(
                b.get("text", "") for b in c if isinstance(b, dict) and b.get("type") == "text"
            )
        return str(c or "")

    final_text = _to_text(result.value["messages"][-1])

    print("\n" + "=" * 60)
    print("📄 最终回答")
    print("=" * 60)
    print(final_text or "(空回答,请检查 agent 流程)")
    print("=" * 60)

finally:
    print("\n🧹 正在销毁沙箱...")
    modal_sandbox.terminate()
    print("✅ 完成")

5.6 Thread-scoped 沙箱

python 复制代码
"""
Thread-scoped 沙箱 · Modal 完整示例
"""
import os
import modal
from dotenv import load_dotenv
from langchain.chat_models import init_chat_model
from deepagents import create_deep_agent
from langchain_modal import ModalSandbox

load_dotenv()

model = init_chat_model(
    model="qwen-max",
    model_provider="openai",
    api_key=os.getenv("DASHSCOPE_API_KEY"),
    base_url=os.getenv("DASHSCOPE_BASE_URL"),
    temperature=0,
    max_retries=10,
)

SANDBOX_REGISTRY: dict[str, str] = {}

app = modal.App.lookup("my-deep-agent", create_if_missing=True)
image = modal.Image.debian_slim(python_version="3.11")


def get_or_create_sandbox(thread_id: str) -> modal.Sandbox:
    """根据 thread_id 复用或新建 Modal 沙箱。"""
    existing_id = SANDBOX_REGISTRY.get(thread_id)
    if existing_id:
        try:
            sandbox = modal.Sandbox.from_id(existing_id)
            if sandbox.poll() is None:
                print(f"♻️  复用已有沙箱:{existing_id}")
                return sandbox
        except Exception:
            pass

    sandbox = modal.Sandbox.create(
        app=app,
        image=image,
        timeout=60 * 60,
    )
    SANDBOX_REGISTRY[thread_id] = sandbox.object_id
    print(f"🆕 创建新沙箱:{sandbox.object_id}")
    return sandbox


thread_id = "thread-demo-001"
sandbox = get_or_create_sandbox(thread_id)
backend = ModalSandbox(sandbox=sandbox)

agent = create_deep_agent(
    model=model,
    backend=backend,
    system_prompt="你是一个编码助手,可以在沙箱里写代码并运行。",
)

try:
    result = agent.invoke(
        {"messages": [{
            "role": "user",
            "content": "写一个 hello world 的 Python 脚本到 /workspace/hello.py 并运行。",
        }]},
        config={"configurable": {"thread_id": thread_id}},
        version="v2",
    )
    print(result.value["messages"][-1].content)
except Exception:
    sandbox.terminate()
    SANDBOX_REGISTRY.pop(thread_id, None)
    raise

5.6 Assistant-scoped 沙箱

python 复制代码
"""
Assistant-scoped 沙箱 · Modal 完整示例
"""
import os
import json
import pathlib
import modal
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from deepagents import create_deep_agent
from langchain_modal import ModalSandbox

load_dotenv()

model = ChatOpenAI(
    model="qwen-max",
    api_key=os.getenv("DASHSCOPE_API_KEY"),
    base_url=os.getenv("DASHSCOPE_BASE_URL"),
    temperature=0,
    max_retries=10,
)

ASSISTANT_CONFIG = pathlib.Path("./.assistant-config.json")

app = modal.App.lookup("my-coding-assistant", create_if_missing=True)
image = modal.Image.debian_slim(python_version="3.11").apt_install("git")


def load_or_create_assistant_sandbox() -> modal.Sandbox:
    cfg = {}
    if ASSISTANT_CONFIG.exists():
        cfg = json.loads(ASSISTANT_CONFIG.read_text())

    sandbox_id = cfg.get("sandbox_id")
    if sandbox_id:
        try:
            sandbox = modal.Sandbox.from_id(sandbox_id)
            if sandbox.poll() is None:
                print(f"♻️  Assistant 复用既有沙箱:{sandbox_id}")
                return sandbox
        except Exception:
            print(f"⚠️  既有沙箱 {sandbox_id} 已失效,准备重建")

    sandbox = modal.Sandbox.create(
        app=app,
        image=image,
        timeout=60 * 60 * 24,
    )
    ASSISTANT_CONFIG.write_text(json.dumps({"sandbox_id": sandbox.object_id}))
    print(f"🆕 Assistant 创建新沙箱:{sandbox.object_id}")
    return sandbox


sandbox = load_or_create_assistant_sandbox()
backend = ModalSandbox(sandbox=sandbox)

agent = create_deep_agent(
    model=model,
    backend=backend,
    system_prompt=(
        "你是长期工作的编码助手。沙箱里的 /workspace 是你的项目目录,"
        "你之前装过的依赖、clone 过的仓库下次对话还会在。"
    ),
)

result = agent.invoke(
    {"messages": [{
        "role": "user",
        "content": "在 /workspace 下新建 hello-app 目录,写一个最小 Python 包并跑一下。",
    }]},
    version="v2",
)
print(result.value["messages"][-1].content)
print(f"\n💾 沙箱 ID 已存到 {ASSISTANT_CONFIG}")

5.7 文件传输 API

python 复制代码
# 上传文件到沙箱
backend.upload_files([
    ("/workspace/data.csv", b"city,sales\nShanghai,100\nBeijing,80\n"),
    ("/workspace/config.json", b'{"target": "SH"}'),
])
python 复制代码
# 从沙箱下载文件
results = backend.download_files([
    "/workspace/data.csv",
    "/workspace/config.json",
])
for r in results:
    if r.content is not None:
        print(f"📄 {r.path}: {len(r.content)} bytes")
    else:
        print(f"❌ 下载失败 {r.path}: {r.error}")

5.7 完整工作流:上传 → agent 处理 → 下载 → 销毁

python 复制代码
"""
完整工作流 · 上传 CSV → agent 在沙箱里分析 → 下载报告 → 销毁
"""
import os
import pathlib
import modal
from dotenv import load_dotenv
from langchain.chat_models import init_chat_model
from deepagents import create_deep_agent
from langchain_modal import ModalSandbox

load_dotenv()

model = init_chat_model(
    model="qwen-max",
    model_provider="openai",
    api_key=os.getenv("DASHSCOPE_API_KEY"),
    base_url=os.getenv("DASHSCOPE_BASE_URL"),
    temperature=0,
    max_retries=10,
)

app = modal.App.lookup("upload-download-demo", create_if_missing=True)
image = modal.Image.debian_slim(python_version="3.11")

sandbox = modal.Sandbox.create(
    app=app,
    image=image,
    timeout=60 * 10,
)
backend = ModalSandbox(sandbox=sandbox)
print(f"✅ 沙箱已就绪:{sandbox.object_id}")

try:
    local_csv = pathlib.Path("./uploads/sales.csv")
    local_csv.parent.mkdir(exist_ok=True)
    local_csv.write_bytes(
        b"city,sales\n"
        b"Shanghai,100\n"
        b"Beijing,80\n"
        b"Shenzhen,120\n"
        b"Guangzhou,90\n"
    )

    sandbox.exec("mkdir", "-p", "/workspace").wait()
    sandbox.filesystem.write_bytes(local_csv.read_bytes(), "/workspace/data.csv")
    print("📤 已上传 data.csv 到沙箱 /workspace/")

    SYSTEM_PROMPT = """你是数据分析助手。

【固定工作流,严格按顺序执行】
1. read_file 读 /workspace/data.csv
2. write_file 把分析脚本写到 /workspace/analyze.py
3. execute 跑 `python /workspace/analyze.py`
4. read_file 读 /workspace/report.md
5. 用自然语言把报告内容完整告诉用户

【硬性规则】
- 禁止 `python -c "..."` 内联
- 只用标准库
"""

    agent = create_deep_agent(
        model=model,
        backend=backend,
        system_prompt=SYSTEM_PROMPT,
    )

    result = agent.invoke(
        {"messages": [{
            "role": "user",
            "content": "分析 /workspace/data.csv,给出基本统计并指出销售最好的城市。",
        }]},
        version="v2",
    )

    def _to_text(msg) -> str:
        c = msg.content
        if isinstance(c, str):
            return c
        if isinstance(c, list):
            return "\n".join(
                b.get("text", "") for b in c
                if isinstance(b, dict) and b.get("type") == "text"
            )
        return str(c or "")

    print("\n" + "=" * 60)
    print("🤖 Agent 回答")
    print("=" * 60)
    print(_to_text(result.value["messages"][-1]))

    output_dir = pathlib.Path("./output")
    output_dir.mkdir(exist_ok=True)

    artifact_paths = [
        "/workspace/report.md",
        "/workspace/analyze.py",
    ]
    print("\n📥 下载产出物:")
    for remote_path in artifact_paths:
        try:
            content = sandbox.filesystem.read_bytes(remote_path)
            local_path = output_dir / pathlib.PurePosixPath(remote_path).name
            local_path.write_bytes(content)
            print(f"  ✅ {remote_path} → {local_path} ({len(content)} bytes)")
        except FileNotFoundError as e:
            print(f"  ❌ 下载失败 {remote_path}: {e}")
        except Exception as e:
            print(f"  ❌ 下载失败 {remote_path}: {type(e).__name__}: {e}")

finally:
    print("\n🧹 销毁沙箱...")
    sandbox.terminate()
    print("✅ 完成")

5.8 多租户 namespace 隔离

python 复制代码
"""
多租户 namespace 隔离 · 本地完整示例
"""
import os
from dataclasses import dataclass
from dotenv import load_dotenv
from langchain.chat_models import init_chat_model
from langgraph.store.memory import InMemoryStore
from deepagents import create_deep_agent
from deepagents.backends import CompositeBackend, StateBackend, StoreBackend

load_dotenv()

model = init_chat_model(
    model="qwen-max",
    model_provider="openai",
    api_key=os.getenv("DASHSCOPE_API_KEY"),
    base_url=os.getenv("DASHSCOPE_BASE_URL"),
    temperature=0,
    max_retries=10,
)

@dataclass
class AppContext:
    user_id: str

shared_store = InMemoryStore()

backend = CompositeBackend(
    default=StateBackend(),
    routes={
        "/memories/": StoreBackend(
            namespace=lambda rt: (rt.context.user_id,),
        ),
    },
)

agent = create_deep_agent(
    model=model,
    backend=backend,
    store=shared_store,
    system_prompt=(
        "你是研究助手。当用户告诉你某个偏好时,把它写到 /memories/preferences.md;"
        "之后再被问到偏好时,先 read_file 读 /memories/preferences.md,"
        "如果存在就照里面的写;如果文件不存在,就如实告诉用户你没记录过。"
    ),
)


def simulate(user_id: str, message: str, thread_id: str):
    print(f"\n{'='*60}")
    print(f"👤 用户 {user_id} · thread={thread_id}")
    print(f"💬 {message}")
    print("-" * 60)
    result = agent.invoke(
        {"messages": [{"role": "user", "content": message}]},
        config={"configurable": {"thread_id": thread_id}},
        context=AppContext(user_id=user_id),
        version="v2",
    )
    msg = result.value["messages"][-1]
    text = msg.content if isinstance(msg.content, str) else "\n".join(
        b.get("text", "") for b in msg.content if isinstance(b, dict)
    )
    print(f"🤖 {text}")


simulate("alice",   "请记住:我偏爱用 markdown 表格输出研究结果。",      "thread-A1")
simulate("alice",   "我之前告诉过你的输出偏好是什么?",                 "thread-A2")
simulate("bob",     "我之前告诉过你的输出偏好是什么?",                 "thread-B1")
simulate("bob",     "请记住:我偏爱用 yaml 输出,不要 markdown。",         "thread-B1")
simulate("charlie", "我之前告诉过你的输出偏好是什么?",                 "thread-C1")

print("\n" + "=" * 60)
print("📦 Store 实际命名空间布局")
print("=" * 60)
for ns in shared_store.list_namespaces():
    items = shared_store.search(ns)
    print(f"  namespace={ns}  →  {len(items)} item(s)")
    for it in items:
        print(f"    └ key={it.key}")

5.9 安全:凭证留在本机

python 复制代码
"""
正路 A · 凭证留在本机,沙箱只跑代码
"""
import os
import modal
import smtplib
from email.message import EmailMessage
from dotenv import load_dotenv
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
from deepagents import create_deep_agent
from langchain_modal import ModalSandbox

load_dotenv()

model = ChatOpenAI(
    model="qwen-max",
    api_key=os.getenv("DASHSCOPE_API_KEY"),
    base_url=os.getenv("DASHSCOPE_BASE_URL"),
    temperature=0,
    max_retries=10,
)

@tool
def send_email(to: str, subject: str, body: str) -> str:
    """发送一封邮件。沙箱里的代码无法访问 SMTP_PASS 环境变量。"""
    smtp_user = os.environ["SMTP_USER"]
    smtp_pass = os.environ["SMTP_PASS"]
    msg = EmailMessage()
    msg["From"] = smtp_user
    msg["To"] = to
    msg["Subject"] = subject
    msg.set_content(body)
    return f"[mock] 已发送邮件给 {to}"

app = modal.App.lookup("secure-agent-demo", create_if_missing=True)
image = modal.Image.debian_slim(python_version="3.11")
sandbox = modal.Sandbox.create(app=app, image=image, timeout=60 * 10)
backend = ModalSandbox(sandbox=sandbox)

agent = create_deep_agent(
    model=model,
    tools=[send_email],
    backend=backend,
    system_prompt=(
        "你可以在沙箱里跑 Python 代码处理数据,处理完调用 send_email "
        "把简报发给用户。注意:send_email 在主机上执行,不在沙箱里。"
    ),
)

try:
    agent.invoke(
        {"messages": [{
            "role": "user",
            "content": "用沙箱跑代码算 1+2+...+100 的结果,然后用邮件把答案发给 me@example.com。",
        }]},
        version="v2",
    )
finally:
    sandbox.terminate()

第六章 子智能体、记忆与技能体系

6.5 并行子代理实战

python 复制代码
"""
并行子代理:让主 agent 在一轮里并行调用 task,显著提速。
"""
import os
import time
from dotenv import load_dotenv
from langchain.chat_models import init_chat_model
from deepagents import create_deep_agent

load_dotenv()

main_model = init_chat_model(
    model="qwen-max",
    model_provider="openai",
    api_key=os.getenv("DASHSCOPE_API_KEY"),
    base_url=os.getenv("DASHSCOPE_BASE_URL"),
    temperature=0,
    max_retries=10,
)

sub_model = init_chat_model(
    model="qwen-turbo",
    model_provider="openai",
    api_key=os.getenv("DASHSCOPE_API_KEY"),
    base_url=os.getenv("DASHSCOPE_BASE_URL"),
    temperature=0,
    max_retries=10,
)


def get_company_info(name: str) -> str:
    """查询公司基本信息(模拟 1 秒网络延迟)。"""
    time.sleep(1)
    fake_db = {
        "阿里巴巴": "电商与云计算巨头,2024 年营收 9411 亿元,员工 22 万",
        "腾讯": "社交与游戏巨头,2024 年营收 6602 亿元,员工 11 万",
        "字节跳动": "短视频与广告巨头,2024 年营收约 1.55 万亿元,员工 16 万",
        "美团": "本地生活服务平台,2024 年营收 3376 亿元,员工 8 万",
        "京东": "电商与物流,2024 年营收 1.16 万亿元,员工 51 万",
    }
    return fake_db.get(name, f"未找到 {name} 的信息")


researcher = {
    "name": "researcher",
    "description": (
        "调研单家公司,返回核心数据。"
        "适合:'调研 XXX 公司'。"
        "返回:1-2 句话的核心数据(营收+员工+主业)。"
        "不适合:对比、综合分析。"
    ),
    "system_prompt": (
        "你是公司分析师。调用 get_company_info 查询给定公司,"
        "用 1-2 句话总结其核心数据(营收+员工+主业)。"
        "不要发挥,只复述事实。"
    ),
    "tools": [get_company_info],
    "model": sub_model,
}

agent = create_deep_agent(
    model=main_model,
    subagents=[researcher],
    system_prompt=(
        "你是研究主管。\n"
        "处理多家公司任务时,**必须在同一轮里并行调用多次 task 工具**,"
        "每家公司一个 task 调用,把任务委派给 researcher 子代理。\n"
        "禁止串行------同一轮调用应该一次性发出所有 task。\n"
        "收齐所有子代理返回后,综合写一份对比简报。"
    ),
)


if __name__ == "__main__":
    start = time.time()
    result = agent.invoke(
        {"messages": [{
            "role": "user",
            "content": "并行调研:阿里巴巴、腾讯、字节跳动、美团、京东。最后给我一份对比简报。"
        }]},
        version="v2",
    )
    elapsed = time.time() - start

    print("=" * 60)
    print(f"⏱️  耗时:{elapsed:.1f} 秒")
    print("=" * 60)
    print("📊 最终简报:")
    print(result.value["messages"][-1].content)

6.9 综合记忆策略

python 复制代码
"""
综合记忆策略示例
"""
import os
from dataclasses import dataclass
from dotenv import load_dotenv
from langchain_core.utils.uuid import uuid7
from langchain_openai import ChatOpenAI
from langgraph.store.memory import InMemoryStore
from langgraph.checkpoint.memory import MemorySaver
from deepagents import create_deep_agent
from deepagents.backends import CompositeBackend, StateBackend, StoreBackend
from deepagents.backends.utils import create_file_data

load_dotenv()

model = ChatOpenAI(
    model="qwen-max",
    api_key=os.getenv("DASHSCOPE_API_KEY"),
    base_url=os.getenv("DASHSCOPE_BASE_URL"),
    temperature=0,
    max_retries=10,
)

store = InMemoryStore()

store.put(
    ("alice",),
    "/memories/AGENTS.md",
    create_file_data("""## Alice 的偏好
- 回复用中文
- 偏好简洁的 bullet point
- 输出 markdown 格式
"""),
)


@dataclass
class AppContext:
    user_id: str


agent = create_deep_agent(
    model=model,
    memory=["/memories/AGENTS.md"],
    backend=CompositeBackend(
        default=StateBackend(),
        routes={
            "/memories/": StoreBackend(
                namespace=lambda rt: (rt.context.user_id,),
            ),
            "/reports/": StoreBackend(
                namespace=lambda rt: (rt.context.user_id,),
            ),
        },
    ),
    store=store,
    checkpointer=MemorySaver(),
    system_prompt=(
        "你是研究助手。\n"
        "- 启动时读取 /memories/AGENTS.md 了解用户偏好\n"
        "- 任务产出报告时写到 /reports/<topic>.md\n"
        "- 临时草稿写到 /tmp/ 或不写文件\n"
        "- 严格遵循用户偏好"
    ),
)


if __name__ == "__main__":
    config = {"configurable": {"thread_id": str(uuid7())}}
    result = agent.invoke(
        {"messages": [{"role": "user", "content": "请帮我简单介绍下 RAG 是什么"}]},
        config=config,
        context=AppContext(user_id="alice"),
        version="v2",
    )

    print("=" * 60)
    print("📝 Alice 的偏好(从 store 读取):")
    print("=" * 60)
    items = store.search(("alice",))
    for it in items:
        print(f"  📄 {it.key}")
        print(f"  内容片段: {it.value['content'][:80]}...")

    print("\n" + "=" * 60)
    print("🤖 Agent 回答:")
    print("=" * 60)
    msg = result.value["messages"][-1]
    content = msg.content if isinstance(msg.content, str) else "\n".join(
        b.get("text", "") for b in msg.content if isinstance(b, dict)
    )
    print(content)

6.10 研究助手 v3(终极版)

python 复制代码
"""
研究助手 v3:完整记忆与技能体系
"""
import os
import time
from dataclasses import dataclass
from dotenv import load_dotenv
from langchain.chat_models import init_chat_model
from langchain_core.utils.uuid import uuid7
from langgraph.store.memory import InMemoryStore
from langgraph.checkpoint.memory import MemorySaver
from deepagents import create_deep_agent
from deepagents.backends import CompositeBackend, StateBackend, StoreBackend
from deepagents.backends.utils import create_file_data

load_dotenv()

main_model = init_chat_model(
    model="qwen-max",
    model_provider="openai",
    api_key=os.getenv("DASHSCOPE_API_KEY"),
    base_url=os.getenv("DASHSCOPE_BASE_URL"),
    temperature=0,
    max_retries=10,
)

sub_model = init_chat_model(
    model="qwen-turbo",
    model_provider="openai",
    api_key=os.getenv("DASHSCOPE_API_KEY"),
    base_url=os.getenv("DASHSCOPE_BASE_URL"),
    temperature=0,
    max_retries=10,
)


def search_web(keyword: str) -> str:
    """搜索网页(演示数据)。"""
    time.sleep(1)
    return (
        f"关于「{keyword}」的搜索结果:\n"
        "1. 该主题近 6 个月有 3 项重大进展...\n"
        "2. 行业头部公司 A、B、C 均有相关产品...\n"
        "3. 主要争议点在于...\n"
    )


store = InMemoryStore()

research_skill = """---
name: research-methodology
description: 用于做主题调研时,提供标准方法论 SOP
---

# 研究方法论 SOP

## 1. 拆解主题
把主题拆成 3-5 个维度。

## 2. 分维度检索
对每个维度调用 search_web。

## 3. 综合
读所有笔记,综合写报告。

## 4. 引用规范
所有事实陈述后必须附 [来源:xxx]。
"""

store.put(
    ("alice",),
    "/skills/research-methodology/SKILL.md",
    create_file_data(research_skill),
)

store.put(
    ("alice",),
    "/memories/AGENTS.md",
    create_file_data("""## Alice 的偏好
- 回复用中文
- 报告用 markdown 表格对比
- 输出尽量简洁,核心结论在前
"""),
)


researcher = {
    "name": "researcher",
    "description": (
        "调研单个话题,可并行调用。"
        "返回:3-5 条关键事实。"
    ),
    "system_prompt": (
        "你是调研专家。\n"
        "对给定主题调用 search_web 1-2 次,"
        "整理出 3-5 条关键事实。"
    ),
    "tools": [search_web],
    "model": sub_model,
}


@dataclass
class AppContext:
    user_id: str


agent = create_deep_agent(
    model=main_model,
    tools=[search_web],
    subagents=[researcher],
    skills=["/skills/"],
    memory=["/memories/AGENTS.md"],
    backend=CompositeBackend(
        default=StateBackend(),
        routes={
            "/skills/":   StoreBackend(namespace=lambda rt: (rt.context.user_id,)),
            "/memories/": StoreBackend(namespace=lambda rt: (rt.context.user_id,)),
            "/reports/":  StoreBackend(namespace=lambda rt: (rt.context.user_id,)),
        },
    ),
    store=store,
    checkpointer=MemorySaver(),
    system_prompt=(
        "你是研究主管。处理多主题对比任务的标准流程:\n"
        "1. 先 write_todos 列出计划\n"
        "2. **同一轮里并行调用多次 task** 把每个主题委派给 researcher 子代理\n"
        "3. 收齐所有子代理结果后,综合写报告\n"
        "4. 用 write_file 把报告写到 /reports/<日期>-<主题摘要>.md\n"
        "5. 把报告内容作为最终回答\n"
    ),
)


if __name__ == "__main__":
    config = {"configurable": {"thread_id": str(uuid7())}}
    start = time.time()
    result = agent.invoke(
        {"messages": [{
            "role": "user",
            "content": (
                "请并行调研三个主题:「Multi-Agent 框架」、「Agent 评估方法」、"
                "「Agent 部署平台」,最后给我对比报告。"
            )
        }]},
        config=config,
        context=AppContext(user_id="alice"),
        version="v2",
    )
    elapsed = time.time() - start

    state = result.value

    print("=" * 60)
    print(f"⏱️  总耗时:{elapsed:.1f} 秒")
    print("=" * 60)

    print("\n📋 任务清单:")
    for todo in state.get("todos", []):
        icon = {"completed": "✅", "in_progress": "🔄", "pending": "⏳"}[todo["status"]]
        print(f"  {icon} {todo['content']}")

    print("\n📁 当前 state 中的文件:")
    for path in state.get("files", {}).keys():
        print(f"  📄 {path}")

    print("\n📁 Store 中 Alice 的持久文件:")
    for it in store.search(("alice",)):
        print(f"  📄 {it.key}")

    print("\n" + "=" * 60)
    print("📰 最终对比报告:")
    print("=" * 60)
    msg = state["messages"][-1]
    content = msg.content if isinstance(msg.content, str) else "\n".join(
        b.get("text", "") for b in msg.content if isinstance(b, dict)
    )
    print(content)

第七章 人机协同(HITL)与 Profiles

7.4 HITL 最小完整示例

python 复制代码
"""
HITL 最小完整示例:删除文件需要人工确认
"""
import os
from dotenv import load_dotenv
from langchain.chat_models import init_chat_model
from langchain.tools import tool
from langchain_core.utils.uuid import uuid7
from langgraph.checkpoint.memory import MemorySaver
from langgraph.types import Command
from deepagents import create_deep_agent

load_dotenv()

model = init_chat_model(
    model="qwen-max",
    model_provider="openai",
    api_key=os.getenv("DASHSCOPE_API_KEY"),
    base_url=os.getenv("DASHSCOPE_BASE_URL"),
    temperature=0,
    max_retries=10,
)

@tool
def delete_file(path: str) -> str:
    """删除指定路径的文件。"""
    return f"✅ 已删除 {path}"

agent = create_deep_agent(
    model=model,
    tools=[delete_file],
    interrupt_on={"delete_file": True},
    checkpointer=MemorySaver(),
    system_prompt="你是文件管理助手。用户要求删除文件时,调用 delete_file 工具。",
)

config = {"configurable": {"thread_id": str(uuid7())}}

result = agent.invoke(
    {"messages": [{"role": "user", "content": "把 /tmp/old.log 删了"}]},
    config=config,
    version="v2",
)

if result.interrupts:
    interrupt_value = result.interrupts[0].value
    for action in interrupt_value["action_requests"]:
        print(f"请审批: {action['name']}({action['args']})")

    user_input = input("批准吗?[y/n/edit]: ").strip().lower()

    if user_input == "y":
        decisions = [{"type": "approve"}]
    elif user_input == "edit":
        new_path = input("改成什么路径: ")
        decisions = [{
            "type": "edit",
            "edited_action": {
                "name": "delete_file",
                "args": {"path": new_path},
            },
        }]
    else:
        decisions = [{"type": "reject"}]

    result = agent.invoke(
        Command(resume={"decisions": decisions}),
        config=config,
        version="v2",
    )

print("\n" + "=" * 50)
print(" 最终回答:")
print(result.value["messages"][-1].content)

7.7 工具内部主动 interrupt()

python 复制代码
import os
from langchain.agents import create_agent
from langchain.chat_models import init_chat_model
from langchain.messages import HumanMessage
from langchain.tools import tool
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.types import Command, interrupt
from deepagents.middleware.subagents import CompiledSubAgent
from dotenv import load_dotenv
from deepagents import create_deep_agent

load_dotenv()

model = init_chat_model(
    model="qwen-max",
    model_provider="openai",
    api_key=os.getenv("DASHSCOPE_API_KEY"),
    base_url=os.getenv("DASHSCOPE_BASE_URL"),
    temperature=0,
    max_retries=10,
)

@tool(description="在执行某个动作前请求人工批准。")
def request_approval(action_description: str) -> str:
    """Request human approval using the interrupt() primitive."""
    approval = interrupt({
        "type": "approval_request",
        "action": action_description,
        "message": f"请批准或拒绝:{action_description}",
    })

    if approval.get("approved"):
        return f"动作 '{action_description}' 已批准,继续执行..."
    else:
        return f"动作 '{action_description}' 被拒绝。原因:{approval.get('reason', '未说明')}"


def main():
    checkpointer = InMemorySaver()

    compiled_subagent = create_agent(
        model=model,
        tools=[request_approval],
        name="approval-agent",
    )

    parent_agent = create_deep_agent(
        model=model,
        checkpointer=checkpointer,
        subagents=[
            CompiledSubAgent(
                name="approval-agent",
                description="可以请求人工批准的子代理",
                runnable=compiled_subagent,
            )
        ],
    )

    config = {"configurable": {"thread_id": "test_interrupt_directly"}}

    result = parent_agent.invoke(
        {
            "messages": [HumanMessage(
                content="用 task 工具调起 approval-agent 子代理,让它通过 request_approval 工具,"
                        "请求批准 '部署到生产环境' 这个动作。"
            )]
        },
        config=config,
        version="v2",
    )

    if result.interrupts:
        interrupt_value = result.interrupts[0].value
        print(f"🛑 中断已触发!")
        print(f"   类型: {interrupt_value.get('type')}")
        print(f"   动作: {interrupt_value.get('action')}")
        print(f"   提示: {interrupt_value.get('message')}")

        result2 = parent_agent.invoke(
            Command(resume={"approved": True}),
            config=config,
            version="v2",
        )

        if not result2.interrupts:
            tool_msgs = [m for m in result2.value.get("messages", []) if m.type == "tool"]
            if tool_msgs:
                print(f"\n✅ 工具结果: {tool_msgs[-1].content}")


if __name__ == "__main__":
    main()

7.8 Permissions 声明式文件权限

python 复制代码
from deepagents import create_deep_agent, FilesystemPermission

# 隔离到 workspace 目录
agent = create_deep_agent(
    model=model,
    backend=backend,
    permissions=[
        FilesystemPermission(operations=["read", "write"], paths=["/workspace/**"], mode="allow"),
        FilesystemPermission(operations=["read", "write"], paths=["/**"], mode="deny"),
    ],
)
python 复制代码
# 只读 memory
agent = create_deep_agent(
    model=model,
    backend=CompositeBackend(
        default=StateBackend(),
        routes={
            "/memories/": StoreBackend(namespace=lambda rt: (rt.server_info.user.identity,)),
            "/policies/": StoreBackend(namespace=lambda rt: (rt.context.org_id,)),
        },
    ),
    permissions=[
        FilesystemPermission(
            operations=["write"],
            paths=["/memories/**", "/policies/**"],
            mode="deny",
        ),
    ],
)
python 复制代码
# 子代理独立权限
agent = create_deep_agent(
    model=model,
    backend=backend,
    permissions=[
        FilesystemPermission(operations=["read", "write"], paths=["/workspace/**"], mode="allow"),
        FilesystemPermission(operations=["read", "write"], paths=["/**"], mode="deny"),
    ],
    subagents=[{
        "name": "auditor",
        "description": "只读的代码审查员",
        "system_prompt": "审查代码,找出问题。",
        "permissions": [
            FilesystemPermission(operations=["write"], paths=["/**"], mode="deny"),
            FilesystemPermission(operations=["read"], paths=["/workspace/**"], mode="allow"),
            FilesystemPermission(operations=["read"], paths=["/**"], mode="deny"),
        ],
    }],
)

7.11 HarnessProfile

python 复制代码
from deepagents import (
    GeneralPurposeSubagentProfile,
    HarnessProfile,
    register_harness_profile,
)

register_harness_profile(
    "openai:gpt-5.4",
    HarnessProfile(
        system_prompt_suffix="Respond in under 100 words.",
        excluded_tools={"execute"},
        excluded_middleware={"SummarizationMiddleware"},
        general_purpose_subagent=GeneralPurposeSubagentProfile(enabled=False),
    ),
)
python 复制代码
# 不同模型的 suffix 调优
register_harness_profile(
    "anthropic:claude-sonnet-4-6",
    HarnessProfile(
        system_prompt_suffix="When multiple independent tool calls are needed, batch them in parallel."
    ),
)

register_harness_profile(
    "openai:gpt-5.4",
    HarnessProfile(
        system_prompt_suffix="Respond concisely. Avoid restating what the user said."
    ),
)

register_harness_profile(
    "openai:qwen-max",
    HarnessProfile(
        system_prompt_suffix="始终用中文回答。回答要简洁,避免冗余。"
    ),
)

7.12 ProviderProfile

python 复制代码
from deepagents import ProviderProfile, register_provider_profile

register_provider_profile(
    "openai",
    ProviderProfile(init_kwargs={"temperature": 0}),
)

7.14 从配置文件加载 profile

python 复制代码
# openai.yaml
base_system_prompt: You are helpful.
system_prompt_suffix: Respond briefly.
excluded_tools:
  - execute
  - grep
excluded_middleware:
  - SummarizationMiddleware
  - my_pkg.middleware:TelemetryMiddleware
general_purpose_subagent:
  enabled: false
python 复制代码
import yaml
from deepagents import HarnessProfileConfig, register_harness_profile

with open("openai.yaml") as f:
    register_harness_profile(
        "openai",
        HarnessProfileConfig.from_dict(yaml.safe_load(f)),
    )

7.15 综合实战:多模型 + HITL + Permissions + Profiles

python 复制代码
"""
第七章综合实战:多模型 + HITL + Permissions + Profiles
"""
import os
from dataclasses import dataclass
from dotenv import load_dotenv
from langchain.tools import tool
from langchain_openai import ChatOpenAI
from langchain_core.utils.uuid import uuid7
from langgraph.checkpoint.memory import MemorySaver
from langgraph.types import Command
from langgraph.store.memory import InMemoryStore

from deepagents import (
    create_deep_agent,
    FilesystemPermission,
    HarnessProfile,
    GeneralPurposeSubagentProfile,
    register_harness_profile,
)
from deepagents.backends import CompositeBackend, StateBackend, StoreBackend
from deepagents.backends.utils import create_file_data

load_dotenv()


register_harness_profile(
    "anthropic:claude-sonnet-4-6",
    HarnessProfile(
        system_prompt_suffix=(
            "When multiple independent tool calls are needed, batch them in parallel.\n"
            "All factual claims must cite their source."
        ),
    ),
)

register_harness_profile(
    "openai:qwen-max",
    HarnessProfile(
        system_prompt_suffix="始终用中文回答。所有事实陈述后附 [来源:xxx]。",
        general_purpose_subagent=GeneralPurposeSubagentProfile(enabled=False),
    ),
)


@tool
def search_web(keyword: str) -> str:
    """搜索网页(演示数据)。"""
    return f"关于「{keyword}」的检索结果:(这里是 2000 字的资料...)[来源:demo]"


@tool
def send_report_email(to: str, subject: str, body: str) -> str:
    """把研究报告发邮件给指定人(高风险,需要审批)。"""
    return f"✅ 已发送邮件给 {to},主题:{subject}"


@dataclass
class AppContext:
    user_id: str


store = InMemoryStore()
store.put(
    ("alice",),
    "/memories/AGENTS.md",
    create_file_data("""## Alice 的偏好
- 回复用中文
- 报告控制在 500 字以内
- 不要打广告
"""),
)


model = ChatOpenAI(
    model="qwen-max",
    api_key=os.getenv("DASHSCOPE_API_KEY"),
    base_url=os.getenv("DASHSCOPE_BASE_URL"),
    temperature=0,
    max_retries=10,
)

agent = create_deep_agent(
    model=model,
    tools=[search_web, send_report_email],

    interrupt_on={
        "send_report_email": {"allowed_decisions": ["approve", "edit", "reject"]},
    },

    permissions=[
        FilesystemPermission(operations=["write"], paths=["/memories/**"], mode="deny"),
        FilesystemPermission(operations=["read", "write"], paths=["/reports/**", "/workspace/**"], mode="allow"),
    ],

    backend=CompositeBackend(
        default=StateBackend(),
        routes={
            "/memories/": StoreBackend(namespace=lambda rt: (rt.context.user_id,)),
            "/reports/":  StoreBackend(namespace=lambda rt: (rt.context.user_id,)),
        },
    ),
    store=store,
    memory=["/memories/AGENTS.md"],

    checkpointer=MemorySaver(),

    system_prompt=(
        "你是研究助手。工作流:\n"
        "1. 启动时读 /memories/AGENTS.md 了解用户偏好\n"
        "2. 用 search_web 收集资料\n"
        "3. 把报告写到 /reports/<topic>.md\n"
        "4. 如果用户要求发邮件,调用 send_report_email\n"
    ),
)


if __name__ == "__main__":
    config = {"configurable": {"thread_id": str(uuid7())}}

    result = agent.invoke(
        {"messages": [{
            "role": "user",
            "content": "调研一下「Deep Agents 框架」,写份简报到 /reports/deepagents.md,然后发邮件给 alice@example.com。",
        }]},
        config=config,
        context=AppContext(user_id="alice"),
        version="v2",
    )

    while result.interrupts:
        interrupt_value = result.interrupts[0].value
        action_requests = interrupt_value["action_requests"]
        config_map = {cfg["action_name"]: cfg for cfg in interrupt_value["review_configs"]}

        print("\n" + "=" * 60)
        print("🛑 检测到中断,待审批:")
        print("=" * 60)
        for action in action_requests:
            review_config = config_map[action["name"]]
            print(f"  工具: {action['name']}")
            print(f"  参数: {action['args']}")
            print(f"  允许决策: {review_config['allowed_decisions']}")

        decisions = [{"type": "approve"} for _ in action_requests]

        result = agent.invoke(
            Command(resume={"decisions": decisions}),
            config=config,
            context=AppContext(user_id="alice"),
            version="v2",
        )

    state = result.value
    print("\n" + "=" * 60)
    print("📁 当前文件:")
    for path in state.get("files", {}):
        print(f"  📄 {path}")

    print("\n📁 Alice 的持久文件:")
    for it in store.search(("alice",)):
        print(f"  📄 {it.key}")

    print("\n" + "=" * 60)
    print("🤖 最终回答:")
    print("=" * 60)
    msg = state["messages"][-1]
    content = msg.content if isinstance(msg.content, str) else "\n".join(
        b.get("text", "") for b in msg.content if isinstance(b, dict)
    )
    print(content)

第七章(续)流式输出

7.18 v2 stream:updates 模式

python 复制代码
"""
v2 流式输出 · updates 模式
"""
import os
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from deepagents import create_deep_agent

load_dotenv()

model = ChatOpenAI(
    model="qwen-max",
    api_key=os.getenv("DASHSCOPE_API_KEY"),
    base_url=os.getenv("DASHSCOPE_BASE_URL"),
    temperature=0,
    max_retries=10,
)

def search_web(keyword: str) -> str:
    """搜索网页(演示数据)。"""
    return f"关于「{keyword}」的检索结果:...(2000 字)"

agent = create_deep_agent(
    model=model,
    tools=[search_web],
    subagents=[{
        "name": "researcher",
        "description": "调研单个话题,返回核心结论",
        "system_prompt": "调用 search_web,返回 3 条要点。",
        "tools": [search_web],
    }],
    system_prompt="你是研究主管。把调研任务委派给 researcher。",
)

for chunk in agent.stream(
    {"messages": [{"role": "user", "content": "调研「AI Agent 框架」"}]},
    stream_mode="updates",
    subgraphs=True,
    version="v2",
):
    if chunk["type"] != "updates":
        continue

    if not chunk["ns"]:
        for node_name in chunk["data"]:
            print(f"🤖 [主 agent] 跑完节点: {node_name}")
    else:
        sub_id = chunk["ns"][0]
        for node_name in chunk["data"]:
            print(f"   🔬 [{sub_id}] 跑完节点: {node_name}")

7.18 v2 stream:messages 模式(打字机效果)

python 复制代码
current_source = None

for chunk in agent.stream(
    {"messages": [{"role": "user", "content": "调研「AI Agent 框架」"}]},
    stream_mode="messages",
    subgraphs=True,
    version="v2",
):
    if chunk["type"] != "messages":
        continue

    token, metadata = chunk["data"]

    is_subagent = any(s.startswith("tools:") for s in chunk["ns"])
    source = chunk["ns"][0] if is_subagent else "main"

    if source != current_source:
        print(f"\n\n=== [{source}] ===")
        current_source = source

    if token.content:
        print(token.content, end="", flush=True)

7.18 v2 stream:custom 模式(自定义事件)

python 复制代码
import time
from langchain.tools import tool
from langgraph.config import get_stream_writer

@tool
def analyze_data(topic: str) -> str:
    """分析数据并实时汇报进度。"""
    writer = get_stream_writer()

    writer({"status": "starting", "topic": topic, "progress": 0})
    time.sleep(0.5)

    writer({"status": "analyzing", "progress": 50})
    time.sleep(0.5)

    writer({"status": "complete", "progress": 100})
    return f"分析完成,「{topic}」满意度 85%"
python 复制代码
# 消费 custom 事件
for chunk in agent.stream(
    {"messages": [{"role": "user", "content": "分析客户满意度"}]},
    stream_mode="custom",
    subgraphs=True,
    version="v2",
):
    if chunk["type"] == "custom":
        is_sub = any(s.startswith("tools:") for s in chunk["ns"])
        prefix = chunk["ns"][0] if is_sub else "main"
        print(f"[{prefix}] {chunk['data']}")

7.19 v3 stream_events

python 复制代码
"""
v3 stream_events 基础用法
"""
from langchain_community.chat_models.tongyi import ChatTongyi

model = ChatTongyi(model="qwen-max", dashscope_api_key=os.getenv("DASHSCOPE_API_KEY"))

# ... agent 创建同上 ...

run = agent.stream_events(
    {"messages": [{"role": "user", "content": "调研三个 AI Agent 框架"}]},
    version="v3",
)

for subagent in run.subagents:
    print(f"📍 子代理: {subagent.name}")
    print(f"   路径: {subagent.path}")
    print(f"   状态: {subagent.status}")

    for message in subagent.messages:
        print(f"   💬 {message.text}")

7.21 流式版研究助手

python 复制代码
"""
流式研究助手 · v3 stream_events 版
"""
import os
import time
from dotenv import load_dotenv
from langchain_community.chat_models import ChatTongyi
from deepagents import create_deep_agent

load_dotenv()

main_model = ChatTongyi(model="qwen-max", dashscope_api_key=os.getenv("DASHSCOPE_API_KEY"))
sub_model = ChatTongyi(model="qwen-turbo", dashscope_api_key=os.getenv("DASHSCOPE_API_KEY"))


def search_web(keyword: str) -> str:
    """搜索网页(模拟 1 秒网络延迟)。"""
    time.sleep(1)
    return (
        f"关于「{keyword}」的检索结果:\n"
        "1. 近 6 个月有 3 项重大进展...\n"
        "2. 头部公司 A、B、C 有相关产品...\n"
        "3. 主要争议点在于...\n"
    )


researcher = {
    "name": "researcher",
    "description": "调研单个话题,返回 3 条要点",
    "system_prompt": (
        "你是调研专家。调用 search_web 一次,"
        "用 3 条 bullet 总结结果,每条不超过 50 字。"
    ),
    "tools": [search_web],
    "model": sub_model,
}

agent = create_deep_agent(
    model=main_model,
    tools=[search_web],
    subagents=[researcher],
    system_prompt=(
        "你是研究主管。处理多主题任务时,"
        "**必须在同一轮里并行调用多次 task** 委派给 researcher。"
        "收齐所有子代理结果后,综合写对比简报。"
    ),
)


def main():
    run = agent.stream_events(
        {"messages": [{
            "role": "user",
            "content": "并行调研「Multi-Agent 框架」、「Agent 评估方法」、「Agent 部署平台」",
        }]},
        version="v3",
    )

    print("=" * 60)
    print("🚀 开始研究...")
    print("=" * 60)

    subagent_results = {}

    for name, item in run.interleave("messages", "subagents"):
        if name == "messages":
            if item.text:
                print(f"\n💬 [主 agent] {item.text}", end="", flush=True)

        elif name == "subagents":
            print(f"\n\n📦 子代理上线: {item.name}")
            print(f"   任务: {item.task_input[:80]}...")

            try:
                output = item.output
                subagent_results[item.name] = output
                print(f"   ✅ {item.name} 完成")
            except Exception as e:
                print(f"   ❌ {item.name} 失败: {e}")

    print("\n\n" + "=" * 60)
    print("📊 调研完成")
    print("=" * 60)
    print(f"成功子代理数: {len(subagent_results)}")


if __name__ == "__main__":
    main()

7.22 异步流式

python 复制代码
import asyncio

async def run_agent():
    async for chunk in agent.astream(
        {"messages": [{"role": "user", "content": "..."}]},
        stream_mode="messages",
        subgraphs=True,
        version="v2",
    ):
        # ...处理 chunk
        pass

asyncio.run(run_agent())
相关推荐
金銀銅鐵14 分钟前
斐波那契数列中连续10项的和总是能被11整除吗?
python·数学
databook17 分钟前
用 Python 计算联合概率和条件概率
python·数据分析
葡萄成熟时 !1 小时前
泛型知识笔记
开发语言·笔记·python
fengyangaiGEO1 小时前
GEO 如何分析竞品的 AI 引用情况?
人工智能·python·信息可视化
2601_966949651 小时前
实时行情中的成交量 Volume 到底是什么?包含盘中撤单量吗?从交易数据语义到 QuantDash 实战解析
开发语言·人工智能·python·量化·quantdash·量化数据源
you鬰2 小时前
datawhale--llm-algo-leetcode9️⃣ SFT Training Loop
python·datawhale
叫我:松哥2 小时前
基于flask仿小米商城管理系统,使用flask开的一个商场网站
数据库·后端·python·flask
智购科技无人售货机工厂2 小时前
2026自动售货机防拆机物理安全设计:从安全螺丝到结构互锁的工程实践~YH
android·网络·驱动开发·python·单片机·安全·云原生
2401_868534782 小时前
校园网规划与设计
python·pygame