坚强的帅 發表於 2026-1-24 16:22:00

langchain 快速入门(三):搭建RAG知识库

<h1 id="简介">简介</h1>
<p>LLM大模型一般训练的数据都是滞后的,这是就需要用到RAG知识库,RAG知识库可以降低大模型在输出答案时的幻觉,也能够让大模型知识拓展。</p>
<h1 id="知识库架构知识">知识库架构知识</h1>
<h3 id="检索流程图">检索流程图</h3>
<pre><code>用户输入 (User Query)
             |
             v
    +-----------------------+
    |   提示词 (Prompt)      |
    +-----------------------+
             |
             | (1) 转化为向量 (Embedding)
             v
    +-----------------------+
    |   文字向量模型 (EMB)    |
    +-----------------------+
             |
             | (2) 相似度检索 (Search)
             v
    +-----------------------+         +-----------------------+
    |RAG 向量数据库 (DB)    | &lt;-----&gt; |   本地知识库/文档集   |
    +-----------------------+         +-----------------------+
             |
             | (3) 召回相关片段 (Context)
             v
    +-----------------------+
    |   大语言模型 (LLM)      | &lt;--- (将提示词与背景片段拼接)
    +-----------------------+
             |
             | (4) 最终生成 (Generation)
             v
    +-----------------------+
    |      输出结果          |
    +-----------------------+
</code></pre>
<h3 id="rag原理解析">RAG原理解析</h3>
<p>构建知识库的流程如下:<br>
<strong>文档内容切片-&gt;文字向量化-&gt;向量数据库</strong></p>
<h5 id="文档内容切片">文档内容切片</h5>
<p>LLM 有上下文长度限制,且向量检索在短文本上更精确。<br>
文本切片方法有:</p>
<ol>
<li>按字数切片</li>
<li>按句切片</li>
<li>递归切片<br>
不管是哪个切片方法,目的是保留语义的完整性,因此<strong>不是</strong>某个好或某个坏,根据<strong>实际需求</strong>进行选择。</li>
</ol>
<h5 id="文字向量化">文字向量化</h5>
<p>将每个切片文字块转换为关系向量</p>
<blockquote>
<p><strong>关系向量是怎么来的?</strong> 这些是通过对模型进行大量的上下文训练,得到的词语与词语,句子与句子的语义关系上的关联,这是LLM的基础。<br>
<mark>不同的文字向量模型输出的向量矩阵也会不同,因此在构建和检索时需要用同一个文字向量模型进行转换。</mark></p>
</blockquote>
<h5 id="向量数据库">向量数据库</h5>
<p>跟平时用的MySql,MS sql等本质上差不多,只不过将文本换成了向量矩阵的增删改查<br>
检索的方法(这里就列举几个):</p>
<ol>
<li>两个向量的余弦角大小</li>
<li>两个向量的欧氏举例</li>
<li>两个向量的分类区域</li>
</ol>
<blockquote>
<p><strong>用文本也能搜索,为什么要用向量呢?</strong> 文本的数据库检索一般通过正则搜索,这种无法关联语义进行检索,因为我们无法保证大模型在检索时能够按照固定的格式。举个例子:</p>
<p>数据库中存储:小明一个月工资3000元<br>
搜索:小明这个月多少钱<br>
正则搜索会直接略过,而向量搜索就能够搜到。</p>
</blockquote>
<h1 id="构建rag知识库">构建RAG知识库</h1>
<p>本次都利用千问的语言模型,和FAISS向量数据库演示,来演示</p>
<h3 id="案例">案例</h3>
<pre><code class="language-python">from operator import itemgetter
import os
from langchain_community.chat_models.tongyi import ChatTongyi
from langchain_community.vectorstores import FAISS
from langchain_community.embeddings import DashScopeEmbeddings
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.documents import Document
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser
from langchain_core.messages import HumanMessage, AIMessage
from sqlalchemy import true

#apikey
os.environ["DASHSCOPE_API_KEY"] = "你的apikey"
FAISS_PATH = "my_faiss_index"

