feat(admin): 服务状态面板(基建/服务探活 + MCP 工具注册)+ mcp-go 工具注册表
管理端新增「运维 › 服务状态」:总览横幅 + 摘要数字 + 请求链路拓扑 (客户端→网关→NATS→调度→MCP,按健康三态着色)+ 应用服务卡(含探针 延迟)+ 基建磁贴 + MCP 工具按能力域分组(中文名/作用)。 探活机制(全走 NATS,无 HTTP): - mcp-go/mcp-py 新增 list_tools 自省工具,能应答=在线 + 上报工具清单 - dispatcher 无端点 → 新增 NATS 心跳主题 sundynix.health.dispatcher (ServeHealth 应答 model/ready/uptime),网关用 bus.Ping 探 - 网关 GET /api/v1/admin/status 并发聚合四探针 + 各项延迟 mcp-go 重构:switch → map 注册表(buildRegistry),dispatch 与 list_tools 共用单一事实源,杜绝漂移;每个工具带中文名 + 作用描述。mcp-py 同样补元信息。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,7 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -28,10 +29,20 @@ type Gateway struct {
|
||||
memory *memory.Store
|
||||
history *history.Store
|
||||
rag *rag.Engine
|
||||
tools map[string]toolDef // 工具注册表:唯一事实源,dispatch 与 list_tools 共用,杜绝漂移
|
||||
}
|
||||
|
||||
// toolDef 是一个注册工具的元信息(中文名 / 作用)+ 处理函数。
|
||||
type toolDef struct {
|
||||
cn string // 中文名
|
||||
desc 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 {
|
||||
return &Gateway{bus: b, search: s, memory: m, history: h, rag: r}
|
||||
g := &Gateway{bus: b, search: s, memory: m, history: h, rag: r}
|
||||
g.tools = g.buildRegistry()
|
||||
return g
|
||||
}
|
||||
|
||||
// Serve 以队列组通配订阅 sundynix.tools.go.>,按工具名分发并阻塞。
|
||||
@@ -47,46 +58,64 @@ func (g *Gateway) Serve(ctx context.Context) error {
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
// dispatch 按 ToolCall.Tool 路由到具体工具实现。
|
||||
// buildRegistry 注册 mcp-go 全部工具:名称 → (中文名, 作用, 处理函数)。
|
||||
// 这是工具的唯一事实源——dispatch 据此路由、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 {
|
||||
data, _ := json.Marshal(g.rag.Status())
|
||||
return &contract.ToolResult{OK: true, Content: string(data)}
|
||||
}},
|
||||
"echo": {"回显", "原样返回入参(调试用)",
|
||||
func(_ context.Context, call *contract.ToolCall) *contract.ToolResult {
|
||||
return &contract.ToolResult{OK: true, Content: fmt.Sprint(call.Args["text"])}
|
||||
}},
|
||||
}
|
||||
}
|
||||
|
||||
// dispatch 按 ToolCall.Tool 从注册表路由到具体工具实现。
|
||||
// list_tools 是元工具(自省),不在业务注册表内,单独处理。
|
||||
func (g *Gateway) dispatch(ctx context.Context, call *contract.ToolCall) *contract.ToolResult {
|
||||
log.Printf("[mcp_go] tool=%s task=%s args=%v", call.Tool, call.TaskID, call.Args)
|
||||
switch call.Tool {
|
||||
case "wiki_search":
|
||||
return g.wikiSearch(ctx, call)
|
||||
case "kb_ingest":
|
||||
return g.kbIngest(ctx, call)
|
||||
case "kb_search":
|
||||
return g.kbSearch(ctx, call)
|
||||
case "kb_graph":
|
||||
return g.kbGraph(ctx, call)
|
||||
case "report_render":
|
||||
return g.reportRender(ctx, call)
|
||||
case "report_store":
|
||||
return g.reportStore(ctx, call)
|
||||
case "report_export":
|
||||
return g.reportExport(ctx, call)
|
||||
case "external_api":
|
||||
return g.externalAPI(ctx, call)
|
||||
case "health":
|
||||
data, _ := json.Marshal(g.rag.Status())
|
||||
return &contract.ToolResult{OK: true, Content: string(data)}
|
||||
case "memory_get":
|
||||
return g.memoryGet(ctx, call)
|
||||
case "memory_upsert":
|
||||
return g.memoryUpsert(ctx, call)
|
||||
case "memory_delete":
|
||||
return g.memoryDelete(ctx, call)
|
||||
case "memory_list":
|
||||
return g.memoryList(ctx, call)
|
||||
case "history_get":
|
||||
return g.historyGet(ctx, call)
|
||||
case "history_append":
|
||||
return g.historyAppend(ctx, call)
|
||||
case "echo":
|
||||
return &contract.ToolResult{OK: true, Content: fmt.Sprint(call.Args["text"])}
|
||||
default:
|
||||
if call.Tool == "list_tools" {
|
||||
return g.listTools()
|
||||
}
|
||||
td, ok := g.tools[call.Tool]
|
||||
if !ok {
|
||||
return &contract.ToolResult{OK: false, Error: "unknown tool: " + call.Tool}
|
||||
}
|
||||
return td.handler(ctx, call)
|
||||
}
|
||||
|
||||
// listTools 自省:上报本服务注册的工具清单(名称 + 中文名 + 作用),供管理端展示。
|
||||
func (g *Gateway) listTools() *contract.ToolResult {
|
||||
type info struct {
|
||||
Name string `json:"name"`
|
||||
CN string `json:"cn"`
|
||||
Desc string `json:"desc"`
|
||||
}
|
||||
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})
|
||||
}
|
||||
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})
|
||||
return &contract.ToolResult{OK: true, Content: string(data)}
|
||||
}
|
||||
|
||||
// memoryGet 召回某用户的常驻画像(已渲染为可注入 prompt 的多行文本)。
|
||||
|
||||
Reference in New Issue
Block a user