17611538698
webmaster@21cto.com

构建自己的 RAG 应用程序:使用 Ollama、Python 和 ChromaDB 在本地设置 LLM 之分步指南

人工智能 1 884 2024-07-08 09:22:52

图片

在数据隐私至关重要的时代,建立自己的本地大语言模型 (LLM),为公司和个人提供了至关重要的解决方案。本教程目标在指导您完成使用Ollama、Python 3和ChromaDB创建自定义聊天机器人的过程,所有这些都托管在您的系统本地。

图片

以下是需要本教程的主要原因:

  • 完全自定义:在本地托管您自己的检索增强生成 (RAG) 应用程序意味着您可以完全控制设置和自定义。您可以微调模型以满足您的特定需求,而无需依赖外部服务。

  • 增强隐私:通过在本地设置 LLM 模型,您可以避免通过互联网发送敏感数据所带来的风险。这对于处理机密信息的公司尤其重要。在本地使用私人数据训练模型可确保您的数据处于您的控制范围内。

  • 数据安全:使用第三方 LLM 模型可能会使您的数据面临潜在的泄露和滥用风险。本地部署可将您的训练数据(例如 PDF 文档)保存在安全环境中,从而降低这些风险。

  • 控制数据处理:当您托管自己的 LLM 时,您可以按照自己的意愿管理和处理数据。这包括将您的私人数据嵌入到 ChromaDB 矢量存储中,确保您的数据处理符合您的标准和要求。

  • 不依赖互联网连接:在本地运行聊天机器人意味着您不依赖互联网连接。这可确保聊天机器人的服务和访问不间断,即使在离线情况下也是如此。


本教程将帮助您构建一个强大且安全的本地聊天机器人,以满足您的个性化需求,同时不影响隐私或控制。

图片

微调模型

检索增强生成 (RAG)


检索增强生成 (RAG)是一种先进的技术,它结合了信息检索和文本生成的优势,可以创建更准确、更符合上下文的响应。下面详细介绍了 RAG 的工作原理及其优势:


什么是 RAG?


RAG 是一种混合模型,它通过整合外部知识库或文档存储来增强语言模型的功能。该过程涉及两个主要部分:

  • 检索:在此阶段,模型根据输入查询从外部源(例如数据库或向量存储)检索相关文档或信息。

  • 生成:然后,生成语言模型使用检索到的信息来产生连贯且适合上下文的响应。


RAG 如何工作?


  • 查询输入:用户输入查询或问题。

  • 文档检索:系统使用查询来搜索外部知识库,检索最相关的文档或信息片段。

  • 响应生成:生成模型处理检索到的信息,并将其与自身的知识相结合以生成详细而准确的响应。

  • 输出:最终响应,丰富了知识库中的具体和相关细节,呈现给用户。


RAG 的优势


  • 增强准确性:通过利用外部数据,RAG 模型可以提供更精确、更详细的答案,尤其是针对特定领域的查询。

  • 上下文相关性:检索组件确保生成的响应基于相关和最新的信息,从而提高响应的整体质量。

  • 可扩展性:RAG 系统可以轻松扩展以包含大量数据,从而使其能够处理各种查询和主题。

  • 灵活性:这些模型只需更新或扩展外部知识库即可适应各个领域,因此具有很高的通用性。


为什么在本地使用 RAG?


  • 隐私和安全:在本地运行 RAG 模型可确保敏感数据保持安全和私密,因为它不需要发送到外部服务器。

  • 定制:您可以定制检索和生成过程以满足您的特定需求,包括集成专有数据源。

  • 独立性:本地设置可确保您的系统即使在没有互联网连接的情况下仍能运行,提供一致、可靠的服务。


通过使用 Ollama、Python 和 ChromaDB 等工具设置本地 RAG 应用程序,您可以享受高级语言模型的好处,同时保持对数据和自定义选项的控制。

图片

RAG 应用程序

图形处理器


运行大型语言模型 (LLM)(如检索增强生成 (RAG) 中使用的模型)需要强大的计算能力。在这些模型中,实现高效处理和嵌入数据的关键组件之一是图形处理单元 (GPU)。


以下是 GPU 对这项任务至关重要的原因以及它们如何影响本地 LLM 设置的性能:


