Runtime、工具注册与公共评测平台
正在读取学习记录…
Runtime(运行时)是负责把 Agent 的决定落实为操作的程序。模型输出“调用 search,参数是退款”,运行时找到对应函数、检查参数、执行查询,再把结果交回去。
如果每个 Agent 都自己写一套工具调用代码,预算、日志和异常处理很容易各走各的。统一运行时把这些重复步骤放在一起。接下来注册一个检索函数,亲手看工具名怎样变成一次真实函数执行。
1. 注册表怎样连接名称与实现
工具注册表是一张“名称 → 实现”的映射表。就像查通讯录:拿到联系人名字,找到对应号码;这里拿到工具名,找到函数。
| 对象 | 当前例子 | 用途 |
|---|---|---|
| 工具名 | search | 请求里用于选择工具 |
| 参数 | query = 退款 | 给工具的输入 |
| 实现 | search 函数 | 在资料列表中检索 |
| 输出 | 退款说明 | 回到 Agent 的观察 |
| 运行记录 | tool=search,status=ok | 检查哪个工具执行了什么结果 |
注册发生在运行前,调用发生在任务中。名称相同的两个实现会让请求含义变得模糊,所以注册时检查重名。工具清单在任务开始时固定,增加新工具时重新审核输入与允许的动作。
实际平台通常为工具保存参数定义、实现版本、所需权限、超时和资源上限。权限检查使用服务端可信身份;模型提交的“我是管理员”仍是待检查的输入。
2. 一次调用经过哪些步骤
输入的工具请求先通过参数校验。这里的工具统一接收一个非空 query 字符串,检查字段名、类型和内容后,再扣一次额度、执行函数。
额度为什么在调用前扣?如果每次失败都免费,故障期间可以无限重试。把“尝试”作为计数单位,任务就有明确上限。合法性检查失败还没进入执行阶段,保持额度不变。
执行记录 trace 是按时间排列的事件列表。我们的记录包含工具名和结果状态;真实平台可以再加入请求 ID、耗时和脱敏错误类型。请求 ID 用来把模型请求、工具调用与最终结果关联起来。
3. 建一个可以直接运行的注册表
在学习目录打开 PowerShell:
mkdir platform-demo
cd platform-demo保存下面文件为 platform_demo.py。search 真正在两份固定资料中查找包含查询词的标题。
"""运行时把工具名映射到函数,再核对参数、扣额度并记录结果。"""
class Runtime:
def __init__(self, budget):
if type(budget) is not int or budget < 0:
raise ValueError("额度需要非负整数")
self.remaining = budget
self.tools = {}
self.trace = []
def register(self, name, function):
# 注册阶段固定工具名,避免后注册的函数覆盖已审核实现。
if not isinstance(name, str) or not name or name in self.tools or not callable(function):
raise ValueError("工具名需要唯一,实现需要可调用")
self.tools[name] = function
def call(self, name, arguments):
if not isinstance(name, str) or name not in self.tools:
raise ValueError("工具未注册")
# 这个注册表中的工具统一接受一个非空 query 字符串。
if (type(arguments) is not dict or set(arguments) != {"query"}
or not isinstance(arguments["query"], str) or not arguments["query"].strip()):
raise ValueError("参数需要一个非空 query 字符串")
if self.remaining == 0:
raise ValueError("调用额度已用完")
self.remaining -= 1 # 每次尝试消耗额度,包括实际执行失败。
entry = {"tool": name, "status": "running"}
self.trace.append(entry)
try:
result = self.tools[name](arguments["query"].strip())
entry["status"] = "ok"
return result
except Exception:
entry["status"] = "error"
raise
def search(query):
# 实际在固定资料列表中查询,结果可由读者逐项核对。
documents = ["退款说明", "物流说明"]
return [document for document in documents if query in document]
def benchmark(budget=2):
cases = [("退款", ["退款说明"]), ("物流", ["物流说明"])]
outcomes = []
for query, expected in cases:
# 每道任务重新创建运行时,避免上一道消耗的额度污染下一道。
runtime = Runtime(budget)
runtime.register("search", search)
try:
outcomes.append(runtime.call("search", {"query": query}) == expected)
except ValueError:
outcomes.append(False)
return outcomes
def main():
runtime = Runtime(2)
runtime.register("search", search)
print("查询结果:", runtime.call("search", {"query": "退款"}))
print("剩余额度:", runtime.remaining)
print("执行轨迹:", runtime.trace)
outcomes = benchmark()
print(f"基准任务通过:{sum(outcomes)}/{len(outcomes)}")
if __name__ == "__main__":
main()再保存 test_platform_demo.py:
测试代码:test_platform_demo.py
"""检查工具注册、实际执行以及基准任务的隔离。"""
import unittest
from platform_demo import Runtime, search, benchmark
class PlatformTests(unittest.TestCase):
def make_runtime(self):
runtime = Runtime(2)
runtime.register("search", search)
return runtime
def test_search_executes_registered_function(self):
runtime = self.make_runtime()
self.assertEqual(runtime.call("search", {"query": "退款"}), ["退款说明"])
self.assertEqual(runtime.remaining, 1)
def test_duplicate_registration(self):
runtime = self.make_runtime()
with self.assertRaises(ValueError):
runtime.register("search", search)
def test_unknown_tool(self):
runtime = self.make_runtime()
with self.assertRaises(ValueError):
runtime.call("delete", {"query": "退款"})
self.assertEqual(runtime.remaining, 2)
def test_parameters_are_checked_before_execution(self):
runtime = self.make_runtime()
with self.assertRaises(ValueError):
runtime.call("search", {"query": 3})
self.assertEqual(runtime.remaining, 2)
def test_extra_parameter(self):
with self.assertRaises(ValueError):
self.make_runtime().call("search", {"query": "退款", "admin": True})
def test_budget(self):
runtime = self.make_runtime()
runtime.call("search", {"query": "退款"})
runtime.call("search", {"query": "物流"})
with self.assertRaises(ValueError):
runtime.call("search", {"query": "退款"})
def test_failed_execution_consumes_attempt(self):
runtime = Runtime(1)
def broken(query):
raise RuntimeError("服务错误")
runtime.register("broken", broken)
with self.assertRaises(RuntimeError):
runtime.call("broken", {"query": "退款"})
self.assertEqual(runtime.remaining, 0)
self.assertEqual(runtime.trace[0]["status"], "error")
def test_benchmark_has_fresh_runtime_per_case(self):
self.assertEqual(benchmark(1), [True, True])
if __name__ == "__main__":
unittest.main()在同一窗口运行:
ls
python -X utf8 platform_demo.py
python -X utf8 -m unittest -v test_platform_demo.py程序输出:
查询结果: ['退款说明']
剩余额度: 1
执行轨迹: [{'tool': 'search', 'status': 'ok'}]
基准任务通过:2/2main 建立预算为 2 的运行时,注册 search,再调用一次;于是剩余额度是 1。benchmark 创建另外两个独立运行时,分别检查退款与物流查询,两个结果都和事先写好的答案一致。
八项测试涵盖名称重复、未知工具、参数类型、多余字段、预算耗尽、执行异常和评测隔离。特别留意失败测试:broken 函数实际抛出异常,记录状态变为 error,额度仍从 1 减到 0。
4. 改额度,区分主任务与评测任务
在 main 中把 runtime = Runtime(2) 改成 runtime = Runtime(1),运行:
python -X utf8 platform_demo.py查询结果不变,剩余额度变成 0,基准仍然通过 2/2。原因是 benchmark 为每道题创建自己的运行时。
接着把 main 的 outcomes = benchmark() 改成 outcomes = benchmark(0),再运行。基准通过变为 0/2:每道题的额度都是零,工具执行被额度检查拦住。按原样恢复这两行,再运行程序和测试。
这两个修改分别改变“当前任务的额度”和“评测任务的额度”。独立任务从明确初始状态开始,比较结果才容易解释。
5. 从注册表扩展为公共平台
插件是可以接入宿主程序的扩展。工具多了以后,可通过显式配置列出插件,或使用 Python 包的 entry points(入口点)声明实现位置。宿主读取声明,加载函数,再登记到注册表。Python 插件指南展示了入口点发现方式。
发现插件与批准插件是两步。安装包具有自己的代码与依赖;上线前应核对来源、版本和运行权限。工具请求记录实现版本,才能在行为变化时定位到具体发布。
| 平台部件 | 负责的事情 | 具体产物 |
|---|---|---|
| 运行时 | 执行单个任务与工具调用 | 状态、结果、调用轨迹 |
| 工具目录 | 管理名称、参数与实现 | 工具清单和版本 |
| 任务集 | 定义要完成的任务 | 输入、初始环境、完成检查 |
| 评测调度器 | 多次运行、隔离任务、汇总结果 | 分任务结果与汇总 |
| 存储与观察 | 保存结果,支持排错 | 脱敏轨迹与错误分类 |
公共评测平台提供别人也能重复运行的任务环境与评价规则。浏览器方向可以阅读 BrowserGym 项目,看任务环境、观察、动作与评价怎样接在一起。自己的结果先按任务输出,再计算汇总指标。
比较两个 Agent 时,固定任务集、工具版本、初始状态、资源额度及随机种子;有随机性就重复多次并展示波动。通过率“2/2”对应这里两道固定检索任务,换成浏览器或多步任务,完成检查也要随任务重新设计。
| 运行现象 | 检查位置 | 操作 |
|---|---|---|
| 提示工具未注册 | register 与 call 的名称 | 使用同一个 search 名称 |
| 参数被拒绝 | arguments | 只保留非空字符串 query |
| 很快耗尽额度 | remaining 与 trace | 区分校验失败、执行失败和重试 |
| 单题通过,整批结果异常 | 每题初始状态 | 为每题新建运行时与环境 |
注册表解决“调用谁”,运行时解决“怎样执行”,评测环境解决“怎样判断完成”。三者边界清楚,新增工具和新增任务都更容易核对。
课后练习展开习题收起习题
正在读取本地记录…
不调用模型,不执行代码。简答、代码和场景题仅保存答案并展示自查标准,不生成评分、徽章或认证。输入内容不会自动保存,请点击提交;不要填写个人信息或密钥。
选择题 · 先判断边界
关于“Runtime、工具注册与公共评测平台”,哪种做法更稳妥?
对应知识点:L9-platform
简答题 · 解释失败
写出一个失败输入并说明记录字段。
对应知识点:L9-platform
代码题 · 边界测试
补充正常、边界、失败三组输入和预期结果。
对应知识点:L9-platform
场景题 · 结果未知
证据不足时如何设计状态和人工接管?
对应知识点:L9-platform
描述会发送给本站维护者,用于修订文章,保存90天。请只填写文章问题,省略个人信息、密钥和内部资料。