数据截至 (上游 commit 898f23f0b672)
03 · 行动层:动作注册表与动态 schema
本章讲 LLM 的「手」是怎么被定义和约束的:动作怎么注册、怎么按当前页面拼成一个只能选一个动作的 pydantic schema 喂给模型(从语法上杜绝模型编造动作)、动作怎么验参和执行,以及任务怎么靠
done结束。
3.1 它要解决的小问题
LLM 输出是自由文本,但我们要的是可执行、参数合法的动作。两个风险:
- 模型编一个不存在的动作(
teleport_to(url)); - 模型给的参数类型/字段不对(
click(index="the blue button"))。
解决思路:用类型系统当护栏——把「这一步允许的所有动作」编译成一个 pydantic schema,交给 LLM 的结构化输出(structured output / tool calling),让模型在语法层面就只能产出合法动作。
3.2 动作怎么注册:@action 装饰器
所有内置动作在 Tools(tools/service.py:441)里用 @self.registry.action(...) 登记。装饰器(tools/registry/service.py:291 Registry.action)做三件事:把函数包装成统一签名、生成参数 模型、存进 registry.actions[name]:
# 示意,非源码;贴近 tools/service.py 里 click 的注册方式
@self.registry.action('Click element by index.', param_model=ClickElementActionIndexOnly)
async def click(params: ClickElementActionIndexOnly, browser_session: BrowserSession):
return await self._click_by_index(params, browser_session)
每个动作带:
description(给 LLM 看的说明,会进 schema);param_model(pydantic 参数模型);- 可选
domains(只在某些域名可用); - 可选
terminates_sequence(标记会换页,§01 的护栏 1 用它,如导航/go_back标True,tools/service.py:461)。
注册结果是一个 RegisteredAction(tools/registry/service.py:314),存进 self.registry.actions[func.__name__]。