第10章实践:受控学习便签助手——工具调用、沙盒与权限边界
预计 2×45 分钟。先完成"观察与运行",再完成"单变量修改与解释"。
第1步:需求与背景
为什么做这个
当你对AI助手说"帮我创建一个学习笔记",你期望它真的创建了一个文件。但大语言模型本身没有任何文件操作能力——它只能生成文字。那文件到底是谁创建的?
答案是:模型提出请求,宿主程序执行请求。模型输出的是一段结构化的 JSON("我想调用 create_note 工具,参数是这些"),然后由运行在电脑上的程序检查这个请求是否合法、是否安全,最后才真正执行。
这就是工具调用(tool calling)的核心机制:模型没有魔法,它只是"提议",真正"执行"的是你写的代码。
项目目标
制作一个受控学习便签助手:
- 输入:自然语言任务(如"帮我创建一个今日学习清单")
- 输出:模型提出工具调用 → 程序校验 → 沙盒执行 → 审计日志
- 三个工具:
create_note(创建笔记)、list_files(列出文件)、calc(数学计算) - 验收标准:正常调用成功执行,攻击调用被拦截并记录
明确不做
- 不做真实的 shell 执行或通用命令运行
- 不访问沙盒目录以外的文件系统
- 不联网或调用外部 API
- 不把模型输出直接当作已执行的结果
系统全景
自然语言任务
↓
模型生成 tool_call JSON(提议)
↓
Schema 校验 → 权限检查 → 路径安全检查 → 内容安全检查
↓(全部通过)
沙盒工具执行(创建文件 / 列出文件 / 计算结果)
↓
审计日志记录:提议、校验结果、执行结果、时间戳
↓
人工确认:日志证明已执行 ≠ 模型声称已创建
第2步:数据与信号
工具定义长什么样
打开 data/tool_schemas.json,你会看到三个工具的完整定义。每个工具包含:
- name:工具名称(如
create_note) - description:工具用途的自然语言说明(模型靠这个决定何时调用)
- parameters:JSON Schema 格式的参数约束(程序靠这个校验合法性)
- permissions:权限声明(文件读写范围、网络访问等)
- risk_level:风险等级
JSON Schema:给参数画边界
JSON Schema 是一种"参数的规则说明书"。以 create_note 为例:
{
"type": "object",
"properties": {
"filename": {
"type": "string",
"pattern": "^[a-zA-Z0-9_\\u4e00-\\u9fa5\\-]+\\.md$"
},
"content": {
"type": "string",
"maxLength": 500
}
},
"required": ["filename", "content"],
"additionalProperties": false
}
这段 Schema 说了什么?
- filename 必须是字符串,且只能包含字母、数字、中文、下划线、短横线,必须以
.md结尾 - ✅
今日清单.md— 合法 - ❌
../../etc/passwd— 包含路径分隔符和.. - ❌
notes.txt— 不是.md后缀 - content 必须是字符串,不超过 500 字符
- ✅
"复习第10章要点"— 合法 - ❌ 一段 1000 字符的长文 — 超过长度限制
- additionalProperties: false — 不允许出现未声明的字段
- ✅
{"filename": "a.md", "content": "hi"}— 合法 - ❌
{"filename": "a.md", "content": "hi", "shell": true}— 多了未声明字段
全局安全规则
除了每个工具自己的 Schema,还有全局规则:
{
"max_content_length": 500,
"allowed_extensions": [".md"],
"blocked_patterns": ["..", "/", "\\", "<script", "import ", "exec(", "eval(", "os.", "sys.", "__"],
"audit_log": "audit.log"
}
这些规则在所有工具调用时都会检查,不管模型怎么提议。
第3步:AI原理
工具调用的完整流程
工具调用不是"模型直接操作文件",而是分四个阶段:
阶段1:提议(Proposal)
模型收到用户请求后,不是直接写文件,而是生成一段结构化 JSON:
{
"tool": "create_note",
"arguments": {
"filename": "今日清单.md",
"content": "1. 复习工具调用\n2. 完成攻击测试\n3. 写实验报告"
}
}
这只是一段文字——模型"说"它想调用这个工具,但它自己什么都没做。
阶段2:校验(Validation)
宿主程序收到这段 JSON 后,依次检查:
- Schema 校验:参数类型、格式、长度是否符合工具定义?
- 权限检查:这个工具是否有权做请求的操作?
- 路径安全:文件名是否包含路径穿越(
..、/、\)? - 内容安全:内容是否包含注入攻击(
<script>、exec())?
任何一步失败,调用就被拒绝,不会执行。
阶段3:执行(Execution)
只有全部校验通过,宿主程序才在沙盒目录中真正执行操作:
# 伪代码
sandbox_path = SANDBOX_ROOT / validated_filename
sandbox_path.write_text(validated_content, encoding="utf-8")
注意:文件写入操作是宿主程序做的,不是模型做的。模型只是"提议"了参数。
阶段4:日志(Logging)
每次调用(无论成功或失败)都写入审计日志:
[2026-08-19T10:30:00] PROPOSE tool=create_note filename=今日清单.md → VALID → EXECUTED
[2026-08-19T10:30:05] PROPOSE tool=create_note filename=../../etc/passwd → REJECTED(reason=path_traversal)
"模型声称"vs"日志证明"
这是本章最核心的区别:
| 模型声称 | 日志证明 | |
|---|---|---|
| 来源 | 模型输出的文字 | 宿主程序的执行记录 |
| 可靠性 | 可能出错、幻觉 | 来自真实文件系统操作 |
| 例子 | "我已经创建了笔记" | audit.log 第5行:create_note 今日清单.md → EXECUTED |
模型可能说"我已经创建了文件",但实际上:
- 参数校验失败了,文件根本没创建
- 模型产生了幻觉,并没有真正发起工具调用
- 工具执行时出错了(磁盘满、权限不足)
只有审计日志才是执行证据。
为什么需要这么多层防护
| 防护层 | 防什么 | 如果缺少 |
|---|---|---|
| JSON Schema | 参数格式不合法 | 模型可能传入错误类型的参数 |
| 路径检查 | 路径穿越攻击 | 模型可能被诱导写入系统文件 |
| 内容检查 | 文档注入 | 笔记内容可能包含恶意代码 |
| 额外属性检查 | 未声明字段 | 模型可能被诱导传入 shell: true |
| 沙盒隔离 | 越权操作 | 文件可能写入沙盒外的目录 |
| 审计日志 | 事后追溯 | 出了问题无法定位原因 |
第4步:协同开发
使用 Qwen Code 完成以下任务(选择一项):
任务A:修改一条 schema 限制
- 把 create_note 的内容长度限制从 500 改为 1000
- 或者把文件名规则改为也允许 .txt 后缀
- 先预测:这个修改扩大了什么能力?新增了什么风险?需要增加什么测试?
任务B:新增一个低风险工具 append_checklist
- 功能:向已有笔记末尾追加一行待办事项
- 要求:定义完整的 JSON Schema、权限和风险等级
- 先预测:这个工具和 create_note 有什么区别?需要哪些额外测试?
协同流程:
1. 在 Qwen Code 中描述你的选择(原始意图)
2. 查看 Qwen Code 的修改建议(AI方案)
3. 审查差异,接受或改写(人工审查)
4. 运行修改后的代码(实际变更)
5. 对比预测和实际结果(最终判断)
第5步:测试与边界
必测项目
| 测试 | 输入 | 预期结果 | 防御层 |
|---|---|---|---|
| 正常-创建 | create_note("清单.md", "复习要点") |
文件创建,日志记录 EXECUTED | — |
| 正常-列出 | list_files() |
返回文件列表 | — |
| 正常-计算 | calc("2+3*4") |
返回 14 | — |
| 攻击-路径穿越 | create_note("../../etc/passwd", "hacked") |
REJECTED: path_traversal | 路径检查 |
| 攻击-超长内容 | create_note("a.md", "x"*1000) |
REJECTED: content_too_long | Schema maxLength |
| 攻击-未声明字段 | create_note("a.md", "hi", shell=true) |
REJECTED: additional_properties | Schema additionalProperties |
| 攻击-文档注入 | create_note("a.md", "<script>alert(1)</script>") |
REJECTED: blocked_pattern | 内容检查 |
| 攻击-危险计算 | calc("os.system('rm -rf /')") |
REJECTED: invalid_expression | Schema pattern |
| 离线 | 无模型可用 | 使用预录 tool_call JSON 完成全部测试 | 离线兜底 |
人工兜底
- 当工具调用被拒绝时,学生应能解释拒绝原因
- 当模型不可用时,使用预录的 tool_call JSON 完成全部测试
- 审计日志中的每条记录都应由学生人工确认
第6步:交付与验收
交付物
- 正常工具轨迹:至少3次正常调用的完整四段证据(提议→校验→执行→日志)
- 攻击防御日志:至少5种攻击的防御结果,每种包含攻击输入、拒绝原因和防御层
- Schema 差异记录:B档修改的 before/after 对比,新增能力和风险分析
- audit.log:完整的审计日志文件
- 人工确认记录:至少一次"模型声称 vs 日志证明"的对比分析
验收标准
- [ ] 文件确实只写入沙盒目录
- [ ] 所有攻击有明确的允许/拒绝结果
- [ ] 模型不能绕过宿主直接操作沙盒外文件
- [ ] 能解释"提议"和"执行"的区别
- [ ] 能指出每种攻击被哪一层防御拦截
- [ ] B档修改有修改前预测和修改后验证
- [ ] 审计日志完整,每条记录可追溯
第7步:迁移与拓展
核心方法迁移
本章学到的"模型提议→程序校验→沙盒执行→审计日志"模式,可以迁移到:
- 真实产品中的AI助手:ChatGPT 的 Code Interpreter、Claude 的 Computer Use 都采用类似机制——模型生成工具调用,平台校验并执行
- 企业内部的AI工作流:AI生成SQL查询 → DBA审核 → 只读副本执行 → 审计日志
- 智能家居控制:AI提议"把空调调到26度" → 权限检查(是否在允许时间) → 设备执行 → 操作日志
拓展任务(选做)
- 为
create_note添加文件大小限制(如不超过 10KB) - 实现一个
read_note工具,并设计相应的权限和攻击测试 - 把审计日志从纯文本改为 JSON 格式,并编写一个简单的日志查看器
- 设计一个"人工确认"机制:高风险操作需要学生输入"确认"才执行
与后续章节的衔接
- 第11章:把单次工具调用升级为多步智能体循环(计划→行动→观察→再行动)
- 第12章:把多个工具组织成有責任链的工作流
- 工具调用、schema 校验和审计日志是后续所有章节的安全基础