工具集
Nova 能做的事全部包装成工具,走 OpenAI 的 function calling 协议交给模型选。当前一共 68 个,但这页不列清单——只讲工具面是怎么设计的。
注册表长什么样
每个工具就是一条记录:
public sealed record NovaTool(
string Name, // 函数名,snake_case
string DisplayName, // 给人看的名字,会出现在确认清单里
string Description, // 给模型看的:什么时候该调它
string ParametersJson, // JSON Schema
bool ReadOnly, // 信任边界的分水岭
Func<JsonElement, CancellationToken, Task<string>> ExecuteAsync);序列化出去就是标准 tools 参数:
{
"type": "function",
"function": {
"name": "read_version_detail",
"description": "查看某个已安装版本的详情:Java 要求、实例设置、已装 mod 列表。",
"parameters": {"type":"object","properties":{"version_id":{"type":"string"}},"required":["version_id"]}
}
}工具本身不做新业务逻辑,全部复用既有服务——删文件、装东西、启动游戏都走启动器原本那条路径,不给 AI 另开一条后门。Description 是写给模型的说明书,所以写成祈使句并带约束,比如 list_versions 的描述是「不要臆造版本号,装版本前先看这里」——这是提示注入的防线之一:模型被告知 ID 必须来自工具结果。
只读与写
ReadOnly 是整个信任模型的分水岭:
- 只读工具(
ReadOnly: true):查询类,Agent 循环里立即执行,结果以role: "tool"回喂,模型据此继续下一轮。 - 写工具(
ReadOnly: false):只生成参数,不执行。NovaAgentLoop把它们攒成一份确认清单交给用户。
| 工具 | 读还是写 | 备注 |
|---|---|---|
get_settings | 读 | 游戏目录、内存、Java、下载源、版本隔离 |
check_environment | 读 | 这版本能不能起来:Java 匹配、内存越界、路径、账号 |
analyze_crash | 读 | 离线诊断崩溃,不依赖云端 |
check_mod_conflicts | 读 | 重复 modId、加载器混装、损坏文件 |
install_content | 写 | 装 mod/资源包/光影,project_id 必须来自搜索结果 |
install_modpack | 写 | 导入 .mrpack 或 CurseForge 导出 zip |
export_modpack | 写 | 把实例导出成可分享的 .mrpack |
backup_version / restore_backup | 写 | 备份 id 来自 list_backups |
有一条贯穿全部写工具的纪律:能回滚就不硬删。模组冲突「处理」是改名加 .disabled 后缀,存档搬家是移动前先压一份 zip 到 backups/。确认清单里写得很直白——「改名 sodium.jar.disabled(不删除,随时可改回来)」。
顺序即语义
Tools 的注册顺序就是发给 LLM 的顺序,注册表把它分成两段:
Tools = readOnlyOnly
? [.. BuildReadTools()]
: [.. BuildReadTools(), .. BuildWriteTools()];只读工具排在前面。这不是美观问题——不少模型对工具列表的顺序敏感,把「查询类」放前面能显著降低它一上来就提议写操作的概率。
readOnlyOnly 这个开关是给跨端最小实现准备的:没有 UI 确认清单执行入口的宿主(比如移动端)只注册只读工具,宁可不给模型这个能力,也不给一个提了也落不了地的写操作。
两个收口
结果统一截断 900 字符。 工具返回的是给人看还是给模型看的都有,但模型那边必须封顶,否则一个 list_versions 就能把上下文撑爆:
private const int MaxResultChars = 900;
var result = await tool.ExecuteAsync(args, ct);
return result.Length <= MaxResultChars ? result : result[..MaxResultChars] + "…(已截断)";宿主相关的能力条件注册。 NovaToolContext 里 Host(录屏 / 插件 / 外观 / 页面跳转)为空时,recording_status、start_recording、list_plugins、set_appearance 这些工具根本不注册;Multiplayer 为空时联机工具同理。Core 的单元测试不依赖 WinUI,这些工具会自动缺席——比在运行时返回一个「当前宿主不支持」更干净,因为模型根本看不到它们就不会去调。
一个例外
open_page 被标成 ReadOnly: true。它只是把界面切到某个页面,不改任何配置——但它存在的意义是引导用户去做只有界面能完成的事(登录账户、看皮肤),所以必须能立刻执行,不能卡在确认清单上。