feat(mcp-go,dispatcher): 自主 agent 工具集动态化 —— list_tools 自描述 + 动态发现

杜绝硬编码:自主 agent 的工具菜单不再写死在 dispatcher,而是从 mcp-go
注册表(单一事实源)动态发现。加工具只改 mcp-go 一处,dispatcher 零改动。

- mcp-go:toolDef 增 agent/agentName/params/inject 元信息(paramSpec 声明
  模型可填参数;inject 声明服务端注入、不暴露给模型的参数如 user_id);
  list_tools 上报这些。当前标 agent 的 4 个:wiki_search / recall_user_memory
  / remember_user_fact / history_get。
- dispatcher:agentTools() 改为调 list_tools → 取 agent_exposed → 按上报的
  params 建 schema.ToolInfo → 生成 mcpTool;inject 参数(user_id/session_id/
  kb/task_id)运行时绑定。删除硬编码的 2 个工具。

验收:实测自主 agent 调用新暴露的 remember_user_fact(memory_upsert)成功——
参数由模型按 schema 自生成(key/value),user_id 服务端注入(map 带 task_id
佐证);make test-go 全绿;管理端状态面板兼容(忽略多余 JSON 字段)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Blizzard
2026-06-23 14:29:20 +08:00
parent 45ad852806
commit 7388f2741d
3 changed files with 133 additions and 53 deletions
+73 -27
View File
@@ -32,11 +32,25 @@ type Gateway struct {
tools map[string]toolDef // 工具注册表:唯一事实源,dispatch 与 list_tools 共用,杜绝漂移
}
// toolDef 是一个注册工具的元信息(中文名 / 作用)+ 处理函数
// paramSpec 是一个工具参数的声明(供自主 agent 据此生成调用入参)
type paramSpec struct {
Name string `json:"name"`
Type string `json:"type"` // string / number / integer / boolean / object / array
Desc string `json:"desc"`
Required bool `json:"required"`
}
// toolDef 是一个注册工具的元信息 + 处理函数。新增 agent 暴露元信息,让自主 agent 能动态发现工具:
// agent=是否给模型自主调用;agentName=模型可见名(空=注册键);params=模型可填参数;
// inject=服务端运行时注入、不暴露给模型的参数名(如 user_id / session_id / kb / task_id)。
type toolDef struct {
cn string // 中文名
desc string // 作用简述
handler func(context.Context, *contract.ToolCall) *contract.ToolResult
cn string
desc string
agent bool
agentName string
params []paramSpec
inject []string
handler func(context.Context, *contract.ToolCall) *contract.ToolResult
}
func NewGateway(b *sharedbus.Bus, s *search.Hybrid, m *memory.Store, h *history.Store, r *rag.Engine) *Gateway {
@@ -60,29 +74,53 @@ func (g *Gateway) Serve(ctx context.Context) error {
// buildRegistry 注册 mcp-go 全部工具:名称 → (中文名, 作用, 处理函数)。
// 这是工具的唯一事实源——dispatch 据此路由、list_tools 据此上报,二者永不漂移。
// 想让某工具能被自主 agent 调用:把 agent 设 true,写清 params(模型可填)与 inject(服务端注入)。
// 加新工具只改这一处——dispatcher 经 list_tools 动态发现,无需改调度代码。
func (g *Gateway) buildRegistry() map[string]toolDef {
return map[string]toolDef{
"wiki_search": {"知识检索", "向量检索知识库(Milvus),返回最相关片段", g.wikiSearch},
"kb_ingest": {"知识入库", "文本切块 → 向量化 → 写入 Milvus / Bleve", g.kbIngest},
"kb_search": {"检索台查询", "结构化返回命中内容与相似度分数", g.kbSearch},
"kb_graph": {"知识图谱", "取某库的实体关系三元组(Neo4j)", g.kbGraph},
"report_render": {"报告渲染", "把结构化报告渲染为 Word.docx", g.reportRender},
"report_store": {"报告存源", "暂存报告源数据,供导出时按需渲染", g.reportStore},
"report_export": {"报告导出", "按需把已存报告导出为 Word / Markdown", g.reportExport},
"external_api": {"外部接口", "受控调用第三方 HTTP API(带 SSRF 校验)", g.externalAPI},
"memory_get": {"记忆召回", "取用户长期画像(已按打分排序)", g.memoryGet},
"memory_upsert": {"记忆写入", "新增 / 更新一条用户偏好(带重要度)", g.memoryUpsert},
"memory_delete": {"记忆删除", "软删一条偏好(对账判定过时 / 矛盾时)", g.memoryDelete},
"memory_list": {"记忆列表", "列出用户全部偏好(供管理面板查看)", g.memoryList},
"history_get": {"历史召回", "取会话最近多轮对话", g.historyGet},
"history_append": {"历史追加", "往会话写入一条消息", g.historyAppend},
"health": {"健康检查", "上报 Milvus / Neo4j / embedding 就绪情况",
func(_ context.Context, _ *contract.ToolCall) *contract.ToolResult {
// —— 暴露给自主 agent 的工具(带参数 schema / 注入声明)——
"wiki_search": {
cn: "知识检索", desc: "检索知识库,返回与查询最相关的资料片段。需要外部知识/事实依据时调用。",
agent: true,
params: []paramSpec{{Name: "q", Type: "string", Desc: "检索查询语句", Required: true}},
inject: []string{"kb"}, handler: g.wikiSearch,
},
"memory_get": {
cn: "记忆召回", desc: "召回当前用户长期画像与偏好(称呼/职业/回答偏好等)。需要个性化、了解“我是谁”时调用。",
agent: true, agentName: "recall_user_memory", inject: []string{"user_id"}, handler: g.memoryGet,
},
"memory_upsert": {
cn: "记忆写入", desc: "把关于用户的一条事实/偏好长期记住(如称呼、职业、回答偏好)。",
agent: true, agentName: "remember_user_fact",
params: []paramSpec{
{Name: "key", Type: "string", Desc: "记忆条目的键,如 称呼/职业/回答偏好", Required: true},
{Name: "value", Type: "string", Desc: "记忆条目的值", Required: true},
},
inject: []string{"user_id"}, handler: g.memoryUpsert,
},
"history_get": {
cn: "历史召回", desc: "取当前会话最近多轮对话,用于理解上下文。",
agent: true, inject: []string{"session_id"}, handler: g.historyGet,
},
// —— 仅内部/流水线/管理用,不暴露给自主 agent ——
"kb_ingest": {cn: "知识入库", desc: "文本切块 → 向量化 → 写入 Milvus / Bleve", handler: g.kbIngest},
"kb_search": {cn: "检索台查询", desc: "结构化返回命中内容与相似度分数", handler: g.kbSearch},
"kb_graph": {cn: "知识图谱", desc: "取某库的实体关系三元组(Neo4j)", handler: g.kbGraph},
"report_render": {cn: "报告渲染", desc: "把结构化报告渲染为 Word.docx", handler: g.reportRender},
"report_store": {cn: "报告存源", desc: "暂存报告源数据,供导出时按需渲染", handler: g.reportStore},
"report_export": {cn: "报告导出", desc: "按需把已存报告导出为 Word / Markdown", handler: g.reportExport},
"external_api": {cn: "外部接口", desc: "受控调用第三方 HTTP API(带 SSRF 校验)", handler: g.externalAPI},
"memory_delete": {cn: "记忆删除", desc: "软删一条偏好(对账判定过时 / 矛盾时)", handler: g.memoryDelete},
"memory_list": {cn: "记忆列表", desc: "列出用户全部偏好(供管理面板查看)", handler: g.memoryList},
"history_append": {cn: "历史追加", desc: "往会话写入一条消息", handler: g.historyAppend},
"health": {cn: "健康检查", desc: "上报 Milvus / Neo4j / embedding 就绪情况",
handler: func(_ context.Context, _ *contract.ToolCall) *contract.ToolResult {
data, _ := json.Marshal(g.rag.Status())
return &contract.ToolResult{OK: true, Content: string(data)}
}},
"echo": {"回显", "原样返回入参(调试用)",
func(_ context.Context, call *contract.ToolCall) *contract.ToolResult {
"echo": {cn: "回显", desc: "原样返回入参(调试用)",
handler: func(_ context.Context, call *contract.ToolCall) *contract.ToolResult {
return &contract.ToolResult{OK: true, Content: fmt.Sprint(call.Args["text"])}
}},
}
@@ -102,16 +140,24 @@ func (g *Gateway) dispatch(ctx context.Context, call *contract.ToolCall) *contra
return td.handler(ctx, call)
}
// listTools 自省:上报本服务注册的工具清单(名称 + 中文名 + 作用),供管理端展示。
// listTools 自省:上报本服务注册的工具清单(名称 + 中文名 + 作用 + agent 暴露元信息),
// 供管理端展示 & dispatcher 动态构建自主 agent 工具集(加工具只改注册表,无需改调度代码)。
func (g *Gateway) listTools() *contract.ToolResult {
type info struct {
Name string `json:"name"`
CN string `json:"cn"`
Desc string `json:"desc"`
Name string `json:"name"`
CN string `json:"cn"`
Desc string `json:"desc"`
Agent bool `json:"agent_exposed"` // 是否给自主 agent
AgentName string `json:"agent_name,omitempty"`// 模型可见名(空=name
Params []paramSpec `json:"params,omitempty"` // 模型可填参数
Inject []string `json:"inject,omitempty"` // 服务端注入参数(不暴露给模型)
}
out := make([]info, 0, len(g.tools))
for name, td := range g.tools {
out = append(out, info{Name: name, CN: td.cn, Desc: td.desc})
out = append(out, info{
Name: name, CN: td.cn, Desc: td.desc,
Agent: td.agent, AgentName: td.agentName, Params: td.params, Inject: td.inject,
})
}
sort.Slice(out, func(i, j int) bool { return out[i].Name < out[j].Name }) // map 无序 → 稳定输出
data, _ := json.Marshal(map[string]any{"service": "mcp-go", "tools": out})