本页记录 2026-07-09 到 07-10 两天的完整实测过程,分三个阶段:先在 235 服务器全新部署基础设施(踩了 7 个坑) · 再把 Mock 模型切换到 223 内网真实三模型 + 用 NMPA 官方说明书替换手写 Demo 语料(接入过程又踩 2 个坑) · 最后跑完整端到端自测(6 环节全通,新发现 2 个 P2/P3 bug)。全部 9 个 bug 都有 root cause + 证据链 + 修复建议。
起点——把 LLM/Embedding/Reranker 三件套从 Mock 切换到 223 内网真实模型 · 顺便把 3 份手写 Demo 语料换成 NMPA 官方说明书。
为了让端到端演示真正代表 MVR 场景 · 把最早为跑通链路手写的 3 段医学常识(每段 ~600 字节)全部替换为 中国 NMPA 注册的原研/仿制说明书全文 · 每份保留 6 个规范字段(适应症/用法用量/不良反应/禁忌/注意事项/药物相互作用)· 来源可追溯:
| 文档 | 主题 | 大小 | Chunks | 数据来源 |
|---|---|---|---|---|
aspirin-real.md |
阿司匹林肠溶片 | 5.0 KB | 12 | 国药准字 H11021614(北京曙光)+ 拜耳原研(丁香园) |
metformin-real.md |
盐酸二甲双胍缓释片 | 6.3 KB | 13 | 恒瑞制药官网 + 国家医保局申报材料 + 信谊药厂 |
atorvastatin-real.md |
阿托伐他汀钙片(立普妥) | 6.8 KB | 15 | 晖致 Viatris 原研 v20260119 + NMPA 药物警戒 |
规模变化:1.8 KB / 12 chunks(手写)→ 18.1 KB / 40 chunks(NMPA 官方) · 3.3× 内容密度 · 让 dense embedding + BM25 + rerank 三通道真实分工。
Agent 端问「阿托伐他汀严重副作用 + 药物相互作用」· 答案完整命中说明书原文:横纹肌溶解 · CYP3A4 抑制剂 · 达芦那韦 AUC 3.45 倍 · 引用 atorvastatin-real.md seg:1/seg:2 可追溯。
本次演示把系统架构里所有 AI 依赖都从 mock 切换到真实内网服务,验证整条 RAG 链路(Ingestion → Search → Rerank → Generate)
在生产等价配置下完整跑通。所有模型部署在同一台 Mac Studio(192.168.1.223),通过 AI Gateway 统一路由。
ornith-1.0-35b-mtplx (LMStudio · reasoning model)http://192.168.1.223:1234/v1text-embedding-nomic-embed-text-v1.5 (768 维)http://192.168.1.223:1234/v1BAAI/bge-reranker-v2-m3 (Cross-encoder)http://192.168.1.223:7997/v1/rerank用户在 Playground 输入「阿托伐他汀有什么严重副作用」,系统内部经历以下 7 步, 全程 20744 tokens in / 1044 tokens out(reasoning model 特性 · 大部分是思考过程), rerank 触发 2 次(冷 547ms · 热 139ms)。
mvr-knowledge-qa skill。
Skill 内部产出 2 个检索 query:阿托伐他汀(短词 · 高召回)Atorvastatin serious adverse effects side effects(英文长句 · 高精确)lmstudio-embedding pool 对 query 生成 768 维向量,
走 AI Gateway 路由:POST /api/v3/gateway/openai/deployments/lmstudio-embedding/embeddings,
Gateway 用 pk-key 鉴权后反代到 http://192.168.1.223:1234/v1/embeddings。
Milvus 一次返回 initial_recall=30 条候选(通过 KbSearchTool 的 top_k=8 + initial_recall=30 配置)。
kb_kb_707811d90aa3426191c95_sections 里的 dense_vector 也是同一模型出的,
所以 query 向量和 doc 向量在同一语义空间。
0.72 vs 0.69 vs 0.67)。
KbSearchTool 检测到 hits.Count(30) > topK(8) 且 rerank_enabled=true · 触发 Reranker:
GatewayRerankService.RerankAsync(query, 30 docs, top_n=8) →
打 POST http://192.168.1.223:7997/v1/rerank ·
bge-reranker 用 Cross-encoder 对 (query, doc) pair 逐对打分,
比 bi-encoder 更准 5-15% NDCG · 但成本更高(O(N)每 doc 一次前向)。
rerank_threshold=0.3 过滤低分噪音 ·
然后进入下一步答案生成。这一步直接决定了引用质量 ——
没有 rerank 时经常 top-1 是"阿司匹林"(无关但语义近);有 rerank 后 top-1 精确锁定"阿托伐他汀 > 不良反应"。
mvr-grounded-answer skill 接收 8 个精排 chunks ·
先做 coverage 评估("现有证据是否充分回答用户问题") ·
然后生成答案 · 每句话必须能追溯到某个 chunk ID([1] doc3.md seg:1)。
postProcessors: [citation]。答案生成后 ·
Citation Processor 扫描文本里所有 [N] 引用标记,
匹配到对应 chunk 的 documentId + chunkId + excerpt ·
以结构化 JSON 附加在回复末尾,前端渲染成"REFERENCES 1" 卡片,支持点击展开。
{"id":"[1]", "documentId":"8b72fa57...", "chunkId":"8b72fa57..._section_3", "filename":"doc3.md", "excerpt":"罕见但严重:横纹肌溶解..."}
这就是 MVR 场景对合规性的关键要求 —— 任何答案都必须可追溯到原始文档。
为什么这次接入很关键:之前的所有测试都用 mock(词袋 hash embedding · fake LLM), 虽然验证了系统接口能通,但没验证语义质量。 本次用真实的 nomic embedding · bge-reranker · ornith 35B 三件套,证明了架构本身能承载生产级模型 —— 换个模型只需改 Gateway pool 配置 · 不需要动业务代码。 这个"模型可插拔"的抽象是 PIKE-RAG 相比一体化 RAG 产品(如 Dify · RAGFlow)的核心差异化优势。
接入完 Real 三件套之后——一次完整的从 KB 关联 → 用户提问 → 检索 → rerank → LLM 生成 → 引用可追溯的端到端跑通,7/7 环节通过 · 64 次真调用统计 · 新发现 2 个 bug。
模拟一个新用户从「打开浏览器」到「拿到带引用的医学回答」的全流程,共 6 大关键环节 + 后端日志验证。测试时间 2026-07-10 上午,登录账号 admin/admin,浏览器 Chromium via Browserbase。
identity_id=1 · 跳转 /knowledge/knowledge-base192.168.1.223Failed to flush token usage buckets, re-queuing 3 entries,PostgreSQL 报唯一约束冲突。fail: Pike.AiGateway.Infrastructure.Services.TokenUsageCollector[0] Failed to flush token usage buckets, re-queuing 3 entries Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred while saving... ---> Npgsql.PostgresException (0x80004005): 23505: duplicate key value violates unique constraint "uq_token_bucket"
TokenUsageCollector.FlushAsync() (line 116) 对唯一键 uq_token_bucket(time_bucket, pool_id, backend_id?) 用 INSERT,并发多请求命中同一时间桶时冲突。ON CONFLICT (time_bucket, pool_id, backend_id) DO UPDATE SET prompt_tokens = uq_token_bucket.prompt_tokens + EXCLUDED.prompt_tokens, completion_tokens = ...,或在 EF Core 用 ExecuteUpdate 手写 UPSERT。/knowledge/vector-search URL → 页面完全空白,SPA lazy chunk 没触发 → 应加 404 fallback 或重定向到 /knowledge/retrieval-validation。clear 按钮点击后不清空对话 → 只能通过导航离开-回来才能重置会话。┌──────────────────┬───────┬─────────────────────────────────────────┐ │ 服务 │ 次数 │ 端点 │ ├──────────────────┼───────┼─────────────────────────────────────────┤ │ nomic-embed │ 44 │ http://192.168.1.223:1234/v1/embeddings │ │ bge-reranker-v2 │ 20 │ http://192.168.1.223:7997/v1/rerank │ │ ornith-1.0-35b │ ~15 │ (走 Gateway · 未直接暴露 URL 到日志) │ │ Milvus │ ~15 │ http://milvus:19530 (hybrid_search) │ └──────────────────┴───────┴─────────────────────────────────────────┘ Rerank 延迟(本次 3 次测量): 第 1 次: 707 ms (冷启动) 第 2 次: 624 ms 第 3 次: 766 ms 历史平均(热): 150-200 ms
核心链路完全可用 —— 用户从「打开浏览器」到「拿到带引用的医学回答」全流程走通。
可以给同事演示的两个入口:
https://pike-rag.nexora.restry.cn (admin/admin) → 智能体 → mvr-pharma → play-circlehttps://www.nexora.restry.cn/static/other/pike-rag-report.html 及本页所在的子报告演示时建议告知的 Caveat:
7 月 9 号在 235 服务器全新部署时踩到的 7 个坑——Mock 时期基础设施 5 坑 + 真模型接入 2 坑。Part II 的端到端测试中又新发现的 P2/P3 各 1 个已在上一章的「测试中新发现的 Bug」处列出,不在此重复。
为了让"召回验证"页面能真跑通,把全套 Docker Compose 环境重新部署到一台新服务器(Ubuntu / Docker 29.3.1 / 30G RAM), 并用 Mock LLM/Embedding + Milvus standalone 全新起。整个过程从零到能在前端做三通道对比检索,共花约 4 小时。 期间踩到 5 个非文档提及的问题,全部有明确 root cause 和证据链,值得进入债务清单。
这些问题在生产环境(既有配置 + grpc 客户端 + 已初始化 collection)里都被"运气好"绕过了, 但只要一次全新部署 / 一次换机 / 一次新 KB 创建,就会 100% 复现。 客户内部 IT 团队做 DR(灾备)演练时必然踩到。
MilvusService.EnsureCollectionAsync 用 SchemaBuilder 声明了
AddBm25Function("bm25_fn", "content", "sparse_vector"),
期望 Milvus 从 content 字段自动生成 sparse 向量。
但 Pike.Milvus.MilvusClient 用的是 HTTP REST v2 API,
Milvus 2.4/2.5 的 REST insert 路径 不触发 BM25 function auto-fill,
而 grpc 路径会。生产环境 Python 侧用 pymilvus (grpc) 所以从未暴露,
但 .NET V3 路径首次 upsert 直接抛
Milvus error (code=1804): missing vector field: sparse_vector。
collection describe 结果里 functions: [],
说明 SchemaBuilder 的 function 声明连注册都没成功,不只是 insert 问题。
lake_to_vector_main → upsert_vector_store activity)。
影响:任何走 .NET V3 完整 Ingestion 流程的场景全部不可用。
目前生产为何"没事":Python V1 workflow 用 pymilvus grpc(同一 collection 也能正常写入)。
BackendKeyEncryptor 用 AES-GCM 加密 backend API key(new AesGcm(_masterKey, 16)),
AES 要求 key 长度必须严格是 16 / 24 / 32 字节。.env.example 提示 "generate with openssl rand -base64 32",
但没做校验——用户如果给了 38 字节或其他长度的 base64,
不在启动时报错,而是在第一次调 AI Gateway 添加 backend 时抛
CryptographicException: Specified key is not a valid size for this algorithm,
返回 500,前端无提示,日志埋在 stack trace 里。
BackendKeyEncryptor 构造函数里加一行
if (_masterKey.Length is not (16 or 24 or 32)) throw new InvalidOperationException("..."),
让 API 启动就 fail-fast,配上清晰错误提示。
MilvusService.EnsureCollectionAsync 只调 CreateCollectionAsync,
没有接着调 LoadCollectionAsync。
Milvus 2.4+ 里 collection 默认 unloaded 状态时 delete/query/search 都会失败。
第一次 upsert 前手动 curl /v2/vectordb/collections/load 才能救。
.AddIndex("sparse_vector", "BM25") 生成的 metric type 参数在 Milvus 2.4 里被拒(
报 "only IP is the supported metric type for sparse index"),
需要显式指定 metricType=IP, index_type=SPARSE_INVERTED_INDEX。
Pike.Milvus 库需要针对 Milvus 2.5 做一轮完整回归测试。
code=65535Section__Key)作为环境变量名,
但 Pike.Knowledge.Infrastructure.DependencyInjection 里读的是
builder.Configuration["MILVUS_HOST"](全大写下划线)——
这是从 Python 那边直接照搬过来的命名,跟 .NET 生态其他地方不一致。
部署时如果按 .NET 惯例传 Milvus__Host,代码读不到,
会 fallback 到默认 localhost,容器网络下直接连不上,报的还是模糊的
TaskCanceledException: The operation didn't complete within the allowed timeout of '00:00:30'。
MilvusClientOptions)+ services.Configure<MilvusClientOptions>(config.GetSection("Milvus")),
环境变量走标准 Milvus__Host / Milvus__Port,
同时保留旧变量作向后兼容一段时间。
MILVUS_HOST)· 2026-07-09 首次部署 30s 超时故障Warning: [antd: Modal] `destroyOnClose` is deprecated. Please use `destroyOnHidden` instead.
和 Warning: [antd: message] Static function can not consume context like dynamic theme. Please use 'App' component instead.。
实测【AI Gateway → Pool 详情 → 添加后端】按钮点击后,弹窗DOM 生成但被 z-index 覆盖不可见,
用户以为按钮没响应(必须绕道直接调 REST API 才建成 backend)。
{ App } component 包住整个应用来接管 message/notification 静态调用。
destroyOnClose + message.xxx() 静态调用shared/milvus/client_factory.py:35 直接写死
DENSE_DIM = 1024 # BGE-M3 output dimension,
所有 KB 的 collection schema 都用这个常量建
FieldSchema(name=DENSE_FIELD, dtype=FLOAT_VECTOR, dim=DENSE_DIM)。
意味着**换任何 embedding 模型**(nomic 768 · text-embedding-3-small 1536 · Cohere 4096 等)
都必须**改代码 + 重建全部 KB**。
Milvus: dimension mismatch, expected 1024 got 768。
int(os.environ.get("MILVUS_DENSE_DIM","1024"))作为过渡;
(2) 中期让每个 KB 存自己的 vector_dim(根据它绑定的 embedding pool 探测一次得出);
(3) 长期 collection schema 里带 embedding_model 字段,系统自动匹配。
这个 bug 在生产环境"从未暴露"是因为一直只用 bge-m3 一个模型;
CKB / 客户自选 embedding / GraphRAG 用不同 dim 模型时必踩。
PUT /api/v3/agentic/agents/{name}/knowledge-bases 的 body 结构在 OpenAPI 里没标清 ——
尝试传 {"knowledgeBaseIds":["kb_xxx"]} 会返回 500,
日志抛 System.NullReferenceException at SetAgentKnowledgeBasesByNameCommandHandler.Handle line 303,
前端只显示 "An unexpected error occurred",不告知字段名错了。
实际正确结构:{"knowledgeBases":[{"knowledgeBaseId":"kb_xxx","isDefault":true}]}。
NotNull 校验字段,让 400 而不是 500;
(2) 补 OpenAPI schema 里 AgentKnowledgeBaseDto 的 required 标注;
(3) Handler 加空判断兜底,返回 "Missing 'knowledgeBases' field. Expected structure: [{knowledgeBaseId, isDefault}]"。
方法论说明:这 7 项是通过 真实全栈冷启动实测 + 真实模型端到端接入触发的,非静态代码审查得出。 测试环境:Ubuntu 24 · Docker 29.3.1 · Milvus 2.5.14 · .NET 10 SDK · 初期用 Mock AI Server(词袋 hash embedding); 后期接入内网 LMStudio(text-embedding-nomic-embed-text-v1.5 · ornith-1.0-35b-mtplx · BGE-reranker-v2-m3) 跑真实推理。最终成功状态: Agent 端到端问答通 —— MVR 医药智能助手基于 KB 检索 + Reranker 精排 + ornith 35B 推理, 回答"阿托伐他汀严重副作用"精准命中横纹肌溶解 + CYP3A4 药物相互作用,附引用来源。