惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

B
Blog
C
Cybersecurity and Infrastructure Security Agency CISA
Microsoft Security Blog
Microsoft Security Blog
B
Blog RSS Feed
云风的 BLOG
云风的 BLOG
G
Google Developers Blog
Recent Announcements
Recent Announcements
A
About on SuperTechFans
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Google Online Security Blog
Google Online Security Blog
Google DeepMind News
Google DeepMind News
S
Schneier on Security
S
Secure Thoughts
T
The Exploit Database - CXSecurity.com
Martin Fowler
Martin Fowler
P
Proofpoint News Feed
Security Latest
Security Latest
Jina AI
Jina AI
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Recorded Future
Recorded Future
T
Tor Project blog
有赞技术团队
有赞技术团队
H
Hackread – Cybersecurity News, Data Breaches, AI and More
N
News | PayPal Newsroom
博客园 - 三生石上(FineUI控件)
MyScale Blog
MyScale Blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Last Week in AI
Last Week in AI
F
Full Disclosure
Hacker News: Ask HN
Hacker News: Ask HN
Forbes - Security
Forbes - Security
D
DataBreaches.Net
人人都是产品经理
人人都是产品经理
NISL@THU
NISL@THU
C
Cisco Blogs
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Google DeepMind News
Google DeepMind News
Project Zero
Project Zero
IT之家
IT之家
T
Threatpost
Cyberwarzone
Cyberwarzone
O
OpenAI News
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
J
Java Code Geeks
P
Proofpoint News Feed
The Last Watchdog
The Last Watchdog
月光博客
月光博客
Latest news
Latest news
MongoDB | Blog
MongoDB | Blog
Apple Machine Learning Research
Apple Machine Learning Research

博客园 - ExplorerMan

大模型sft微调参数优化2 推荐 Prompt 模板(大幅提升 JSON 质量) 渐进式SFT内化 [推荐]双塔模型(介绍) Agno - 轻量级Python多智能体系统框架 Open WebUI:打造友好且强大的自托管 AI 平台 【gradio】使用Gradio快速开发前端界面:基础知识 文本切割方案进化概览:从“机械切割”到“智能解构” 大模型RAG的上下文压缩与过滤 SemanticChunker 语义相似拆分 基于LangChain 实现 Advanced RAG-后检索优化(上)-Reranker 基于LangChain 实现 Advanced RAG-后检索优化(下)-上下文压缩与过滤 多Agent协作入门:基于A2A协议的Agent通信(中) ollama部署与open-webui 0基础也能看懂!从0到1手把手教你本地部署大模型Ollama 什么是 AutoModel 大模型基础应用框架(ReACT\SFT\RAG)技术创新及零售业务落地应用 - ExplorerMan - 博客园 多模态Embedding模型:从文本到多模态的全面选型指南! rag 查询检索轮换
大模型RAG实战,从被骂不靠谱到成为部门MVP,这是我的踩坑全记录【转】
ExplorerMan · 2026-02-11 · via 博客园 - ExplorerMan

大模型RAG实战,从被骂不靠谱到成为部门MVP,这是我的踩坑全记录 - 张不惑 - 博客园

一切的起点是一顿臭骂

上个月,我被领导叫进办公室骂了整整二十分钟。

起因是这样的——我们部门负责维护一套内部知识库系统,里面沉淀了公司近五年的技术文档、故障处理手册、还有各种规范流程。问题是,这玩意儿除了当摆设,几乎没人用。为啥?因为搜索太烂了,关键词匹配的那种,你搜服务器宕机怎么办,它给你返回一堆包含服务器的文档,真正有用的那篇反而排在第三页。

新同事入职问问题,老员工翻文档找答案,大家宁可在群里@人问,也不愿意去知识库里查。

然后领导发话了:你不是天天研究什么大模型吗?能不能整个智能问答,让大家直接问问题就能得到答案?

我当时脑子一热,拍胸脯说没问题。结果第一版上线三天就被骂下来了——用户问我们的MySQL主从切换流程是什么,大模型回答得头头是道,但内容完全是它自己编的!跟我们公司的实际流程八竿子打不着。

这就是所谓的大模型幻觉问题,我当时对RAG的理解还停留在把文档丢进去就行的水平,太天真了。