什么是 GPU?


GPU 是一种专用处理器,旨在加速图像和视频的渲染。与针对顺序处理任务进行优化的中央处理器 (CPU) 不同,GPU 擅长并行处理。这使得它们特别适合机器学习和深度学习模型所需的复杂数学计算。


为什么 GPU 对 LLM 如此重要


  • 并行处理能力:GPU 可以同时处理数千个操作,大大加快了 LLM 中的训练和推理等任务的速度。这种并行性对于处理大型数据集和实时生成响应相关的繁重计算负载至关重要。

  • 处理大型模型的效率:像 RAG 中使用的 LLM 需要大量内存和计算资源。GPU 配备了高带宽内存 (HBM) 和多个内核,使其能够管理这些模型所需的大规模矩阵乘法和张量运算。

  • 更快的数据嵌入和检索:在本地 RAG 设置中,将数据嵌入到 ChromaDB 等矢量存储中并快速检索相关文档对于性能至关重要。高性能 GPU 可以加速这些过程,确保您的聊天机器人及时准确地做出响应。

  • 缩短训练时间:训练 LLM 需要调整数百万(甚至数十亿)个参数。与 CPU 相比,GPU 可以大幅缩短此训练阶段所需的时间,从而可以更频繁地更新和优化模型。


选择正确的 GPU


设置本地 LLM 时,GPU 的选择会显著影响性能。以下是一些需要考虑的因素:

  • 内存容量:模型越大,需要的 GPU 内存就越多。寻找具有更高 VRAM(视频 RAM)的 GPU,以容纳大量数据集和模型参数。

  • 计算能力:GPU 拥有的 CUDA 核心越多,它处理并行处理任务的能力就越强。计算能力越强的 GPU 执行深度学习任务的效率就越高。

  • 带宽:更高的内存带宽允许 GPU 和其内存之间更快地传输数据,从而提高整体处理速度。


适用于 LLM 的高性能 GPU 示例


  • NVIDIA RTX 3090:以其高 VRAM(24 GB)和强大的 CUDA 核心而闻名,它是深度学习任务的热门选择。

  • NVIDIA A100:专为 AI 和机器学习而设计,具有大内存容量和高计算能力,性能卓越。

  • AMD Radeon Pro VII:另一个强有力的竞争者,具有高内存带宽和高效的处理能力。


投资高性能 GPU 对于在本地运行 LLM 模型至关重要。它可确保更快的数据处理、高效的模型训练和快速的响应生成,从而使您的本地 RAG 应用程序更加强大和可靠。通过利用 GPU 的强大功能,您可以充分实现托管自己的自定义聊天机器人的好处,这些聊天机器人可根据您的特定需求和数据隐私要求量身定制。


先决条件


在进行设置之前,请确保您已满足以下先决条件:

  • Python 3:Python 是一种多功能编程语言,您可以使用它来编写 RAG 应用程序的代码。

  • ChromaDB:一个用于存储和管理我们数据嵌入的矢量数据库。

  • Ollama:在我们的本地机器上下载并提供定制化的开源 LLM。


步骤 1:安装 Python 3 并设置环境


要安装和设置我们的 Python 3 环境,请按照以下步骤操作:在您的机器上下载并设置 Python 3。然后确保您的 Python 3 安装并成功运行:

$ python3 --version# Python 3.11.7


为您的项目创建一个新文件夹,例如 local-rag:

$ mkdir local-rag$ cd local-rag


创建一个名为的虚拟环境 venv:

$ python3 -m venv venv


激活虚拟环境:

$ source venv/bin/activate# Windows# venv\Scripts\activate


第 2 步:安装 ChromaDB 和其他依赖项

使用 pip 安装 ChromaDB:

$ pip install --q chromadb


安装 Langchain 工具以便与您的模型无缝协作:

$ pip install --q unstructured langchain langchain-text-splitters$ pip install --q "unstructured[all-docs]"


安装 Flask 以将您的应用作为 HTTP 服务提供:

$ pip install --q flask


步骤3:安装Ollama


要安装 Ollama,请按照以下步骤操作:


前往Ollama 下载页面,然后下载适用于您的操作系统的安装程序。


