从零开始学 langchain 之搭建最小的 RAG 系统

大家好,我是雨飞。RAG 可以说是 23 年以来到现在,最为火热的大模型应用技术了,很多人都有了很多经典的研究。而对于新人来说,有些代码十分复杂,导致只看表象并不理解其原理。今天,就利用 langchain 和大家一起搭建一个最简单的 RAG 系统,一起来学习一下吧。

langchain 安装

目前,langchain 的版本已经更新到 0.1.X,建议使用最新的稳定版本,不然之前的代码会出现兼容性的问题。

Retrieval | ️ LangChain

RAG 原理解析

RAG 的原理已经有很多文章都提到了,这里我们再复习一下,下面是从论文中截取的图,欢迎查看这篇原文。

Retrieval-Augmented Generation for Large Language Models: A Survey

从图中,我们进行进一步的拆解,可以看到,主要分为下面几个步骤:

1、索引建立,将文本数据通过向量化的模型导入到向量数据库进行存储

2、检索,根据用户的输入去检索最相关的 n 个片段

3、生成,将上下文和用户问题拼接成提示词,输入给大模型,得到最后的答案。

索引建立

我们使用 chroma 作为向量数据库去存储用户数据,并调用 BGE 的向量去完成向量化的操作。原始的数据,为了方便展示,使用了 markdown 格式的数据,可以直接用 textloader 进行加载。

详细代码如下:

python 复制代码
import os
from langchain.embeddings.huggingface import HuggingFaceBgeEmbeddings
from langchain_community.document_loaders import TextLoader
from langchain.prompts import ChatPromptTemplate
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.vectorstores import Chroma
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
from model_factory import yi_llm

BGE_MODEL_PATH = "BAAI/bge-large-zh"
root_dir = "./zsxq"

def extract_file_dirs(directory):
    file_paths = []
    for root, dirs, files in os.walk(directory):
        for file in files:
            if file.endswith(".md"):
                fp = os.path.join(root, file)
                file_paths.append(fp)
    return file_paths

files = extract_file_dirs(root_dir)
print(files)
loaders = [TextLoader(f) for f in files]

docs = []
for l in loaders:
    docs.extend(l.load())

text_splitter = RecursiveCharacterTextSplitter(chunk_size=150, chunk_overlap=20)
documents = text_splitter.split_documents(docs)
huggingface_bge_embedding = HuggingFaceBgeEmbeddings(model_name=BGE_MODEL_PATH)
vectorstore = Chroma.from_documents(documents, huggingface_bge_embedding, persist_directory="./vectorstore")

query="在知识星球里,怎么快速找到最有价值的内容?"
result = vectorstore.similarity_search(query, k=3)

for doc in result:
    print(doc.page_content)
    print("********")

检索

我们很容易的使用下面这个语句,将向量数据库转为检索器进行使用。然后可以调用检索器的get_relevant_documents 方法去检索得到相似的文本片段,然后就可以使用 langchain 的 LCEL 语言去调用了。

python 复制代码
retriever = vectorstore.as_retriever()
docs = retriever.get_relevant_documents(query)

生成

我们首先定义一个简单的提示词,将检索得到的上下文片段和用户的问题进行拼接,然后输入给大模型进行回答。为了方便最后对比各种方法的效果,我们使用了 StrOutputParser 去提取最后输出的文本。

代码如下:

python 复制代码
template = """Answer the question based only on the following context:

{context}

Question: {question},请用中文输出答案。
"""
prompt = ChatPromptTemplate.from_template(template)
model = yi_llm


def format_docs(docs):
    return "\n\n".join([d.page_content for d in docs])

chain = (
    {"context": retriever | format_docs, "question": RunnablePassthrough()}
    | prompt
    | model
    | StrOutputParser()
)

response = chain.invoke(query)
print(response)
print(yi_llm.invoke(query))

结果对比

RAG 输出结果

LLM 输出结果

原文

分析,从输出的结果上看,RAG 的输出命中了原文的搜索功能,但是增加了关注订阅,推荐这些原文没有提到的内容,仍然会存在幻觉。

