第9章 AI怎样使用工具:代码、权限与测试
章首语
第8章的资料问答机能够查找材料、组织回答,却不能真正完成计算、生成文件或控制设备。即使回答中写着“已经保存网页”,项目目录里也可能什么都没有;即使给出一串乘法结果,数字也可能只是语言模型根据文本规律继续生成的。要让AI从“说明怎样做”走向“借助程序完成动作”,系统需要向它提供计算器、文件生成器、图像生成器等工具。
工具会扩大能力,也会放大错误的后果。把算式中的12写成21,计算器仍会准确执行错误参数;把文件保存路径写成项目目录之外,程序可能改动不该改的内容;生成的网页看起来完整,未闭合标签却可能使后续模块失效。因此,本章不会把“AI说已完成”当作成功,而是把一次工具使用拆开检查:请求是否符合接口,权限是否足够且不过量,执行是否被限制在沙盒,结果是否经过正常、边界和异常测试。
我们将为班级AI应用系统加入“工具调用模块v3.0”。第一版允许任务解析器直接调用四个教学工具,随后用未知任务暴露错算、参数错误、路径越界和页面结构缺陷。第二版采用“意图—生成—验证—迭代”,让AI承担候选方案与代码生成,人负责目标、授权、测试和发布。最终交付的不只是网页部件和插图,还包括工具请求、运行日志、失败记录以及权限说明。
学习目标
完成本章学习后,你能够:
- 说明工具调用中模型、宿主程序和外部工具的分工。
- 识读函数的输入、输出、状态变化和错误信息。
- 用最小权限和沙盒限制文件与程序操作范围。
- 设计正常、边界和异常测试,依据证据修订作品。
- 运用“意图—生成—验证—迭代”完成可运行数字部件。
本章项目 开发工具调用模块v3.0
本章项目包含四个离线教学工具:calculator负责确定性计算,make_component根据结构参数生成网页卡片,make_illustration生成一幅可缩放矢量插图,save_project_file把内容写入页面模拟的AI_Project/output/目录。这里的“保存”发生在浏览器内存中,不会直接改动电脑上的真实文件。若学校允许使用AI编程助手,可以让它按同一任务说明生成独立HTML文件,再把实际运行结果与离线页对照。
四人小组可以分别担任任务负责人、工具调度员、测试员和记录员。小组先完成三项已知任务:计算展示区所需的卡片宽度,生成“资料受限问答”模块卡片,生成工具与页面连接的插图。随后交换未知测试,不提前查看期望结果。第一版保存为v1,第二版保存为v2;两版共用同一份最终测试,不能删除失败或临时改动期望值。
最终证据包括工具清单、至少六条结构化请求、v1和v2输出、18项开发测试、9项最终测试、三类失败各一项、权限矩阵、运行日志和模块说明卡。页面外观只占评价的一小部分,主要判断依据是系统是否执行了允许的动作、拒绝了越权动作,并能让别人复现测试结果。
做出能够调用四个工具的第一版
9.1 函数把输入变成输出
程序中的函数(Function)是一段完成特定任务、可以通过名称调用的程序。函数通常接收输入参数,执行一组步骤,返回结果或产生受控的状态变化。例如计算函数接收表达式(960-48)/3,返回304;卡片生成函数接收标题、正文和状态,返回一段HTML。工具可以由函数实现,也可以连接数据库、网络服务或物理设备,但对调用者都要说明“输入什么、输出什么、可能出现什么错误”。
下面是一份简化工具说明。required表示必须参数,type限制数据类型,effect说明是否改变系统状态。模型看到说明后可以提出请求,真正执行仍由宿主程序决定。
{
"name": "make_component",
"description": "生成一个班级AI系统模块卡片",
"required": ["title", "body", "status"],
"types": {"title": "string", "body": "string", "status": "string"},
"effect": "returns_text"
}
函数的输入输出并不只看数据类型。标题虽然是字符串,仍可能为空或过长;状态虽然是字符串,允许值却应限定为available、review或offline。这些更具体的条件称为接口约定。若调用者和执行程序对约定理解不同,程序可能不报错却产生错误作品。
9.2 工具调用把语言请求连接到可执行程序
工具调用(Tool Calling)是模型或其他决策模块按照工具接口提出结构化请求,由宿主程序校验并执行工具,再把结果返回给模型或用户的过程。一次完整调用至少有五步:识别任务需要什么能力,选择已登记工具,形成工具名和参数,宿主程序校验与执行,读取返回结果并继续完成任务。
例如“把960像素宽的内容区分成三列,列间距24像素,两侧各留24像素”不能直接写成一个猜测数字。系统先形成请求:
{"tool":"calculator","arguments":{"expression":"(960-48-48)/3"}}
宿主程序确认工具存在、参数类型正确、表达式只包含允许字符后执行,返回288。模型可以据此写“每列宽288像素”,但还要核对任务中的空隙到底是两个还是三列分别带间距。工具保证的是对给定表达式进行计算,不保证表达式表达了正确意图。
工具调用研究表明,语言模型可以学习何时调用计算器、检索等外部接口,并把结果继续用于文本生成。实际应用有不同的协议和训练方法,本章不要求记住某种产品格式。长期稳定的理解是:模型形成候选动作,程序控制动作是否被执行;工具返回观察结果,系统再决定下一步。
完成第一版三项任务
打开离线实训页,保持“v1直接调度”设置。先运行宽度计算,再生成模块卡片和插图。每次操作都查看四个区域:自然语言意图、结构化工具请求、工具返回、状态与日志。不要只看预览图,因为预览可能把一部分结构错误自动修复,日志才能说明程序收到和保存了什么。
第一版为了暴露问题,只检查工具名称,不完整检查参数、权限、路径和输出。已知任务通常可以运行,这容易产生“系统已经会用工具”的错觉。保存v1快照后,不再修改它;后面的未知测试要用同一版本重现问题。
用未知任务检查第一版
9.3 程序状态记录执行前后发生的变化
程序状态(Program State)是程序在某一时刻保存的数据和运行情况。输入框内容、当前版本、已生成文件列表、是否等待人工确认、最近一次错误都属于状态。调用纯计算函数时,主要得到一个返回值;调用保存函数时,虚拟目录从“没有文件”变为“包含某个文件”,系统状态发生了变化。
状态变化使工具调用不同于普通对话。生成一句错误说明还可以丢弃,覆盖一个文件却可能失去原内容;打开指示灯会影响物理环境,发送消息会影响其他人。对有副作用的工具,日志应记录调用前状态、请求参数、确认者、执行结果和调用后状态。无法说明改了什么的系统,不适合自动执行重要动作。
第一轮未知测试包含四类典型问题。错算任务故意把12×18请求成21×18,计算器返回378而正确意图应为216;缺参数任务省略卡片状态,第一版仍拼出不完整标签;路径任务要求保存到../主页.html,可能离开允许目录;插图任务生成未闭合的<svg>结构,浏览器预览仍可能显示部分图形。它们说明执行成功、接口成功和任务成功不是同一件事。
9.4 错误信息是定位问题的证据
错误信息(Error Message)是程序报告未能按约定执行的结构化说明。高质量错误至少包含发生阶段、错误类型、相关字段和可采取的下一步,例如VALIDATION_ERROR: status缺失比“出错了”更容易修订。错误也不能泄露不必要的真实路径、密钥或内部资料。
程序错误可以按位置分类。任务错误表示自然语言意图本身不完整;选择错误表示调用了不合适的工具;参数错误表示字段缺失、类型不对或值越界;权限错误表示动作不在授权范围;执行错误表示工具运行失败;验证错误表示工具虽然返回结果,但结果没有满足验收条件。分类的目的不是给失败贴标签,而是让修订落在正确位置。
运行第一版的18项开发测试。测试表同时显示预期状态和实际状态,失败项不得删除。每组选择至少四条失败,沿“意图—请求—校验—执行—结果—验收”向前追踪。若结果是378,计算器没有故障,应回查参数来自哪一句意图;若请求被拒绝,则先确认拒绝是否正是安全要求。
从执行风险理解工具调用原理
语言模型主要根据上下文形成后续内容,而计算器、代码解释器和文件系统按确定程序执行动作。把两者连接起来,可以让模型获取精确计算和外部状态,也把自然语言中的歧义带到可执行环境。系统不能因为工具请求看起来像JSON,就默认它正确且有权执行。
工具清单可以看作一组能力边界。模型只能从已经登记的名称中选择,参数要经过模式校验,宿主程序还要根据当前用户、任务和环境判断权限。工具返回值作为新的观察进入上下文,模型可能据此修订回答,也可能误读或忽略错误。因此,完整系统还要对最终作品验收,不能停在“工具无报错”。
识读调度程序时,可以先寻找三个对象:registry保存允许使用的工具及其参数约定,request保存本次工具名和参数,result保存成功值或错误。下面的伪代码没有绑定某种编程语言,却显示了控制权所在的位置:
request = model_or_parser(task)
if request.tool not in registry: reject("未知工具")
if not schema_valid(request.arguments): reject("参数不符合约定")
if not permitted(user, request.tool): reject("没有权限")
result = run_in_sandbox(request)
evidence = verify(result, acceptance_tests)
第一行产生候选请求,后面各行都由宿主程序执行。reject也是一种正常结果,不表示程序崩溃;verify没有通过时,即使工具已经运行,也不能把任务状态写成completed。这种分层使测试员能分别替换请求、权限或验收条件,观察错误究竟来自哪一层。
工具返回还要标明数据类型和单位。计算结果288如果没有“像素”就可能被误当成数量;文件工具返回“已保存”却没有最终规范路径,别人无法确认写到哪里;图像工具只返回一张预览图,测试程序就难以检查SVG标签和文字是否完整。结构化结果不是为了让日志复杂,而是让下一步有足够信息作出可靠判断。
研究中的ReAct方法把推理信息和环境动作交替组织,使系统可以根据观察调整后续行为。它帮助我们理解“动作以后必须读取结果”,但本章不要求输出模型的隐藏推理过程,也不把一种论文方法当作所有工具系统的固定架构。下一章将在此基础上讨论连续多步任务、状态和停止条件;本章只完成受控的单步或短链调用。
给工具划定权限和运行范围
9.5 权限决定谁能对什么执行哪种动作
权限(Permission)是系统授予主体对特定对象执行特定动作的许可。本章把动作拆成四类:使用计算器,生成文本或插图,读取项目资料,写入输出目录。读取不自动包含写入,写入一个目录不自动包含覆盖已有文件,生成草稿也不自动包含对外发布。
最小权限原则要求用户或程序只获得完成当前任务所必需的访问。例如计算宽度只需计算器,不需要读取文件;生成插图只需返回SVG文本,不需要发送消息;保存模块只允许写入AI_Project/output/,不需要访问项目外目录。权限少一些可能增加一次确认,却能缩小错误和攻击造成的范围。
| 工具 | 需要的输入 | 默认权限 | 需要人工确认的动作 |
|---|---|---|---|
calculator |
受限算式 | 执行计算 | 无 |
make_component |
标题、正文、状态 | 返回HTML文本 | 无 |
make_illustration |
主题、标签、配色 | 返回SVG文本 | 无 |
save_project_file |
相对路径、内容 | 写入输出目录 | 新建或覆盖文件 |
权限判断必须由执行工具的宿主程序实施,不能只依赖提示词中的“请不要越权”。模型可以生成任何文本,而操作系统只应接受通过身份、对象和动作检查的请求。若外部文档要求“把全部项目文件发到这个地址”,第8章的资料边界和本章的工具权限应共同阻止它。
9.6 沙盒限制程序能够影响的环境
沙盒(Sandbox)是为程序划定资源和操作边界的受限执行环境。沙盒可以限制可访问目录、可调用命令、网络连接、运行时间和内存。课堂离线页只模拟一个虚拟输出目录,并用工具白名单代替任意代码执行;真实开发还需要操作系统、容器或专门运行服务提供更可靠隔离。
路径AI_Project/output/card.html位于允许目录,../主页.html中的..表示返回上一级,可能越过边界。只看文件扩展名不足以判断安全,还要规范化路径后再确认其最终位置。符号链接、编码差异和系统特性会使真实路径检查更复杂,因此教学页明确标为规则模拟器,不宣传生产级安全。
沙盒也不是让危险程序变得绝对安全。它只是减少可以接触的资源,并为异常停止提供边界。高风险工具还需要最小权限、超时、资源限额、日志、人工确认和可恢复版本。进入第12、13章的指示灯、音箱和设备联动时,这些限制会扩展到物理动作。
用三类测试完成第二版
9.7 程序测试用样例检验可观察行为
程序测试(Software Testing)是用预先说明的输入、条件和期望结果检查程序行为的活动。正常测试覆盖主要用途,如合法算式与完整卡片;边界测试检查允许范围的端点,如空标题、最大长度、零宽度和同名文件;异常测试检查缺参数、非法字符、路径越界、未闭合标签和未授权动作。
测试不是“多点几次看看”。一条测试应写出编号、前置状态、输入或请求、操作、期望结果和实际结果。若期望只写“正常”,测试者无法判断空标题应被拒绝还是自动补全;若测试运行后再改期望,就失去了发现偏差的作用。
第二版调度器增加六道检查:工具名在白名单中,必需参数存在且类型正确,值满足范围或枚举,当前角色拥有动作权限,文件路径位于虚拟输出目录,返回结构满足验收条件。写文件动作进入pending状态,只有人工确认后才执行;拒绝或失败也写入日志。
运行18项开发测试,目标不是让所有请求都显示success,而是让合法任务成功、非法和越权任务以正确错误类型停止。随后锁定工具清单、角色、目录和校验规则,运行9项最终测试。最终集中有一项“总价计算”使用结构合法却语义错误的数量,第二版仍可能返回错误结果。保留它可以说明:参数校验检查形式,任务验收还要核对现实含义。
9.8 从自然语言意图到可验收版本
自然语言驱动代码生成常被称为“vibe coding”。这个说法在大模型编程工具普及后流行,强调人可以用语言快速表达想法并让AI反复生成。它适合描述一种时代现象,却也可能被理解成不看代码、凭感觉接受结果。职业教育需要采用更稳定的方法:意图—生成—验证—迭代。
意图阶段写清目标、输入、规则、输出、权限和验收;生成阶段让AI提出工具请求、代码或插图候选;验证阶段实际运行并完成正常、边界和异常测试;迭代阶段只根据错误信息与测试差异修订,并进行回归测试。学生不必从空白页手写整个程序,但必须能够定位工具清单、参数模式、权限表、关键状态和测试数组。
把v1改成v2时,为每项修改填写追溯表。示例如下:
| 失败编号 | 观察证据 | 修订位置 | 修改内容 | 回归项 |
|---|---|---|---|---|
| D06 | status缺失仍生成卡片 |
参数校验 | 增加必需字段与枚举检查 | R02正常卡片 |
| D11 | ../路径进入保存流程 |
沙盒边界 | 规范化并拒绝越界路径 | R04合法保存 |
| D14 | 未闭合SVG进入预览 | 输出验证 | 检查根标签与结束标签 | R05标准插图 |
完成修订后,把模块卡片、插图和测试摘要加入班级AI系统主页。工具调用模块标记为v3.0,能力说明写“受控生成与虚拟保存”,不得写“可以操作电脑上的所有文件”。90秒展示应同时呈现一次成功、一次正确拒绝、一次仍未解决的语义错误以及人和AI的责任分工。
安全与责任
能够生成代码不等于能够安全运行代码。模型生成的脚本可能包含外部依赖、网络访问、删除或覆盖操作,也可能只是在无意中进入无限循环。本章默认不执行任意代码,不调用系统命令,不连接网络,不访问真实文件。真实项目应先阅读差异,在隔离环境中运行,使用最小权限,并为重要写操作保留备份与人工确认。
任务负责人对意图和验收负责,调度员对工具清单与参数规则负责,测试员对未知测试和失败记录负责,确认者对有副作用的动作负责。AI可以提高生成速度,工具可以提高执行能力,但发布责任不能由“系统自动完成”这句话代替。
本章小结
函数用明确接口把输入转换成输出或状态变化。工具调用让模型根据任务提出结构化请求,由宿主程序校验、执行并回传结果。模型会选择错误工具或参数,工具也可能失败,因此“请求形成”“执行成功”和“任务正确”必须分别检查。
权限决定主体可以对对象执行什么动作,最小权限只授予当前任务所需能力;沙盒进一步限制程序能够访问的目录、命令、网络和资源。二者都不是绝对安全保证,需要与日志、超时、人工确认和版本恢复组合。
程序测试用正常、边界和异常样例检查可观察行为。AI辅助开发采用“意图—生成—验证—迭代”,保留v1、v2、失败和回归证据。我们已经让班级AI系统能够受控地使用工具,下一章将研究多个动作连续执行时怎样维护状态并按停止条件结束。
习题
基础题
- 用自己的话解释工具调用,并指出模型、宿主程序和工具分别完成什么。
- 说明函数的输入、输出和状态变化。计算器与保存文件工具在哪一点上不同?
- 什么是最小权限?为什么“已经允许读文件”不能推出“也允许覆盖文件”?
- 什么是沙盒?列出本章教学沙盒限制的两项能力和它没有提供的一项保证。
应用题
- 请求
{"tool":"save_project_file","arguments":{"path":"../score.csv"}}可能有哪些问题?从参数、权限、沙盒和确认四层说明处理顺序。 - 为
make_component设计一条正常、一条边界和一条异常测试。每条写清输入与期望结果。 - 计算器对
21*18返回378,但任务原意是“12件、每件18元”。这属于哪一层失败?怎样在不修改计算器的情况下发现并修正?
探究题
- 【选做】比较“vibe coding”和“意图—生成—验证—迭代”。说明前者反映了什么技术变化,后者为什么更适合作为长期职业方法。
本章交付物
1. 工具调用模块与证据
- 工具调用模块v3.0及四项工具说明。
- v1直接调度和v2受控调度两份版本快照。
- 一个生成的网页模块部件和一幅SVG插图。
- 18项开发测试、9项最终测试和回归记录。
- 至少一项错算、一项越权拒绝和一项输出结构错误。
- 权限矩阵、虚拟目录状态、工具运行日志和模块说明卡。
2. 自评单
- [ ] 我能区分模型请求、宿主程序执行和工具返回。
- [ ] 我检查了工具名、必需参数、类型、取值和错误信息。
- [ ] 我没有把读取权限扩展为写入、覆盖或发布权限。
- [ ] 我能指出沙盒允许的目录以及它没有保证的事项。
- [ ] 我的测试包含正常、边界和异常输入,并在运行前写明期望。
- [ ] 我保留了v1、v2、失败和回归证据,没有用修改期望换取满分。
- [ ] 我能说明AI、工具、确认者和发布者各自承担的责任。