Skip to main content
Glama

KB MCP Server · 企业级 B2B 客服工单知识库 RAG 系统

基于 MCP 协议 + Python 3.13 的可调用外部 API 的知识库。给 Agent 一套「检索知识 + 调用实时数据 + 结构化合成答复」的工具,让客服工单场景的问答既有据可依(知识库)、又实时准确(订单/库存等外部系统)。

🌐 在线演示:https://Doy369.github.io/kb-mcp-server/

演示为离线模式(本地私有化检索 + mock 实时数据),无需任何外部依赖,开箱即跑。 (页面挂在 GitHub Pages;后端 Render 可选——见「演示部署」章节。静态部署模式下侧边栏会显示"📦 静态演示"。)


✨ 特性

  • MCP 工具化:通过 MCP 协议把知识库能力暴露给任意 MCP 客户端(Claude Desktop、自研 Agent 等)。

  • 混合检索(RAG):向量语义召回 + 关键词,命中片段带相似度分数与可解释来源。

  • 知识图谱(P6 · GraphRAG):摄取时自动抽取三元组建图;问答时语义召回与图谱多跳并行,答复附带可解释关系路径(路径即证据)。

  • 实时数据接入(P4):内置订单状态 / 库存适配器,按问题意图自动调用外部 API,合成为「知识 + 实时数据」结构化答复(含置信度与轨迹)。

  • 多格式摄取:.md / .txt / .docx(零依赖解析),支持单文档、文件夹批量、文件上传。

  • 可切换存储 / 嵌入:

    • 存储:memory(纯 Python 离线开发)⇄ pgvector(生产,Postgres + 向量索引)。

    • 嵌入:dev(离线条目哈希,零依赖)⇄ bge(sentence-transformers 本地模型,数据不出域)。

  • Web 控制台:内置前端(static/index.html),知识摄取、检索、指标、知识图谱、Agent 协作、接口配置、对话一体。

  • 生产加固(P5):可选 Bearer 鉴权、按 IP 限流、结构化访问日志、/api/metrics 指标。

  • 实时数据接入韧性(P0-2):订单 / 库存适配器统一支持重试(指数退避)+ 熔断 (按后端独立,开路期不发调用)+ 降级 + 可重试性分级——后端不可用时返回 degraded 卡片并在 答复中写明「实时数据暂不可用」,问答主链路不中断;4xx(除 408/429)是确定性失败, 不重试、也不计入熔断(否则一个 401 配置错误会把熔断打开、掩盖真因); 404 单独判为「查不到」终态,渲染成「未查询到该单号(后端明确返回不存在,非故障)」, 与「后端挂了」严格区分;/api/status 暴露熔断状态与降级 / 查不到计数。 真实 HTTP 往返已由 CI adapters job 每次推送验证。

  • 多 agent 协作(P7 / P1-4):编排器调度多个职责 agent,按问题动态组队(LLM 任务分解)、 子任务按 depends_on 依赖分层(同层并行、层间串行)、证据不足时双向协商补人 (请求 / 应答账本,沉默不算数)、需复核时转人工队列;全过程留可观测轨迹。

  • 动作型工具 + 真实 MCP(P2-9):agent 不止会答,还能真正执行「建工单 / 改单 / 退款」, 按风险分级(read / write / destructive)+ 确认门(不可逆动作未确认绝不执行)+ 全量审计; 执行结果可回写知识图谱(KB_ACTION_GRAPH),打通「执行 → 关系 → 再检索」; MCP 侧已由真实 stdio 子进程 + 官方客户端完成协议端到端验证(CI mcp job)。

  • 质量保障(P2-10):358 例 pytest 单测(离线约 12s 跑完)+ GitHub Actions CI 九 job 全绿 (CI #23 / run 36677436460;八 job 基线:CI #21 / run 36673194795;七 job 基线:CI #19 / run 36663136008) —— ① 单测 → 回归评测 → 基线校验(通过率低于阈值即阻断合并); ② 容器镜像构建 → 启动 → 健康检查 → 容器内端到端冒烟; ③ 真实 Postgres + pgvector:建表 → 完整回归跑在 PG → 校验 schema 与落库数据; ④ 真实 MCP 协议:stdio 子进程 + 客户端握手 → 工具调用 → 动作确认门与审计; ⑤ 负载测试:真实 HTTP 并发,断言零错误 / 并发写入不丢数据 / 限流精确 / 指标自洽; ⑥ 真实 HTTP 往返(实时适配器):起真实 socket 替身后端 + 真实 app 子进程,断言 重试计数 / 超时受约束 / 熔断后请求数冻结 / 404 与 401 不重试 / 429 重试 / 端到端带出真实后端数据; ⑦ 真实 Apache AGE:扩展 → 建图 → 不静默降级 → 语法能力探针 → 真实摄取写入 → 原始 ag_catalog 表对账(真跑凭据:7 顶点 / 6 边); ⑧ 真实 bge 嵌入(夜间 + 手动,阈值 0.95)——保「质量上限」,不拖慢每次推送。

  • 容器化(P0-3):Dockerfile + docker-compose.yml,镜像按 build arg 分档 (轻量 ~150MB / 含 bge / 含 pgvector),内置 HEALTHCHECK;构建与启动由 CI 每次提交验证。


Related MCP server: mcp-business-bot

🏗 架构

   MCP Client ──▶ MCP Server (FastMCP)
   (Agent / IDE)  ingest · search · ask_with_live
                  graph_query · graph_expand · graph_paths
                          │
                          ▼
     ┌──────────────────────────────────────────────────┐
     │               KB MCP Server 核心                 │
     │                                                  │
     │  Ingestion ──┬─▶ Embeddings ────▶ 向量库          │
     │              └─▶ TripleExtractor ▶ 知识图谱       │
     │                                                  │
     │  Retrieval(向量+BM25) ─┐                         │
     │  graph.expand_facts ───┼─▶ Synthesis 合成答复     │
     │  Adapters(实时数据) ───┘                         │
     └────┬──────────────┬───────────────┬─────────────┘
          │              │               │
          ▼              ▼               ▼
   memory / pgvector  memory / AGE    订单 · 库存 · CRM
   (向量:管语义)    (图谱:管关系)  (实时数据)
          └──────── 同一个 PostgreSQL 实例 ─────────┘
                          │
                          ▼
   Web 控制台 (app.py → static/index.html)
   知识库 / 检索·指标 / 知识图谱 / 接口配置 / 对话

GraphRAG 的分工:向量检索回答「哪段话像这个问题」,知识图谱回答「这些实体之间什么关系、能推出什么」。 两者互补——图谱是向量库的关系增强层,不是替代品。


📁 目录结构

kb-mcp-server/
├── kb_mcp_server/          # 核心包
│   ├── server.py           # MCP Server(FastMCP 工具定义,含图谱与多 agent 工具)
│   ├── config.py           # 配置(环境变量 + 运行时配置持久化)
│   ├── embeddings.py       # 嵌入层(DevEmbedder / BGEEmbedder)
│   ├── storage.py          # 向量存储(MemoryVectorStore / PGVectorStore)
│   ├── graph.py            # 知识图谱(本体 / 双后端 / 三元组抽取 / 多跳查询)
│   ├── llmclient.py        # 本地 LLM 客户端(代理绕过 + 60s 熔断,三处调用共用)
│   ├── ingestion.py        # 摄取管线(切片 + 嵌入 + 写入,顺带抽取三元组入图)
│   ├── retrieval.py        # 混合检索
│   ├── synthesis.py        # 合成层(知识 + 图谱 + 实时数据 + 动作结果 → 结构化答复)
│   ├── adapters.py         # 外部 API 适配器(订单/库存)
│   ├── actions.py          # 动作型工具实现层(P2-9:ActionRunner 校验/确认门/审计 + 4 个动作)
│   ├── agents/             # 多 agent 协作层(P7)
│   │   ├── base.py         #   AgentContext 黑板 + BaseAgent 模板(计时/异常兜底/参与闸门)
│   │   ├── workers.py      #   GraphBuilder / Retriever / GraphReasoner / LiveData / ActionAgent / Synthesizer
│   │   └── orchestrator.py #   编排器(deterministic / llm 路由,多路并行 + 协商补轮 + HITL)
│   └── __main__.py
├── app.py                  # Web 控制台(HTTP 服务 + 前端)
├── run_demo.py             # 演示入口(离线播种,用于云端/演示部署)
├── static/index.html       # 前端控制台页面(含知识图谱面板)
├── demo_offline.py         # 离线自检脚本(摄取→检索整条链路)
├── demo_graph.py           # 图谱自检脚本(建图→多跳→推理路径→融合答复)
├── demo_agents.py          # 多 agent 自检脚本(补图→并行召回→合成→轨迹)
├── setup_db.py             # pgvector 建表初始化(--graph 额外初始化 AGE 图)
├── make_samples.py         # 生成多格式测试样本
├── fake_order_api.py       # 本地假订单后端(验证字段映射用)
├── eval_run.py             # 回归评测入口(golden 集 → 基线指标 + eval_report.json)
├── bench_scale.py          # 规模化召回评测(992 条公开语料,Recall/MRR/NDCG/Precision)
├── pytest.ini
├── tests/                  # 自动化测试(358 例,离线零依赖,见 tests/README.md)
├── scripts/
│   ├── check_baseline.py   # CI 基线校验:通过率低于阈值则退出码 1(阈值/报告路径可覆盖)
│   ├── check_pg.py         # CI 存储校验:schema 落库 / 数据非空(堵静默退回 memory)
│   ├── check_mcp.py        # P2-9 真实 MCP 协议端到端(stdio 子进程 + 官方客户端)
│   ├── check_load.py       # P2-10 补:真实 HTTP 并发压测(零错误 / 不丢数据 / 限流精确)
│   ├── check_adapters_live.py  # P0-2 收尾:真实 HTTP 往返(真实 socket 替身后端,26 项断言)
│   ├── check_age.py        # P2-10 补:真实 Apache AGE 图侧(扩展/建图/不降级/原始表对账)
│   └── verify_collaboration.py  # P1-4 动态协作真实链路联调(依赖分层 / 双向协商,28 项断言)
├── .github/workflows/ci.yml  # CI 九 job:单测+评测+基线 / 容器 / PG / MCP / 负载 / 适配器 / 协作 / AGE / bge(夜间)
├── Dockerfile              # 容器镜像(默认轻量档,ARG 可选 bge / pgvector)
├── docker-compose.yml      # 编排:web + 数据卷 + 可选 pgvector 服务
├── .dockerignore
├── requirements.txt
├── .env.example            # 全部配置项示例
└── samples/ test-docs/     # 示例知识库文档

🚀 快速开始(离线模式)

1. 环境

  • Python 3.13+

  • 离线默认模式零额外依赖(仅 pydantic、python-dotenv)

pip install pydantic python-dotenv
# 生产模式按需:pip install "psycopg[binary]" pgvector

2. 运行 Web 控制台(最直观)

python app.py
# 自动打开浏览器:http://localhost:8000

控制台包含:

  • 📚 知识库:粘贴文本入库 / 载入示例 FAQ / 选择文件夹或输入路径批量导入(自动建图)。

  • 🔍 检索 · 指标:测试查询、查看命中片段与相似度、查看 /api/metrics 运行指标。

  • 🕸 知识图谱:图谱统计、实体列表、点实体查多跳路径、用问题试关系侧召回、一键重建。

  • 🤝 Agent 协作:agent 团队清单、协作问答——答复附带每个 agent 的耗时/成败/摘要轨迹。

  • ⚙ 接口配置:在页面上填写外部 API 地址、鉴权、字段路径等,立即生效并持久化。

  • 💬 对话:多轮问答,自动召回知识 + 图谱路径 + 拉取实时数据(订单/库存)合成答复。

3. 运行 MCP Server

python -m kb_mcp_server.server
# 也可 mcp.run() 默认 stdio 传输,供 MCP 客户端连接

4. 演示 / 云端部署入口

python run_demo.py
# 强制离线默认 + 重新播种示例知识库 + mock 实时数据
# 端口优先读 $PORT,回退 $KB_WEB_PORT,再回退 8000

🧰 MCP 工具一览

工具

说明

ping

健康检查

ingest_document(doc_id, text, chunk_size, overlap)

把文档灌入知识库,返回切片数

search_knowledge(query, top_k, mode)

混合检索,返回带相似度分数的知识片段

ask_with_live_data(question, top_k, order_id, sku, history)

检索 + 调外部 API + 查图谱 + 合成结构化答复(含置信度/关系路径/轨迹),支持多轮 history

list_documents()

列出知识库所有文档(含片段数与预览)

delete_document(doc_id)

删除某篇文档及其全部片段,并清理它在图谱中抽出的关系

知识图谱工具(P6)

工具

说明

graph_expand(query, top_k, depth)

GraphRAG 关系侧召回:从问题抽实体 → 查图谱多跳邻居 → 返回可解释路径

graph_query(entity, relation, direction, depth)

查某实体的关联(1-4 跳),返回每个邻居的完整路径

graph_paths(src, dst, max_depth)

查两实体间的推理路径,例如「物流配送」到「全额退款」走了几跳

graph_entities(name, node_type, limit)

列出图谱实体(模糊匹配 / 按类型过滤),按关联度排序

graph_stats()

图谱概况:节点/边数量、类型分布、本体定义

graph_rebuild()

用向量库已有文档重新抽取建图(存量知识补建 / 换本体后重建)

多 agent 协作工具(P7)

工具

说明

multi_agent_ask(question, top_k, order_id, sku, history)

多 agent 协作问答:编排器调度 5 个职责 agent,答复附带每个 agent 的耗时/成败/摘要轨迹

agent_status()

agent 清单、当前编排模式、规划器与动作层概况

动作型工具(P2-9)—— 与检索型工具的本质区别是有副作用

工具

说明

list_actions()

列出可用动作(风险分级 / 是否需要确认 / 参数契约)+ 最近动作审计

run_action(action, params, confirmed, actor)

执行动作(建工单 / 改单 / 退款 / 查工单)。destructive 动作未 confirmed=true 一律不执行,只回 status=needs_confirmation

风险分级:read(只读,如查工单)/ write(改业务数据,如建工单、改单)/ destructive(不可逆或涉及资金,如退款)。 每次尝试(含被拒 / 待确认)都写动作审计(KB_ACTION_LOG,默认 kb_actions.jsonl)。


🕸 知识图谱(GraphRAG 关系层)

它解决什么问题

纯向量检索答不准关系型多跳问题——「这个客户的问题是不是和前几单同一根因」 「这条产品线适用哪条 SLA 例外条款」。这类问题的答案不在任何一段文本里,而在实体之间的关系里。 知识图谱就是把这层关系显式建出来,给向量检索补上「推理 + 可解释」。

本体(最小可行:8 类节点 / 7 类关系)

本体驱动的意义:抽取与查询都在这个边界内,不合规的三元组直接丢弃,图谱不会发散。

节点类型:

类型

含义

类型

含义

IssueCategory

问题类别

Product

产品

SLAClause

SLA 条款

RootCause

根因

Solution

解决方案

Ticket

工单

Document

文档(知识来源)

Customer

客户

关系(带主宾类型约束):

关系

含义

主语 → 宾语

GOVERNED_BY

适用条款

问题类别 → SLA 条款

SOLVED_BY

解决方案为

问题类别 → 解决方案

CAUSED_BY

根因为

问题类别/工单 → 根因

ABOUT_PRODUCT

涉及产品

问题类别/工单 → 产品

CATEGORY_OF

归类为

工单 → 问题类别

SUBMITTED_BY

由…提交

工单 → 客户

MENTIONS

提及

文档 → 各类实体

三元组怎么来的

  • 本地 LLM 抽取(KB_LLM_ENABLED=1):按本体约束输出 JSON 三元组,数据不出域。

  • 规则词典兜底(默认,离线零依赖):关键词/正则匹配问题类别、SLA 时限、解决方案、产品、根因。 例:「付款后 48 小时内发货」→ (物流配送) --适用条款--> (48小时内发货)。

  • 抽取挂在摄取管线上:每摄入一篇文档顺带把三元组写进图,不需要额外步骤。 图谱失败绝不阻断摄取——向量库写成功即算成功。

三种用法

  1. MCP 工具(给任意 agent):graph_expand / graph_query / graph_paths。

  2. Web 控制台:侧栏「🕸 知识图谱」——看统计、翻实体、点实体查多跳、用问题试关系侧召回。

  3. 代码:

    from kb_mcp_server.graph import get_graph_store, expand_facts
    
    g = get_graph_store()
    g.add_triples(triples)                       # 建图(本体校验内置)
    facts = expand_facts(g, "物流超时怎么赔偿")    # 关系侧召回,返回带路径的事实

自检

python demo_graph.py

跑完整链路:4 篇示例文档摄取建图 → 图谱统计 → 多跳查询 → 推理路径 → GraphRAG 融合答复。 实测输出(memory 后端、离线零依赖):

节点 34 / 边 34
按类型: Document 4, IssueCategory 6, SLAClause 12, Solution 6, Product 4, RootCause 2
─
物流配送 --适用条款--> 48小时内发货
退款退货 --解决方案为--> 全额退款
退款退货 --提及--> sla_policy ; sla_policy --提及--> 服务响应 ; 服务响应 --解决方案为--> 全额退款

最后一行正是图谱的价值所在:两段知识在向量空间里毫无相似之处,图谱却把它们串成了一条 跨文档、可解释的推理链——这就是「路径即证据」。


🤝 多 agent 协作(P7)

架构:按职责切分,共享同一张图

                Orchestrator(编排 · 路由)
                 │ 增量补图
                 ▼
                GraphBuilder ──写入──▶ 知识图谱
                 │
     ┌───────────┼───────────┬───────────┐  (无依赖 worker 并行执行)
     ▼           ▼           ▼           ▼
  Retriever  GraphReasoner  LiveData   ActionAgent  ← 各自只写黑板(AgentContext)里自己的字段
  语义召回     图谱多跳       订单/库存    动作执行(P2-9,默认关闭)
     └───────────┼───────────┴───────────┘
                 ▼
              Synthesizer ──▶ 结构化答复 + agent 轨迹 + 动作结果
  • 按职责切,不按知识域切(初期知识域太小,按域切会切出一堆空 agent)。

  • 协作靠共享黑板(AgentContext)传递中间结果,不引入消息总线——协议仍然是 MCP(决策 D6)。

  • 每个 agent 只写自己负责的字段;单个 agent 失败只降级、不阻断链路(沿用全项目的降级约定)。

两种编排模式(KB_AGENT_MODE)

模式

行为

适用

deterministic(默认)

固定流水线,零 LLM 依赖

离线演示 / 生产兜底

llm

本地 LLM 判断要不要查图 / 查实时数据,裁剪流水线

LLM 可用时降低延迟;失败自动回退 deterministic

动态协作(P1-4):按问题临时组队,而不是固定全跑

三个可选开关,默认全关时行为与旧版固定流水线完全一致:

配置

默认

作用

KB_AGENT_PLANNER

跟随 KB_AGENT_MODE(llm 模式 → llm)

llm 时由 LLM 把问题动态分解为子任务、从能力清单里选人;LLM 不可达 / 输出非法一律回退确定性规划

KB_AGENT_MAX_ROUNDS

1

>1 启用双向协商:worker 产出后由 EvidenceCritic 评估证据缺口,向缺失能力的 agent 发出请求;对方必须应答 provided / declined / unavailable——沉默不算数。只提「未执行过且未被拒」的能力,必然收敛

KB_AGENT_HITL

0

1 时护栏判定「需人工复核」的答复带 pending_human=true,供上层挂起等待人工放行

子任务依赖编排(P1-4 收尾):Subtask.depends_on 不再是预留字段,真的被消费—— 一轮内按依赖分层,同层并行、层间串行。三条边界写死在 _dep_layers:

  • 无依赖 → 恰好一层,且顺序 = 计划顺序。所以默认(确定性规划器不产出依赖)行为与旧版逐字等价。

  • 依赖指向本轮之外(已执行过 / 不在计划里)视为已满足,不等待、不报错。

  • 成环不重排、不卡死:降级成一层照跑,并在该轮记 dep_cycles——「顺序略有偏差」远好于「卡住」。

双向消息协商(P1-4 收尾):不再只有「评审点名」这一个方向。worker 也能在 on_run 里 主动委托(BaseAgent.request_help → 对方 reply_to),评审缺口则登记为同一本账上的请求。 账本 AgentContext.negotiation 是 append-only 的,四种结局都能被追问:

结局

含义

provided

被点名方跑完确实产出了该能力的新证据

declined

跑完了但没有证据——明确说「我也没有」,不是含糊放过

unavailable

共同体里没人具备该能力 / 本应服务者已执行过 → 当场关账

悬空请求

不允许存在:轮次结束仍无人应答的请求一律由编排器关账

被拒 / 无主的能力进「别再提」名单(declined),既不重复索要,也让「为什么没补轮」可解释。

协作过程落在返回的 collaboration 段(每轮分工 / 子任务理由 / 协商结论),例如:

规划器=llm  轮次=2  协商=True
  第1轮 [llm] Retriever          → 协商[需补轮] 缺: ['live']
      · Retriever(retrieval):纯知识问答,只需语义检索
  第2轮 [negotiation] LiveData

真发生协商时另带一段账本摘要(未协商则不出现,不给轨迹添噪音):

"negotiation": {
  "requests": 1, "replies": 1, "declined": [], "delegated": false,
  "ledger": [
    {"kind": "request", "id": "req-1", "by": "EvidenceCritic", "capability": "live", "ask": "..."},
    {"kind": "reply",   "id": "req-1", "by": "LiveData", "status": "provided", "note": "已产出该项证据"}
  ]
}

delegated=true 表示这轮补人是 worker 自己提的(而非评审),补轮来源相应记为 delegation。

设计取舍:LLM 只用在「任务分解」上(这一步真的需要语义理解), 「证据够不够」这类判定用确定性规则——可解释、零额外 token、离线可测。 两条降级路径都保证「开启协作不会比不开更脆弱」。 declined 名单同理是确定性规则:把「谁被问过、谁拒绝了」变成可解释的状态,而不是再问一次模型。

轨迹即可观测性

multi_agent_ask 的返回里带 agents.trace——每个 agent 的名称、耗时、成败、一句话摘要:

[OK] GraphBuilder    10ms  图谱已是最新(4 篇已入图)
[OK] Retriever        18ms  召回 1 个片段
[OK] GraphReasoner    18ms  命中 1 个实体,10 条关系事实
[OK] LiveData          1ms  取到 1 条实时数据
[OK] Synthesizer       1ms  合成完成(template,置信度高,5 条关系路径)

动作型工具(P2-9):从「只会答」到「能执行」

编排层默认只检索不动作。开 KB_AGENT_ACTIONS=1 后多一个 ActionAgent, 它识别「退款 / 改单 / 建工单」等动作意图并真正执行;有副作用,因此设三道闸:

闸门

机制

参与闸门

ActionAgent.gated_by = KB_AGENT_ACTIONS,默认关;extensions.agent_gate_open 保证未获准的 agent 连 LLM 动态组队的候选池都进不去——「谁有权动手」是治理问题,不交给模型决定

意图闸门

只认规则能明确识别的动作意图,识别不出就什么都不做(宁可不做,不可乱做:猜错一次就是一次错误的退款)

确认闸门

ActionAgent 的 confirmed 恒为 False。destructive 动作只会停在 needs_confirmation,是否放行永远由人工 / HITL 决定

与 P1-4 闭环:存在待确认动作时,若 KB_AGENT_HITL=1,答复一并标 pending_human=true (否则「agent 发起了退款」会静默通过,确认门形同虚设)。合成层新增【执行动作】段, 三种终态——已执行 / 待确认 / 被拒——在答复与前端里都能一眼区分。

三条安全底线收口在 ActionRunner(校验 → 确认门 → 执行 → 审计),新动作注册即自动获得:

$ python scripts/check_mcp.py     # 真实 stdio 子进程 + 官方 MCP 客户端
  [OK] 未确认的退款被拦在待确认(未执行)
  [OK] 待确认动作没有产生业务结果(确实没执行)
  [OK] 确认后执行成功:RF-6552F732
  [OK] list_tickets 能回读出前面建的工单(count=1)   ← 副作用真的落盘了

动作结果回写图谱(P2-9 收尾):把「执行」接回「再检索」

动作默认只落审计与答复,图谱里不留痕迹——于是「执行 → 关系 → 再检索」是断的: 客户接着问「刚给 A 客户建的工单涉及哪个产品」,图谱答不出来。

本体的 Ticket / Customer / Product 与 SUBMITTED_BY / ABOUT_PRODUCT 早已就绪, 但只有「LLM 从文档抽取」这一条写入路径。开 KB_ACTION_GRAPH=1 后补上第二条:

Ticket:TKT-XXXXXXXX   props: subject / priority / sla_response / status
  ├─ SUBMITTED_BY ──→ Customer:ACME        (customer 为空则整条跳过,不建空节点)
  └─ ABOUT_PRODUCT ─→ Product:LM3-ARM      (sku 来自提问上下文或入参)

三条底线,与前三条闸门同一性质(写死在代码里,不可配置):

底线

为什么

只有真执行成功才写

needs_confirmation(确认门拦住)与 rejected 一律不写——被拦下的动作在真实世界什么都没发生,图谱里也不该有它

写失败不改动作结论

图谱不可用 → 返回 graph.applied=false + reason,动作仍 ok=true。动作已经发生,记账失败若报成失败,会诱导调用方重试 = 第二次执行(写操作重复执行才是真事故)

映射必须过本体校验

三元组统一过 Triple.is_valid();写错的映射只会进 dropped 并被丢弃,不可能污染图谱(本体驱动 D3 的意义就是不让图谱被随手扩张)

映射是声明式的(action_graph.ACTION_EFFECTS,一个动作一条 Effect), 所以「新增动作要不要回写、写成什么形状」是一个可评审的条目,而不是散落的 if。

刻意不回写的动作(不是遗漏):update_order / request_refund —— 本体里没有 Order / Refund 节点,而它们的真相在实时后端(P0-2)不在知识图谱里; 把运行态数据塞进知识图会污染检索。list_tickets 是只读动作,不产生新事实。

方向语义(踩过):回写建立的是 Ticket → Customer 出边,所以「按客户查工单」是入边, 必须 direction=both / in;用默认的 out 会静默返回空列表—— 又一个「不报错、答案悄悄变错」的例子。scripts/check_age.py 阶段七把这条也钉成断言。

真跑在真实 Apache AGE 上验(CI age job,全部旁路对账,不信 store 的话): 关掉开关时动作照常成功且 AGE 里零写入 → 打开后 Ticket / SUBMITTED_BY / ABOUT_PRODUCT label 表真的多出行来 → 独立 Cypher 读回 props → 从客户反查得到该工单。

自检

python demo_agents.py                  # 固定流水线协作(只入向量库 → 自动补图 → 多路召回 → 合成)
python scripts/verify_collaboration.py # P1-4 动态协作:任务分解 / 依赖分层 / 双向协商 / 降级 / 人工介入(28 项断言)
python scripts/check_mcp.py            # P2-9 真实 MCP 协议端到端(握手 / 工具 / 动作确认门 / 审计 / 多 agent)
python scripts/check_load.py           # P2-10 补:真实 HTTP 并发压测(会临时起 app 子进程)
python scripts/check_age.py            # P2-10 补:真实 Apache AGE 图侧(含 P2-9 收尾的动作回写,需目标 PG 带 age 扩展)

check_load.py 的四个场景与实际断言:

[1] 只读并发        并发 8 发 160 次 /api/ask:~150 req/s,p50 29ms / p95 38–58ms / p99 ~515ms
[2] 混合读写        并发 40 写 + 80 读:写入成功 40,store 实际 +40(丢失 0)
[3] 限流门          限流 5/min:并发发 24 次 → 放行 5、限流 19;/healthz 不受限流误伤
[4] 指标自洽        /api/metrics requests_total 不少于本次发送数

压测脚本会启动真实 app 子进程并强制关闭浏览器自动打开(KB_NO_BROWSER=1): 浏览器会真的加载控制台页面、打出一串 API 请求,从而吃掉限流配额并污染指标计数—— 这是实测踩到的坑(限流场景「应放行 5」被观测成「放行 1」),也是 KB_NO_BROWSER 存在的理由。

完整演示「知识只入向量库 → GraphBuilder 自动补图 → 多路并行召回 → 合成 → 第二轮秒回」。 LLM 不可达时自动熔断(60s 内不再重试)并回退规则/模板,链路照常跑通。


⚙️ 配置

复制 .env.example 为 .env 后按需修改,关键项:

分组

变量

说明

存储

KB_STORAGE_BACKEND

memory(离线) / pgvector(生产)

图谱

KB_GRAPH_BACKEND

memory(离线纯 Python 图) / age(Postgres + Apache AGE,与 pgvector 同库)

图谱

KB_GRAPH_ENABLED

总开关,摄取时是否自动抽取三元组入图(默认 1)

图谱

KB_GRAPH_NAME

AGE 模式下的图名(默认 kb_graph)

嵌入

KB_EMBEDDING_BACKEND

dev(零依赖) / bge(本地模型)

合成

KB_LLM_ENABLED / KB_LLM_BASE_URL / KB_LLM_MODEL

本地 LLM(OpenAI 兼容,如 Ollama),失败自动回退模板

实时 API

KB_API_MOCK / KB_ORDER_API_URL / KB_INVENTORY_API_URL / 字段路径

KB_API_MOCK=1 走样例;配 URL 且 =0 走真实 HTTP

加固

KB_API_TOKEN / KB_RATE_LIMIT / KB_LOG_FILE

Bearer 鉴权 / 每 IP 限流 / 结构化日志

协作

KB_AGENT_MODE / KB_AGENT_PLANNER / KB_AGENT_MAX_ROUNDS / KB_AGENT_HITL

编排模式 / 动态任务分解 / 证据协商轮次 / 人工介入

动作

KB_AGENT_ACTIONS / KB_ACTION_LOG

动作执行者是否参与编排(默认 0) / 动作审计日志路径(off=关闭)

动作

KB_ACTION_GRAPH

执行成功的动作是否把结果回写知识图谱(默认 0;写失败只降级,不改动作结论)

页面「接口配置」提交的配置会持久化到 runtime_config.json,优先级高于 .env。


🏭 生产部署

方式一 · 容器(推荐)

docker compose up -d          # 起 web 服务,数据落 named volume
# 打开 http://localhost:8000

镜像默认只装离线档依赖(memory 存储 + dev 嵌入),约 150MB;需要更强能力用 build arg 分档:

docker build --build-arg ENABLE_BGE=true      -t kb-mcp-server:bge .   # 本地 bge 语义嵌入(拉 torch,镜像 2GB+)
docker build --build-arg ENABLE_PGVECTOR=true -t kb-mcp-server:pg  .   # pgvector 客户端

环境变量

默认

说明

KB_DATA_DIR

/app/data

数据目录(compose 已挂 named volume 持久化)

PORT

8000

监听端口

KB_STORAGE_BACKEND

memory

memory ⇄ pgvector

KB_EMBEDDING_BACKEND

dev

dev ⇄ bge

KB_API_MOCK

1

实时数据走 mock

KB_API_MAX_RETRIES

2

实时后端重试次数(总尝试 = 该值 + 1)

KB_API_RETRY_BACKOFF

0.5

指数退避基数(秒),单次退避上限 8s

KB_API_CIRCUIT_BREAKER

1

熔断开关;开路期不再发起调用

KB_API_CIRCUIT_THRESHOLD

5

连续失败多少次后开路

KB_API_CIRCUIT_COOLDOWN

30

开路冷却秒数,之后放半开探测

KB_AGENT_MAX_ROUNDS

1

>1 启用双向协商补轮(Subtask.depends_on 依赖分层无需开关,默认行为与旧版等价)

KB_AGENT_ACTIONS

0

1 时 ActionAgent 参与编排(不可逆动作仍只停在待确认)

KB_ACTION_GRAPH

0

1 时执行成功的动作把结果回写知识图谱(默认等价旧行为:不开则零写入)

KB_ACTION_LOG

kb_actions.jsonl

动作审计日志路径(off=不落盘)

镜像内置 HEALTHCHECK 探 /healthz(docker compose ps 可直接看健康状态)。 需要 pgvector 时,docker-compose.yml 里已备好注释掉的 pgvector/pgvector:pg16 服务,取消注释即可。

这一步已被 CI 持续验证:每次提交都会构建镜像、启动容器、轮询健康检查, 并在容器内真实调用 /api/ask 做端到端冒烟(.github/workflows/ci.yml 的 docker job)。 因此「镜像能构建、容器能跑」不是一次性检查,而是提交门禁的一部分。

pgvector 生产存储同样被 CI 真跑:pg job 用 pgvector/pgvector:pg16 service container 起真实 Postgres → python setup_db.py --graph 建表 → 完整回归评测读写全部落在 PG → scripts/check_pg.py 校验「vector 扩展 / kb_chunks 表 / HNSW 索引 / vector(1024) 维度」与 「表内非空」。最后两项专门堵住「存储连不上 → 悄悄退回 memory → 评测照样过」的静默降级。 注:该镜像不含 Apache AGE,--graph 的建图部分会按设计降级为 memory 图(不阻断)。

方式二 · 直接跑(无容器)

  1. 存储切 pgvector

    pip install "psycopg[binary]" pgvector
    # 目标 PG 安装 vector 扩展后:
    KB_STORAGE_BACKEND=pgvector KB_DATABASE_URL=postgresql://user:pass@host:5432/kb python setup_db.py
  2. 图谱切 AGE(可选,与 pgvector 同库,不额外引入数据库)

    # 前提:目标 PG 已安装 age 扩展(CREATE EXTENSION age 可用)
    KB_STORAGE_BACKEND=pgvector KB_GRAPH_BACKEND=age \
      KB_DATABASE_URL=postgresql://user:pass@host:5432/kb python setup_db.py --graph

    AGE 不可用时会自动回退 memory 图并在日志里说明,主链路不受影响。

  3. 嵌入切 bge(数据不出域)

    pip install sentence-transformers
    KB_EMBEDDING_BACKEND=bge   # 默认 BAAI/bge-large-zh-v1.5
  4. 启用本地 LLM 合成:KB_LLM_ENABLED=1 + 可达的 KB_LLM_BASE_URL(如 Ollama http://localhost:11434/v1)。

  5. 接真实外部 API:在 .env 配 KB_ORDER_API_URL / KB_INVENTORY_API_URL + 字段路径,并设 KB_API_MOCK=0。

  6. 加固:设置 KB_API_TOKEN 启用 Bearer 鉴权,KB_RATE_LIMIT 限流,KB_LOG_FILE 落结构化日志。

演示部署 · GitHub Pages(仅前端,零成本)

适合作品集/汇报/界面预览。零成本、零后端,但功能受限——页面上按钮可点,但 摄取/检索/对话等 API 调用会返回 404(页面顶部会出现"📦 静态演示模式"蓝色横幅提示)。

Step 1 · 启用 GitHub Pages

仓库 → Settings → Pages:

  • Source: Deploy from a branch

  • Branch: main · Folder: /docs

  • Save

几分钟后站点出现在 https://<user>.github.io/kb-mcp-server/(默认 Doy369 用户)。

无需修改任何代码——docs/index.html 里 window.KB_API_BASE 保持空字符串即可。


🧪 质量保障(P2-10)

自动化测试

pip install pytest
python -m pytest tests/ -v      # 358 例,离线约 12s

覆盖范围(全部零外部依赖,dev 嵌入 + memory 后端):

文件

例数

覆盖重点

test_ingestion_storage.py

43

分块三要素(标题前缀 / 一行多档拆条 / Q-A 成对)、嵌入单例、存储持久化与容错、图谱本体约束、PG 懒连接 / 注册时序 / 写库参数适配

test_retrieval.py

27

BM25 分数越界回归护栏、RRF 融合、MMR 去重、硬阈值、分词、余弦边界

test_eval_guardrail.py

36

数字边界断言(防假通过)、证据段剔除、禁止词反向断言、护栏分级、审计容错

test_agents_mcp.py

24

Agent 异常兜底、黑板隔离、路由裁剪、降级链路、合成契约、16 工具注册完整性

test_adapters_resilience.py

51

重试次数语义与指数退避封顶、熔断状态机、降级不抛异常、失败不写缓存、可重试性分级(4xx 不重试 / 不计熔断)、not_found 终态与 HTTPStatusError 转换

test_planner_collaboration.py

72

动态任务分解的四条降级回退、协商只提未执行能力、多轮补轮不重复执行、HITL 开关;P1-4 收尾:依赖分层(无依赖单层等价旧行为 / 跨层串行 / 轮外依赖视为满足 / 自依赖忽略 / 成环降级不卡死)、depends_on 解析(悬空 / 自依赖 / 能力别名都要安全丢弃)、协商账本(唯一 id / 空账本不出现)、双向协商(worker 委托 / 沉默不算数 / 无主请求关账 / 已执行不重跑)

test_actions.py

53

动作参数契约、确认门、审计三终态、意图识别与参数抽取、参与闸门、HITL 闭环

test_action_graph.py

26

动作结果回写图谱(P2-9 收尾):默认关时零写入且返回/审计里连 graph 字段都不出现、开时节点与关系真落图(含 props 与双向可达)、确认门拦住与 rejected 不写、只读/无映射动作不写、本体校验丢弃非法映射、图谱报错时动作仍成功且审计留降级原因、重复回写幂等

test_baseline_gate.py

11

基线校验脚本本身:阈值覆盖、报告路径三来源、恰好等于阈值放行、低于必红、缺失必失败

test_graph_age_cypher.py

15

AGE cypher() 两条硬约束:① 列定义列表列数必须等于 RETURN 表达式数;② 图名与 Cypher 原文必须是常量字面量(内联 + 美元引用,不得出现 $1/$2)。假连接抓 SQL 断言,不需要真实 PG

test_baseline_gate.py 的理由:该脚本同时把守 test 与 bge 两个 job。 参数解析写错时表现不是报错,而是静默失效——CI 照样绿,只是不再拦任何东西。

test_graph_age_cypher.py 锁的是两条只在真机上才暴露的 AGE 契约:cypher() 会被 post_parse_analyze_hook 拦截做语法改写,因此 ① 列定义列表列数必须等于 RETURN 表达式数(否则 return row and column definition list do not match); ② 图名与 Cypher 原文必须是常量字面量(否则 AGE 读不到原文,报 a name constant is expected)。_cypher() 曾同时违反这两条,而本地单测(memory 后端) 根本不生成 SQL——「一直没错」;直到 age job 上真机才被一一打挂。把契约钉进单测后, 任何新接口忘了传 nout、或有人改回绑定参数,都会在本地就红。

其中多条用例直接锁住 ROADMAP 记录过的真 bug——不报错、只是答案悄悄变差的那类问题, 没有测试就只能靠肉眼发现。

CI 流水线

.github/workflows/ci.yml 九个 job:

job

触发

内容

验证什么

test

推送 / PR

单测 → 回归评测 → 基线校验

逻辑正确性;通过率低于基线(72%)则阻断合并

docker

推送 / PR

构建镜像 → 启动 → 健康检查 → 容器内端到端冒烟

「镜像能构建」且「容器能跑」(build 成功 ≠ 能跑)

pg

推送 / PR

起真实 PG + pgvector → 建表 → 回归跑在 PG → 校验 schema 与落库数据

生产存储路径真通,且没有静默退回 memory

mcp

推送 / PR

stdio 子进程 + 官方客户端走完整 JSON-RPC

MCP 是协议实质而非标题;动作确认门与审计真落盘

load

推送 / PR

真实 HTTP 并发压测(只读 / 混合读写 / 限流门 / 指标自洽)

并发下不丢数据、限流精确、零错误

adapters

推送 / PR

起真实 socket + 真实 HTTP 协议的本地替身后端,让真实 urllib 适配器打过去(26 项断言)+ 真实 app 子进程端到端

出站适配层真能完成一次 HTTP 往返:重试计数 / 超时受约束 / 熔断后请求数冻结 / 404 与 401 不重试 / 429 重试

collab

推送 / PR

scripts/verify_collaboration.py:真实 worker 链路(只把 LLM 换替身)走 7 个场景、28 项断言

协作轨迹结构真对:依赖真分层 / 补轮真来源 / 账本真关账 / 沉默真不算数(不是「脚本跑完没报错」)

age

推送 / PR

官方 apache/age 容器 → 扩展 → 建图 → AGE 语法能力探针(含多列 RETURN 列数契约)→ 真实摄取写入 → 原始表对账 → 动作回写(执行 → 关系 → 再检索)

图侧真跑,且 get_graph_store() 没有静默降级成 memory 图;动作结果真的落进 AGE 的 label 表

bge

夜间 03:00 + 手动

真实 bge 嵌入回归(阈值 0.95)

质量上限;缓存 1.3GB 模型,不拖慢每次推送

评测报告作为 artifact 归档 30 天;load / adapters / collab / age 的报告同样归档。

七 job 基线实测:CI #19 / run 36663136008,全绿(bge 按 schedule 跳过属正常); 后新增 adapters job(真实 HTTP 往返)成八 job,CI #21 / run 36673194795 八 job 全绿; 再加 collab job(P1-4 收尾)成九 job,CI #23 / run 36677436460 九 job 全绿—— collab job 的 ::notice:: 注解凭据:共 28 项,失败 0 项;语料 63 片段;多轮场景轮次 2。 其中 age job 的真跑凭据由 ::notice:: 注解直接可见:age=1.6.0, 顶点 Document 1 / IssueCategory 3 / SLAClause 2 / Product 1、 边 MENTIONS 3 / GOVERNED_BY 2 / ABOUT_PRODUCT 1(= 7 顶点 / 6 边,与 stats() 口径一致)。

为何 bge 不跟每次推送:dev 嵌入下通过率 91%(语义级断言前 82%;唯一 FAIL 是 P2 哨兵用例, 受限于 dev 对 P0/P1/P2 字面相似片段的区分能力),bge 下 100%。 装 torch + 下 1.3GB 模型会让单次 CI 从 ~40s 涨到数分钟——高频 job 一旦变慢就会被习惯性忽略。 所以职责分离:推送跑快口径(保「不许变差」),夜间跑重口径(保「质量上限」), 两者共用同一份 check_baseline.py,只是阈值与报告路径不同。

本地复现 CI 的基线校验

python eval_run.py                 # 产出 eval_report.json(dev 口径)
python scripts/check_baseline.py   # 与 CI 同一份逻辑,通过则退出码 0

# bge 口径(报告写独立文件,别覆盖 dev 基线;阈值单独指定)
python eval_run.py --embedding bge --out eval_report_bge.json
KB_BASELINE_MIN=0.95 python scripts/check_baseline.py eval_report_bge.json

📌 备注

  • runtime_config.json、kb_store.json、.env 均已 gitignore,不进仓库(含本地私有数据)。

  • 离线 dev 嵌入为条目哈希实现,语义召回能力有限,仅用于跑通管线与演示;生产请用 bge。

  • 外部 API 适配器为可扩展框架:继承 APIAdapter 实现 call() + _mock(),在 build_registry 注册即可新增后端(CRM / 工单系统等)。


📄 License

内部 B2B 项目,使用请遵循团队内部约定。

Related MCP Connectors

Related MCP Servers