AgentDepthAgent 工程学习指南
输入概念或问题,检索正文和待编写目录。
第一次学习?从 L0 总览开始 →

Runtime、工具注册与公共评测平台

我的学习 →

正在读取学习记录…

Runtime(运行时)是负责把 Agent 的决定落实为操作的程序。模型输出“调用 search,参数是退款”,运行时找到对应函数、检查参数、执行查询,再把结果交回去。

如果每个 Agent 都自己写一套工具调用代码,预算、日志和异常处理很容易各走各的。统一运行时把这些重复步骤放在一起。接下来注册一个检索函数,亲手看工具名怎样变成一次真实函数执行。

1. 注册表怎样连接名称与实现

工具注册表是一张“名称 → 实现”的映射表。就像查通讯录:拿到联系人名字,找到对应号码;这里拿到工具名,找到函数。

对象当前例子用途
工具名search请求里用于选择工具
参数query = 退款给工具的输入
实现search 函数在资料列表中检索
输出退款说明回到 Agent 的观察
运行记录tool=search,status=ok检查哪个工具执行了什么结果

注册发生在运行前,调用发生在任务中。名称相同的两个实现会让请求含义变得模糊,所以注册时检查重名。工具清单在任务开始时固定,增加新工具时重新审核输入与允许的动作。

实际平台通常为工具保存参数定义、实现版本、所需权限、超时和资源上限。权限检查使用服务端可信身份;模型提交的“我是管理员”仍是待检查的输入。

2. 一次调用经过哪些步骤

输入的工具请求先通过参数校验。这里的工具统一接收一个非空 query 字符串,检查字段名、类型和内容后,再扣一次额度、执行函数。

工具注册表连接search名称与实现,运行时依次校验参数、扣额度、执行函数并记录成功或失败
图 1:注册表提供实现;运行时负责执行过程。两个出口都保留调用记录,执行失败也消耗一次尝试额度。

额度为什么在调用前扣?如果每次失败都免费,故障期间可以无限重试。把“尝试”作为计数单位,任务就有明确上限。合法性检查失败还没进入执行阶段,保持额度不变。

执行记录 trace 是按时间排列的事件列表。我们的记录包含工具名和结果状态;真实平台可以再加入请求 ID、耗时和脱敏错误类型。请求 ID 用来把模型请求、工具调用与最终结果关联起来。

3. 建一个可以直接运行的注册表

在学习目录打开 PowerShell:

Bash
mkdir platform-demo
cd platform-demo

保存下面文件为 platform_demo.py。search 真正在两份固定资料中查找包含查询词的标题。

Python
"""运行时把工具名映射到函数,再核对参数、扣额度并记录结果。"""


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
Python
"""检查工具注册、实际执行以及基准任务的隔离。"""
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()

在同一窗口运行:

Bash
ls
python -X utf8 platform_demo.py
python -X utf8 -m unittest -v test_platform_demo.py

程序输出:

TXT
查询结果: ['退款说明']
剩余额度: 1
执行轨迹: [{'tool': 'search', 'status': 'ok'}]
基准任务通过:2/2

main 建立预算为 2 的运行时,注册 search,再调用一次;于是剩余额度是 1。benchmark 创建另外两个独立运行时,分别检查退款与物流查询,两个结果都和事先写好的答案一致。

八项测试涵盖名称重复、未知工具、参数类型、多余字段、预算耗尽、执行异常和评测隔离。特别留意失败测试:broken 函数实际抛出异常,记录状态变为 error,额度仍从 1 减到 0。

4. 改额度,区分主任务与评测任务

在 main 中把 runtime = Runtime(2) 改成 runtime = Runtime(1),运行:

Bash
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区分校验失败、执行失败和重试
单题通过,整批结果异常每题初始状态为每题新建运行时与环境

注册表解决“调用谁”,运行时解决“怎样执行”,评测环境解决“怎样判断完成”。三者边界清楚,新增工具和新增任务都更容易核对。

课后练习展开习题收起习题

4 道练习 · 客观题规则评分 · 其余题目待批改

正在读取本地记录…

不调用模型,不执行代码。简答、代码和场景题仅保存答案并展示自查标准,不生成评分、徽章或认证。输入内容不会自动保存,请点击提交;不要填写个人信息或密钥。

练习 1 / 4 · v1.0.1

选择题 · 先判断边界

关于“Runtime、工具注册与公共评测平台”,哪种做法更稳妥?

对应知识点:L9-platform

选择一个答案
练习 2 / 4 · v1.0.0

简答题 · 解释失败

写出一个失败输入并说明记录字段。

对应知识点:L9-platform

练习 3 / 4 · v1.0.0

代码题 · 边界测试

补充正常、边界、失败三组输入和预期结果。

对应知识点:L9-platform

练习 4 / 4 · v1.0.0

场景题 · 结果未知

证据不足时如何设计状态和人工接管?

对应知识点:L9-platform

图片查看器
100%

文章配图 · 按 Esc 关闭