数据截至 (上游 commit db580e952e54)
第 4 章 · 资源、工具与沙箱
前几章讲的是 Agent 的"脑子"(循环、记忆)。本章讲"手脚"——模型说要查某张表、跑某段代码,这句话怎么精确落到真实的数据库连接、真实的函数、真实的容器上。
4.1 它要解决的小问题
模型输出的是文本:"我要查 orders 表"、"我要跑这段 Python"。但数据库连接、Python 函数、知识库检索器是完全不同的东西。需要一个统一抽象,让 Agent 不管挂的是哪种能力,都用同一套接口去用;同时,AI 生成的代码不能直接在主机上跑——得隔离。
4.2 思路/直觉:Resource 是统一工具箱
DB-GPT 用 Resource 抽象把所有"外部能力"统一起来(dbgpt/agent/resource/base.py:90-288)。不管是数据库、工具、知识库还是沙箱,都是一个 Resource,都实现同一组方法:
Resource (抽象,resource/base.py)
┌──────────┬──────────┬──────────┬───────────┐
│ get_prompt│ execute │ is_async │sub_resources│ ← 统一接口
└──────────┴──────────┴──────────┴───────────┘
│ │ │ │
┌────┴───┐ ┌───┴────┐ ┌───┴────┐ ┌───┴─────┐
│database│ │ tool │ │knowledge│ │ pack │
│DBResource│ │BaseTool│ │ 知识检索 │ │多资源打包 │
└────────┘ └────────┘ └─────────┘ └─────────┘
几个关键方法(resource/base.py):
type()/type_alias():这是什么类型的资源(ResourceType枚举,base.py:40-)。get_prompt(...)(base.py:173-):把资源描述成给模型看的 prompt——比如数据库资源会把表结构渲染成文字,塞进 system prompt 让模型知道有哪些表/字段。execute(...)/async_execute(...)(base.py:227-241):真正执行。sub_resources/is_pack(base.py:243-248):支持把多个资源打包(ResourcePack),Agent 可以一次挂一组。
资源怎么被 Agent 用上?在第 1 章 think 之前,_load_thinking_messages 会调 load_resource(base_agent.py:1380-1389),把资源的 get_prompt 结果注入上下文。这就是为什么 DataScientist 能"看到"表结构。
4.3 把普通函数变成工具:FunctionTool 与 @tool
最常用的资源是工具。DB-GPT 让你用装饰器把任意 Python 函数变成工具,核心是 FunctionTool(dbgpt/agent/resource/tool/base.py:140-229)和 @tool 装饰器(tool/base.py:232-)。
# 示意,基于真实 @tool API:把一个普通函数登记成工具
@tool(description="查询某城市天气")
def get_weather(city: str) -> str:
return weather_api(city)
# 之后把它作为 resource 绑到 Agent 上,模型就能"调用"它
@tool 内部做了什么(tool/base.py:240-249):把函数包成 FunctionTool,自动从函数签名和 docstring 解析出参数 schema 和描述(tool/base.py:156-162 的 _parse_args / _parse_docstring)。所以你写好类型注解和 docstring,工具的"说明书"就自动生成了。
执行时区分同步/异步(tool/base.py:195-229):
# tool/base.py:209-211 真实片段
if self._is_async:
raise ValueError("The function is asynchronous")
return self._func(*args, **kwargs)
is_async 在构造时就用 asyncio.iscoroutinefunction(func) 判好(tool/base.py:164),调错通道直接报错——避免在同步路径里 await 一个协程这类隐蔽 bug。
4.4 一个具体资源:数据库
DBResource(dbgpt/agent/resource/database.py)是 Text-to-SQL 的关键资源。第 1 章 DataScientist 的 correctness_check 里那句 await self.database.query(sql=..., db=...)(data_scientist_agent.py:134-137),self.database 就是从挂载的资源里取出来的 DBResource(data_scientist_agent.py:92-100)。它对外暴露 dialect(方言,拼进 prompt 让模型写对方言的 SQL)和 query(执行 SQL)。
这串起了整个 Text-to-SQL 闭环:资源把表结构喂进 prompt → 模型写 SQL → 资源执行 SQL → 结果回到 verify。资源既是"信息来源"又是"执行器"。
4.5 安全执行:dbgpt-sandbox
AI 写的代码必须隔离跑。dbgpt-sandbox 包用容器做隔离,分了清晰的层(packages/dbgpt-sandbox/src/dbgpt_sandbox/sandbox/):
| 层 | 干什么 |
|---|---|
| control_layer | 接任务、分发(connect/configure/execute/disconnect…) |
| execution_layer | 真正的运行时:docker / podman / nerdctl / local |
| display_layer | 把执行结果/产物展示出来 |
| user_layer | 对外服务接口 |
隔离靠容器的限额参数,创建会话时设上(execution_layer/docker_runtime.py:50-54):
# docker_runtime.py:50-54 真实片段
"mem_limit": self.config.max_memory,
"cpuset_cpus": str(self.config.max_cpus),
...
"network_disabled": self.config.network_disabled,
默认限额相当保守(sandbox/config.py:29-35):内存 256MB、CPU 50%、单次执行 30 秒、文件 10MB、最多 10 个进程。每种语言用固定的官方 slim 镜像(config.py:5-14),工作目录固定 /workspace(config.py:16)。
运行时选择是 fail-closed 的:SANDBOX_RUNTIME 不设时自动探测容器运行时,探测不到就失败关闭(config.py:53-56 注释),而不是悄悄退回"直接在主机跑"——除非你显式 SANDBOX_ALLOW_LOCAL_RUNTIME=1。这个默认值体现了"宁可不跑,也不裸跑"的安全取向。
4.6 边界与诚实
- 隔离是"限额 + 容器"级别,不是形式化沙箱:代码里见到的是 mem/cpu/network/进程数限额和容器隔离(
config.py、docker_runtime.py),没见到 seccomp profile、cap_drop、只读根文件系统这类更硬的内核级裁剪配置。对抗强对手时,这层隔离的强度取决于底层容器运行时的默认安全策略,而非 DB-GPT 额外加固。 - local runtime 是逃生门:
SANDBOX_ALLOW_LOCAL_RUNTIME打开后代码会在主机本地跑,失去隔离——生产环境别开。 - 工具说明书依赖 docstring:
@tool没给 description、函数又没 docstring,会直接raise ValueError("The description is required")(tool/base.py:156-159)——逼你写说明,也意味着模型看到的工具质量取决于你的注释质量。
4.7 代码地图
| 主题 | 文件路径 | 关键符号 |
|---|---|---|
| 资源抽象 | dbgpt-core/.../agent/resource/base.py | Resource, ResourceType, AgentResource |
| 工具基类与装饰器 | dbgpt-core/.../agent/resource/tool/base.py | BaseTool, FunctionTool, tool |
| 数据库资源 | dbgpt-core/.../agent/resource/database.py | DBResource |
| 资源加载进 prompt | dbgpt-core/.../agent/core/base_agent.py | load_resource, generate_resource_variables |
| 沙箱控制层 | dbgpt-sandbox/.../sandbox/control_layer/control_layer.py | ControlLayer.handle_task |
| 沙箱 docker 运行时 | dbgpt-sandbox/.../sandbox/execution_layer/docker_runtime.py | DockerSandboxSession, DockerRuntime |
| 沙箱安全默认值 | dbgpt-sandbox/.../sandbox/config.py | MAX_MEMORY, SANDBOX_ALLOW_LOCAL_RUNTIME |