工具、MCP 与沙盒
本文档基于 Aivory v2.4.7 编写。相关界面:“能力与集成 → 工具 / MCP 服务 / 技能 / 提示词”。沙盒架构与 HTTP 契约另见 Python 沙箱说明 与 沙箱 API。
工具把模型回复变成对外部系统、网络、文件或代码执行环境的操作,是一个独立的权限面:模型能调用工具,不等于所有用户、所有模型、所有工作区都应获得该能力。
工具启用存在两层控制:
- 全局可用性(“能力与集成 → 工具”):停用后该工具从所有模型中移除,包括模型页明确允许它的。
- 模型默认项(“AI 与模型 → 模型 → 内置工具 / MCP 工具”):决定该模型对话中默认勾选哪些工具;用户仍可在单个对话中调整。
内置工具清单与每轮预算
平台工具及单轮对话上限(超出后模型只能基于已有结果作答):
| 工具 | 作用 | 单轮上限 | 未配置后端时 |
|---|---|---|---|
aivory_web_search | 联网搜索 | 16 | 禁用搜索后端即不可用 |
web_fetch | 抓取网页内容 | 12 | 始终可用 |
fetch_image | 抓取图片 | 16 | 始终可用 |
image_generate | 图像生成 | 8 | 需要先配置图片模型 |
python_execute | 沙盒内执行 Python | 16 | 沙盒未配置时对所有模型隐藏 |
save_memory | 保存长期记忆 | — | 随记忆功能 |
use_skill | 按需加载技能 | — | 随技能库 |
所有工具共享每轮总量上限(默认 48 次,AIVORY_LLM_MAX_TOOL_CALLS_PER_TURN);“快速”模式收紧到 12;深度研究模式放宽到约 150。相应环境变量与服务商轮次上限见环境变量进阶。
README 里“48 次工具调用、12 个服务商轮次”的说法并不精确:48 是正常模式每轮工具调用总上限,服务商请求轮次上限是独立配置(默认 20)。对外承诺能力时以本页数值为准。

联网搜索
“工具”页的“联网搜索”区块配置 aivory_web_search 的后端:
| 字段 | 说明 | 示例 | 默认 |
|---|---|---|---|
| 搜索后端 | SearXNG 自部署(无需密钥);Serper / Brave 为付费 | SearXNG(自部署) | 禁用 |
| Base URL | SearXNG 实例根地址 | https://searx.example.com | 空 |
| SearXNG 搜索引擎 | 逗号/空格分隔的引擎名或快捷键,留空用实例默认 | bing, wikipedia | 空 |
| API Key | Serper / Brave 必填 | — | 空 |
启动默认值由环境变量 SEARCH_PROVIDER / SEARCH_API_KEY / SEARCH_BASE_URL 提供,后台保存后以数据库为准。
推荐上线顺序:选定满足地区、隐私、额度和延迟要求的提供方 → 保存后用确定答案的问题验证结果质量 → 确认目标模型允许调用搜索、为用户群设置积分/配额 → 观察费用、失败率与不当内容风险,再决定开放深度研究与网页抓取。
搜索 Key 是服务端机密,不要进前端变量。可访问内网的实例还要评估 web_fetch / fetch_image 的抓取目标风险:不要让模型有理由去抓 http://169.254.169.254 或内部服务地址。
MCP 服务
Aivory 只接受 Streamable HTTP 传输的 MCP 服务(不支持 stdio 子进程)。注册流程:
- 进入“能力与集成 → MCP 服务”→“新建 MCP 服务”。
- 填写:
| 字段 | 说明 | 示例 |
|---|---|---|
| 名称 / 图标 / 工具描述 | 均必填;描述决定模型何时选用该服务,写成清晰的“能做什么” | 12306 火车查询 |
| Streamable HTTP URL | 端点必须能接收 MCP JSON-RPC 请求 | https://mcp.example.com/mcp |
| 请求头 | 可选,随每次 MCP 请求发送;已保存的敏感值以掩码显示 | Authorization: Bearer … |
| 对用户可用 | 建议先测试并同步工具后再开启 | — |
- 点“测试”验证连接(状态:未测试 / 连接正常 / 连接错误),再点“同步工具”拉取方法快照;列表显示“已发现 N 个 / 上次同步于 …”。
- 需要给某些模型默认勾选时,到模型编辑页的“MCP 工具”区块选择。未同步的服务即使启用也无法运行;服务被删除或停用后,模型默认项会显示“已删除/已停用/未同步”的失效标记,应清理或恢复。

