WeClaw_42_Agent工具注册全链路:从BaseTool到意图识别的标准化接入

WeClaw_42_Agent工具注册全链路:从BaseTool到意图识别的标准化接入

作者 : WeClaw 开发团队
日期 : 2026-03-29
版本 : v1.0
标签: Agent 工具、BaseTool、意图识别、渐进式暴露、延迟注入


📖 摘要

本文系统讲解 WeClaw Agent 工具注册的完整链路 。当需要将一个新功能(如远程文件分享)注册为 LLM 可调用的 Agent 工具时,需要经历 8 个步骤的标准化流程。文章以 remote_file_share 工具为实战案例,深入剖析 BaseTool 抽象基类、ActionDef 定义、tools.json 配置、意图识别三表联动、渐进式工具暴露引擎、延迟依赖注入等核心机制。

核心收获

  • 🧩 掌握 BaseTool 抽象基类和 ActionDef 模型

  • ⚙️ 理解 tools.json 声明式工具注册

  • 🎯 学会意图识别三表联动(CATEGORIES + MAPPING + PRIORITY)

  • 🔍 掌握渐进式工具暴露的三级策略

  • 💉 了解延迟依赖注入模式的应用场景


🎯 需求背景:为什么需要工具注册?

从"能做"到"会调"

在第 41 篇中,我们实现了 send_file_to_pwa() 等文件传输方法。但这些方法只是 RemoteBridgeClient 的内部 API,大模型(LLM)无法自动调用

复制代码
用户: "帮我做个 PPT 发到手机上"



❌ 没有工具注册时:

  AI: "PPT 已生成,保存在 generated/xxx.pptx"  ← 无法发送



✅ 注册为 Agent 工具后:

  AI: 1. 调用 ppt_generator 生成 PPT

      2. 自动调用 remote_file_share_send_file(file_path="generated/xxx.pptx")

      3. "PPT 已生成并发送到你的手机!"

完整注册链路一览

复制代码
① 创建工具模块  →  ② tools.json  →  ③ 意图关键词  →  ④ 工具映射

       ↓                  ↓                 ↓                ↓

  BaseTool 子类     声明式注册      INTENT_CATEGORIES   INTENT_TOOL_MAPPING

                                                             ↓

⑧ 全链路校验  ←  ⑦ GUI 注入  ←  ⑥ 构造参数  ←  ⑤ 工具名前缀

       ↓              ↓              ↓                ↓

  validate.py    gui_app.py     registry.py    tool_exposure.py

🧩 步骤一:创建工具模块

BaseTool 抽象基类

所有 Agent 工具都继承自 BaseTool,需实现两个核心方法:

python 复制代码
from src.tools.base import ActionDef, BaseTool, ToolResult, ToolResultStatus





class RemoteFileShareTool(BaseTool):

    """远程文件分享工具。"""

    

    name = "remote_file_share"

    emoji = "📤"

    title = "远程文件分享"

    description = "将桌面端文件发送到 PWA 端"

    timeout = 180  # 大文件上传超时

    

    def get_actions(self) -> list[ActionDef]:

        """定义工具支持的动作列表。"""

        ...

    

    async def execute(self, action: str, params: dict) -> ToolResult:

        """执行指定动作。"""

        ...

ActionDef 定义

每个 Action 定义为一个 ActionDef,包含名称、描述和 JSON Schema 参数:

python 复制代码
def get_actions(self) -> list[ActionDef]:

    return [

        ActionDef(

            name="send_file",

            description=(

                "将桌面端本地文件发送到 PWA 端用户。"

                "当你为远程 PWA 用户生成了文件,必须调用此工具将结果发送回去。"

            ),

            parameters={

                "file_path": {

                    "type": "string",

                    "description": "要发送的本地文件绝对路径",

                },

                "description": {

                    "type": "string",

                    "description": "文件描述(会显示在 PWA 端)",

                },

                "user_id": {

                    "type": "string",

                    "description": "目标 PWA 用户 ID(留空则发送给当前会话用户)",

                },

            },

            required_params=["file_path"],

        ),

        ActionDef(

            name="send_files",

            description="将多个桌面端本地文件批量发送到 PWA 端用户。",

            parameters={

                "file_paths": {

                    "type": "array",

                    "items": {"type": "string"},

                    "description": "要发送的本地文件路径列表",

                },

                # ...

            },

            required_params=["file_paths"],

        ),

        ActionDef(

            name="send_voice",

            description="将语音/音频文件作为语音消息发送到 PWA 端。",

            parameters={

                "file_path": {"type": "string", ...},

                "transcript": {"type": "string", ...},

                # ...

            },

            required_params=["file_path"],

        ),

    ]

