news 2026/8/27 21:38:43

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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),把ChatOpenAIbase_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", encoding="utf-8") docs = loader.load() # 2. 切分文本,chunk_size 和 overlap 需要按文档结构调整 splitter = RecursiveCharacterTextSplitter( chunk_size=500, chunk_overlap=50, separators=["\n\n", "\n", "。", "!", "?", " ", ""] ) chunks = splitter.split_documents(docs) # 3. 生成向量并写入本地 Chroma 库 embeddings = OpenAIEmbeddings(model="text-embedding-3-small") vectorstore = Chroma.from_documents( documents=chunks, embedding=embeddings, 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(model="gpt-4o-mini", temperature=0) 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, k=k * 2) # 关键词召回 bm25_scores = self.bm25.get_scores(jieba.lcut(query)) bm25_top_indices = sorted( range(len(bm25_scores)), key=lambda i: bm25_scores[i], reverse=True )[: 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, key=rrf_scores.get, reverse=True)[:k] return [self.documents[i] for i in sorted_ids]

RRF(Reciprocal 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( model="gpt-4o-mini", messages=messages, tools=tools, tool_choice="auto", ) # 模型决策:是否需要调用工具 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_ascii=False), }) # 第二轮:模型基于工具结果生成最终回答 final_response = client.chat.completions.create( model="gpt-4o-mini", messages=messages, tools=tools, ) print(final_response.choices[0].message.content) else: print(response.choices[0].message.content)

这个例子的核心不在代码量,而在于交互逻辑:

  1. 用户提问;
  2. 模型判断“需要查库存”,于是返回一个工具调用请求;
  3. 你的程序执行真实函数;
  4. 函数结果作为新的上下文回合回传给模型;
  5. 模型基于工具结果生成最终答案。

这样一个简单闭环,就让助手从“翻文档”升级到了“查系统”。如果你接入的工具足够多,它就能处理更复杂的任务——这就是 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 应用的门槛。

如果你的业务需要接入更多外部系统,可以考虑 MCP(Model 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 数据级权限过滤

如果你的知识库面向不同角色用户,比如普通员工和管理员,那么检索阶段就要做数据隔离。不能把所有文档都放进一个向量库让所有人检索。常见做法:

  • 文档入库时打上权限标签,例如department=financelevel=manager
  • 查询时带上用户上下文,过滤掉无权访问的标签;
  • 可以在 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 应用评估的内容,欢迎关注。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/27 21:38:11

从零搭建虚拟NPU:手把手实现AI芯片驱动与矩阵乘法

AI芯片这个关键词最近热度很高,不少讨论集中在公司估值、流片进度和算力数字上,但如果把视角切到开发者一侧,真正决定芯片能不能落地的其实是软件栈,尤其是“AI芯片驱动开发”这条链路。一颗 AI 加速芯片从裸片到能被上层框架调用…

作者头像 李华
网站建设 2026/8/27 21:33:54

零基础机器人学习路线:从ROS2仿真到真机实战完整指南

机器人学习路线最怕的,不是资料少,而是资料太多。今天收藏一套 ROS 2 教程,明天看一个机械臂视频,过两天又保存一篇 MoveIt 实战总结,一个月下来收藏夹多了几十个链接,真正在自己电脑上跑通的 demo 可能一个…

作者头像 李华
网站建设 2026/8/27 21:33:00

城市健康影响因素分析:多源数据融合与空间建模实战

1. 项目概述与核心价值最近刚带着团队做完一个挺有意思的数据分析项目,核心就是围绕“影响城市居民身体健康的因素”这个主题展开深度挖掘。这其实源于去年我们内部的一次头脑风暴,当时大家讨论到,现在各种健康数据、城市数据那么多&#xff…

作者头像 李华
网站建设 2026/8/27 21:25:32

天猫活动提报系统:夜间全自动客服,3分钟内回复率100%

天猫活动提报系统:夜间全自动客服,3分钟内回复率100% 做店群的老板都知道,天猫的自动提报活动,是店群运营中最耗人力也最容易出错的环节。 平台大促活动报名是流量红利窗口,但提报流程极其繁琐。每个活动要填商品ID、…

作者头像 李华