图片

通过运行以下命令验证您的 Ollama 安装:


$ ollama --version
# ollama version is 0.1.47


拉取您需要的 LLM 模型。例如,要使用 Mistral 模型:

$ ollama pull mistral

拉取文本嵌入模型。例如,要使用 Nomic Embed Text 模型:

$ ollama pull nomic-embed-text
然后运行属于你的 Ollama 模型:
$ ollama serve


构建 RAG 应用程序


现在您已经使用 Python、Ollama、ChromaDB 和其他依赖项设置了环境,现在是时候构建自定义本地 RAG 应用了。在本节中,我们将介绍实际的 Python 代码,并概述如何构建应用程序。


app.py


这是 Flask 应用程序的主文件。它定义了将文件嵌入到矢量数据库以及从模型中检索响应的路由。

import osfrom dotenv import load_dotenv
load_dotenv()
from flask import Flask, request, jsonifyfrom embed import embedfrom query import queryfrom get_vector_db import get_vector_db
TEMP_FOLDER = os.getenv('TEMP_FOLDER', './_temp')os.makedirs(TEMP_FOLDER, exist_ok=True)
app = Flask(__name__)
@app.route('/embed', methods=['POST'])def route_embed(): if 'file' not in request.files: return jsonify({"error": "No file part"}), 400
file = request.files['file']
if file.filename == '': return jsonify({"error": "No selected file"}), 400
embedded = embed(file)
if embedded: return jsonify({"message": "File embedded successfully"}), 200
return jsonify({"error": "File embedded unsuccessfully"}), 400
@app.route('/query', methods=['POST'])def route_query(): data = request.get_json() response = query(data.get('query'))
if response: return jsonify({"message": response}), 200
return jsonify({"error": "Something went wrong"}), 400
if __name__ == '__main__': app.run(host="0.0.0.0", port=8080, debug=True)


embed.py


该模块处理嵌入过程,包括保存上传的文件、加载和分割数据以及将文档添加到矢量数据库。


import os
from datetime import datetimefrom werkzeug.utils import secure_filenamefrom langchain_community.document_loaders import UnstructuredPDFLoaderfrom langchain_text_splitters import RecursiveCharacterTextSplitterfrom get_vector_db import get_vector_db
TEMP_FOLDER = os.getenv('TEMP_FOLDER', './_temp')
# Function to check if the uploaded file is allowed (only PDF files)def allowed_file(filename): return '.' in filename and filename.rsplit('.', 1)[1].lower() in {'pdf'}
# Function to save the uploaded file to the temporary folderdef save_file(file): # Save the uploaded file with a secure filename and return the file path ct = datetime.now() ts = ct.timestamp() filename = str(ts) + "_" + secure_filename(file.filename) file_path = os.path.join(TEMP_FOLDER, filename) file.save(file_path)
return file_path
# Function to load and split the data from the PDF filedef load_and_split_data(file_path): # Load the PDF file and split the data into chunks loader = UnstructuredPDFLoader(file_path=file_path) data = loader.load() text_splitter = RecursiveCharacterTextSplitter(chunk_size=7500, chunk_overlap=100) chunks = text_splitter.split_documents(data)
return chunks
# Main function to handle the embedding processdef embed(file): # Check if the file is valid, save it, load and split the data, add to the database, and remove the temporary file if file.filename != '' and file and allowed_file(file.filename): file_path = save_file(file) chunks = load_and_split_data(file_path) db = get_vector_db() db.add_documents(chunks) db.persist() os.remove(file_path)
return True
return False


query.py


该模块通过生成查询的多个版本、检索相关文档并根据上下文提供答案来处理用户查询。