不过,后来的故事还算圆满。我花了将近三周时间重构了整个方案,现在这套系统已经成了部门的标配工具,月活跃用户从0涨到了200多,领导在季度会上还专门表扬了一回。今天这篇文章,我就把整个踩坑过程原原本本地记录下来,包括代码、架构设计、以及那些教科书上不会告诉你的实战细节。

一、RAG到底在解决什么问题

在动手之前,我想先聊聊RAG这个概念,因为很多刚接触的朋友容易搞混。
image.png

大模型很强,但它有两个致命弱点:

第一,知识有截止日期。 GPT-4的训练数据截止到某个时间点,它不知道你们公司上周发布的新规范,也不知道你们昨天刚修复的那个bug是怎么解决的。

第二,会一本正经地胡说八道。 当大模型遇到它不知道的问题时,它不会老老实实说我不知道,而是会基于它学过的通用知识,给你编一个看起来很合理但其实是错的答案。这就是所谓的幻觉(Hallucination)。

RAG(Retrieval-Augmented Generation,检索增强生成)的核心思路其实很简单:别让大模型靠想象力答题,先帮它把参考资料找出来,让它照着资料回答。
image.png
具体来说分三步:

  1. 把你的私有文档切成小块,转成向量存起来
  2. 用户提问时,先根据问题检索出最相关的文档片段
  3. 把问题和检索到的内容一起喂给大模型,让它基于这些材料生成答案

听起来不复杂对吧?我当时也是这么想的,然后就踩了一堆坑。

二、第一个大坑:文档切分没那么简单

我最初的方案特别粗暴——用LangChain的RecursiveCharacterTextSplitter,设置chunk_size=500,overlap=50,直接把所有文档切成小块。
image.png
代码写起来确实很简单:

from langchain.text_splitter import RecursiveCharacterTextSplitter

def naive_split(text):
    最初的简单切分方案——后来证明这是个坑
    splitter = RecursiveCharacterTextSplitter(
        chunk_size=500,
        chunk_overlap=50,
        separators=[\n\n, \n, 。, !, ?,  , ]
    )
    chunks = splitter.split_text(text)
    return chunks

看起来没毛病是吧?但实际用起来问题大了。

有一次用户问:MySQL切换前需要做哪些检查?系统返回的文档片段是这样的:

确认没有正在执行的大事务
- 通知相关业务方,确认切换时间窗口

## 2. 切换步骤
2.1 在主库执行只读设置
SET GLOBAL read_only = 1;

发现问题了吗?这个片段恰好从检查步骤的中间切开了!第一条检查项确认从库同步状态正常被切到了上一个chunk里。用户问的是需要做哪些检查,结果我们给大模型的参考资料里,第一条检查项就没包含进去。

核心教训:机械地按字数切分,会打断文档的语义完整性。

后来我改成了基于语义结构的切分策略:

import re
from typing import List, Dict

class SmartDocumentSplitter:
    
    语义感知的文档切分器
    核心思路:尊重文档的原有结构,按标题、段落等语义边界切分
    
    
    def __init__(self, max_chunk_size=800, min_chunk_size=100):
        self.max_chunk_size = max_chunk_size
        self.min_chunk_size = min_chunk_size
    
    def split_markdown(self, text: str) -> List[Dict]:
        
        针对Markdown文档的切分
        保持标题层级结构,每个chunk都带上完整的上下文路径
        
        chunks = []
        current_headers = {1: , 2: , 3: }  

这样切出来的效果就好多了。每个chunk开头都会带上它的位置信息,大模型在回答时能更准确地理解这段内容的上下文。

不过说实话,这个方案也不是万能的。对于那些格式不规范的老文档(没有清晰的标题结构),切分效果依然一般。后来我又针对不同类型的文档做了差异化处理,这个我们后面再说。

三、第二个大坑:向量检索的语义鸿沟

解决了切分问题,下一步就是向量化和检索了。我用的是开源的BGE模型做Embedding,用Milvus做向量数据库。

第一版的检索代码很直白:

from sentence_transformers import SentenceTransformer
from pymilvus import connections, Collection, FieldSchema, CollectionSchema, DataType, utility
import numpy as np

class VectorStore:
    向量存储和检索
    
    def __init__(self, model_name='BAAI/bge-base-zh-v1.5'):
        

基本功能是没问题的。但实际跑起来,我发现了一个让人抓狂的现象——用户的口语化提问和文档的正式表述之间存在巨大的语义鸿沟

举个例子:

  • 用户问:数据库挂了怎么办
  • 文档标题是:MySQL服务异常恢复操作手册

这两个在语义上是相关的,但向量相似度可能并不高。因为用户说的挂了和文档里的异常,用词差异很大。

更坑的是,有时候检索出的Top 5结果里,真正相关的那篇可能只排在第3或第4位,但前两名是一些看起来相关但实际上文不对题的内容。如果我只取Top 3喂给大模型,可能就漏掉了最关键的信息。

后来我采用了一个两阶段检索的策略:先用向量检索做粗筛,再用重排序模型做精排。

from transformers import AutoModelForSequenceClassification, AutoTokenizer
import torch

class EnhancedRetriever:
    增强版检索器:向量检索 + 重排序
    
    def __init__(self, vector_store: VectorStore):
        self.vector_store = vector_store
        
        

查询扩展这招特别好用。比如用户问数据库挂了怎么办,大模型可能会扩展成:

  • MySQL服务故障如何处理
  • 数据库无法连接的解决方案
  • 数据库宕机恢复步骤

这几个查询一起检索,能覆盖更多的相关文档。

四、第三个大坑:Prompt工程的门道比想象中深

检索的问题解决了,接下来就是把检索到的内容和用户问题一起喂给大模型了。这一步我本以为最简单,没想到也踩了不少坑。

最初的Prompt特别朴素:

def build_naive_prompt(query: str, context_docs: list) -> str:
    最初的简单Prompt——后来证明太天真了
    context = \n\n.join([doc['content'] for doc in context_docs])
    
    prompt = f根据以下参考资料回答用户问题。

参考资料:
{context}

用户问题:{query}

请回答:
    
    return prompt

这个Prompt有几个严重问题:

问题一:大模型不知道什么时候该说不知道。 当参考资料里确实没有答案时,它还是会编一个出来。

问题二:没有引导大模型说明信息来源。 用户看到答案,不知道是从哪篇文档里来的,无法追溯和验证。

问题三:对于复杂问题,回答的结构不够清晰。

后来迭代了很多版,最终稳定下来的Prompt是这样的:

def build_rag_prompt(query: str, context_docs: list, 
                     include_sources: bool = True) -> str:
    
    生产环境使用的Prompt模板
    关键设计:明确角色定位、限制回答范围、要求标注来源
    
    
    

关于Prompt,我还想分享一个很重要的经验:不要试图在一个Prompt里塞太多指令

一开始我把各种要求都写进去:回答要准确、要简洁、要友好、要专业、要标注来源、要分步骤、遇到不确定要说不知道……结果发现模型反而被绕晕了,有时候顾了这个忘了那个。

后来我的做法是:区分核心指令和优化指令,核心指令必须保留,优化指令可以根据问题类型动态调整。

class PromptBuilder:
    Prompt构建器:根据问题类型动态调整
    
    # 核心指令——任何情况都必须包含
    CORE_INSTRUCTIONS = 
1. 只使用参考资料中的信息回答,不要编造
2. 资料中没有的信息,明确说无法找到相关信息
3. 标注信息来源【资料X】

    
    

五、串起来:完整的RAG Pipeline

前面说了一堆细节,现在把它们串成一个完整的Pipeline:
Mermaid Chart - Create complex, visual diagrams with text.-2026-01-13-113354.png

from openai import OpenAI
from typing import List, Dict, Optional
import json

class RAGPipeline:
    
    完整的RAG处理流程
    文档切分 -> 向量化存储 -> 检索 -> 重排序 -> 生成回答
    
    
    def __init__(self, 
                 llm_base_url: str = https://api.deepseek.com,
                 llm_api_key: str = your-api-key,
                 llm_model: str = deepseek-chat):
        
        

六、上线后的一些经验教训

系统上线到现在差不多两个月了,期间又踩了不少坑,这里挑几个印象最深的说说。

教训一:用户的问题千奇百怪

我们在设计时假设用户会问MySQL怎么做主从切换这种正常问题。但实际上呢?

有人问:上次那个事故怎么处理的来着?——没有任何上下文,系统根本不知道那个事故是哪个。

有人问:帮我写个SQL。——这根本不是知识库问答,这是让大模型帮写代码。

还有人问:在吗?——我也不知道他想干啥。

后来我加了一个意图识别层,先判断用户的问题是否属于知识库问答的范畴:

def classify_intent(self, query: str) -> str:
    """识别用户意图"""
    intent_prompt = f"""判断用户输入的意图类别,只输出类别名称:
- knowledge_query:查询知识库信息(如询问流程、规范、操作方法)
- code_request:请求生成代码
- chitchat:闲聊或无明确意图
- other:其他

用户输入:{query}

意图类别:"""
    
    response = self.llm_client.chat.completions.create(
        model=self.llm_model,
        messages=[{"role": "user", "content": intent_prompt}],
        temperature=0,
        max_tokens=20
    )
    
    return response.choices[0].message.content.strip()

对于非知识库问答的意图,给用户一个友好的提示而不是硬着头皮检索。

教训二:冷启动时的尴尬

系统刚上线时,知识库里的文档不多,覆盖的场景有限。用户问了几个问题都答不上来,体验特别差,于是就不来用了。

后来的解决办法:

  1. 上线前先梳理高频问题,确保至少这些问题能回答好
  2. 搞了一个问题收集功能,对于答不上来的问题,记录下来反馈给内容团队,让他们补充相关文档
  3. 做了一个兜底策略——如果检索不到高相关度的内容,就展示相关推荐,把一些相似度尚可的文档标题列出来,引导用户自己去看

教训三:文档更新的同步问题

知识库的文档是会更新的。老版本的操作手册废弃了,新版本发布了。但如果向量数据库里还存着老版本的内容,用户检索到的可能是过时信息。

这个问题说起来简单,做起来挺麻烦的。我们最后的方案是:

  1. 每个文档入库时记录版本号和更新时间
  2. 定期全量重新入库(我们是每周一次)
  3. 对于紧急更新的重要文档,支持手动触发单篇重入库

七、性能优化:让系统不那么慢

RAG系统有个让人头疼的问题——慢。

整个流程跑一遍:Embedding编码、向量检索、重排序、LLM生成,全部加起来可能要好几秒。用户体验就很差,问一个问题要等半天。

image.png

几个优化措施:

import asyncio
from functools import lru_cache
import hashlib

class OptimizedRAG:
    性能优化版RAG
    
    def __init__(self):
        

流式输出这一点特别重要。用户问完问题后,马上就能看到回答在打字,心理上就不会觉得那么慢了。

八、回顾与思考

把这套系统从被骂下线到成为部门标配,前后折腾了将近一个月。趟过的坑挺多,但收获也很大。

几点核心总结:

1. RAG不是万能的,选好适用场景

RAG适合有明确知识库、答案可追溯的场景。如果你的需求是让大模型发挥创造力(比如写文案、做创意),那RAG反而是个约束。

2. 切分和检索是根基

大家往往把注意力放在大模型本身,觉得用更强的模型就能解决问题。但实际上,如果前面的切分和检索做得不好,再强的模型也是巧妇难为无米之炊。

3. Prompt工程真的是门手艺

同样的检索结果,不同的Prompt可能带来天壤之别的回答效果。这个没什么捷径,就是多试、多看、多迭代。

4. 上线只是开始

真正的挑战在上线之后。用户的各种奇葩输入、文档的持续更新、性能的优化、效果的监控……每一项都是持续的工作。

image.png

最后,附上这套系统目前的一些核心指标:

  • 日均查询量:200+次
  • 平均响应时间:2.3秒(开启流式后首字符延迟约0.8秒)
  • 用户满意度(通过回答后的点赞/点踩收集):约72%
  • 无法回答的比例:约22%(这部分会定期分析,推动补充文档)

数字不算特别亮眼,但比起之前那个没人用的关键词搜索,已经是质的飞跃了。如果你也在做类似的项目,希望这篇文章能帮你少踩一些坑。有问题欢迎评论区交流!