在自然语言处理和人工智能领域,检索增强生成(RAG)是一种强大的技术,它结合了检索和生成能力,以提供更准确、更有针对性的回答。本文将详细介绍如何使用豆包相关工具来创建 RAG,让我们逐步深入了解这个过程。(文章末尾有完整代码)
准备工作
在开始之前,确保已经安装了必要的库:
langchain_community
:提供了各种与语言处理相关的工具和类,如向量存储、文本分割等。openai
:用于与 OpenAI 的 API 进行交互。
构建豆包 Embeddings
首先,我们需要创建一个用于生成豆包 Embeddings 的类。这个类将与 OpenAI 的 API 进行交互,以生成文本的向量表示。
注意:DoubaoEmbeddings中只用的是Embedding模型,非通用模型
# 构建豆包Embeddings
class DoubaoEmbeddings():
client: OpenAI = None
api_key: str = "<your api key>"
model: str = "<your model>"
def __init__(self, **data: any):
super().__init__(**data)
if self.api_key == "":
self.api_key = os.environ["OPENAI_API_KEY"]
self.client = OpenAI(
api_key=self.api_key,
base_url='<your base url>'
)
def embed_query(self, text: str) -> List[float]:
"""
生成输入文本的embedding.
Args:
texts (str): 要生成embedding的文本.
Return:
embeddings (List[float]): 输入文本的embedding,一个浮点数值列表.
"""
embeddings = self.client.embeddings.create(
model=self.model,
input=text,
encoding_format="float"
)
return embeddings.data[0].embedding
def embed_documents(self, texts: List[str]) -> List[List[float]]:
return [self.embed_query(text) for text in texts]
class Config:
arbitrary_types_allowed = True
在这个类中,我们定义了__init__
方法来初始化 OpenAI 客户端,以及embed_query
和embed_documents
方法来生成单个文本和多个文本的 Embeddings。
加载和切割文本
接下来,我们需要加载文本数据,并将其切割成较小的块,以便于处理。 文本内容如下:
曾国藩虽然是湘乡人,但他的祖籍却在衡阳。他在《大界墓表》中叙述其先世源流说:“府君之先,六世祖曰孟学,初迁湘乡者也。曾祖曰元吉,别立祀典者也。祖曰辅仁,考竟希。”这里的府君指的是祖父曾玉屏。从曾玉屏算起,他的第六世祖曾孟学始迁湘乡,具体时间不详,曾国藩自己也只说“国初徙湘乡”。以曾玉屏为基准,其上六世,至曾国藩又三世,共九世,据此推算,曾孟学迁湘乡应该在康熙初年。湘乡曾氏虽然自曾孟学始迁,但曾国藩的先辈们立祀的始祖却不是曾孟学,而是曾孟学的孙子曾应贞。曾国藩在其所作《祖四世元吉公墓铭》中说明其事由并墓铭的缘起说:“道光岁戊申,家叔父为太高祖考妣置祠宇。其明年,又为修其坟域。乃邮书于京师,命国藩记其原委。”然则“家叔父”何以不为始迁的六世祖立祠而为中间的四世祖立祠呢?曾国藩在墓铭中述其原委说:“公讳应贞,字元吉,迁湘四世祖也。少贫,手致数千金产,室庐数处,尽以予其子,而自置衡邑之靛塘湾田四十亩以老焉。公殁后子孙岁分其租以为常。至嘉庆岁丁巳,家祖及族长尊三、以彰二公,纠族之人议,积一岁之租以为公清明之祀,今所置圳上之田是也,家叔父所修祠宇在焉。”原来曾应贞是湘乡曾氏第一个发迹的人。因为他生前为自己留了四十亩养老田,死后年积月累,于是就有了祭祀立祠、修墓的物质基础。其六世祖曾孟学因为没有这样的基础,也就只好诸事阙如了。
曾应贞因为由贫致富,“手致数千金产”,不仅自己能安度晚年,死后又有专祠祭享,而且六个儿子也各人分得一份可观的财产。虽然如此,但也没有谁想到要读书入仕。不仅湘乡曾氏如此,衡阳曾氏也不例外,所以曾国藩在《台洲墓表》中说:“吾曾氏由衡阳至湘乡五六百载,曾无人与于科目秀才之列,至是乃若创获,何其难也。自国初徙湘乡,累世力农,至我王考星冈府君,乃大以不学为耻。讲求礼制,宾接文士,教督我考府君,穷年磨砺,期于有成。”由此可知,曾应贞是湘乡曾氏经济基业的开创者,而曾玉屏则是该宗族思想文化的开创者,尽管他的开创仍然带有相当浓厚的土财主气息。
代码如下:
# 加载文本
loader = TextLoader("./zengguofan.txt")
documents = loader.load()
# 切割文本
text_splitter = CharacterTextSplitter(chunk_size=300, chunk_overlap=50)
chunks = text_splitter.split_documents(documents)
这里使用TextLoader
从指定文件加载文本,然后使用CharacterTextSplitter
将文本切割成大小为 300 个字符,重叠部分为 50 个字符的块。
生成 Embeddings 并存储向量
有了切割后的文本块,我们可以使用之前定义的DoubaoEmbeddings
类来生成它们的 Embeddings,并将这些向量存储到向量数据库中。
# 生成豆包Embeddings
embeddings = DoubaoEmbeddings()
# 存储向量后的数据
vectorstore = Chroma.from_documents(chunks, embeddings)
这一步将为每个文本块生成向量表示,并使用Chroma
向量数据库存储这些向量。
从向量数据库中检索相关文档
为了能够根据用户的问题检索相关的文档,我们需要创建一个检索器。
# 从vectorstore中检索相关文档对象
retriever = vectorstore.as_retriever()
这个检索器可以根据用户输入的问题从向量数据库中检索出相关的文档。
构建豆包问答
现在,我们需要构建一个问答模板,用于根据检索到的文档生成回答。
# 构建豆包问答
template = """
以下问题基于提供的context,分析问题并给出合理的建议回答:
<context>
{context}
</context>
Question: {input}
"""
prompt = ChatPromptTemplate.from_template(template)
这个模板将接受检索到的文档作为context
,以及用户的问题作为input
,并生成一个用于提问的提示。
调用豆包 OpenAI
为了生成回答,我们需要调用 OpenAI 的语言模型。
# 调用豆包OpenAI
llm = ChatOpenAI(
openai_api_key="<your api key>",
openai_api_base="<your base url>",
model_name="<your model>"
)
这里配置了ChatOpenAI
,指定了 API 密钥、API 基础地址和模型名称。
构建豆包问答链
最后,我们将所有的组件组合在一起,构建一个完整的问答链。
# 构建豆包问答链
rag_chain = (
{"context": retriever, "input": RunnablePassthrough()}
| prompt
| llm
| StrOutputParser()
)
这个问答链首先使用检索器获取相关文档,然后将其与用户问题一起传递给提示模板,再将提示传递给语言模型,最后使用StrOutputParser
解析语言模型的输出。
调用豆包问答链
现在,我们可以使用构建好的问答链来回答用户的问题了。
# 调用豆包问答链
query = "曾国藩祖籍在哪里"
print(rag_chain.invoke(query))
通过以上步骤,我们成功地使用豆包相关工具创建了一个 RAG 系统,它可以根据用户的问题从文本数据中检索相关信息,并生成准确、有针对性的回答。
希望本文对你理解和使用豆包创建 RAG 有所帮助。在实际应用中,你可以根据自己的需求和数据特点对代码进行进一步的优化和扩展。
完整代码
注意:DoubaoEmbeddings中只用的是Embedding模型,非通用模型
from langchain_community.vectorstores import Chroma
from langchain.text_splitter import CharacterTextSplitter
from openai import OpenAI
from langchain_community.document_loaders import TextLoader
import os
from typing import List
from langchain_core.prompts import ChatPromptTemplate
from langchain.schema.runnable import RunnablePassthrough
from langchain.schema.output_parser import StrOutputParser
from langchain_openai import ChatOpenAI
# 构建豆包Embeddings
class DoubaoEmbeddings():
client: OpenAI = None
api_key: str = "<your api key>"
model: str = "<your model>"
def __init__(self, **data: any):
super(). __init__(**data)
if self.api_key == "":
self.api_key = os.environ["OPENAI_API_KEY"]
self.client = OpenAI(
api_key = self.api_key,
base_url='<your base url>'
)
def embed_query(self, text: str) -> List[float]:
"""
生成输入文本的 embedding.
Args:
texts (str): 要生成 embedding 的文本.
Return:
embeddings (List[float]): 输入文本的 embedding,一个浮点数值列表.
"""
embeddings = self.client.embeddings.create(
model=self.model,
input=text,
encoding_format="float"
)
return embeddings.data[0].embedding
def embed_documents(self, texts: List[str]) -> List[List[float]]:
return [self.embed_query(text) for text in texts]
class Config:
arbitrary_types_allowed = True
# 加载文本
loader = TextLoader("./zengguofan.txt")
documents = loader.load()
# 切割文本
text_splitter = CharacterTextSplitter(chunk_size=300, chunk_overlap=50)
chunks = text_splitter.split_documents(documents)
# 生成豆包Embeddings
embeddings = DoubaoEmbeddings()
# embeded_result = embeddings.embed_query("曾国藩祖籍在哪里")
# print(embeded_result)
# 存储向量后的数据
vectorstore = Chroma.from_documents(chunks, embeddings)
# 从vectorstore中检索相关文档对象
retriever = vectorstore.as_retriever()
#docs_result = retriever.get_relevant_documents("曾国藩祖籍在哪里")
#print(docs_result)
# 构建豆包问答
template = """
以下问题基于提供的 context,分析问题并给出合理的建议回答:
<context>
{context}
</context>
Question: {input}
"""
prompt = ChatPromptTemplate.from_template(template)
# 调用豆包OpenAI
llm = ChatOpenAI(
openai_api_key="<your api key>",
openai_api_base="<your base url>",
model_name="<your model>"
)
# 构建豆包问答链
rag_chain = (
{"context": retriever, "input": RunnablePassthrough()}
| prompt
| llm
| StrOutputParser()
)
# 调用豆包问答链
query = "曾国藩祖籍在哪里"
print(rag_chain.invoke(query))