实现一个只读 Tool 插件

实践目标

理解并运行一个独立的 text_stats Tool plugin:先测 Tool 契约,再验证 entry point 发现,最后用 DeepSeek 完成一次真实 Tool Call。全程不修改 nanobot core。

前置知识

  • 已完成 Workspace Skill 实践;
  • 能创建/使用独立 Python 虚拟环境;
  • 已读 ToolLoader 和 ToolRegistry;
  • 使用实验 workspace 和 config。

示例位置

本学习库提供可运行示例:

example/nanobot/learning-CN/examples/text-stats-tool-plugin/
├── pyproject.toml
├── nanobot_text_stats/
│   ├── __init__.py
│   └── tool.py
└── tests/
    └── test_tool.py

需要阅读的代码

先读示例,再对照:

  • nanobot/agent/tools/base.py::Tool
  • nanobot/agent/tools/schema.py
  • nanobot/agent/tools/loader.py::ToolLoader
  • nanobot/agent/tools/registry.py::ToolRegistry
  • tests/agent/test_tool_loader_entrypoints.py

先写下预测

  • entry point key 与 Tool.name 是否必须相同?
  • read_only=True 会影响什么?
  • 缺少 text 参数时,是 execute() 还是 Registry 返回错误?
  • plugin 与内置 Tool 同名时谁优先?

实践步骤

1. 阅读 Tool

TextStatsTool:

  • 名称为 text_stats;
  • 参数 text 必填,长度 1–20000;
  • 返回 JSON 字符串;
  • 不读取文件或网络;
  • read_only=True,因此在非 exclusive 情况下可并发。

先用你自己的话解释每个设计选择。

2. 安装源码 checkout

在专用虚拟环境中:

Set-Location "example\nanobot\code-EN"
python -m pip install -e .

3. 安装 plugin

python -m pip install -e "..\learning-CN\examples\text-stats-tool-plugin" --no-deps

--no-deps 是因为上一步已经安装当前源码 checkout,避免又从 PyPI 安装另一份 nanobot。

4. 运行 Tool 单元测试

从 example/nanobot/learning-CN/examples/text-stats-tool-plugin:

python -m pytest tests -q

5. 验证 entry point

python -c "from nanobot.agent.tools.loader import ToolLoader; print(sorted(ToolLoader()._discover_plugins()))"

预期包含:

text-stats

注意:这是 entry point key;模型调用的函数名是 text_stats。

6. 验证 Registry

可在调试器或短暂 Python REPL 中:

  1. 创建 ToolContext;
  2. 创建 ToolRegistry;
  3. ToolLoader().load(ctx, registry);
  4. 检查 registry.has("text_stats");
  5. 执行合法与非法参数。

7. 真实 DeepSeek smoke test

重新启动 nanobot 进程,让 entry point 重新发现。发送:

必须使用 text_stats 工具统计这段文本,并告诉我 characters、non_whitespace_characters、lines:
第一行
second line

在 Runner 断点确认 Tool Call name 与 arguments。不要只凭最终自然语言判断工具确实被调用。

预期观察

  • ToolLoader 能发现独立 package;
  • Provider tool schema 中出现 text_stats;
  • 缺失/超长参数在 Registry 层失败;
  • 合法结果先作为 tool message 回给模型;
  • Plugin 未修改 AgentLoop;
  • read_only 使基类 concurrency_safe 为真。

完成条件

  • 示例 tests 通过;
  • entry point 可发现;
  • Registry 能执行;
  • 至少一次真实模型 Tool Call;
  • 能解释 entry point key、class name 和 Tool name 三者;
  • example/nanobot/code-EN 无源码 diff。

验证方法

额外增加一个测试:

  • 空字符串必须失败;
  • 或 20001 字符必须失败;
  • 或验证换行计数。

这是允许修改的示例区域。修改后重新运行 plugin tests,并检查 diff 只落在示例目录。

复盘问题

  1. 为什么示例选择纯函数只读 Tool?
  2. read_only=True 是否由框架自动证明?
  3. 为什么要在真实请求前先测 Registry?
  4. 如果将来加入 URL 参数,需要补哪些安全机制?

下一篇:综合扩展设计评审