尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

LLM接入数据只是21%:RAG工程化实战指南

LLM接入数据只是21%:RAG工程化实战指南 你可能已经听过这样一句话Connecting an LLM to Your Data Is the 21% Solution把大语言模型接到你的数据上只解决了 21% 的问题。第一次看到时我也以为这只是标题党的夸张表达。但真把一个 RAG 应用从“能跑”推进到“好用到敢上线”之后才意识到这 21% 的说法不仅不夸张甚至还有点保守。很多团队在搭建 LLM 应用时最容易踩的坑就是把“文档喂进去了”“向量库能检索了”当作项目完成了。结果上线一测模型要么答非所问要么编造数据要么连最基本的多轮对话和权限隔离都做不好。为什么因为从“LLM 能读到数据”到“LLM 能稳定解决业务问题”中间还隔着检索质量、上下文工程、工具调用、Agent 编排、评估反馈、数据权限等一系列问题。这篇文章我想围绕“21% 解决方案”这句话拆解一个 LLM 知识库助手从原型到工程落地的完整过程。你会看到为什么“连接数据”只占整个解决方案的一小部分那 79% 的工程工作量到底花在哪里一个带检索增强、工具调用、Agent 编排的完整代码案例高频报错排查、安全边界和生产环境最佳实践。如果你正在做 RAG、知识库问答、LLM Agent或者正准备把大模型接入内部系统这篇文章应该能帮你少走不少弯路。1. 21% 是从哪来的连接数据不等于解决问题先解释一下这句话的来龙去脉。在国外技术社区里“Connecting an LLM to Your Data Is the 21% Solution”是一篇流传很广的文章标题它想表达的核心观点是把大模型接到企业数据上只解决了整个问题的一小部分。为什么是 21%因为很多开发者在最初的兴奋期里认为只要把 PDF、Word、数据库记录向量化再丢给 LLM 做相似度检索就能得到一个可用的 AI 助手。但实际上这仅仅完成了“数据可达”这一层。真正决定一个 LLM 应用能不能在生产环境跑起来的是后面的工程能力。我们以一个企业内部知识库助手为例。这个场景的完整链路至少包含环节典型工作是否属于“连接数据”数据接入文档解析、去重、切分、向量化是检索优化混合检索、rerank、语义召回调优部分属于上下文组装动态压缩、多轮记忆、Prompt 设计否工具调用查库存、查订单、写工单否Agent 编排任务拆解、路由、状态管理否权限与安全数据级 ACL、脱敏、防注入否评估与回归golden set、指标监控、bad case 分析否成本与性能缓存、降级、模型路由、延迟优化否看到没有“连接数据”在整条链路里只是第一大步。如果你只做这一步你的助手或许能“引经据典”但它不会“干活”也不具备生产系统应有的稳定性、安全性和可维护性。所以21% 并不是说“连接数据”不重要而是提醒我们不要以为数据接完了就结束了真正解决问题的工程化工作才刚刚开始。2. 那 79% 是什么从“能回答”到“靠谱”的系统工程理解 21% 之后更重要的是搞清楚剩余 79% 的内部结构。我习惯把 LLM 应用的完整能力模型拆成三层。2.1 数据访问层解决“模型能读到什么”这一层是所有工作的地基。除了简单的文档向量化还包含数据源接入文件系统、wiki、数据库、API、消息队列数据清洗去重、格式统一、敏感信息识别数据切分按语义边界切分而不是机械地按字符数硬切索引策略向量索引、倒排索引、结构化元数据过滤。很多团队在这一层就会犯错。比如直接把整本产品手册塞成一个 chunk导致检索时把大量无关内容拼进上下文又比如没有对文档做版本管理旧文档还在向量库里干扰结果。2.2 推理与工具层解决“模型能做什么”这是最容易被忽略的一层。数据接进来之后LLM 要真正解决业务问题不能只靠“查资料然后回答”。它还需要调用工具查实时库存而不是从静态文档里猜创建工单、发送通知、更新状态查询数据库但必须是受控的、只读的、带权限约束的查询。这些能力在 LLM 工程里统称为 Function Calling函数调用或 Tool Use工具使用。如果把 RAG 理解成“让模型读资料”工具调用就是“让模型动手干活”。一个完整的 LLM 应用通常两者都需要。2.3 编排与评估层解决“模型怎么稳定输出”这一层是 79% 里最花时间的部分。你需要考虑多轮对话记忆怎么管理多个工具之间怎么选择、怎么回退任务太复杂时怎么拆分成多个子任务答案质量怎么评估怎么避免模型在资料不足时强行编造用户权限不同检索范围是否也跟着不同。这些需求会推动你使用编排框架比如 Spring AI、LangChain4j、LlamaIndex或者自己设计一套调度器。为什么 LLM 应用需要编排框架因为模型本身只是推理引擎它不负责会话状态、工具注册、异常重试、token 预算管理。这些能力必须由框架或业务代码来兜底。一句话总结21% 是“数据可达”79% 是“业务可靠、安全、可控地解决问题”。3. 实战从最简 RAG 到带工具调用的 LLM 助手下面我们用一个具体案例把上面说的 21% 和 79% 都走一遍。场景是做一个“企业产品问答助手”它既要能回答产品文档里的问题也要能查询商品实时库存。技术栈选型上我先用 Python 做快速原型演示然后给出 Java / Spring AI 的工程化改造思路。这样的好处是原型阶段迭代快工程化阶段好维护。3.1 环境准备本文示例以 Python 3.10 为例建议创建独立虚拟环境python -m venv venv source venv/bin/activate # Windows 下执行 venv\Scripts\activate pip install openai chromadb langchain langchain-openai langchain-community rank-bm25 jieba代码里的模型名、API 地址需要根据你实际可用的服务调整。如果你使用的是本地部署模型比如 Ollama、vLLM把ChatOpenAI的base_url指向本地服务即可。3.2 第 1 步先做一个最简 RAG感受一下“21%”先写一个最基础的 RAG 流程加载文档、切分、向量化、检索、生成。# rag_basic.py from langchain_community.document_loaders import TextLoader from langchain_text_splitters import RecursiveCharacterTextSplitter from langchain_openai import OpenAIEmbeddings from langchain_community.vectorstores import Chroma from langchain_openai import ChatOpenAI from langchain_core.prompts import ChatPromptTemplate from langchain_core.runnables import RunnablePassthrough from langchain_core.output_parsers import StrOutputParser # 1. 加载本地文档 loader TextLoader(docs/product_manual.txt, encodingutf-8) docs loader.load() # 2. 切分文本chunk_size 和 overlap 需要按文档结构调整 splitter RecursiveCharacterTextSplitter( chunk_size500, chunk_overlap50, separators[\n\n, \n, 。, , , , ] ) chunks splitter.split_documents(docs) # 3. 生成向量并写入本地 Chroma 库 embeddings OpenAIEmbeddings(modeltext-embedding-3-small) vectorstore Chroma.from_documents( documentschunks, embeddingembeddings, persist_directory./chroma_db ) # 4. 构建检索器 retriever vectorstore.as_retriever(search_kwargs{k: 5}) # 5. 构造 RAG Prompt prompt ChatPromptTemplate.from_template( 你是一个企业知识库助手。请只根据下面的资料回答用户问题。 如果资料里没有相关信息请直接回答“资料库中没有找到相关信息”不要编造。 资料 {context} 问题{question} ) # 6. 组装 RAG 链 llm ChatOpenAI(modelgpt-4o-mini, temperature0) rag_chain ( {context: retriever, question: RunnablePassthrough()} | prompt | llm | StrOutputParser() ) # 测试 result rag_chain.invoke(我们公司的退款政策是什么) print(result)这段代码看起来很简单但它已经是一个能跑的“21% 解决方案”了。你可以把 docs 替换成任意业务文档体验一下模型“基于资料回答”的效果。但请注意这个版本并不能直接用于生产。它的检索质量是“一刀切”的也没有权限隔离、工具调用和评估机制。3.3 第 2 步用混合检索 重排序把“查得准”做扎实最简 RAG 最常见的痛点是“搜不准”。向量检索擅长语义相似但遇到精确型号、编号、产品名时经常失灵。这时候需要引入混合检索向量召回 关键词召回再做结果融合。下面给一个演示思路# hybrid_retriever.py # 演示混合检索 RRF 融合思路需要按你的实际环境调整 from rank_bm25 import BM25Okapi import jieba class HybridRetriever: def __init__(self, documents, vectorstore): # documents 是切分后的 Document 列表 self.documents documents self.vectorstore vectorstore # BM25 基于分词结果构建 self.bm25 BM25Okapi([jieba.lcut(doc.page_content) for doc in documents]) def retrieve(self, query: str, k: int 5): # 向量召回取 2 倍候选再做融合 vector_results self.vectorstore.similarity_search_with_score(query, kk * 2) # 关键词召回 bm25_scores self.bm25.get_scores(jieba.lcut(query)) bm25_top_indices sorted( range(len(bm25_scores)), keylambda i: bm25_scores[i], reverseTrue )[: k * 2] # RRF融合两个召回结果 rrf_scores {} for rank, (doc, _score) in enumerate(vector_results): doc_id self.documents.index(doc) rrf_scores[doc_id] rrf_scores.get(doc_id, 0) 1 / (60 rank 1) for rank, doc_id in enumerate(bm25_top_indices): rrf_scores[doc_id] rrf_scores.get(doc_id, 0) 1 / (60 rank 1) sorted_ids sorted(rrf_scores, keyrrf_scores.get, reverseTrue)[:k] return [self.documents[i] for i in sorted_ids]RRFReciprocal Rank Fusion是一种简单有效的多路召回融合算法它不依赖分数归一化只需要排名信息工程上很实用。如果你追求更高的检索精度可以在融合之后再加一个 rerank 模型例如 bge-reranker把候选片段重新排序。这一步做完之后你会明显感觉到回答准确率提升。但注意这仍然属于“数据访问层”的优化还没到 79% 的核心。3.4 第 3 步加 Function Calling让 LLM 连接“系统”而不是“文档”接下来是关键一步让模型不仅仅基于文档回答还能调用工具获取实时数据。比如用户问“A100 商品还有货吗”文档里可能只有商品介绍没有实时库存。正确做法是让 LLM 识别出意图然后调用库存查询函数。以下是用 OpenAI 函数调用格式写的一个最小示例# function_calling_demo.py import json from openai import OpenAI client OpenAI() # 注意配置 API Key 和 base_url def get_inventory(product_id: str) - dict: 查询商品实时库存示例函数实际应调用后端服务 # 实际项目中这里会请求库存系统接口 return {product_id: product_id, stock: 32, unit: 件} # 向模型声明可用工具 tools [ { type: function, function: { name: get_inventory, description: 查询商品实时库存, parameters: { type: object, properties: { product_id: { type: string, description: 商品 ID例如 A100 } }, required: [product_id] } } } ] # 第一轮用户提问 messages [ {role: user, content: A100 号商品还有库存吗} ] response client.chat.completions.create( modelgpt-4o-mini, messagesmessages, toolstools, tool_choiceauto, ) # 模型决策是否需要调用工具 if response.choices[0].message.tool_calls: tool_call response.choices[0].message.tool_calls[0] args json.loads(tool_call.function.arguments) # 执行真实函数 result get_inventory(args[product_id]) # 把函数结果回传给模型 messages.append(response.choices[0].message) messages.append({ role: tool, tool_call_id: tool_call.id, content: json.dumps(result, ensure_asciiFalse), }) # 第二轮模型基于工具结果生成最终回答 final_response client.chat.completions.create( modelgpt-4o-mini, messagesmessages, toolstools, ) print(final_response.choices[0].message.content) else: print(response.choices[0].message.content)这个例子的核心不在代码量而在于交互逻辑用户提问模型判断“需要查库存”于是返回一个工具调用请求你的程序执行真实函数函数结果作为新的上下文回合回传给模型模型基于工具结果生成最终答案。这样一个简单闭环就让助手从“翻文档”升级到了“查系统”。如果你接入的工具足够多它就能处理更复杂的任务——这就是 Agent 的雏形。3.5 第 4 步用编排框架把 RAG Agent 工具串起来手动维护上面的对话循环短期没问题但一旦工具数量超过 5 个、还需要多轮记忆和权限过滤时代码就会失控。这时候你会理解“LLM 应用为什么需要编排框架”。以 Java 生态为例可以选用 Spring AI。Spring AI 提供了一套面向 Spring Boot 的 LLM 应用抽象支持 ChatClient、Advisor、Tool、VectorStore 等概念。你可以把 RAG、函数调用、Agent 能力通过注解和配置组合起来。下面是一个基于 Spring AI 的 RAG 服务示例思路如下// 文件路径src/main/java/com/example/llmagent/config/RagConfig.java Configuration public class RagConfig { Bean ChatClient chatClient( ChatClient.Builder builder, VectorStore vectorStore) { // QuestionAnswerAdvisor 是 Spring AI 内置的 RAG 增强组件 // 它会在每次对话前自动从 VectorStore 检索相关片段并注入 Prompt。 return builder .defaultAdvisors(QuestionAnswerAdvisor.builder(vectorStore).build()) .build(); } }接着注册一个工具// 文件路径src/main/java/com/example/llmagent/tool/InventoryTool.java Component public class InventoryTool { Tool(查询商品实时库存) public String getInventory(String productId) { // 实际项目里调用库存服务 return 商品 productId 当前库存 32 件; } }当你通过 ChatClient 发起用户请求时Spring AI 会自动完成“检索向量库 拼装上下文 判断是否调用 InventoryTool 把结果返回给模型”的闭环。这个抽象大大降低了 Agent 应用的门槛。如果你的业务需要接入更多外部系统可以考虑 MCPModel Context Protocol标准。MCP 本质上是一种让 LLM 应用统一发现和调用外部工具/数据源的协议。把内部服务封装成 MCP Server配置好后LLM 应用就能通过标准接口调用而不必为每个系统单独写一套适配代码。这也是目前 Agent 工程化的重要趋势之一。4. 数据质量与知识组织不能只靠“喂文档”回到“连接数据”这个话题。很多团队把 RAG 效果不好归因于模型不行但实际上数据噪声、知识重复、文档结构混乱才是最主要的原因。这里我要推荐一个思路在建设知识库时不要只做“文档切块向量化”还要构建结构化的知识组织层。Andrej Karpathy 在个人知识库实践中提出的 “llm wiki” 思路本质上就是用 LLM 辅助把非结构化的笔记和资料整理成结构化的知识卡片再用知识图谱或结构化索引统一管理。这个思路放到企业知识库中同样适用先让 LLM 对原始文档做摘要、实体抽取、关系识别将摘要和知识三元组单独建索引用户提问时先检索“知识卡”再回到原始文档找依据这样既保证召回精度又方便追溯来源。举个简单例子产品手册里写“A100 支持 24 小时连续工作”如果只是按文本块切分这句话很容易和其他描述混在一起。但如果先抽取出“A100 - 运行方式 - 连续工作 24h”这样的三元组再建立索引检索“A100 能长时间运行吗”时就能精准命中。所以数据连接不只是“把文档丢进向量库”。数据质量、知识结构、索引方式直接决定了你那个 21% 能发挥出多少价值。5. 权限、安全与生产环境红线LLM 应用接入真实业务数据后安全边界就成了必须优先考虑的问题。很多安全事故并不是模型本身“坏”而是应用层没有做好权限控制。5.1 数据级权限过滤如果你的知识库面向不同角色用户比如普通员工和管理员那么检索阶段就要做数据隔离。不能把所有文档都放进一个向量库让所有人检索。常见做法文档入库时打上权限标签例如departmentfinance、levelmanager查询时带上用户上下文过滤掉无权访问的标签可以在 Chroma 等向量库中用 metadata 过滤条件实现。retriever vectorstore.as_retriever( search_kwargs{ k: 5, filter: {department: user_department} } )5.2 工具调用必须限制边界工具调用权限比检索权限更敏感。如果模型能调用数据库查询工具你必须确保使用只读账号禁止连接生产写库SQL 查询工具只允许白名单范围内的 SQL 模板所有高危操作删除、更新、转账必须走人工审批每次工具调用都记录审计日志包括入参、出参和调用人。“最小权限原则”在 LLM 应用里同样是铁律。5.3 Prompt 注入防护用户输入可能夹带恶意指令例如“忽略之前的系统提示”。缓解措施包括在 Prompt 中明确把“用户输入”和“工具结果”标记为不可信内容对模型输出做二次校验不允许输出内部 system prompt对长文本输入做长度限制避免用超长内容干扰上下文。5.4 生产环境变更流程如果你要修改检索策略、更换 Embedding 模型、更新知识库文档务必先在隔离环境验证效果再灰度发布。涉及线上数据清理或重建索引时先备份再操作并准备好回滚方案。LLM 应用最大的特点是非确定性任何一次 Prompt 调整都可能影响全局不能改完不测就上线。6. 常见问题与排查思路下面整理几个 LLM 数据项目中高频出现的问题。如果你的助手表现不佳可以按表格里的顺序排查。问题现象常见原因解决思路回答内容与资料无关检索召回质量差检查切分策略加入混合检索、rerank可视化召回结果模型总在编造事实Prompt 未做约束temperature 过高增加“没有资料就回答不知道”的指令temperature 调低专有名词、型号查不到纯向量检索无法精确匹配增加关键词检索/BM25或使用带精确匹配的混合检索回答速度很慢召回数量过大、模型推理时间长减小 k 值使用更小模型增加结果缓存请求报 413 request entity too large上下文过长超出网关或模型限制压缩历史消息、精简 Prompt必要时调整服务端 body 限制提示“文本向量 API 未配置”embedding 服务地址或 API Key 缺失检查环境变量、base_url、模型名是否可用不同用户问同样问题能看到不同数据缺少权限过滤在 metadata 中加权限标签检索时按用户过滤模型调用工具后一直报错工具入参格式与函数签名不匹配检查工具描述和参数 schema加入入参校验知识库更新后回答没变化向量库没有增量更新或缓存未失效建立索引更新机制更新后重建或 upsert 对应文档排查 RAG 问题时最有效的做法不是猜而是把链路各阶段的数据可视化出来用户输入了什么、召回了哪些片段、最后拼进 Prompt 的上下文是什么。只要能看到中间结果问题定位会快很多。7. LLM 应用工程化最佳实践最后总结几条我在这类项目里反复验证过的经验希望能帮你把精力花在真正重要的地方。7.1 先定评估集再调 Prompt不要靠“感觉变好了”来迭代。准备 30 到 100 条真实业务问题作为 golden set为每个问题标注正确答案或参考文档。每次调整 Prompt、检索策略、模型版本后都跑一遍回归记录正确率、无答案率、错误率。没有评估闭环LLM 应用很难持续稳定。7.2 检索不是堆数量而是拼精度很多团队为了提升召回率把 k 值调到 10 甚至 20结果上下文被大量无关片段塞满模型反而更糊涂。更好的做法是用较小 k 值4 到 6做粗召回用 rerank 模型精排只把 top 2 到 3 个片段放入最终上下文。7.3 RAG 和 Agent 要分场景不是所有问题都需要 Agent。简单知识问答用 RAG 就够复杂任务才需要多步工具调用。给应用设计一个“路由层”先判断用户意图再决定走检索问答还是 Agent 工作流。这样既节省成本也降低故障率。7.4 数据和模型分层治理数据侧做文档质量分级、版本管理、权限打标模型侧做 Prompt 模板版本管理、模型路由简单问题用小模型复杂问题用大模型、缓存策略。两侧各自独立演进互不阻塞。7.5 日志和审计是底线LLM 应用的请求日志、召回日志、工具调用日志必须保留。一方面用于问题回溯另一方面也是合规审计的要求。遇到用户投诉“AI 回答不准确”时没有日志几乎无法复盘。7.6 关注模型精度与成本平衡如果你的应用使用开源模型私有化部署还要关注大模型的精度问题比如 FP16、FP32、BF16 对回答质量的影响。FP16 通常能满足多数场景且显存占用更低对数值敏感的生成任务可能需要 FP32 或针对性评测。不要盲目追求大模型或高精度先跑评测集用数据说话。8. 收尾从 21% 走向完整方案回到文章标题Connecting an LLM to Your Data Is the 21% Solution。如果你正准备启动一个“LLM 连接数据”的项目我的建议是第一花时间把数据治理好。文档去重、权限打标、知识结构化这些工作前期看起来繁琐却是后面所有效果的根基。第二不要只做“问答”。把工具调用、Agent 编排、权限隔离一起纳入架构设计。哪怕第一版只做 RAG也要预留工具扩展的接口。第三从第一天就建立评估集。LLM 应用是概率系统没有回归测试改一行 Prompt 都可能带来灾难性的波动。把 LLM 接到数据上是入门让它在数据之上稳定、安全、可控地解决问题才是真正的实战。希望这篇文章能帮你把那缺失的 79% 补上来。如果这篇文章对你有帮助可以收藏备用。后续我还会写更多关于 RAG 实战、Agent 编排、LLM 应用评估的内容欢迎关注。
返回列表