LLM 输出的结果,看起来是对的,但实际上和原文并不相符,应该是用的自己内部的知识,也存在幻觉问题。

我们只是搭建了一个简单的示例,因此,RAG 的结果,是还有待改进的,不能立马满足我们的要求。

总代码

所有代码汇总如下,其中 yi_llm,可以参考历史的文章:

雨飞:使用 Yi-34B 和 langchain 去实现 OpenAI 的多工具调用

python 复制代码
import os
from langchain.embeddings.huggingface import HuggingFaceBgeEmbeddings
from langchain_community.document_loaders import TextLoader
from langchain.prompts import ChatPromptTemplate
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.vectorstores import Chroma
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
from model_factory import yi_llm

BGE_MODEL_PATH = "BAAI/bge-large-zh"
root_dir = "./zsxq"

def extract_file_dirs(directory):
    file_paths = []
    for root, dirs, files in os.walk(directory):
        for file in files:
            if file.endswith(".md"):
                fp = os.path.join(root, file)
                file_paths.append(fp)
    return file_paths

files = extract_file_dirs(root_dir)
print(files)
loaders = [TextLoader(f) for f in files]

docs = []
for l in loaders:
    docs.extend(l.load())

text_splitter = RecursiveCharacterTextSplitter(chunk_size=150, chunk_overlap=20)
documents = text_splitter.split_documents(docs)
huggingface_bge_embedding = HuggingFaceBgeEmbeddings(model_name=BGE_MODEL_PATH)
vectorstore = Chroma.from_documents(documents, huggingface_bge_embedding, persist_directory="./vectorstore")

query="在知识星球里,怎么快速找到最有价值的内容?"
result = vectorstore.similarity_search(query, k=3)

for doc in result:
    print(doc.page_content)
    print("********")

retriever = vectorstore.as_retriever()

template = """Answer the question based only on the following context:

{context}

Question: {question},请用中文输出答案。
"""
prompt = ChatPromptTemplate.from_template(template)
model = yi_llm


def format_docs(docs):
    return "\n\n".join([d.page_content for d in docs])

chain = (
    {"context": retriever | format_docs, "question": RunnablePassthrough()}
    | prompt
    | model
    | StrOutputParser()
)

response = chain.invoke(query)
print("RAG 输出结果:",response)

print("LLM 输出结果:",yi_llm.invoke(query).content)

雨飞同行

  • 雨飞
  • 主业是推荐算法
  • 希望通过自媒体开启自己不上班只工作的美好愿景
  • 微信:1060687688
  • 欢迎和我交朋友🫰

好了,我们第一期的代码就到这里了,有帮助欢迎点赞评论一键三连,我们下期再见。

相关推荐
是Yu欸3 分钟前
【AI视频】从单模型,到AI Agent工作流
人工智能·ai·ai作画·aigc·音视频·实时音视频
朝新_8 分钟前
【优选算法】第一弹——双指针(上)
算法
用户51914958484521 分钟前
Aniyomi扩展开发指南与Google Drive集成方案
人工智能·aigc
艾莉丝努力练剑30 分钟前
【C++STL :stack && queue (一) 】STL:stack与queue全解析|深入使用(附高频算法题详解)
linux·开发语言·数据结构·c++·算法
CoovallyAIHub1 小时前
ICLR 2026 惊现 SAM 3,匿名提交,实现“概念分割”,CV领域再迎颠覆性突破?
深度学习·算法·计算机视觉
IT古董1 小时前
【第五章:计算机视觉-计算机视觉在工业制造领域中的应用】1.工业缺陷分割-(2)BiseNet系列算法详解
算法·计算机视觉·制造
电鱼智能的电小鱼1 小时前
服装制造企业痛点解决方案:EFISH-SBC-RK3588 预测性维护方案
网络·人工智能·嵌入式硬件·算法·制造
用户5191495848451 小时前
云防护栏理论:应对云配置错误的安全防护策略
人工智能·aigc
yan8626592461 小时前
于 C++ 的虚函数多态 和 模板方法模式 的结合
java·开发语言·算法
小此方1 小时前
C语言自定义变量类型结构体理论:从初见到精通(下)
c语言·数据结构·算法