L02 把工具调用变成受控能力,而非任意函数调用
适用章节:M02。本实验允许多种正确实现;验收不变量固定,不强制特定框架。
工程事故
模型给出格式完全合法的订单号A100,但当前用户来自另一个租户。你又发现一个“通用call_function”工具允许模型传入任意Python函数名。结构化输出解决不了这两个问题。
最终交付
只读Tool Gateway、最小能力矩阵、输入与输出Schema、5条负面测试;选修一个仅暴露只读工具的MCP适配层。
已提供材料
specs/PROJECT_CONTRACT.md、data/retail/orders.json、reference/agentlab/business.py
先独立写出预期行为再看参考解。所有商店、订单、试次与价格单位都是教学构造;不连接真实资金系统。
接口与不可破坏的约束
身份由服务器注入Principal,工具参数只有业务必要字段。不要接受模型传来的tenant_id来切换身份。订单查询必须同时匹配tenant_id、owner_id、order_id;无权限与不存在统一返回NOT_FOUND。金额只接受整数分,bool不是有效整数金额。
工具描述明确“做什么/不做什么/是否有副作用/结果来源/错误码”。参数合法只说明形状合法。读取、提案、批准、执行四种能力必须区分;模型不能批准自己的提案。
实施步骤
- 列出工具:get_order、get_shipment、create_refund_proposal。把approve_refund留给经过认证的人类入口,不作为普通模型工具。
- 实现字段白名单和严格类型校验。未知字段、空订单号、额外tenant字段都要被拒绝。
- 将工具名映射到显式函数,不用eval/getattr任意定位函数。
- 在业务层重新做权限和资格校验;即使绕过模型网关,业务服务也不应裸露无授权退款。
- 比较shop-a/u-alice和shop-b/u-bob查询同名A100的不同结果。
- 可选MCP实验:把get_shipment包装为MCP tool,固定协议版本。分别测试协议发现、参数验证和真实授权,记录三者不是同一件事。
验收矩阵
| 案例 | 结果 |
|---|---|
| Alice查自己的A100 | paid=10000 |
| Bob查自己的A100 | paid=33000,不读到Alice订单 |
| Alice查A111(同租户其他人) | NOT_FOUND |
| 模型添加tenant_id=shop-b | INVALID_ARGUMENTS |
| 参数order_id=true | INVALID_ARGUMENTS |
| 任意未知工具名 | UNKNOWN_TOOL,无代码执行 |
| 未审批直接提交退款 | APPROVAL_REQUIRED,账本仍0 |
如何运行已有参考测试
在包根目录运行:
python -m unittest discover -s reference/tests -v
本实验主要参考测试类为BusinessTests。已有测试默认验证reference/;测试通过不代表你的starter/实现通过。接入自己的实现时,将被测模块显式替换/注入,保持测试和领域契约不变,不要修改断言来迁就实现。
评分
授权35;Schema/白名单25;错误与信息最小化15;测试15;协议与业务边界解释10。
出现跨租户泄露、未经批准的退款或伪造结果时,不以其他分数抵消。允许记录未完成的选修功能,但必须说明范围。
进阶挑战
为工具Schema增加版本;模拟工具从只读变成会产生副作用时是否需要重新审批。检查工具结果中的恶意指令能否改变权限。
提交前自查
保存代码版本、运行命令、输入数据版本、输出日志、失败案例与设计决策。解释至少一个被你否决的方案。没有运行的测试标记“未运行”,不写“预计通过”。参考解见对应答案。