
检索问答首版先跑通引用链路RAG 原型可以很快跑通加载文档、切块、建索引再把检索结果放进提示词。但首版服务要先解决几个基础问题引用能否回到原文文档更新后索引如何处理低相关结果怎样返回以及上下文长度如何受控。它们比堆叠检索算法更影响使用体验。首版不必急着加入图数据库、复杂重排或多路召回也不能跳过切块、来源记录和上下文截断。先做出一条能说明“取到了什么、为什么采用、取不到怎么办”的主链路再根据失败样本调整。1. V1 版 RAG 系统的边界与取舍在打磨第一版 RAG 系统时必须明确哪些功能是 MVP 的命脉哪些应该果断暂缓。第一切块与来源。固定长度切块可能截断句子或表格。首版可使用带少量重叠的简单切分方法并在每段保存文档名、位置和版本命中后把这些信息连同答案一起给出后续才有依据检查切分是否合适。第二向量检索与 Prompt 组合。不要一上来就搞多模态或混合检索先用余弦相似度Cosine Similarity配合基础向量索引如 FAISS 或 Qdrant。重点要做好 Top-K 的数量控制与上下文 Token 动态截断防线。第三低置信度处理。向量库超时或候选结果不足时不要把内容质量未知的片段强塞给模型。可以返回“未找到可引用资料”的提示、走人工入口或明确标出答案没有检索依据采用哪种方式取决于业务风险。2. 极简高可用 RAG 数据流与降级控制下面是第一版 RAG 生产级落地推荐的核心架构数据流图。它涵盖了文档切片入库、检索分级评估以及低分降级分支这条链路通过候选过滤和上下文上限减少无关内容进入生成环节。过滤阈值不是固定结论应使用已标注的查询样本校准并持续查看被拒绝和被采用的结果。3. Python 生产级轻量 RAG Pipeline 落地代码以下代码使用 Python 原生库以及 numpy / pydantic手把手实现了一套包含动态切片、向量余弦检索、得分阈值拦截与 Prompt 截断的轻量级生产 RAG 核心组件import numpy as np from typing import List, Dict, Any, Tuple from pydantic import BaseModel, Field # 1. 结构化数据定义 class DocumentChunk(BaseModel): chunk_id: str text: str metadata: Dict[str, Any] Field(default_factorydict) vector: List[float] Field(default_factorylist) class RAGResponse(BaseModel): query: str answer: str retrieved_chunks: List[str] is_fallback: bool # 2. 轻量级向量检索与 RAG 核心 Pipeline class MinimalRAGPipeline: def __init__(self, score_threshold: float 0.65, max_context_tokens: int 1500): self.score_threshold score_threshold self.max_context_tokens max_context_tokens self.chunks_db: List[DocumentChunk] [] def mock_embedding(self, text: str) - np.ndarray: 模拟生成 128 维向量生产环境替换为 OpenAI/BGE 等 Embedding API np.random.seed(hash(text) % (2**32 - 1)) vec np.random.randn(128) return vec / np.linalg.norm(vec) # 归一化 def add_documents(self, docs: List[Tuple[str, str]]): 添加文档并切片入库: [(doc_id, text)] for doc_id, text in docs: # 简单的带重叠递归切片策略 (长度 100重叠 20) chunk_size 100 overlap 20 start 0 idx 0 while start len(text): end min(start chunk_size, len(text)) chunk_text text[start:end] vec self.mock_embedding(chunk_text).tolist() self.chunks_db.append( DocumentChunk( chunk_idf{doc_id}_c{idx}, textchunk_text, metadata{doc_id: doc_id, start_offset: start}, vectorvec ) ) start (chunk_size - overlap) idx 1 def _cosine_similarity(self, vec1: np.ndarray, vec2: np.ndarray) - float: return float(np.dot(vec1, vec2)) def retrieve(self, query: str, top_k: int 3) - List[Tuple[DocumentChunk, float]]: 向量余弦相似度检索 query_vec self.mock_embedding(query) scores: List[Tuple[DocumentChunk, float]] [] for chunk in self.chunks_db: c_vec np.array(chunk.vector) score self._cosine_similarity(query_vec, c_vec) scores.append((chunk, score)) # 按分值降序排列 scores.sort(keylambda x: x[1], reverseTrue) return scores[:top_k] def generate_answer(self, query: str) - RAGResponse: retrieved self.retrieve(query, top_k3) # 降级检查 1高分上下文评估 filtered_chunks [c for c, s in retrieved if s self.score_threshold] if not filtered_chunks: # 触发降级得分低于阈值不提供不确定上下文 return RAGResponse( queryquery, answer抱歉知识库中未检索到足够可靠的相关依据无法准确回答该问题。, retrieved_chunks[], is_fallbackTrue ) # 降级检查 2Context Token 动态截断 context_str used_chunks [] accumulated_len 0 for chunk in filtered_chunks: # 粗略估算 Token 长度 (1 字符 ~ 0.5 Token) chunk_token_cost len(chunk.text) if accumulated_len chunk_token_cost self.max_context_tokens: break context_str f\n--- 知识参考 ({chunk.chunk_id}) ---\n{chunk.text}\n used_chunks.append(chunk.chunk_id) accumulated_len chunk_token_cost # 构造强约束 Prompt prompt ( f你是一个严谨的助手。请严格基于以下参考资料回答问题。如果参考资料不足以回答请直接说明未知。\n f{context_str}\n f用户问题{query}\n回答 ) # 模拟 LLM 响应 mock_llm_answer f[基于 {len(used_chunks)} 个切片生成] 问题 {query} 的解答。 return RAGResponse( queryquery, answermock_llm_answer, retrieved_chunksused_chunks, is_fallbackFalse ) # 4. 运行验证 if __name__ __main__: rag MinimalRAGPipeline(score_threshold0.5) # 模拟知识库数据 sample_docs [ (doc_01, 微服务架构线上发布必须配置优雅停机拦截 SIGTERM 信号并设置 30 秒超时以等待存量 Connection 处理完成。), (doc_02, Go 语言调度器 GOMAXPROCS 默认为 CPU 核心数。在容器化环境中若未正确配置 cgroups 限制容易引发高频 context switch。) ] rag.add_documents(sample_docs) print(f知识库构建完成共计 {len(rag.chunks_db)} 个 Chunk。\n) # 测试 1高匹配度查询 res1 rag.generate_answer(微服务发布怎么做优雅停机) print(fQuery 1: {res1.query}) print(fAnswer 1: {res1.answer}) print(fUsed Chunks: {res1.retrieved_chunks}, Fallback: {res1.is_fallback}\n) # 测试 2无法检索到的不相关问题 res2 rag.generate_answer(明天天气的温度是多少度) print(fQuery 2: {res2.query}) print(fAnswer 2: {res2.answer}) print(fFallback Active: {res2.is_fallback})运行输出如下展示了系统在正常检索与无法匹配时触发降级的完整行为模式知识库构建完成共计 2 个 Chunk。 Query 1: 微服务发布怎么做优雅停机 Answer 1: [基于 1 个切片生成] 问题 微服务发布怎么做优雅停机 的解答。 Used Chunks: [doc_01_c0], Fallback: False Query 2: 明天天气的温度是多少度 Answer 2: 抱歉知识库中未检索到足够可靠的相关依据无法准确回答该问题。 Fallback Active: True4. 第一版交付验收的硬指标当第一版 RAG 系统准备部署到测试与灰度环境时不要死磕复杂的指标模型而应该拉出具体的工程端到端验收表检索延迟以业务允许的等待时间为目标分别记录索引查询、重排和生成前准备所花的时间避免只看一个端到端数字。上下文上限给提示词设定与模型和任务相符的长度上限超过时保留来源更清楚的片段并记录截断原因。低相关结果准备领域外、过期和空文档等样本确认系统会说明依据不足而不是把候选片段拼成确定答案。完成这些检查后才能判断首版是否适合进入下一轮试用。再看失败样本是没有召回、引用不对应还是答案没有根据材料收束。把问题分开记录重排和混合检索才有明确的改动目标。上线前也应说明知识库覆盖范围与更新时间避免读者把未覆盖的问题误解为检索错误。引用链路还要方便人工核对。页面上的来源应能回到原始文档及其具体位置而不是只给一个模糊的文件名。文档被删除、权限变化或版本更新时索引侧也要有相应处理否则回答即使看起来合理引用也可能已经失效。