用RAG打通测试文档与结果查询
作者将维基、测试管理系统和测试结果数据库接入 PostgreSQL 与 pgvector,让大模型检索资料后回答 QA 问题。方案列出了 Docker、Python 依赖和只读凭据配置,作者称动手搭建约需90分钟,数据导入耗时另计。
AI 深度解读
QA 问「上个迭代为什么失败」「接口有没有限流」「支付重试有哪些用例覆盖」,答案往往散落在 wiki、测试管理系统和结果数据库里。作者做了一个连接器,把三处资料汇入 PostgreSQL,再让 LLM 根据检索结果回答,自己动手搭建约花了 90 分钟。
它值得读的地方是把一条 RAG 链路拆得足够具体:权限怎么分、数据怎么切、重复导入怎么办、上线前检查什么。不过,90 分钟是作者的搭建经历,导入耗时另算,也不能当作一般团队的交付承诺。
先把数据接对,模型才有东西可答
RAG,即检索增强生成,在这里就是先找资料,再让模型依据资料作答。模型不会自动理解三个业务系统,连接器要先通过 API 或 SQL 取出内容,切成片段,用 embedding 模型转成向量,再统一存储。
作者认为,最费力的环节是确认三个数据源的端点和权限。这一点很实际:wiki 的基础路径漏掉 /wiki,后面检索设计得再好也没有用。
他的环境是 Linux、macOS 或 Windows with WSL2,命令运行在 bash 中;Docker + Docker Compose 用于运行带 pgvector 的 PostgreSQL,脚本使用 Python 3,安装 requests、psycopg2-binary、fastapi 和 uvicorn[standard]。通过 python3 -m venv .venv 创建环境后,pip show fastapi 的 Location 应落在 .venv 内。
权限要按来源拆开:wiki 用只读 API token,测试管理系统用独立用户和 API token,业务数据库使用只能 SELECT 的角色。.env 保存连接参数且不提交仓库;LLM_BASE_URL、LLM_API_KEY、LLM_MODEL 和 EMBEDDING_MODEL 分别指定服务地址、密钥、聊天模型和向量模型。
原文刻意没填模型名和维度,需要从供应商文档确认。embedding 维度必须与数据库的 vector(<dim>) 一致。 作者把维度填错列为首次插入失败最常见的原因,但没有提供统计数据。
接入后先验通路:查询 pg_extension 应看到 plpgsql 和 vector;wiki 请求 /rest/api/content?type=page&limit=1 应返回 200。作者将 401、404 分别作为排查 token 和基础路径的线索,他自己遇到的是漏写 /wiki。
一张表能统一检索,但不等于同步已经完整
三个来源共用 documents 表,保留来源、原始 ID、标题、URL、片段序号、正文和向量。下面这段建表语句同时说明了存储结构和重复导入的处理基础:
CREATE TABLE documents (
id bigserial PRIMARY KEY,
source text NOT NULL CHECK (source IN ('wiki','testmgmt','db')),
external_id text NOT NULL,
title text NOT NULL,
url text,
chunk_no int NOT NULL DEFAULT 0,
body text NOT NULL,
updated_at timestamptz NOT NULL DEFAULT now(),
embedding vector(<dim>), -- <dim> 从 embedding 模型文档获取
UNIQUE (source, external_id, chunk_no)
);
CREATE INDEX documents_embedding_idx ON documents USING hnsw (embedding vector_cosine_ops);
重点看 UNIQUE (source, external_id, chunk_no):它为每个来源中的每个片段建立唯一身份,配合 ON CONFLICT,重跑时可以更新已有记录。hnsw 索引使用 vector_cosine_ops;原文提醒,较旧的 pgvector 构建可能不支持 hnsw,但没有指定最低版本。
wiki 示例访问 /rest/api/content,限定 spaceKey,以 limit=50 分页,并用 expand=body.storage,version 获取正文等信息,再沿 _links.next 翻页。
正文切分采用最直接的字符窗口:
def chunk_text(text, size=1200, overlap=200):
for i in range(0, len(text), size - overlap):
yield text[i:i + size]
要看的是 size - overlap:每次前进 1000 个字符,相邻片段重叠 200 个字符,降低上下文刚好被边界切断的概率。这里按 Python 字符串长度切分,不是按 token,也没有理解标题、段落或代码块。
随后脚本每批取 50 个片段,向 LLM_BASE_URL 下的 /embeddings 发送 model 和 input,请求超时为 60 秒;wiki 抓取超时为 30 秒。入库使用 execute_values,模板里的 %s::vector 负责向量类型转换。
重复导入时,示例只更新 body、embedding、updated_at。因此,唯一键能防止同一片段重复插入,却不能完整解决同步:文档缩短后多出来的旧片段、已删除页面,以及变化的标题和 URL,示例都没有处理。
先证明抓全了,再考虑自动回答
作者建议先运行 python ingest.py,按 source 执行 count(*),确认三个来源都有数据,再评估导入时间和磁盘占用,最后接入 cron。
如果 wiki 记录数恰好停在 50、100,作者建议优先检查分页。这适合作为异常线索,不能直接判定故障:表里统计的是片段数量,不是页面数量,正常结果也可能碰巧是整百。
检索服务的设计很薄:用 uvicorn 在端口 8080 提供入口,把问题转成向量,按余弦距离排序,取最相关的片段交给聊天模型。
但原文没有给出 FastAPI 路由、检索 SQL、取多少个片段或聊天请求代码。测试管理系统和 SQL views 的抓取实现也未展开,只说复用相同流程、替换抓取函数。因此,它展示的是可继续补齐的骨架,不能直接视为三源接入完成的成品。
还要补几处运行条件。脚本调用 dotenv.load_dotenv(),安装命令却漏了 python-dotenv;代码读取 page['space']['key'] 来拼 URL,请求中的 expand 又没包含 space,返回结构需要核实。Docker Compose 配置也没有提供,照着正文还无法启动完整环境。
我的判断
我认可作者把端点、权限、分页和重复导入放在模型前面。对已有 PostgreSQL 的团队,把资料向量放进同一数据库,可能比另外维护一套检索服务更容易起步。
但这是工程经验分享,没有回答准确率、召回率、延迟、费用,也没有关键词搜索与向量检索的对照实验。原文对跨系统关键词搜索的负面评价,不能推导成向量检索在所有 QA 问题上更好。错误码、接口路径、用例 ID 这类精确查询,仍应与关键词或 SQL 检索一起比较。
还有两个边界需要分清。业务源数据库的读取账号应该只能 SELECT,目标知识库的写入账号则必须能插入和更新;原文的 qa_ai 又拥有 qa_knowledge,不能把两者理解成同一种权限。原文也没有说明,集中导入后如何保留不同用户的资料访问范围。
抓取成功、向量入库和回答可信,是三件需要分别验证的事。 示例直接切 wiki 的 body.storage.value,未展示 HTML 清洗,也未展示答案引用和缺少证据时的处理。它足以说明链路怎么搭,尚不足以证明能稳定回答业务问题。
能拿来做什么
- 给团队做 QA 查询原型时,先限定一个 wiki 空间、一个测试项目和少量 SQL views,逐项确认端点、认证方式、分页规则和只读权限,再扩大范围。
- 做前端或全栈 AI 编程助手时,可以借用它的资料组织方式,保留来源、原始 ID、片段序号和 URL,让接口约束、测试用例与失败记录有机会一起进入检索上下文。
- 把上线前验收拆成具体检查:模型维度是否匹配、页面是否抓全、重跑是否重复、文档缩短后是否残留旧片段,以及答案能否找到对应依据。
- 先拿限流说明、支付重试覆盖、迭代失败原因这些真实问题做评估,同时比较关键词、SQL 与向量检索的表现,再决定是否增加检索复杂度。