- 该服务可读取或修改哪些外部数据?
- 请求头 / Bearer Key / OAuth 凭据是否只授予最小权限?
- 哪些模型、用户组或工作区应当看到这些工具?
- 服务不可用、工具输出异常或出现高频调用时如何降级?
把生产写入型 MCP + 个人访问令牌直接开放给所有聊天模型,等于把提示注入变成外部系统风险。先添加一个只读测试服务,检查发现的方法与参数,再小范围启用。每次服务端更新、方法变化或凭据轮换后都要重新同步和测试。
技能与提示词
“技能”是可复用的操作手册(名称、图标、何时使用(一句话)、完整指令 Markdown、附件资产),“提示词”是供用户加入个人库的公共模板。技能增强工具使用,但不能替代权限控制:
- 技能中不要写长期 API Key、数据库密码、Cookie 或一次性登录链接。
- 一个技能应说明它需要哪些工具、可访问的数据范围、失败时的安全行为。
- “何时使用”会被注入系统提示索引并决定召回率,写成触发条件而不是宣传语。
- 上传资产前检查版权、隐私与文件类型;管理员资产与用户附件的备份和访问控制不同。
- 每个新技能建立可重复的测试对话,确认模型不会因模糊指令调用写入型工具。
- 用户组页的“权限”区块可以按组限制可见的提示词、技能、工具和 MCP。

Python 沙盒:三种启用方式
python_execute 依赖可访问的沙盒服务。“工具”页“代码沙箱”区块显示当前状态;没有可用沙盒时显示**“请先配置沙盒”**,Python 开关保持关闭——这在个人版和完整版都一样,它反映实例能否安全连接到沙盒,与管理员权限无关。不要试图强制打开解释器绕过它。
| 运行方式 | 适用 | 配置要点 |
|---|---|---|
| 完整版内置 sidecar | 标准完整版 | Compose 已在内部网络提供 http://sandbox:8000,应用侧默认值已接好,无需暴露端口 |
| 个人版可选 profile | 可信单机 | 用 --profile sandbox 启动,并在 .env.personal 设置 SANDBOX_BASE_URL=http://sandbox:8000、SANDBOX_API_KEY=aivory-personal-sandbox |
| 外部沙盒 | 已有执行平台或需要更强隔离 | 在“工具”页(或环境变量 SANDBOX_BASE_URL / SANDBOX_API_KEY)填写 HTTPS 地址与相同 Bearer Key;独立部署的 sidecar 默认发布在 48217 端口,绝不可公网裸露 |
后台字段(“工具”页“代码沙箱”区块):
| 字段 | 说明 | 默认 |
|---|---|---|
| 沙箱地址 | 留空使用部署环境配置的地址 | 环境值 |
| 沙箱密钥 | 调用沙盒的 Bearer 密钥 | 环境值 |
| 执行超时(秒) | 单次运行最长秒数,范围 10–600;留空 = 120 | 120 |
| 闲置回收(秒) | 空闲多久回收会话(回收前归档工作区),范围 60–86400 | 1800 |
官方两个 compose 文件都把 sidecar 的执行上限设为 SANDBOX_EXEC_TIMEOUT_CAP_MS=600000(10 分钟);后台“执行超时”会被夹到 [10, 600] 秒并预留 120 秒客户端余量。文档与 README 中偶尔出现的“2 分钟”是 sidecar 未显式配置时的代码默认值,不是部署值。
会话生命周期与持久工作区
- 每个会话对应一个锁定严格的 Docker 容器:永远
--network none(不可配置),会话内不能 pip install 或访问网络;需要依赖就构建进运行镜像(Dockerfile.runner已预装 numpy/pandas/matplotlib/python-docx 等)。 - 会话按对话维度维护
/workspace;空闲超过回收阈值后被 reap,工作区 tar 归档到存储(local或 S3/OSS,归档键为对话 ID),下次执行自动恢复。多应用副本必须使用共享对象存储,本地归档只适合单节点。 - 归档受“存储与上传”中的归档保留天数控制(默认 30 天)。持久工作区 ≠ 永久保留所有输出:文件保留、删除与用户导出策略仍要管理员制定。
- 完整版与个人版沙盒 profile 都会启动镜像保活服务,防止宿主机
docker image prune -a清掉约 600MB 的运行镜像后,下一次python_execute冷拉取导致超时 500。不要把它当作多余容器删除。
安全边界
sidecar 通过宿主 Docker socket 创建会话容器,non-root、--cap-drop ALL、no-new-privileges、只读根文件系统、限额的 tmpfs。但 Docker socket 本身近似宿主机 root,因此:
- 只在完全受控的服务器启用本机 sidecar,不放不受信任的共享宿主机。
- 不给
sandbox服务加ports,不通过反向代理暴露它;沙盒 Bearer Key 泄露即可被用来创建/访问执行会话。 - 保留
SANDBOX_READ_ONLY_ROOTFS=1,除非完全理解关闭后的磁盘与写入风险。 - 用
SANDBOX_MEMORY(默认 2g)、SANDBOX_CPUS(1)、SANDBOX_PIDS_LIMIT(256)、SANDBOX_MAX_SESSIONS、SANDBOX_WORKSPACE_SIZE(512m)、SANDBOX_MAX_UPLOAD_BYTES(40MiB)给宿主机保留余量。 - 当前隔离等级不是 gVisor/microVM 级;更强需求可替换执行后端——保持沙箱 API 的 HTTP 契约不变即可。
模型对话中的真实效果(推理 + 工具调用过程可见):

