目录
前言
"检索增强生成”(RAG)系列教程3:Routing 路由模块,即怎么将用户问题连接到最相关的数据源。
一、概述
1-1、RAG概念
概念:目前的LLM通常是用很多已经存在的文字数据训练出来的。这就导致一个问题:LLM对最新的信息或者个人隐私信息不太了解,因为这些内容在训练时没有被包括进去。虽然可以通过“微调”(也就是针对特定任务再训练一下LLM)来解决这个问题,但微调成本很高,技术相对比较复杂,现在出现了一种新的方法,叫“检索增强生成”(RAG)。这个方法的思路是:从外部的数据源(比如数据库或者网页)中找到相关的资料,然后把这些资料“喂”给聊天机器人,帮助它更好地回答问题。这种方法就像是给聊天机器人提供了一个“外挂”,让它能够接触到更多的知识。
1-2、前置知识
1-2-1、ModelScopeEmbeddings 词嵌入模型
ModelScope Embeddings 是阿里巴巴达摩院推出的嵌入模型,旨在将文本、图像等数据转换为高维向量,便于机器学习模型处理。这些嵌入向量能够捕捉数据的语义信息,广泛应用于自然语言处理(NLP)、计算机视觉(CV)等领域。
安装库:
pip install modelscope
Demo:
from langchain.embeddings import ModelScopeEmbeddings
model_id = "damo/nlp_corom_sentence-embedding_english-base"
embeddings = ModelScopeEmbeddings(model_id=model_id)
text = "This is a test document."
query_result = embeddings.embed_query(text)
doc_results = embeddings.embed_documents(["foo"])
输出:
1-2-2、FAISS介绍&安装 (向量相似性搜索)
FAISS(Facebook AI Similarity Search)是由 Meta(前 Facebook)开发的一个高效相似性搜索和密集向量聚类库。它主要用于在大规模数据集中进行向量相似性搜索,特别适用于机器学习和自然语言处理中的向量检索任务。FAISS 提供了多种索引类型和算法,可以在 CPU 和 GPU 上运行,以实现高效的向量搜索。
FAISS 的主要特性
- 高效的相似性搜索:支持大规模数据集的高效相似性搜索,包括精确搜索和近似搜索。
- 多种索引类型:支持多种索引类型,如扁平索引(Flat Index)、倒排文件索引(IVF)、产品量化(PQ)等。
- GPU 加速:支持在 GPU 上运行,以加速搜索过程。
- 批量处理:支持批量处理多个查询向量,提高搜索效率。
- 灵活性:支持多种距离度量,如欧氏距离(L2)、内积(Inner Product)等。
安装:
# cpu或者是GPU版本
pip install faiss-cpu
# 或者
pip install faiss-gpu
Demo分析: 使用 LangChain 库来处理一个长文本文件,将其分割成小块,然后使用 Hugging Face 嵌入和 FAISS 向量存储来执行相似性搜索。
- CharacterTextSplitter:用于将长文本分割成小块。
- FAISS:用于创建向量数据库。
- TextLoader:用于加载文本文件。
- HuggingFaceEmbeddings:另一个用于生成文本嵌入向量的类。
from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.text_splitter import CharacterTextSplitter
from langchain.vectorstores import FAISS
from langchain.document_loaders import TextLoader
from langchain.embeddings import HuggingFaceEmbeddings
# This is a long document we can split up.
with open('./index.txt', encoding='utf-8') as f:
state_of_the_union = f.read()
text_splitter = CharacterTextSplitter(
chunk_size = 100,
chunk_overlap = 0,
)
docs = text_splitter.create_documents([state_of_the_union])
embeddings = HuggingFaceEmbeddings()
db = FAISS.from_documents(docs, embeddings)
query = "学生的表现怎么样?"
docs = db.similarity_search(query)
print(docs[0].page_content)
输出:
Notice: 查询分数,这里的分数为L2距离,因此越低越好
1-2-3、Tiktoken 分词工具
Tiktoken 是 OpenAI 开发的一个高效的分词工具,专门用于处理 GPT 系列模型(如 GPT-3、GPT-4)的文本输入和输出。它能够将自然语言文本转换为模型可以理解的 token 序列,同时支持从 token 序列还原为文本。Tiktoken 的设计目标是高效、灵活且易于集成到各种自然语言处理(NLP)任务中。
安装:
pip install tiktoken
使用:
import tiktoken
# 编码器的加载
encoder = tiktoken.get_encoding("cl100k_base")
text = "这是一个示例文本。"
# 对文本进行编码
tokens = encoder.encode(text)
print(tokens)
# 对文本进行解码
decoded_text = encoder.decode(tokens)
print(decoded_text)
二、Rag From Scratch: Routing
Rag From Scratch: Routing模块: 几种将用户查询路由到最相关的数据源的方法。
2-1、Logical routing(通过逻辑路由)
2-1-1、结构化输出
- RouteQuery: 该数据模型定义了结构化输出的格式。枚举值:“python_docs”, “js_docs”, “golang_docs”。确保LLM的输出必须是以上的一种。
from typing import Literal
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.pydantic_v1 import BaseModel, Field
from langchain_openai import ChatOpenAI
# Data model
class RouteQuery(BaseModel):
"""Route a user query to the most relevant datasource."""
datasource: Literal["python_docs", "js_docs", "golang_docs"] = Field(
...,
description="Given a user question choose which datasource would be most relevant for answering their question",
)
2-1-2、初始化LLM
- ChatOpenAI:初始化一个 LLM 实例,使用 qwen-max 模型。
- temperature=0: 控制生成文本的随机性,值为 0 时生成确定性结果。
- max_tokens=1024: 限制生成文本的最大长度。
- base_url:指定 API 的基础 URL。
- **llm.with_structured_output: ** 将LLM的输出限制为 RouteQuery的结构。
from langchain_openai import ChatOpenAI
import os
llm = ChatOpenAI(
model="qwen-max",
temperature=0,
max_tokens=1024,
timeout=None,
max_retries=2,
api_key=os.environ.get('DASHSCOPE_API_KEY'),
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
)
structured_llm = llm.with_structured_output(RouteQuery)
2-1-3、路由构建
- Prompt: 你是将用户问题路由到适当数据源的专家。根据问题所引用的编程语言,将其路由到相关的数据源。
- router: 将问题传递给structured_llm,生成结构化输出。
- choose_route: 根据结构化输出来返回对应的字符串
- full_chain: 结合 router 和 choose_route 函数,实现完整的路由和处理逻辑。
- RunnableLambda: 将一个普通的python函数包装成为一个可运行的LangChain组件,使其能够无缝地与其他LangChain组件组合在一起。(入参为Python函数)
# Prompt
system = """You are an expert at routing a user question to the appropriate data source.
Based on the programming language the question is referring to, route it to the relevant data source."""
prompt = ChatPromptTemplate.from_messages(
[
("system", system),
("human", "{question}"),
]
)
# Define router
router = prompt | structured_llm
question = """Why doesn't the following code work:
from langchain_core.prompts import ChatPromptTemplate
prompt = ChatPromptTemplate.from_messages(["human", "speak in {language}"])
prompt.invoke("french")
"""
result = router.invoke({"question": question})
print(result)
def choose_route(result):
if "python_docs" in result.datasource.lower():
### Logic here
return "chain for python_docs"
elif "js_docs" in result.datasource.lower():
### Logic here
return "chain for js_docs"
else:
### Logic here
return "golang_docs"
from langchain_core.runnables import RunnableLambda
full_chain = router | RunnableLambda(choose_route)
print(full_chain.invoke({"question": question}))
输出:
datasource=‘python_docs’
chain for python_docs
参考文章:
rag-from-scratch 官方GitHub仓库.
总结
反方向的钟🤣