mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-09-27 21:03:16 +00:00
真机实跑实证(独立实例 /tmp/seqtest,未触碰生产):
seq_run 报「工具 knowledge_list 不存在或未注册」,
而**同一轮模型直接调 knowledge_list 是成功的**。
根因:`memory_*` / `knowledge_*` / `doc_*` / `person_*` 这 20+ 个是
**内核内置**工具,在 core.executeToolCallInner 里按**前缀分派**,
由 buildToolDefs 直接生成 schema,**从不进 StageHost / IOManager**
⇒ ToolAPI(只有插件工具 + IO 设备工具)既查不到也调不了。
后果:序列只能编排插件/设备工具,无法编排记忆/知识/文档/人物
——恰恰是最常用的能力。
方案 B 的实现:
· internal/sdk: 新增 BuiltinProvider(Defs/Exec)与 SetBuiltinProvider。
用**晚绑定注入**而非让 toolImpl 依赖 core,理由:ToolAPI 是**全局单例**
却需要 per-agent 数据(驻留子是轻量内核,memory 为 nil;内置工具可见性
由 `if a.memory != nil` 等门控)。sdk 不能依赖 core(方向反了)。
· internal/sdk/tool_impl.go: ToolDefByName / ExecuteTool 补查内置工具。
⚠️ ExecuteTool 只在「io 确实没有该工具」时才转内置;io 的**执行失败**
必须如实上抛 —— 否则会把「设备离线」误报成「工具不存在」,让调用方
按 missing 策略跳过(与 P3 修过的父 io 吞错误同一族陷阱)。
内置工具**默认不声明 ParallelSafe**(含 SQLite 写与召回)。
· internal/agent/core/builtin_toolapi.go: Agent 侧 provider。
★ Defs **复用 buildToolDefs 的同一批生成逻辑**(筛出不在
StageHost/IOManager 中的那些),保证"模型看得到什么"与"插件看得到什么"
门控完全一致 —— 避免两套语义。
Exec 复用 executeToolCall 完整路径(授权闸 + 异常处理)。
· cmd/homed/bootstrap.go: agent 构造后注入。
★ 不会让模型看到重复工具(已核实):模型侧走 buildToolDefs
(a.io / a.stageHost **直调**),ToolAPI 只经 PluginSDK.Tool() 暴露给插件
—— 两条不重叠的路径。
判据(builtin_toolapi_test.go,6 条):
· 内置工具能从 ToolAPI 查到
· ★ 查到 ≠ 调得通:必须真的能执行
· 门控语义保持:未接 memory/knowledge 时不得声称有那些工具
· ★ 接了 knowledge 时必须可见(这正是要补的缺口)
· ToolAPI 上"不存在"必须是类型化 not-found(供 seq 的 missing 策略用)
· 内置工具默认不声明并发安全
真机复验(同一隔离实例,新二进制):
序列 "smoke2" 执行完毕(1/1 组)— 工具 1 个
变量槽: summary = cangjie/central-repo/agreement/...
⇒ knowledge_list 成功执行并回填具名槽。上一次的「不存在或未注册」已消除。
已知局限(记入待定):ToolAPI 单例而内置工具面是 per-agent,
多 agent 下看到的是"最近一个注入者"的面。本次不解决。
90 lines
4.3 KiB
Go
90 lines
4.3 KiB
Go
package sdk
|
||
|
||
// ToolSource 描述内核工具注册表/执行器的只读视图(由 StageHost 实现)。
|
||
// 用接口而非具体类型,避免 sdk 依赖内核包(内核包反向依赖 sdk)。
|
||
type ToolSource interface {
|
||
GetToolDefs() []ToolDef
|
||
ToolDef(name string) *ToolDef
|
||
ExecuteTool(name string, args map[string]interface{}) (interface{}, error)
|
||
}
|
||
|
||
// ToolAPI exposes the kernel tool registry and executor
|
||
// (StageHost for plugin tools + IOManager for device/channel tools).
|
||
type ToolAPI interface {
|
||
// GetToolDefs returns all tools registered on the stage host (plugin tools).
|
||
GetToolDefs() []ToolDef
|
||
// GetAllTools returns all tools exposed by IO devices/channels.
|
||
GetAllTools() []ToolDef
|
||
// CanUse 报告「执行该工具是否被授权」。
|
||
//
|
||
// 存在的理由(D4):设备类工具的授权闸原本只存在于 core 的
|
||
// executeToolCallInner,即**「agent 收到模型 tool_call」那条路径**。
|
||
// 而 ExecuteTool 是**另一条**独立入口,不经那道闸 —— 于是凡是走
|
||
// ToolAPI 的调用都能绕过 AllowedOutputs。实测范围不止序列:
|
||
// cli 插件的 /terminal 就直接经 ToolAPI 调 agentcli 的终端工具
|
||
// (cli/plugin.go:1038 自陈"ToolAPI 已允许跨插件调用工具")。
|
||
//
|
||
// 语义与内核那道闸**必须一致**(core 的 TestCanUseAgreesWithInnerPath
|
||
// 钉住这一点),否则两条路径判定不同同样是漏洞。
|
||
//
|
||
// 零值实现返回 true:未实现者行为不变(存量插件与测试替身不受影响)。
|
||
CanUse(toolName string, args map[string]interface{}) bool
|
||
// ToolDefByName 按名字查任一来源(StageHost 插件工具 / IO 设备工具 /
|
||
// 内核内置工具)的声明。
|
||
// 插件需要它来在**运行前**判断目标是否存在、是否并发安全 ——
|
||
// 而工具是动态注册的,"不存在"是常态(见 seq 包的 missing 策略)。
|
||
// 查不到返回 nil(调用方按"不存在"处理,不得 panic)。
|
||
ToolDefByName(name string) *ToolDef
|
||
// ExecuteTool executes a tool by name, resolving across the stage host first.
|
||
ExecuteTool(name string, args map[string]interface{}) (interface{}, error)
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// 内核内置工具的注册面(方案 B)
|
||
// ---------------------------------------------------------------------------
|
||
//
|
||
// 背景:`memory_*` / `knowledge_*` / `doc_*` / `person_*` 这 20+ 个工具是
|
||
// **内核内置**的,在 core.executeToolCallInner 里按**前缀分派**,
|
||
// 从不进 StageHost / IOManager ⇒ 插件经 ToolAPI 既查不到也调不了。
|
||
// 真机实跑实证:seq_run 报「工具 knowledge_list 不存在或未注册」,
|
||
// 而同一轮模型直接调 knowledge_list 是成功的。
|
||
//
|
||
// 为什么用**晚绑定注入**而不是让 toolImpl 直接依赖 core:
|
||
// toolImpl 在 internal/sdk,而内置工具的执行依赖 core 的 *Agent
|
||
// (它持有 memory/knowledge/store 等状态,且这些状态**逐 agent 不同**
|
||
// —— 驻留子是轻量内核,memory 为 nil)。sdk 不能依赖 core(方向反了),
|
||
// 且 ToolAPI 是**全局单例**,无法天然携带 per-agent 状态。
|
||
//
|
||
// 注入方(core/bootstrap)负责提供两个函数;未注入时行为与今天完全一致
|
||
// (内置工具在 ToolAPI 上不可见),故存量插件与测试替身不受影响。
|
||
|
||
// BuiltinToolDef 是内核内置工具的声明。
|
||
type BuiltinToolDef struct {
|
||
Name string
|
||
Description string
|
||
Parameters map[string]interface{}
|
||
}
|
||
|
||
// BuiltinProvider 由内核注入(导出:core 需实现它)。
|
||
type BuiltinProvider interface {
|
||
// Defs 返回当前 agent 可见的内置工具声明(**已按运行期状态门控**)。
|
||
Defs() []BuiltinToolDef
|
||
// Exec 执行一个内置工具,返回给模型看的文本。
|
||
Exec(name string, args map[string]interface{}) (string, error)
|
||
}
|
||
|
||
var builtinProv BuiltinProvider
|
||
|
||
// SetBuiltinProvider 注入内核内置工具的查询/执行面。
|
||
//
|
||
// 传 nil 表示撤销注入(测试隔离用)。注入后,内置工具对插件可见可调。
|
||
func SetBuiltinProvider(p BuiltinProvider) { builtinProv = p }
|
||
|
||
// builtinDefs 返回注入方提供的内置工具声明(未注入时为空)。
|
||
func builtinDefs() []BuiltinToolDef {
|
||
if builtinProv == nil {
|
||
return nil
|
||
}
|
||
return builtinProv.Defs()
|
||
}
|