按需加载工具和技能#

智能体不需要在每次对话中都具备所有能力。让普通工具从一开始就可用的,只简要描述技能,并在任务需要时再加载它们的完整指令。对于规模较大的工具集,受支持的 Responses 端点还可以通过搜索发现延迟加载的工具定义。

这是两个相互独立的选择:技能提供指令,而工具负责执行函数。加载指令不会给模型带来额外的权限。

用纯文本创建技能#

每个技能都存放在包含 SKILL.md 文件的目录中:

skills/
└── code-review/
    └── SKILL.md

这个文件需要名称、简短描述和 Markdown 正文:

skills/code-review/SKILL.md#
---
name: code-review
description: Review a code change for correctness, edge cases, and missing tests.
---

# Code review

1. Read the change and its surrounding code before drawing conclusions.
2. Check correctness, edge cases, and whether tests cover the new behavior.
3. Explain actionable findings with a concrete example or reproduction.
4. State what you could not verify. Do not claim to have run tests you did not run.

名称中请使用小写字母、数字和单个连字符。描述帮助模型判断何时读取该技能;正文说明如何完成任务。无效文件会被跳过,因此如果某个技能没有出现,请检查extension.skills。

选择要加载的目录#

请显式传入根目录,让你的应用控制指令的来源:

from pathlib import Path

from zett_agent.client import create_agent
from zett_agent.extensions.memory import InMemoryMessageAccumulator
from zett_agent.extensions.skill import SkillExtension
from zett_agent.extensions.tool_guidelines import ToolGuidelinesExtension

skills = SkillExtension([Path("skills")])
client = await create_agent(
    model,
    extensions=[
        InMemoryMessageAccumulator(),
        ToolGuidelinesExtension(),
        skills,
    ],
)
reply = await client.run("Review this change for correctness.")

模型会收到一个由名称和描述组成的目录,并可以调用read_skill(name="code-review")读取完整文件。它不会预先收到每个技能的正文。使用真实模型时,它是否选择读取某个技能,取决于你的提示词和它的能力。

技能发现发生在构造扩展的时候。添加文件或修改目录元数据之后,需要重新创建扩展。修改已有技能的正文会影响后续读取,但不会重写已经在对话中的指令。当名称冲突时,靠前的根目录优先。

如果不传入根目录,发现过程会使用用户级别的~/.zett/skills、~/.agent/skills、~/.claude/skills和~/.cursor/skills目录。项目本地的技能不会自动包含进来。传入显式列表会替换这些默认值;如果不想发现任何技能,可以使用SkillExtension([])。

技能名称限制为 64 个字符,描述限制为 1,024 个字符。解析器接受简单的 YAML 风格元数据;它不是通用的 YAML 配置加载器。请保持名称、描述和正文简单,并使用与应用相同的根目录来测试技能发现。加载器只提供SKILL.md的内容,而不会提供每个被引用的资源文件。如果你的技能需要配套文件,请添加作用域合适的文件读取工具。

备注

显式的extensions=[...]列表会替换这些可选的默认扩展。示例中包含了历史记录和工具 guidelines,以同时保留这两种行为。

运行一个完整的离线示例#

这个示例使用预设脚本的模型请求加载代码审查技能,然后返回最终回答。它无需网络请求,就能运行真实的工具往返过程。

async def main() -> None:
    skills = SkillExtension([Path(__file__).parent / "skills"])
    client = await create_agent(
        ReviewModel(),
        extensions=[InMemoryMessageAccumulator(), ToolGuidelinesExtension(), skills],
    )
    reply = await client.run("Load the review instructions.")
    assert reply.content == "Review correctness, edge cases, and tests."
    print("Skill loaded: code-review")
    print(reply.content)

在源码检出目录中运行:

uv run python docs/_examples/on_demand.py

预期输出:

Skill loaded: code-review
Review correctness, edge cases, and tests.

完整程序使用了这个 SKILL.md 文件。请把技能放在已下载的脚本旁边的skills/code-review/SKILL.md处。

延迟加载大型工具集#

使用兼容的 Responses 端点或 Anthropic Messages API 时,可以把很少使用的工具标记为延迟加载,并添加 ToolSearchExtension:

from zett_agent.extensions.tool_search import ToolSearchExtension
from zett_agent.tools.base import tool

@tool(deferred=True, guidelines="Use to check current stock before promising availability.")
def warehouse_stock(sku: str) -> str:
    """Look up available warehouse stock by product SKU."""
    return inventory_service.stock(sku)

client = await create_agent(
    responses_model,
    tools=[warehouse_stock],
    extensions=[
        InMemoryMessageAccumulator(),
        ToolGuidelinesExtension(),
        ToolSearchExtension(),
    ],
)

这里的 responses_model 是通过 response=True 配置的兼容适配器(也可以是 AnthropicProvider);inventory_service 则是你应用的库存客户端。模型会搜索相关能力,并在调用它们之前收到匹配的定义。没有设置 deferred=True 的工具仍然作为普通工具可见。

排序始终在本地完成,使用 BM25 检索每个工具的名称、描述、参数描述、snippet 和 guidelines。不同的只是传输协议:

提供商

延迟加载的工具如何到达模型

Responses API

搜索结果以 tool_search_output 定义的形式返回;延迟加载的工具不会预先发送。

Anthropic Messages API

延迟加载的工具会带着 defer_loading 声明;搜索结果以 tool_reference 块的形式返回,指明要加载的工具。它们的 guidelines 放在描述里一起发送。

ToolGuidelinesExtension 会跳过延迟加载的工具,所以它的描述是模型读到相关指引的唯一地方。注册了该扩展时,描述里带哪些内容由它的 snippets 和 guidelines 开关决定,与 placement 无关;没有该扩展时,延迟加载的工具只带它的 guidelines。

区别在于一个可选字段 native_deferred_tools,它由 NativeDeferredToolsModel 声明。它在 AnthropicProvider 上为 True,在其他所有内置适配器上为 False。自定义适配器的协议能够按引用加载延迟加载的工具时,就把它设为 True。

告诉模型可以搜索什么#

延迟加载的工具在加载之前是不可见的,只靠搜索关键词的模型只能猜测有哪些工具。因此 ToolSearchExtension 会列出这些延迟加载的工具(每个工具的名称和描述,以及它的 snippet 和 guidelines),并把这份列表放在你选择的位置:

from zett_agent.extensions.tool_search import ToolCatalogPlacement, ToolSearchExtension

# The default: appended to the search tool's own description.
ToolSearchExtension()

# A "# Searchable tools" section of the system instructions instead.
ToolSearchExtension(catalog=ToolCatalogPlacement.SYSTEM_PROMPT)

# Shorter entries, or no list at all.
ToolSearchExtension(catalog_snippets=False, catalog_guidelines=False)
ToolSearchExtension(catalog=ToolCatalogPlacement.NONE)

这份列表独立于 ToolGuidelinesExtension,有自己的开关,所以两者可以在任何放置方式和任何顺序下组合使用。列表会列出为该请求注册的所有延迟加载工具,而不会随着工具被找到而缩短:如果列表在每次搜索后都变化,请求前缀就会每次改变,使提供商的提示缓存失效。延迟加载的工具很多时,列表本身也会消耗 token,可以用这些开关精简它,或者使用 NONE 并依赖搜索关键词。

重要

客户端工具搜索需要 Responses 工具搜索协议或 Anthropic Messages API。Chat Completions、Google 和 Ollama 都不支持该扩展。技能加载使用的是普通本地工具,没有这个限制。

本功能不会做什么#

技能是可编辑的指令,而不是自动学习系统。这两个扩展都不会生成工具、不会从对话中学习、不会自行修改技能文件,也不会卸载正在运行的代码。工具以你的进程权限执行。请把技能文件和搜索结果当作输入,而不是执行特权操作的授权。

如果想在会话过程中创建和改进 Python 工具,需要主动启用构建并复用 JIT 能力。它是一个独立的受信任代码功能,而不是技能加载或延迟工具搜索的隐含行为。

下一步:保存并恢复对话用于保存对话,或者用组合内置扩展选择其他能力。

相关 API:SkillExtension、ToolSearchExtension。