85a5c2c1e7
检索从向量单路升级为混合:向量(Milvus) + 全文(Bleve BM25) → RRF 融合 → 可选 rerank(DashScope gte-rerank)。 - rag/bleve.go: Bleve 全文索引(内存,随 ingest 写入;kb 过滤);ingest 同步写 Milvus+Bleve - rag/fuse.go: RRF(Reciprocal Rank Fusion, k=60, 按文本去重)融合多路排序 - rag/rerank.go: DashScope gte-rerank 客户端(可选,env 配置,失败降级 RRF) - rag/rag.go: Search 改混合(向量+全文→RRF→可选rerank→topK);main 读 RERANK_* env - 验证: 全模块 build✓ + e2e PASS; live——入库写双索引;查'NATS'→全文精确命中#1+向量 →RRF NATS 排首(向量=4 全文=1);接 DashScope gte-rerank(百炼 key 有权限)→relevance score 0.19 真重排;retriever 节点端到端→DeepSeek 答 Milvus - 边界: Neo4j 图路(GraphRAG,需实体抽取)推迟;Bleve 内存索引重启重建;rerank 走 env (TODO 同 embedding 搬控制面 kind=rerank) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
85 lines
2.9 KiB
Go
85 lines
2.9 KiB
Go
// Command server 启动 sundynix-mcp-go —— 第 5 层 Go I/O 型 MCP 工具微服务。
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"log"
|
|
"os"
|
|
"os/signal"
|
|
"syscall"
|
|
"time"
|
|
|
|
sharedbus "github.com/sundynix/sundynix-shared/bus"
|
|
"github.com/sundynix/sundynix-shared/contract"
|
|
|
|
"github.com/sundynix/sundynix-mcp-go/internal/history"
|
|
"github.com/sundynix/sundynix-mcp-go/internal/mcp"
|
|
"github.com/sundynix/sundynix-mcp-go/internal/memory"
|
|
"github.com/sundynix/sundynix-mcp-go/internal/rag"
|
|
"github.com/sundynix/sundynix-mcp-go/internal/search"
|
|
)
|
|
|
|
func main() {
|
|
natsURL := envOr("NATS_URL", "nats://localhost:4222")
|
|
pgDSN := envOr("POSTGRES_DSN", "postgres://sundynix:sundynix@localhost:5432/sundynix?sslmode=disable")
|
|
redisAddr := envOr("REDIS_ADDR", "localhost:6379")
|
|
milvusAddr := envOr("MILVUS_ADDR", "localhost:19530")
|
|
embBase := envOr("EMBED_BASE_URL", "") // OpenAI 兼容 embeddings 端点(空=向量检索降级)
|
|
embKey := envOr("EMBED_API_KEY", "")
|
|
embModel := envOr("EMBED_MODEL", "")
|
|
rerankBase := envOr("RERANK_BASE_URL", "") // DashScope 文本重排端点(空=不启用 rerank)
|
|
rerankKey := envOr("RERANK_API_KEY", "")
|
|
rerankModel := envOr("RERANK_MODEL", "")
|
|
|
|
b, err := sharedbus.Connect(natsURL)
|
|
if err != nil {
|
|
log.Fatalf("[mcp_go] nats connect: %v", err)
|
|
}
|
|
defer b.Close()
|
|
log.Printf("[mcp_go] connected %s", natsURL)
|
|
|
|
engine := search.NewHybrid() // LLM Wiki 混合检索:Bleve + Milvus + Neo4j
|
|
mem := memory.Open(pgDSN) // 偏好记忆:sundynix_user_profile(连不上则降级)
|
|
defer mem.Close()
|
|
hist := history.Open(redisAddr) // 会话短期历史:Redis(连不上则降级)
|
|
defer hist.Close()
|
|
|
|
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
|
defer stop()
|
|
|
|
// RAG 核心链:embedding(env 初值) + Milvus(向量) + Bleve(全文) + 可选 rerank
|
|
ragEngine := rag.Open(ctx, milvusAddr, embBase, embKey, embModel, rerankBase, rerankKey, rerankModel)
|
|
defer ragEngine.Close()
|
|
|
|
// 配置控制面:启动取激活 embedding 配置 + 订阅热更新(覆盖 env,持久化由 Gateway 管)。
|
|
applyEmbed := func(cfg *contract.ModelConfig) {
|
|
if cfg != nil {
|
|
ragEngine.SetEmbedding(cfg.BaseURL, cfg.APIKey, cfg.Model)
|
|
}
|
|
}
|
|
cctx, ccancel := context.WithTimeout(ctx, 3*time.Second)
|
|
if cfg, _ := b.RequestConfig(cctx, contract.ConfigKindEmbedding); cfg != nil {
|
|
applyEmbed(cfg)
|
|
} else {
|
|
log.Println("[mcp_go] 未取到 embedding 控制面配置(用 env 或降级)")
|
|
}
|
|
ccancel()
|
|
if _, err := b.SubscribeConfigUpdated(contract.ConfigKindEmbedding, applyEmbed); err != nil {
|
|
log.Printf("[mcp_go] subscribe embedding config: %v", err)
|
|
}
|
|
|
|
gw := mcp.NewGateway(b, engine, mem, hist, ragEngine)
|
|
|
|
log.Println("[mcp_go] serving MCP over sundynix.tools.go.* (Ctrl-C to quit)")
|
|
if err := gw.Serve(ctx); err != nil && err != context.Canceled {
|
|
log.Fatalf("[mcp_go] exit: %v", err)
|
|
}
|
|
}
|
|
|
|
func envOr(key, def string) string {
|
|
if v := os.Getenv(key); v != "" {
|
|
return v
|
|
}
|
|
return def
|
|
}
|