raw_text = """
【公司内部机密:代号“深蓝计划”】
1. 项目目标:开发一款能听懂猫语的翻译器。
2. 核心技术:基于Transformer的“喵声波”分析算法。
3. 团队暗号:如果有人问“今天天气怎么样?”,必须回答“我想吃鱼”。
4. 截止日期:2026年12月31日。
5. 经费预算:仅剩50元人民币,主要用于购买猫条。
"""

#初始化文本
docs =
text_splitter = RecursiveCharacterTextSplitter(chunk_size=100, chunk_overlap=20)
split_docs = text_splitter.split_documents(docs)

#初始化模型
llm = ChatTongyi(model="qwen-plus")
embeddings = DashScopeEmbeddings(model="text-embedding-v1")

#创建向量数据库
if os.path.exists(FAISS_PATH):
&nbsp; &nbsp; print("向量数据库已存在")
&nbsp; &nbsp; ragdb = FAISS.load_local(FAISS_PATH, embeddings, allow_dangerous_deserialization=True)
else:
&nbsp; &nbsp; print("创建向量数据库")
&nbsp; &nbsp; ragdb = FAISS.from_documents(split_docs, embeddings)
&nbsp; &nbsp; ragdb.save_local(FAISS_PATH)

#构建提示词chain
def format_docs(docs):
&nbsp; &nbsp; return "\n\n".join(doc.page_content for doc in docs)

final_prompt = ChatPromptTemplate.from_messages([
&nbsp; &nbsp; ("system", """
&nbsp; &nbsp; 你是一个专业的问答助手,你的任务是根据上下文简洁的回答用户的问题。
&nbsp; &nbsp; &lt;context&gt;
&nbsp; &nbsp; {context}
&nbsp; &nbsp; &lt;/context&gt;
&nbsp; &nbsp; """),
&nbsp; &nbsp; MessagesPlaceholder(variable_name="history"),
&nbsp; &nbsp; ("human", "{input}")
])

chain = (
&nbsp; &nbsp; #查询rag
&nbsp; &nbsp; RunnablePassthrough.assign(
&nbsp; &nbsp; &nbsp; &nbsp; context = itemgetter("input") | ragdb.as_retriever() | format_docs
&nbsp; &nbsp; )
&nbsp; &nbsp; | RunnablePassthrough.assign(
&nbsp; &nbsp; &nbsp; &nbsp; answer = {"input":itemgetter("input"), "context":itemgetter("context"), "history":itemgetter("history")} | final_prompt | llm | StrOutputParser()
&nbsp; &nbsp; )
)

history = []

while true:
&nbsp; &nbsp; input_q = input("我:")

&nbsp; &nbsp; respond = chain.invoke({
&nbsp; &nbsp; &nbsp; &nbsp; "input": input_q,
&nbsp; &nbsp; &nbsp; &nbsp; "history": history})

&nbsp; &nbsp; print("answer:" + respond["answer"])
&nbsp; &nbsp; print("=="*30)
&nbsp; &nbsp;
&nbsp; &nbsp; history.append(HumanMessage(content=input_q))
&nbsp; &nbsp; history.append(AIMessage(content=respond['answer']))
</code></pre>
<h3 id="代码解释">代码解释</h3>
<p>代码的流程如下:</p>
<ol>
<li>初始化RAG:文本切片-&gt;文本向量模型-&gt;构建向量数据库</li>
<li>询问ai:提示词-&gt;文本向量模型-&gt;向量数据库检索-&gt;组合prompt-&gt;喂给LLM-&gt;回答问题-&gt;记录历史对话</li>
</ol>
<h5 id="文本切片">文本切片</h5>
<pre><code class="language-python">docs =
text_splitter = RecursiveCharacterTextSplitter(chunk_size=100, chunk_overlap=20)
split_docs = text_splitter.split_documents(docs)
</code></pre>
<p>这里利用了langchain提供的文本分词器<code>RecursiveCharacterTextSplitter</code>(递归分词)</p>
<h5 id="构建向量数据库">构建向量数据库</h5>
<pre><code class="language-python">llm = ChatTongyi(model="qwen-plus")
embeddings = DashScopeEmbeddings(model="text-embedding-v1")