运行与故障检查
Python 不可用时依次检查:
- “工具”页是否显示沙盒已配置,地址与密钥是否匹配(两边留空时都用环境值,容易“都以为对方配了”)。
- 完整版或本机 profile 的
sandbox容器是否运行、是否健康(GET /healthz返回{ok, docker, image})。 - 宿主 Docker daemon 与 socket 是否可用,运行时镜像是否已被误删(看 keepalive 服务)。
- 执行超时、并发会话上限、上传大小是否设置过低。
- 目标模型是否被全局禁用工具、模型内置工具默认项或工作区策略限制。
不要通过公开沙盒端口、关闭鉴权(SANDBOX_ALLOW_NO_AUTH 仅限本机开发)或无限增大资源上限来消除报错。先读脱敏后的应用与沙盒日志,定位是网络、鉴权、镜像、资源还是模型授权问题。
手工探活(外部沙盒)
沙盒是普通 HTTP 服务,可以直接验证契约(把 $KEY 换成沙盒密钥):
curl -s http://<sandbox-host>:48217/healthz -H "Authorization: Bearer $KEY"
# {"ok":true,"docker":true,"image":"..."}
SID=$(curl -s -X POST http://<sandbox-host>:48217/sessions \
-H "Authorization: Bearer $KEY" -H "content-type: application/json" \
-d '{}' | jq -r .session_id)
curl -s -X POST http://<sandbox-host>:48217/exec \
-H "Authorization: Bearer $KEY" -H "content-type: application/json" \
-d "{\"session_id\":\"$SID\",\"code\":\"print(6*7)\"}"
/healthz 里 docker:false 说明 socket 挂了或权限不对;返回 401 说明两边密钥不一致。
工具类故障速查
| 症状 | 优先检查 |
|---|---|
| 工具页显示“请先配置沙盒” | 后台或环境里 SANDBOX_BASE_URL + SANDBOX_API_KEY 成对配置了吗 |
| Python 偶发 500,日志提到 pull image | 运行镜像被 prune——检查 keepalive 服务与镜像策略 |
| 执行总在同一秒数被掐断 | 后台“执行超时”、sidecar SANDBOX_EXEC_TIMEOUT_CAP_MS、代理读超时三者取最小 |
| 换副本后工作区文件消失 | 归档写的是本地存储而应用多副本——改共享对象存储 |
| 搜索总是“不可用” | 搜索后端选择与 Key 是否匹配(SearXNG 无需 Key、Serper/Brave 必须有) |
| 模型说没有某工具 | 全局禁用 > 模型内置工具默认项 > 用户组权限 > 工作空间策略,四层任一拦截 |
| MCP 已启用但模型用不了 | “MCP 工具可用性”里该服务是否勾选、是否同步过工具 |