问题:工具一多就散
早期我习惯一个动作一个工具:
search_notes
read_note
create_note
update_note
delete_note
list_notes
领域一多,工具列表立刻膨胀。带来的问题很实际:
- 模型选错工具:名字相近,描述重叠
- 参数约定分散:每个工具一套字段,风格不统一
- 上下文更长:工具定义本身就吃 token
- 维护成本高:加一个动作就要加一个工具并改提示词
我后来改用一个更省事的做法:同领域合并,用 type 切换动作。
我现在的约定
工具名 {"type": "...", ...其它数据字段}
规则只有四条:
- 一个领域 = 一个工具(notes / orders / configs…)
type只能是英文(见下表)- 数据参数挂在顶层(等于传统
**arys) help是必须动作:先问用法,再填业务参数
type 取值
| type | 含义 | 典型参数 |
|---|---|---|
help | 帮助 / 用法说明 | 无(或可选 topic) |
search | 搜索 | query, 可选 limit |
get | 按 id 查一条 | id |
list | 列表 | 可选 limit, offset |
create | 新增 | 领域字段,如 title, content |
update | 修改 | id + 要改的字段 |
delete | 删除 | id |
中文口语里我会说「增查改删搜索帮助」,但 落进 schema 的 type 一律英文,避免模型中英混填、也方便日志检索。
一个工具长什么样
from langchain.tools import tool
HELP = """notes tool
type must be one of: help | search | get | list | create | update | delete
help:
{}
search:
{"type":"search","query":"...","limit":5}
get:
{"type":"get","id":"n-01"}
list:
{"type":"list","limit":10,"offset":0}
create:
{"type":"create","title":"...","content":"...","tags":["agent"]}
update:
{"type":"update","id":"n-01","content":"..."}
delete:
{"type":"delete","id":"n-01"}
Rules:
- Call help first if unsure.
- Never invent note ids.
- delete is destructive; confirm with user when possible.
"""
@tool
def notes(type: str, query: str | None = None, id: str | None = None,
title: str | None = None, content: str | None = None,
tags: list[str] | None = None, limit: int | None = None,
offset: int | None = None) -> str:
"""Single tool for personal notes: help/search/get/list/create/update/delete.
Always start with type. If unsure, set type to help.
"""
if type == "help":
return HELP
if type == "search":
return do_search(query or "", limit or 5)
if type == "get":
return do_get(id or "")
if type == "list":
return do_list(limit or 10, offset or 0)
if type == "create":
return do_create(title or "", content or "", tags or [])
if type == "update":
return do_update(id or "", content, title, tags)
if type == "delete":
return do_delete(id or "")
return f"ERROR: invalid type '{type}'. Call help for allowed values."
要点:
- 工具名只有一个:
notes - 模型看到的「能力面」很窄
- 真正的能力在
type分支里,由我们控制 - 非法
type直接返回可行动错误,而不是抛异常炸整轮
为什么 help 必须有
帮助不是装饰。它的作用是:
- 在工具列表变少后,仍能恢复细节
- 给出精确示例参数,降低瞎猜
- 写死危险规则(如 delete 要确认)
- 错误时可自纠:模型再调一次
help就能改参
系统提示里我会写一句:
If unsure about a tool's parameters, call that tool with type=help before guessing.
和「一动作一工具」对比
| 维度 | 多小工具 | type 合并工具 |
|---|---|---|
| 工具数量 | 多 | 少 |
| 选错工具 | 更常见 | 更少见 |
| 加新动作 | 新工具 + 新描述 | 加一个 type 分支 |
| 参数一致性 | 易漂移 | 易统一 |
| 风险 | 分散 | 集中在一处治理 |
合并的风险也存在:一个工具塞太满会变成「万能巨石」。
所以我仍坚持:只合并同一领域,不要把 notes + payments + email 揉成一个工具。
实用边界
适合合并:
- 同一实体的 CRUD + search
- 领域稳定、动作可枚举
- 希望减少工具定义 token
不适合合并:
- 动作语义差很远(只读检索 vs 不可逆外发)
- 权限模型不同,最好拆开
- 领域还在狂变,分支会失控
高危动作即使合并进大工具,也要在实现里做确认或降权。
调用示例
notes {"type":"help"}
notes {"type":"search","query":"ReAct loop"}
notes {"type":"get","id":"n-03"}
notes {"type":"create","title":"工具合并笔记","content":"用 type 收敛..."}
notes {"type":"update","id":"n-03","content":"补充 help 规则..."}
日志里只出现一个工具名,但 type 仍可统计、可告警。
结论
工具多的时候,我一般不先删能力,而是:
- 按领域合并成一个工具
type用英文枚举(help/search/get/list/create/update/delete)- 数据参数放顶层
help必调,错误返回可行动提示
这样工具面更干净,模型更不容易选错,我们也好维护。
少工具不是目的,可控的能力面才是。