chat.nvim v1.10.0:懒加载工具发现与 find_tool
chat.nvim v1.10.0 发布了(2026-09-11),距 v1.9.0 约一个月。这个版本的核心主题是省钱和干净——用懒加载工具发现机制砍掉请求里冗余的工具 schema,再用一轮 UTF-8 卫生修复堵住脏数据。
本版共 44 个提交:12 feat + 14 fix + 4 docs + 7 test + 2 ci + 2 refactor + 3 chore。发布时 56 个测试文件、1193 个用例全绿。
懒加载工具发现:本版重点
问题:工具越多,token 越贵
chat.nvim 的内置工具已经积累到几十个:文件读写、git 系列、web 搜索、zettelkasten、日程任务……每次请求,所有工具的 JSON schema 都会被塞进请求体。但你一次对话往往只用其中三五个,其余几十个 schema 纯属浪费——这些 token 每轮请求都在计费。
这个问题的想法最早记在今年 2 月的笔记里,当时的方案是在配置里写死一个静态列表:
tools = {'find_files', 'read_file'}, -- 只加载这两个工具
静态列表的缺点很明显:你得提前猜这次对话需要什么工具,猜少了 AI 就”断手断脚”,猜多了等于没省。
v1.10.0 落地的是一个动态方案:默认只发送 essential 工具集 + find_tool,其余工具由 AI 按需发现。
工作机制
每轮请求实际发送的工具,按这个顺序组装:
- essential 工具集(默认 5 个):
read_file/list_directory/search_text/find_files/get_time,覆盖最常见的文件操作 - 当前回合历史中已调用的工具(自愈,见下文)
- 本会话已激活的懒加载工具
- find_tool(始终在最后兜底)
find_tool 内嵌全部工具的实时目录——每个工具的名称加一行简介。AI 需要目录之外的能力时,用名称或关键词查询 find_tool:
- 精确名称匹配(不区分大小写):返回完整 schema 并激活该工具,下一轮响应即可调用
- 唯一部分匹配:直接返回 schema,省一轮往返
- 多个候选:返回候选列表让 AI 细化
- 无匹配:返回全目录辅助查询
- 查询
list/all/catalog或留空:返回全目录
MCP 工具也在目录里,同样可经 find_tool 发现。
回合生命周期
懒加载的激活状态是回合制的:
用户提问
→ 请求只带 essential + find_tool
→ AI 需要 git 工具 → 查询 find_tool
→ git 工具 schema 激活并入请求
→ AI 调用 git 工具完成任务
→ AI 输出纯文本回复(回合结束)
→ 激活状态清空
→ 下回合从 essential 重新开始
关键实现是 on_progress_done:一旦 AI 返回纯文本回复(没有 tool_calls),就清空已激活的工具。激活状态只存内存、永不落盘——既省 token,又不污染会话历史。
自愈机制
工具调用循环有个脆弱点:如果请求中断或 Neovim 重启,AI 正在用的工具可能不在 essential 里,直接重发会报”工具不存在”。
scan_history_tool_names 解决了这个问题:扫描会话历史,把当前回合(尚未以纯文本回复结束)内调用过的工具重新纳入请求。遇到纯文本回复即清空累计。也就是说,中断恢复后工具调用循环可以继续跑,不会卡死在半个回合上。
配置
默认开箱即用,无需任何配置:
require('chat').setup({
tools = {
lazy = true, -- 默认值,开启懒加载
essential = { 'read_file', 'list_directory', 'search_text', 'find_files', 'get_time' },
},
})
tools.essential 可以自定义——比如你常写 git 相关的任务,可以把 git_status、git_diff 常驻。想回退到老行为,设 tools.lazy = false 即恢复全量发送。
另外一个小细节:find_tool 自己被排除在工具目录之外,避免”目录嵌目录”的递归;它的输出和 MCP 工具的 schema 同样经过 UTF-8 清洗,保证请求体内所有 schema 合法。
完整文档在 docs/tools/find_tool.md。
其他新特性
write_file 支持 fileformat
write_file 工具新增 fileformat 参数,可选 unix / dos / mac 三种行尾。省略时自动检测并保留现有文件的行尾,新文件默认 unix。改 Windows 项目的 CRLF 文件不再被悄悄换成 LF。
Markdown 感知消息分块器
所有 IM 集成(Discord、Telegram、Slack、Lark、钉钉、企微、微信,共 7 个)共享一个新的分块器:按 Markdown 结构切分长消息,不会把代码块或表格从中间砍断。
HTTP API 增强
- 新增
/skills端点,含用户自定义 skill - 新增
GET/DELETE /logs:GET 支持level/name/tail过滤(可组合),DELETE 清空运行时日志 /sessions系列端点返回 session token 用量统计,/session/new的创建响应也含 usage——配合懒加载,token 省没省一目了然
脏工作区自动 nudge
AI 回复后如果工作区仍有未处理的改动(比如忘了 commit),chat.nvim 会自动提醒 AI 处理。async 工具未完成时跳过 nudge,避免干扰进行中的任务。
两个 skill 重命名
/disable-force-push→/toggle-force-push:按会话阻止 AI 执行 force push,语义更准确(原来名字暗示永久禁用)/messages→/nvim-messages:捕获:messages的报错信息交给 LLM 分析修复,重命名避免歧义
另外,未安装任何集成时,/list 现在会显示可用的集成列表,而不是一片空白。
UTF-8 卫生:请求前的最后防线
14 个 bug 修复里,最有体系的是 UTF-8 卫生系列。上游 API(比如返回 NonUTF8Body 错误的那些)对请求体的字节合法性要求严格,v1.10.0 在请求链路上做了四道清洗:
- sessions:请求前清理消息内容中的非法 UTF-8(55b738a)
- providers:请求体编码前 sanitize(0515532)
- tools:MCP 工具 schema 清洗为合法 UTF-8(ada483a)
- find_tool:目录简介按 UTF-8 边界截断,多字节字符(中文)不撕裂(3094494)
集成方面也修了一批实际问题:Discord GET 请求不再在 stdin 上永久挂起;微信 send_jobid 为 nil 导致最后一条消息未发出的崩溃;Lark/Slack/企微/钉钉/Telegram 的排队分块正确传递。
工程建设
- 新增
make coverage:luacov 行覆盖,支持COV_THRESHOLD门槛(如make coverage COV_THRESHOLD=80),报告写入 coverage.log - junit 测试结果体系:所有 CI matrix 任务上传 junit artifacts;
make junit支持ARTIFACT_NAME参数;artifact 下载有三重回退(unzip → python3 zipfile → busybox);本地复现 CI 测试:make junit ARTIFACT_ID=9277505915 ARTIFACT_NAME=junit-Windows-stable - 发布时 56 个测试文件、1193 个用例全绿
一个花絮:官方 CHANGELOG 只列出 38 条提交,另外 6 个(含 junit 体系和懒加载流程文档 2a0cc7f)因为 release-please 默认不输出 ci/chore 段落、以及 rebase 时序问题被漏掉了——本文的数据来自逐个 commit 核对,比 CHANGELOG 更全。
升级
已安装用户直接更新即可:
{
'wsdjeg/chat.nvim',
-- lazy.nvim 会自动拉取最新 tag
}
懒加载默认开启,升级后无需改配置就能生效。如果发现某个工具 AI 总要”绕一圈”才能找到,把它加进 tools.essential 即可。
总结
v1.10.0 的懒加载工具发现是个典型的”用约束换效率”设计:AI 能力不减(所有工具仍可发现),但每轮请求只携带当下需要的 schema。配合回合制激活和自愈机制,这个方案在省 token 的同时没有牺牲可靠性。
从 2 月笔记里的静态配置列表,到 9 月落地的动态发现,这个功能走了七个月——好的设计值得等。
相关链接: