RAG 案例全流程详解

简易 RAG 原型,没有向量数据库,直接用句子相似度做检索,分为 4 大模块:知识库、召回模块、生成模块、主程序入口。

RAG 全称 Retrieval‑Augmented‑Generation,检索增强生成。核心思想:不直接丢给大模型回答,先从自有知识库检索相关片段,把片段塞给大模型,让大模型基于检索出来的资料做回答,减少幻觉,可以接入私有业务文档

1、知识库 knowledge_base

knowledge_base = [
    "宁波银行理财产品到期后,资金自动回到活期账户,一般T+1工作日到账。",
    "查询持有的理财产品,可以登录手机银行,在我的资产页面查看理财持仓。",
    "银行卡预留手机号修改,需要本人带身份证到线下网点办理。",
    "宁波银行智能语音助手波波之音可以查询余额、理财持仓,部分业务支持语音办理。",
    "理财产品不承诺保本,过往收益不代表未来收益。"
]
  • 模拟银行 FAQ 知识库,每一条代表文档切片 chunk
  • 真实业务场景:PDF、Word 文档,会调用文本分割器,把长篇文档切分成一段段短文本,存入知识库 / 向量库,不能直接传整篇大文档。
  • 切片目的:片段短,检索匹配更精准,同时避免 Prompt 超长超限。

2、召回模块(Retrieval 检索阶段)

sim_pipe = pipeline(
    "sentence-similarity",
    model="damo/nlp_structbert_sentence-similarity_chinese-base"
)

def retrieve_top_k(query, docs, k=2):
    score_list = []
    for doc in docs:
        out = sim_pipe({"text": query, "text_pair": doc})
        score = out["scores"][0]
        score_list.append((doc, score))
    score_list.sort(key=lambda x:x[1], reverse=True)
    return [item[0] for item in score_list[:k]]

步骤拆解

  1. 加载structbert句子相似度模型:输入两段文本,输出 0‑1 相似度分数,越接近 1 代表语义越接近。
  2. 入参:
    • query:用户提问,例:怎么看我买的理财?
    • docs:全部知识库
    • k=2:召回分数最高的 2 条文档。
  3. 循环遍历知识库每一条,把用户问题和知识库片段两两计算相似度,保存(文档,分数)。
  4. sort(reverse=True)按分数从高到低降序排序。
  5. 返回 top‑2 最匹配的文档片段。

当前代码缺点

这是 Demo 写法,不能用于海量知识库

  • 每次用户提问,循环遍历全部知识库做推理,知识库几千上万条的时候速度极慢。
  • 生产环境做法:不实时循环算相似度;提前把所有知识库文本转为 embedding 向量存入 FAISS/Chroma 向量库;用户提问时,把问题转 embedding,向量库做向量相似度搜索,毫秒级拿到 topK 片段。

3、增强 + 生成模块(Augment+Generation)

def generate_answer(user_query, context_docs):
    context_text = "\n".join(context_docs)
    prompt = f"""请根据下面参考资料回答用户问题,只使用参考资料内容,不要编造。
参考资料:
{context_text}
用户问题:{user_query}
回答:"""
    return f"【模拟LLM输出】根据参考文档:{context_docs[0]}"
  1. Augment 增强:把上一步召回拿到的context_docs(top2 文档)拼接,拼到 Prompt 提示词里面。把私有知识塞给大模型上下文。
  2. Prompt 指令约束:只使用参考资料内容,不要编造,金融业务用来抑制大模型幻觉,不能输出知识库没有的业务规则。
  3. 当前代码做了模拟返回,没有真正调用大模型,规避大模型下载、模型不存在报错。

真实场景:把这个模拟返回替换成 Qwen/ChatGLM 等大模型 pipeline,传入完整 prompt,返回大模型输出结果。

Prompt 结构(标准 RAG 提示词)

系统约束指令
参考资料:【召回回来的业务文档】
用户问题:【用户输入】
回答:

4、主程序入口,完整链路串联

if __name__ == "__main__":
    user_question = "怎么看我买的理财?"
    print(f"用户问题:{user_question}\n")
    top_docs = retrieve_top_k(user_question, knowledge_base, k=2)
    print("【召回得到的参考文档】")
    for d in top_docs:
        print("-", d)
    print("\n【RAG最终回答】")
    ans = generate_answer(user_question, top_docs)
    print(ans)

链路顺序:

  1. 用户输入问题:怎么看我买的理财?
  2. 调用retrieve_top_k检索知识库,拿到 top2 匹配片段

本例中最高匹配片段:查询持有的理财产品,可以登录手机银行,在我的资产页面查看理财持仓。

  1. 将召回片段拼接进 prompt
  2. 送入大模型生成答案,输出给用户。

执行输出示例

用户问题:怎么看我买的理财?

【召回得到的参考文档】
- 查询持有的理财产品,可以登录手机银行,在我的资产页面查看理财持仓。
- 宁波银行智能语音助手波波之音可以查询余额、理财持仓,部分业务支持语音办理。

【RAG最终回答】
【模拟LLM输出】根据参考文档:查询持有的理财产品,可以登录手机银行,在我的资产页面查看理财持仓。

面试延伸知识点(这个 Demo 的短板,生产优化方向)

  1. Embedding 向量库替换循环检索:FAISS,预计算全部知识库向量,查询做向量检索,解决知识库大时性能差。
  2. 重排序 Reranker:召回出来 top50,再用 reranker 模型二次打分筛选 top3,提升召回准确率。
  3. 文档切分优化:RecursiveCharacterTextSplitter,设置 chunk_size、chunk_overlap 重叠,避免语义被切断。
  4. 幻觉处理:Prompt 约束、引用溯源,输出答案带上参考文档来源;如果召回片段和问题完全无关,输出 “知识库未查询到相关内容”。
  5. Query 改写:用户提问模糊,对 query 做扩展、改写,提升召回效果。
  6. 评估指标:召回率 Recall、精确率 Precision,评估 RAG 效果。
posted @ 2026-09-16 15:36  m516606428  阅读(5)  评论(0)    收藏  举报