第二章 安装与快速上手
2.1 安装
# uv 方式
uv init deep-agents-tutorial
cd deep-agents-tutorial
uv add deepagents langchain langchain-openai python-dotenv
# 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"
# 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 密钥
# .env
DASHSCOPE_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx
DASHSCOPE_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
2.3 第一个 Deep Agent
"""
你的第一个 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 显式构造
# 单一字符串简写
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 长任务的连接弹性配置
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 返回值
agent = create_deep_agent(...)
print(type(agent))
# <class 'langgraph.graph.state.CompiledStateGraph'>
# 流式输出小例子
for chunk in agent.stream(
{"messages": [{"role": "user", "content": "上海天气怎么样?"}]},
stream_mode=["updates", "custom"],
version="v2",
):
print(chunk["data"])
第三章 create_deep_agent 参数详解
3.2 model 参数:三种传入方式
# 方式 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=[...])
# 写法 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])
# 工具最佳实践:docstring + 类型注解
def search(query: str, top_k: int = 5) -> list[dict]:
"""在公司知识库中搜索相关文档。
Args:
query: 搜索关键词,建议 3-10 个字
top_k: 返回的文档数量,默认 5
Returns:
文档列表,每个文档包含 title / url / snippet 字段
"""
# 工具内部捕获异常
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 拼接
# 调试时查看完整 prompt
for event in agent.stream(
{"messages": [{"role": "user", "content": "你好"}]},
stream_mode="debug",
):
print(event)
3.5 middleware 中间件机制
# ❌ 错误示范:不要在 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
# subagents
agent = create_deep_agent(
model=model,
tools=[...],
subagents=[
{
"name": "researcher",
"description": "专门负责网络检索与信息汇总",
"system_prompt": "你是检索专家,只输出事实,不要主观判断。",
"tools": [web_search, read_url],
},
],
)
# skills
agent = create_deep_agent(
model=model,
tools=[...],
skills=["./skills/contract-review", "./skills/financial-analysis"],
)
# memory
agent = create_deep_agent(
model=model,
tools=[...],
memory=["~/.deepagents/researcher/AGENTS.md", "./project-AGENTS.md"],
)
# 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)},
),
)
# 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(),
)
# 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)
"""
让 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 虚拟文件系统
"""
研究助手:先把多个来源的资料分别保存为文件,再综合写报告
"""
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 子智能体配置示例
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。最后由你综合写报告。"
),
)
# 主强子弱模型搭配
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,
},
],
)
# 子代理结构化输出(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],
)
# 异步子代理(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 上下文管理:自定义阈值
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,
),
],
)
# 手动触发摘要的 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)
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,
)
# 处理中断(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)
# 多个工具调用的批量审批
decisions = [
{"type": "approve"}, # 第一个:delete_file 批准
{"type": "reject"}, # 第二个:send_email 拒绝
]
result = agent.invoke(
Command(resume={"decisions": decisions}),
config=config,
version="v2",
)
# 编辑参数后批准
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 配置
# 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)
# 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/"],
)
# 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
"""
研究助手 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 内置后端
# StateBackend(默认)
from deepagents.backends import StateBackend
agent = create_deep_agent(
model=model,
tools=[...],
backend=StateBackend(),
)
# FilesystemBackend
from deepagents.backends import FilesystemBackend
agent = create_deep_agent(
model=model,
tools=[...],
backend=FilesystemBackend(
root_dir="/Users/me/myproject",
virtual_mode=True,
),
)
# LocalShellBackend
from deepagents.backends import LocalShellBackend
agent = create_deep_agent(
model=model,
tools=[...],
backend=LocalShellBackend(
root_dir=".",
env={"PATH": "/usr/bin:/bin"},
),
)
# 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,),
),
)
# 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,),
),
},
),
)
5.4 Modal 沙箱配置
uv sync
modal token new
modal profile current
cat ~/.modal.toml
"""
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 沙箱
"""
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 沙箱
"""
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
# 上传文件到沙箱
backend.upload_files([
("/workspace/data.csv", b"city,sales\nShanghai,100\nBeijing,80\n"),
("/workspace/config.json", b'{"target": "SH"}'),
])
# 从沙箱下载文件
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 处理 → 下载 → 销毁
"""
完整工作流 · 上传 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 隔离
"""
多租户 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 安全:凭证留在本机
"""
正路 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 并行子代理实战
"""
并行子代理:让主 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 综合记忆策略
"""
综合记忆策略示例
"""
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(终极版)
"""
研究助手 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 最小完整示例
"""
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()
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 声明式文件权限
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"),
],
)
# 只读 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",
),
],
)
# 子代理独立权限
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
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),
),
)
# 不同模型的 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
from deepagents import ProviderProfile, register_provider_profile
register_provider_profile(
"openai",
ProviderProfile(init_kwargs={"temperature": 0}),
)
7.14 从配置文件加载 profile
# 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
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
"""
第七章综合实战:多模型 + 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 模式
"""
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 模式(打字机效果)
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 模式(自定义事件)
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%"
# 消费 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
"""
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 流式版研究助手
"""
流式研究助手 · 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 异步流式
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())