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:
@@ -85,42 +85,75 @@ func (m *mcpTool) InvokableRun(ctx context.Context, argsJSON string, _ ...tool.O
|
||||
return res.Content, nil
|
||||
}
|
||||
|
||||
// agentTools 构建 ReAct 可用的工具集(按当前任务上下文绑定 uid/kb)。
|
||||
// 工具名是给"模型看"的语义名;mcpName 是实际 NATS 调用名。context 参数(user_id)服务端注入,不暴露给模型。
|
||||
// toolCatalogEntry 是 MCP list_tools 上报的一条工具元信息(与 mcp-go listTools 输出对齐)。
|
||||
type toolCatalogEntry struct {
|
||||
Name string `json:"name"`
|
||||
CN string `json:"cn"`
|
||||
Desc string `json:"desc"`
|
||||
Agent bool `json:"agent_exposed"`
|
||||
AgentName string `json:"agent_name"`
|
||||
Params []struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Desc string `json:"desc"`
|
||||
Required bool `json:"required"`
|
||||
} `json:"params"`
|
||||
Inject []string `json:"inject"`
|
||||
}
|
||||
|
||||
// agentTools 动态构建 ReAct 可用的工具集:调 MCP list_tools 自描述目录 → 取 agent_exposed 的工具
|
||||
// → 用上报的参数 schema 建 InvokableTool;inject 参数(user_id/session_id/kb/task_id)服务端运行时
|
||||
// 绑定、不暴露给模型。新增工具只需在 mcp-go 注册表标 agent,无需改这里(杜绝硬编码)。
|
||||
func (o *Orchestrator) agentTools(b *board, taskID string, tr *execTracer) []tool.BaseTool {
|
||||
if o.tools == nil {
|
||||
return nil
|
||||
}
|
||||
kbBind := map[string]any{}
|
||||
if b.kb != "" {
|
||||
kbBind["kb"] = b.kb
|
||||
cctx, cancel := context.WithTimeout(context.Background(), toolCallTimeout)
|
||||
defer cancel()
|
||||
res, err := o.tools.CallTool(cctx, contract.ToolSubjectGo("list_tools"), &contract.ToolCall{Tool: "list_tools"})
|
||||
if err != nil || res == nil || !res.OK {
|
||||
return nil
|
||||
}
|
||||
return []tool.BaseTool{
|
||||
&mcpTool{
|
||||
mcpName: "wiki_search",
|
||||
var cat struct {
|
||||
Tools []toolCatalogEntry `json:"tools"`
|
||||
}
|
||||
if json.Unmarshal([]byte(res.Content), &cat) != nil {
|
||||
return nil
|
||||
}
|
||||
// inject 参数名 → 本任务的运行时值(不进模型菜单)。
|
||||
injectVal := map[string]any{"user_id": b.uid, "session_id": b.sid, "task_id": taskID, "kb": b.kb}
|
||||
|
||||
var out []tool.BaseTool
|
||||
for _, e := range cat.Tools {
|
||||
if !e.Agent {
|
||||
continue
|
||||
}
|
||||
params := map[string]*schema.ParameterInfo{}
|
||||
for _, p := range e.Params {
|
||||
params[p.Name] = &schema.ParameterInfo{Type: schema.DataType(p.Type), Desc: p.Desc, Required: p.Required}
|
||||
}
|
||||
bind := map[string]any{}
|
||||
for _, inj := range e.Inject {
|
||||
if v, ok := injectVal[inj]; ok && v != "" {
|
||||
bind[inj] = v
|
||||
}
|
||||
}
|
||||
name := e.AgentName
|
||||
if name == "" {
|
||||
name = e.Name
|
||||
}
|
||||
out = append(out, &mcpTool{
|
||||
mcpName: e.Name,
|
||||
subject: contract.ToolSubjectGo,
|
||||
caller: o.tools, taskID: taskID, tr: tr,
|
||||
bind: kbBind,
|
||||
bind: bind,
|
||||
info: &schema.ToolInfo{
|
||||
Name: "wiki_search",
|
||||
Desc: "检索知识库,返回与查询最相关的资料片段。需要外部知识/事实依据时调用。",
|
||||
ParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{
|
||||
"q": {Type: schema.String, Desc: "检索查询语句", Required: true},
|
||||
}),
|
||||
Name: name, Desc: e.Desc,
|
||||
ParamsOneOf: schema.NewParamsOneOfByParams(params),
|
||||
},
|
||||
},
|
||||
&mcpTool{
|
||||
mcpName: "memory_get",
|
||||
subject: contract.ToolSubjectGo,
|
||||
caller: o.tools, taskID: taskID, tr: tr,
|
||||
bind: map[string]any{"user_id": b.uid},
|
||||
info: &schema.ToolInfo{
|
||||
Name: "recall_user_memory",
|
||||
Desc: "召回当前用户的长期画像与偏好(称呼/职业/回答偏好等)。需要个性化、了解“我是谁”时调用。",
|
||||
ParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{}),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// runReactAgent 执行带"自主工具"的 agent 节点:模型在 ReAct 循环里自行决定调哪些 MCP 工具。
|
||||
|
||||
Reference in New Issue
Block a user