Schema 生成机制

BaseTool.get_schema() 自动将 ActionDef 转换为 OpenAI Function Calling 兼容格式:

python 复制代码
# 函数名格式:{tool_name}_{action_name}

# 例如:remote_file_share_send_file



# 生成的 schema 示例:

{

    "type": "function",

    "function": {

        "name": "remote_file_share_send_file",

        "description": "将桌面端本地文件发送到 PWA 端用户...",

        "parameters": {

            "type": "object",

            "properties": {

                "file_path": {"type": "string", ...},

                "description": {"type": "string", ...},

                "user_id": {"type": "string", ...},

            },

            "required": ["file_path"]

        }

    }

}

⚙️ 步骤二:tools.json 声明式注册

json 复制代码
{

    "remote_file_share": {

        "enabled": true,

        "module": "src.tools.remote_file_share",

        "class": "RemoteFileShareTool",

        "display": {

            "name": "远程文件分享",

            "emoji": "📤",

            "description": "将桌面端本地文件发送到 PWA 端",

            "category": "communication"

        },

        "config": {},

        "security": {

            "risk_level": "low",

            "require_confirmation": false

        },

        "actions": ["send_file", "send_files", "send_voice"]

    }

}

字段说明

| 字段 | 作用 |

|------|------|

| module / class | 动态导入路径 |

| display.category | 意图分类关联 |

| config | 传递给构造函数的参数 |

| security.risk_level | 高风险工具需用户确认 |

| actions | 声明支持的动作(用于校验) |


🎯 步骤三~四:意图识别三表联动

WeClaw 的意图识别系统基于三张核心映射表协同工作:

表一:INTENT_CATEGORIES --- 关键词 → 意图

python 复制代码
INTENT_CATEGORIES: dict[str, list[str]] = {

    "communication": [

        "发送文件", "发文件到手机", "传文件", "分享文件",

        "发到PWA", "发送到手机", "传到手机", "发给手机",

        "发送语音", "语音消息", "发语音", "录音发送",

        "远程分享", "远程发送", "文件传输", "文件分享",

        "发到浏览器", "发送到浏览器",

    ],

    # ... 其他 17 个意图维度

}

表二:INTENT_TOOL_MAPPING --- 意图 → 工具列表

python 复制代码
INTENT_TOOL_MAPPING: dict[str, list[str]] = {

    "communication": ["wechat", "remote_file_share"],

    # ... 其他意图

}

表三:INTENT_PRIORITY_MAP --- 工具优先级

python 复制代码
INTENT_PRIORITY_MAP: dict[str, dict[str, list[str]]] = {

    "communication": {

        "recommended": ["wechat", "remote_file_share"],

        "alternative": [],

    },

    # ... 其他意图

}

三表协同工作流

复制代码
用户输入: "把这份报告发到手机上"

    ↓

INTENT_CATEGORIES 匹配: "发到手机" → communication (置信度 0.9)

    ↓

INTENT_TOOL_MAPPING 查找: communication → ["wechat", "remote_file_share"]

    ↓

INTENT_PRIORITY_MAP 排序: recommended → ["wechat", "remote_file_share"]

    ↓

渐进式暴露引擎: 高置信度 → 仅暴露 recommended 工具

    ↓

LLM 可见工具: [wechat, remote_file_share] (而非全部 51+ 工具)

🔍 步骤五:工具名前缀注册

多下划线工具名的解析难题

LLM 调用函数时使用 {tool_name}_{action_name} 格式:

复制代码
remote_file_share_send_file

│                 │  │       │

└─── tool_name ───┘  └ action┘

但简单按第一个下划线拆分会得到错误结果:

python 复制代码
"remote_file_share_send_file".split("_")[0]  # → "remote" ❌

解决方案:known_prefixes 前缀表

python 复制代码
def _extract_tool_name(func_name: str) -> str:

    """从函数名中提取工具名。"""

    known_prefixes = [

        "browser_use", "app_control", "voice_input",

        "remote_file_share",  # 本次新增

        "daily_task", "medication",

        # ... 共 40+ 前缀

    ]

    

    for prefix in known_prefixes:

        if func_name.startswith(prefix + "_") or func_name == prefix:

            return prefix

    

    # 默认:取第一个下划线前的部分

    return func_name.split("_")[0] if "_" in func_name else func_name

注意事项validate_tool_chain.py 中也有一份硬编码的前缀列表,需同步更新。


🔧 步骤六~七:构造参数与依赖注入

registry.py --- 构造参数映射

python 复制代码
def _build_init_kwargs(self, tool_name: str, cfg: dict) -> dict:

    """从工具配置中提取构造参数。"""

    kwargs = {}

    tool_config = cfg.get("config", {})

    

    if tool_name == "shell":

        kwargs["timeout"] = tool_config.get("timeout", 30)

    elif tool_name == "browser":

        kwargs["headless"] = tool_config.get("headless", False)

    elif tool_name == "remote_file_share":

        # 无构造参数,bridge_client 通过延迟注入

        pass

    # ... 50+ 工具分支

    

    return kwargs

延迟依赖注入模式

RemoteFileShareTool 依赖 RemoteBridgeClient,但两者的创建时机不同:

复制代码
时间线:

1. gui_app.py 创建 ToolRegistry → 注册所有工具(含 RemoteFileShareTool)

2. gui_app.py 创建 MainWindow → MainWindow 创建 RemoteBridgeClient

3. gui_app.py 将 bridge_client 注入到 RemoteFileShareTool ← 延迟注入

注入代码

python 复制代码
# gui_app.py --- MainWindow 创建后

remote_bridge = getattr(self._window, '_remote_bridge', None)

if remote_bridge:

    rfs_tool = self._tool_registry.get_tool("remote_file_share")

    if rfs_tool and hasattr(rfs_tool, "set_bridge_client"):

        rfs_tool.set_bridge_client(remote_bridge)

        logger.info("已为 remote_file_share 工具注入 bridge_client")

工具端接口

python 复制代码
class RemoteFileShareTool(BaseTool):

    def __init__(self) -> None:

        self._bridge_client = None  # 延迟注入

    

    def set_bridge_client(self, bridge_client) -> None:

        """注入 RemoteBridgeClient 实例。"""

        self._bridge_client = bridge_client

    

    def _check_bridge(self) -> ToolResult | None:

        """检查 bridge 是否可用。"""

        if not self._bridge_client:

            return ToolResult(status=ToolResultStatus.ERROR,

                error="远程桥接客户端未初始化")

        if not self._bridge_client.is_connected:

            return ToolResult(status=ToolResultStatus.ERROR,

                error="远程桥接未连接")

        return None

自动选取 PWA 用户

当 LLM 未指定 user_id 时,自动选取第一个在线 PWA 用户:

python 复制代码
def _resolve_user_id(self, params: dict) -> str:

    """解析目标用户 ID。"""

    user_id = params.get("user_id", "")

    if user_id:

        return user_id

    

    # 自动选取当前在线的 PWA 用户

    if self._bridge_client and self._bridge_client.stats.pwa_connections:

        first_conn = self._bridge_client.stats.pwa_connections[0]

        return first_conn.user_id

    

    return ""

✅ 步骤八:全链路校验

validate_tool_chain.py 七项检查

bash 复制代码
$ python scripts/validate_tool_chain.py



============================================================

WinClaw 工具全链路一致性校验

============================================================

