主旨:问答系统搭建使用langchain要token,本文目的在于一、解析langchain调用过程,二、不使用langchain规避token,而使用本地化部署的方案方向。主要是本地向量化库的建立。
一、lagnchain问答系统实现优缺点
优点:几个python段落,方便
缺点:需要调用2次接口,需要外部模型token
系统实现:本地文档的问答
二、替代方案-逻辑验证
想法:拆解使用ai中使用token步骤,两个操作。寻找本地化替代方案即可
①问题拆解多样化,使得从不同角度解析问题,方便数据查找
②对使用文本、问题进行重组,以更人性化的方式输出(有寻找过不用这一步,但回答结果太多,不专注)
# 1、MultiQueryRetriever
# 实现:INFO:langchain.retrievers.multi_query:Generated queries: ["1. How much does YiSuXianHua's salary pay its employees?", '2. What is the wage structure at YiSuXianHua?', '3. Can you provide information on the remuneration packages offered by YiSuXianHua?']
# 2、qa_chain链建立,是传入content 即qestion涉及所有内容(而不是整个向量库传递)
三、python代码-调用ai链版本(黄佳版本)
'''欢迎来到LangChain实战课
https://time.geekbang.org/column/intro/100617601
作者 黄佳'''
import os
# os.environ["OPENAI_API_KEY"] = '你的OpenAI API Key'
from dotenv import load_dotenv # 用于加载环境变量
load_dotenv() # 加载 .env 文件中的环境变量
# 1.Load 导入Document Loaders
from langchain_community.document_loaders import PyPDFLoader
from langchain_community.document_loaders import Docx2txtLoader
from langchain_community.document_loaders import TextLoader
# 加载Documents
# base_dir = '.\OneFlower' # 文档的存放目录
base_dir='/Volumes/G/projecttest/langchain-in-action/02_文档QA系统/OneFlower'
documents = []
for file in os.listdir(base_dir):
# 构建完整的文件路径
file_path = os.path.join(base_dir, file)
if file.endswith('.pdf'):
loader = PyPDFLoader(file_path)
documents.extend(loader.load())
elif file.endswith('.docx'):
loader = Docx2txtLoader(file_path)
documents.extend(loader.load())
elif file.endswith('.txt'):
loader = TextLoader(file_path)
documents.extend(loader.load())
# 2.Split 将Documents切分成块以便后续进行嵌入和向量存储
from langchain.text_splitter import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(chunk_size=200, chunk_overlap=10)
chunked_documents = text_splitter.split_documents(documents)
# 3.Store 将分割嵌入并存储在矢量数据库Qdrant中
from langchain_community.vectorstores import Qdrant
from langchain_openai import OpenAIEmbeddings
vectorstore = Qdrant.from_documents(
documents=chunked_documents, # 以分块的文档
embedding=OpenAIEmbeddings(), # 用OpenAI的Embedding Model做嵌入
location=":memory:", # in-memory 存储
collection_name="my_documents",) # 指定collection_name
# 4. Retrieval 准备模型和Retrieval链
import logging # 导入Logging工具
from langchain_community.chat_models import ChatOpenAI # ChatOpenAI模型
from langchain.retrievers.multi_query import MultiQueryRetriever # MultiQueryRetriever工具
from langchain.chains import RetrievalQA # RetrievalQA链
# 设置Logging
logging.basicConfig()
logging.getLogger('langchain.retrievers.multi_query').setLevel(logging.INFO)
# 实例化一个大模型工具 - OpenAI的GPT-3.5
llm = ChatOpenAI(model_name="gpt-3.5-turbo", temperature=0)
# 实例化一个MultiQueryRetriever
retriever_from_llm = MultiQueryRetriever.from_llm(retriever=vectorstore.as_retriever(), llm=llm)
# 实例化一个RetrievalQA链
qa_chain = RetrievalQA.from_chain_type(llm,retriever=retriever_from_llm)
# 5. Output 问答系统的UI实现
from flask import Flask, request, render_template
app = Flask(__name__) # Flask APP
@app.route('/', methods=['GET', 'POST'])
def home():
if request.method == 'POST':
# 接收用户输入作为问题
question = request.form.get('question')
# RetrievalQA链 - 读入问题,生成答案
result = qa_chain({"query": question})
# 把大模型的回答结果返回网页进行渲染
return render_template('index.html', result=result)
return render_template('index.html')
if __name__ == "__main__":
app.run(host='0.0.0.0',debug=True,port=5010)
#实战后备注:
# 1、MultiQueryRetriever
# 实现:INFO:langchain.retrievers.multi_query:Generated queries: ["1. How much does YiSuXianHua's salary pay its employees?", '2. What is the wage structure at YiSuXianHua?', '3. Can you provide information on the remuneration packages offered by YiSuXianHua?']
# 2、qa_chain链建立,是传入content 即qestion涉及所有内容(而不是整个向量库传递)
#3、导入库的位置有改变
文档中无答案时候回答
文档中有答案时候回答
四、本地化部署(不用ai拆解方案)-仅方案
目标:我想实现文件的向量切分并存储在本地系统中。同时根据问题匹配度获取相应key的结果。想避免token的使用,免费的好的解决方案
4.1 代码
from sentence_transformers import SentenceTransformer
import numpy as np
import pickle
import os
from typing import List, Dict, Tuple
class LocalVectorSearch:
def __init__(self, model_name: str = 'paraphrase-multilingual-MiniLM-L12-v2'):
"""
初始化向量搜索系统
Args:
model_name: sentence-transformers模型名称,支持多语言
"""
self.model = SentenceTransformer(model_name)
self.vectors = {} # 存储文档向量
self.chunks = {} # 存储文档片段
def process_document(self,
file_path: str,
chunk_size: int = 500,
overlap: int = 50) -> None:
"""
处理文档并存储向量
Args:
file_path: 文档路径
chunk_size: 文档分块大小
overlap: 重叠部分大小
"""
# 读取文档
with open(file_path, 'r', encoding='utf-8') as f:
text = f.read()
# 分块
chunks = self._split_text(text, chunk_size, overlap)
# 计算向量
vectors = self.model.encode(chunks)
# 存储
doc_id = os.path.basename(file_path)
self.vectors[doc_id] = vectors
self.chunks[doc_id] = chunks
def _split_text(self,
text: str,
chunk_size: int,
overlap: int) -> List[str]:
"""
将文本分割成重叠的块
"""
words = text.split()
chunks = []
for i in range(0, len(words), chunk_size - overlap):
chunk = ' '.join(words[i:i + chunk_size])
chunks.append(chunk)
return chunks
def search(self,
query: str,
top_k: int = 3) -> List[Tuple[str, str, float]]:
"""
搜索最相关的文档片段
Args:
query: 搜索查询
top_k: 返回结果数量
Returns:
List of (doc_id, chunk_text, similarity_score)
"""
# 计算查询向量
query_vector = self.model.encode([query])[0]
results = []
# 对每个文档进行搜索
for doc_id, doc_vectors in self.vectors.items():
# 计算相似度
similarities = np.dot(doc_vectors, query_vector)
# 获取最相关的片段
top_indices = np.argsort(similarities)[-top_k:]
for idx in top_indices:
results.append((
doc_id,
self.chunks[doc_id][idx],
float(similarities[idx])
))
# 按相似度排序
results.sort(key=lambda x: x[2], reverse=True)
return results[:top_k]
def save(self, path: str) -> None:
"""保存向量和文档片段到本地"""
with open(path, 'wb') as f:
pickle.dump((self.vectors, self.chunks), f)
def load(self, path: str) -> None:
"""从本地加载向量和文档片段"""
with open(path, 'rb') as f:
self.vectors, self.chunks = pickle.load(f)
4.2 解析本地化方案
4.2.1 这个解决方案使用了以下开源组件:
- sentence-transformers库 - 提供多语言文本向量化能力
- NumPy - 用于向量计算和相似度匹配
- pickle - 用于本地存储向量数据
4.2.2 主要功能包括:
- 文档分块处理
- 向量化存储
- 相似度搜索
- 本地持久化
4.2.3 使用示例
# 初始化系统
searcher = LocalVectorSearch()
# 处理文档
searcher.process_document('document1.txt')
searcher.process_document('document2.txt')
# 保存向量到本地
searcher.save('vectors.pkl')
# 加载已有向量
searcher.load('vectors.pkl')
# 搜索相关内容
results = searcher.search("你的问题")
for doc_id, chunk, score in results:
print(f"文档: {doc_id}")
print(f"相关内容: {chunk}")
print(f"相似度: {score}\n")
4.2.4 使用优点,注意事项
4.2.4.1 优点是:
- 完全本地运行,不需要联网
- 使用开源模型,无需付费
- 支持中文等多语言
- 可以持久化存储,重复使用
4.2.4.2 需要注意的是:
- 首次下载模型需要联网
- 根据文档量可能需要较大内存
- 向量化计算可能需要GPU来提升性能