import os
from langchain_community.chat_models import ChatOllamafrom langchain.prompts import ChatPromptTemplate, PromptTemplatefrom langchain_core.output_parsers import StrOutputParserfrom langchain_core.runnables import RunnablePassthroughfrom langchain.retrievers.multi_query import MultiQueryRetrieverfrom get_vector_db import get_vector_db
LLM_MODEL = os.getenv('LLM_MODEL', 'mistral')
# Function to get the prompt templates for generating alternative questions and answering based on contextdef get_prompt(): QUERY_PROMPT = PromptTemplate( input_variables=["question"], template="""You are an AI language model assistant. Your task is to generate five different versions of the given user question to retrieve relevant documents from a vector database. By generating multiple perspectives on the user question, your goal is to help the user overcome some of the limitations of the distance-based similarity search. Provide these alternative questions separated by newlines. Original question: {question}""", )
template = """Answer the question based ONLY on the following context: {context} Question: {question} """
prompt = ChatPromptTemplate.from_template(template)
return QUERY_PROMPT, prompt
# Main function to handle the query processdef query(input): if input: # Initialize the language model with the specified model name llm = ChatOllama(model=LLM_MODEL) # Get the vector database instance db = get_vector_db() # Get the prompt templates QUERY_PROMPT, prompt = get_prompt()
# Set up the retriever to generate multiple queries using the language model and the query prompt retriever = MultiQueryRetriever.from_llm( db.as_retriever(), llm, prompt=QUERY_PROMPT )
# Define the processing chain to retrieve context, generate the answer, and parse the output chain = ( {"context": retriever, "question": RunnablePassthrough()} | prompt | llm | StrOutputParser() )
response = chain.invoke(input)
return response
return None



get_vector_db.py


该模块初始化并返回用于存储和检索文档嵌入的向量数据库实例。

import osfrom langchain_community.embeddings import OllamaEmbeddingsfrom langchain_community.vectorstores.chroma import Chroma
CHROMA_PATH = os.getenv('CHROMA_PATH', 'chroma')COLLECTION_NAME = os.getenv('COLLECTION_NAME', 'local-rag')TEXT_EMBEDDING_MODEL = os.getenv('TEXT_EMBEDDING_MODEL', 'nomic-embed-text')
def get_vector_db(): embedding = OllamaEmbeddings(model=TEXT_EMBEDDING_MODEL,show_progress=True)
db = Chroma( collection_name=COLLECTION_NAME, persist_directory=CHROMA_PATH, embedding_function=embedding )
return db


现在,运行你的应用程序!

创建.env文件来存储您的环境变量:

TEMP_FOLDER = './_temp'CHROMA_PATH = 'chroma'COLLECTION_NAME = 'local-rag'LLM_MODEL = 'mistral'TEXT_EMBEDDING_MODEL = 'nomic-embed-text'


运行该app.py文件来启动你的应用服务器:

$ python3 app.py


服务器运行后,您可以开始向以下端点发出请求:

  • 嵌入 PDF 文件 (例如 resume.pdf) 的示例命令:


$ curl --request POST \  --url http://localhost:8080/embed \  --header 'Content-Type: multipart/form-data' \  --form file=@/Users/nassermaronie/Documents/Nasser-resume.pdf
# Response{ "message": "File embedded successfully"}向模型提出问题的示例命令:
$ curl --request POST \ --url http://localhost:8080/query \ --header 'Content-Type: application/json' \ --data '{ "query": "Who is Nasser?" }'
# Response{ "message": "Nasser Maronie is a Full Stack Developer with experience in web and mobile app development. He has worked as a Lead Full Stack Engineer at Ulventech, a Senior Full Stack Engineer at Speedoc, a Senior Frontend Engineer at Irvins, and a Software Engineer at Tokopedia. His tech stacks include Typescript, ReactJS, VueJS, React Native, NodeJS, PHP, Golang, Python, MySQL, PostgresQL, MongoDB, Redis, AWS, Firebase, and Supabase. He has a Bachelor's degree in Information System from Universitas Amikom Yogyakarta."}


结语

按照这些说明,您可以根据自己的需求使用 Python、Ollama 和 ChromaDB 有效地运行自定义本地 RAG 应用并与之交互。根据需要调整和扩展功能以增强应用程序的功能。

通过利用本地部署的功能,您不仅可以保护敏感信息,还可以优化性能和响应能力。无论您是增强客户互动还是简化内部流程,本地部署的 RAG 应用程序都能提供灵活性和稳健性,以适应和满足您的需求。

各位同学可以检查这个 repo 中的开源代码:

https://github.com/firstpersoncode/local-rag

祝各位编码愉快!

作者:校长

评论