#创建向量数据库
if os.path.exists(FAISS_PATH):
&nbsp; &nbsp; print("向量数据库已存在")
&nbsp; &nbsp; ragdb = FAISS.load_local(FAISS_PATH, embeddings, allow_dangerous_deserialization=True)
else:
&nbsp; &nbsp; print("创建向量数据库")
&nbsp; &nbsp; ragdb = FAISS.from_documents(split_docs, embeddings)
&nbsp; &nbsp; ragdb.save_local(FAISS_PATH)
</code></pre>
<p><mark>这部分要注意:新版FAISS读取现有数据库要设置:allow_dangerous_deserialization=True,不然会报错</mark></p>
<h5 id="提示词模板">提示词模板</h5>
<pre><code class="language-python">final_prompt = ChatPromptTemplate.from_messages([
&nbsp; &nbsp; ("system", """
&nbsp; &nbsp; 你是一个专业的问答助手,你的任务是根据上下文简洁的回答用户的问题。
&nbsp; &nbsp; &lt;context&gt;
&nbsp; &nbsp; {context}
&nbsp; &nbsp; &lt;/context&gt;
&nbsp; &nbsp; """),
&nbsp; &nbsp; MessagesPlaceholder(variable_name="history"),
&nbsp; &nbsp; ("human", "{input}")
])
</code></pre>
<p>之前没有讲到历史对话记录,这次补充下:</p>
<blockquote>
<p><code>MessagesPlaceholder</code>这个是langchain框架的占位符(其实是框架写好了prompt模板,告诉ai这个是历史对话),使用时将历史对话记录的数组放在这里设置的字段中,在添加历史对话时要使用相关的类进行声明对话(告诉ai这句话是ai说的还是用户说的)</p>
<pre><code>history.append(HumanMessage(content=input_q))
history.append(AIMessage(content=respond['answer']))
</code></pre>
</blockquote>
<h5 id="chain链的解释核心逻辑">Chain链的解释(核心逻辑)</h5>
<pre><code class="language-python">chain = (
&nbsp; &nbsp; #查询rag
&nbsp; &nbsp; RunnablePassthrough.assign(
&nbsp; &nbsp; &nbsp; &nbsp; context = itemgetter("input") | ragdb.as_retriever() | format_docs
&nbsp; &nbsp; )
&nbsp; &nbsp; | RunnablePassthrough.assign(
&nbsp; &nbsp; &nbsp; &nbsp; answer = {"input":itemgetter("input"), "context":itemgetter("context"), "history":itemgetter("history")} | final_prompt | llm | StrOutputParser()
&nbsp; &nbsp; )
)
</code></pre>
<p>Chain链流程:</p>
<ol>
<li>查询RAG的chain:获取<code>input</code>字段-&gt;内容交给向量数据库检索-&gt;将检索的内容(数组)转换为字符串格式-&gt;保存到<code>context</code>字段并传递给下一个任务</li>
<li>询问LLM的chain:获取<code>input</code>,<code>context</code>,<code>history</code>字段-&gt;填充上面定义的prompt模板-&gt;喂给LLM模型-&gt;解析成文本并保存在<code>answer</code>字段</li>
</ol>
<blockquote>
<p><code>itemgetter</code>是获取上一个任务传递过来的字段内容。</p>
</blockquote>
<p><strong>如果❤喜欢❤本系列教程,就点个关注吧,后续不定期更新~</strong></p>


</div>
<div id="MySignature" role="contentinfo">
    <p>本文来自博客园,作者:ClownLMe,转载请注明原文链接:https://www.cnblogs.com/ClownLMe/p/19526904</p><br><br>
来源:https://www.cnblogs.com/ClownLMe/p/19526904
頁: [1]
查看完整版本: langchain 快速入门(三):搭建RAG知识库