已加载 tools.json: 58 个启用工具



  ✅ [1/7] INTENT_TOOL_MAPPING 覆盖: 58 工具

  ✅ [2/7] INTENT_TOOL_MAPPING 引用有效

  ✅ [3/7] INTENT_PRIORITY_MAP 引用有效

  ✅ [4/7] _extract_tool_name 已知前缀覆盖

  ✅ [5/7] dependencies 引用有效

  ✅ [6/7] _build_init_kwargs 覆盖

  ✅ [7/7] 三表 key 对齐: 18 个意图



结果: 7 通过, 0 警告, 0 失败

============================================================

校验项说明

| 校验 | 检查内容 | 确保 |

|------|---------|------|

| [1] | tools.json 中的工具是否都在 MAPPING 中 | 不遗漏 |

| [2] | MAPPING 引用的工具是否都在 tools.json 中 | 不多引 |

| [3] | PRIORITY 引用的工具是否都在 tools.json 中 | 不多引 |

| [4] | 多下划线工具名在 known_prefixes 中 | 解析正确 |

| [5] | 依赖的 input_sources 工具存在 | 依赖有效 |

| [6] | _build_init_kwargs 有对应分支 | 构造正确 |

| [7] | 三表的 key 完全对齐 | 意图一致 |


💡 经验教训

1. 前缀表需双重同步

教训 :只更新了 tool_exposure.pyknown_prefixes,校验仍然失败。

原因validate_tool_chain.py 中有一份硬编码的副本,也需要同步更新。

最佳实践:每次新增多下划线工具,必须同时更新两处。

2. 延迟注入的时序依赖

教训:工具注册在 ToolRegistry 创建时完成,但 bridge 在 MainWindow 创建时才存在。

解决方案 :使用 set_bridge_client() 模式,与 CronTool 的 set_agent_dependencies() 保持一致。

3. 意图关键词的覆盖度

教训:初始只添加了"发送文件"一个关键词,用户说"传到手机"时无法匹配。

解决方案:充分枚举同义变体(18 个关键词覆盖各种口语表达)。


📊 架构总结

工具注册八步标准流程

| 步骤 | 文件 | 内容 |

|------|------|------|

| ① | src/tools/xxx.py | 创建 BaseTool 子类 |

| ② | config/tools.json | 声明式注册 |

| ③ | src/core/prompts.py | INTENT_CATEGORIES 关键词 |

| ④ | src/core/prompts.py | INTENT_TOOL_MAPPING + PRIORITY |

| ⑤ | src/core/tool_exposure.py | known_prefixes 前缀 |

| ⑥ | src/tools/registry.py | _build_init_kwargs 分支 |

| ⑦ | src/ui/gui_app.py | 延迟依赖注入(按需) |

| ⑧ | scripts/validate_tool_chain.py | 全链路校验 |


字数统计: 约 5,200 字

阅读时间: 约 14 分钟

代码行数: 约 350 行

复制代码
                                                                                                                                                                                                                          4. 3. 2.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        4.       3.     - - - - - > > > > 
相关推荐
CV矿工2 小时前
VLA(Vision-Language-Action)模型在机器人领域的action 输出编码
人工智能·深度学习·机器人
冬奇Lab2 小时前
一天一个开源项目(第62篇):lark-cli - 飞书/Lark 官方 CLI 与 AI Agent Skills
人工智能·开源·资讯
guslegend2 小时前
Ollama
人工智能·大模型
空空潍3 小时前
Spring AI与LangChain对比:组件对应关系、设计差异与选型指南
人工智能·spring·langchain
0 13 小时前
260401日志
人工智能·深度学习·nlp
摇滚侠3 小时前
系统工作台待办实时提醒,取代五分钟刷新一次,判断有没有新的待办,利用 WebSocket 实现
网络·websocket·网络协议
ANii_Aini3 小时前
Claude Code源码架构分析(含可以启动的源码本地部署)
架构·agent·claude·claude code
是有头发的程序猿3 小时前
用Open Claw接口做1688选品、价格监控、货源对比
开发语言·c++·人工智能
chools3 小时前
Java后端拥抱AI开发之个人学习路线 - - Spring AI【第一期】
java·人工智能·学习·spring·ai