Meta AI 的研究人员引入了一种叫做检索增强生成(Retrieval Augmented Generation,RAG)的方法来完成知识密集型的任务。RAG 会接受输入并检索出一组相关/支撑的文档,并给出文档的来源(例如维基百科)。这些文档作为上下文和输入的原始提示词组合,送给文本生成器得到最终的输出。RAG 让语言模型不用重新训练就能够获取最新的信息,基于检索生成产生可靠的输出。RAG 的应用范围广泛且多样,从改进客户服务聊天机器人、促进详细和个性化的内容推荐,到支持业务环境中的复杂决策流程。
下面我们将以“大数据与智能计算”专业课的课件作为我们自己的知识库,通过 vLLM 框架、llama_index 和和 fastapi 搭建一个属于我们自己的专属的问答系统。
第一步为将 vLLM 部署为模仿 OpenAI API 协议的服务器,这使得 vLLM 可以用作使用 OpenAI API 的应用程序的直接替代品。关于该步骤,以及关于 vLLM 的使用说明,这篇文章里面有更加详细的讲解。
使用 vLLM 部署本地 LLM 指南-CSDN博客
这次我使用的 LLM 为 OpenChat-3.5-0106,这个模型是由 mistralai/Mistral-7B-v0.1 微调得到的,参数量为 7B,模型内容链接如下:
openchat/openchat-3.5-0106 · Hugging Face
首先输入以下命令启动服务器,关于该命令的详解可以看上面的 CSDN 博客。
python -m vllm.entrypoints.openai.api_server \
--model "/root/autodl-tmp/kdy/models/openchat-3.5-0106" \
--served-model-name "openchat" \
终端输出如下,服务器启动成功:
首先需要安装 llama_index 和 fastapi。
下面这篇文章里详解了在本地部署 LLM 的教程,较为简明,也不需要使用 fastapi 创建 API 端点,如果只是对 llama_index 感兴趣或是初步接触 RAG 技术,可以先看下面这篇文章在本地进行一个简单的实现,虽然本地的速度慢的很
使用 LlamaIndex 部署本地 Mistral-7b 大模型实现 RAG-CSDN博客
我使用了 fastapi 提供的流式响应功能。该功能可以逐个发送生成器生成的值,而不是一次性发送所有内容。
代码命名为 fastapi_test.py,代码内容如下:
from llama_index import VectorStoreIndex, SimpleDirectoryReader
from llama_index import ServiceContext
from llama_index.llms.openai_like import OpenAILike
from llama_index import set_global_service_context
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from fastapi.responses import StreamingResponse
openai_api_key = "EMPTY"
openai_api_base = "http://localhost:8000/v1"
llm = OpenAILike(
api_key=openai_api_key,
api_base=openai_api_base,
model="openchat",
is_chat_model=True,
context_window=32768,
)
service_context = ServiceContext.from_defaults(llm=llm, embed_model="local:BAAI/bge-small-zh-v1.5", chunk_size=1024)
set_global_service_context(service_context)
documents = SimpleDirectoryReader("/root/autodl-tmp/kdy/RAG/data").load_data()
index = VectorStoreIndex.from_documents(documents)
index.storage_context.persist(persist_dir="/root/autodl-tmp/kdy/RAG/storage")
query_engine = index.as_query_engine(streaming=True, similarity_top_k=5)
app = FastAPI()
# 定义请求模型
class QueryModel(BaseModel):
question: str
# POST端点
@app.post("/query/")
async def answer_query(query: QueryModel):
if query.question:
response = query_engine.query(query.question)
def generator():
for item in response.response_gen:
yield item # 逐个生成值
return StreamingResponse(generator()) # 使用流式响应
raise HTTPException(status_code=400, detail="No question provided")
documents = SimpleDirectoryReader("/root/autodl-tmp/kdy/RAG/data").load_data()
这行代码为读取存放在本地的文档,我这次选取了课程“大数据与智能计算“的 PDF 课件作为示例,如下图所示。
你也可以放更多更大的文件,这里需要注意代码中的service_context = ServiceContext.from_defaults(llm=llm, embed_model="local:BAAI/bge-small-zh-v1.5", chunk_size=1024)
里面的 chunk_size
属性,文档过大时,需要设置该属性讲文档进行切分,详细内容可以看官方文档。
端口号可自行选取,我选用了 6006 端口。
输入以下指令启动fastapi进程,注意命令里面的 fastapi_test 就是上面这段脚本的名称。
uvicorn fastapi_test:app --reload --port 6006
下图记录了进程启动以及响应成功的日志,红框框出的即为响应成功的日志。
输入以下指令可以向该端点发送 POST 请求
curl -X POST "http://127.0.0.1:6006/query/" -H "accept: application/json" -H "Conten
t-Type: application/json" -d '{"question":"大数据有哪些特点?"}'
很快就收到了响应,如下图所示。
这里我们回到第一次启动的 openai.api_server 的日志,可以看到 llama_index 帮我补好的完整的 prompt,它包含了 llama_index 帮我们从 PDF 中检索得到的与问题有关的内容。
但是使用 curl 指令难免有些复杂,所以我们使用 python 自带的 requests 库进行进一步的封装。
由于 FastAPI 后端正确设置了流式响应,所以我的Python客户端脚本也需要能够逐步读取这些数据。故需要使用 requests
库的 .iter_content()
或 .iter_lines()
方法来逐步读取响应。
.iter_lines()
方法可以逐行来读取响应,但是我觉得逐行还是有点慢,因此我使用了**.iter_content()
** 方法来逐字节读取响应。读取方法如下:
for line in response.iter_content(chunk_size=2, decode_unicode=True):
print(line, end="", flush=True)
chunk_size=2
指定每次迭代读取 2 个字节,可以根据需要设置为合适的大小。
chunk_size
(如1-10字节):适用于需要高实时性的应用,例如逐字节或逐字符显示文本。但这可能会降低性能,因为频繁的网络请求和数据处理可能会增加延迟。chunk_size
(如1024字节或1KB):这是一个比较常见的选择,平衡了实时性和性能。对于一般的文本流或逐行读取的情况,这个大小通常足够。chunk_size
(如4096字节或更多):适用于网络带宽较高且对实时性要求不高的情况,例如大文件下载。较大的块可以减少请求次数,提高整体数据传输效率。由于我这里是为了实现类似逐字打印文本的效果,考虑使用较小的 chunk_size
(如1-10字节)。
最终完整的代码如下:
import argparse
import requests
def query_api(question):
url = "http://127.0.0.1:6006/query/"
headers = {
"accept": "application/json",
"Content-Type": "application/json"
}
payload = {"question": question}
response = requests.post(url, json=payload, headers=headers, stream=True)
return response
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Query API with a question.')
parser.add_argument('-q', '--question', required=True, help='The question to query.')
args = parser.parse_args()
response = query_api(args.question)
if response.encoding is None:
response.encoding = 'utf-8'
for line in response.iter_content(chunk_size=2, decode_unicode=True):
print(line, end="", flush=True)
print()
运行这段 python 程序,注意命令行参数 -q 就是你要提问的问题,很快就会收到响应,而且为流式响应。
响应界面如下(未加速),这个速度比之前在本地不知快了多少倍