Appearance
第十三阶段:Qdrant 向量数据库
一、为什么换 Qdrant
上一阶段的接口从 kb_storage 读取索引。kb_storage 只是本地文件夹里的文件,只能被一个进程加载使用。要是想启动多个 API 服务一起分担请求,每个服务都得自己存一份、自己加载一份,数据也很难保持一致。
Qdrant 是一个独立运行的向量数据库服务。数据集中存放在它那里,多个服务可以同时通过网络连接它。把"存向量"和"回答问题"拆成两个服务,是走向分布式的第一步。
二、用 Docker 启动 Qdrant
bash
docker run -d --name qdrant -p 6333:6333 -v qdrant_data:/qdrant/storage qdrant/qdrant| 参数 | 含义 |
|---|---|
docker run | 用一个镜像创建并启动容器 |
-d | 后台运行,不占用当前终端 |
--name qdrant | 给容器起名 qdrant,以后可以用 docker stop qdrant / docker start qdrant 停止或启动 |
-p 6333:6333 | 端口映射:本机 6333 端口转发到容器内的 6333 端口,代码才能访问 |
-v qdrant_data:/qdrant/storage | 把数据存进名为 qdrant_data 的数据卷,删掉容器数据也还在 |
qdrant/qdrant | 镜像名,第一次运行会自动下载 |
启动后,在浏览器打开控制台 http://localhost:6333/dashboard,可以看到里面有哪些数据。
三、安装依赖
bash
pip install llama-index-vector-stores-qdrant qdrant-client四、先建索引,再读取
分成两个脚本:
13_build_qdrant.py:只运行一次,把文档切块、生成向量,写入 Qdrant;13_api.py:接口启动时用VectorStoreIndex.from_vector_store直接连接 Qdrant 里已经存好的数据,不再读本地文件。
collection 相当于数据库里的一张表,这里起名为 kb。
先整理 data 文件夹
建索引会读取 data 里所有的 PDF。请先把扫描版 PDF(移动热修复.pdf)移出 data 文件夹。它读不出文字,只会产生无用的数据。
1. 建索引 13_build_qdrant.py
提示
model_name里的"这里填你的 Embedding 模型名"是占位文字。LM Studio 只加载了一个 Embedding 模型时,照这样写也能运行;想写规范一点,可以换成 LM Studio 里显示的模型名。
python
import qdrant_client
from llama_index.core import SimpleDirectoryReader, VectorStoreIndex, StorageContext, Settings
from llama_index.core.node_parser import SentenceSplitter
from llama_index.vector_stores.qdrant import QdrantVectorStore
from llama_index.embeddings.openai_like import OpenAILikeEmbedding
Settings.embed_model = OpenAILikeEmbedding(
model_name="这里填你的 Embedding 模型名",
api_base="http://localhost:1234/v1", api_key="<redacted>")
client = qdrant_client.QdrantClient(url="http://localhost:6333")
# collection 相当于数据库里的一张表
vector_store = QdrantVectorStore(client=client, collection_name="kb")
docs = SimpleDirectoryReader("data", required_exts=[".pdf"]).load_data()
index = VectorStoreIndex.from_documents(
docs,
storage_context=StorageContext.from_defaults(vector_store=vector_store),
transformations=[SentenceSplitter(chunk_size=512, chunk_overlap=50)],
show_progress=True)
print("已写入 Qdrant,向量数:", client.count("kb").count)运行 python 13_build_qdrant.py,最后会打印写入的向量数。
2. 接口 13_api.py
提示
model_name里的"这里填你的 Embedding 模型名"是占位文字。LM Studio 只加载了一个 Embedding 模型时,照这样写也能运行;想写规范一点,可以换成 LM Studio 里显示的模型名。
说明
这份代码是从 12_api.py 改过来的。开头的注释"复用项目 1 的 kb_storage"是旧的,现在索引其实来自 Qdrant;
load_index_from_storage这些导入也已经用不到了,删掉也不影响运行。
python
import os
import qdrant_client
from llama_index.core import VectorStoreIndex
from llama_index.vector_stores.qdrant import QdrantVectorStore
from dotenv import load_dotenv
from fastapi import FastAPI
from pydantic import BaseModel
from openai import OpenAI
from llama_index.core import StorageContext, load_index_from_storage, Settings
from llama_index.embeddings.openai_like import OpenAILikeEmbedding
load_dotenv()
# 启动时只加载一次索引(复用项目 1 的 kb_storage),不要每次请求都加载
Settings.embed_model = OpenAILikeEmbedding(
model_name="这里填你的 Embedding 模型名",
api_base="http://localhost:1234/v1", api_key="<redacted>")
client = qdrant_client.QdrantClient(url="http://localhost:6333")
index = VectorStoreIndex.from_vector_store(QdrantVectorStore(client=client, collection_name="kb"))
retriever = index.as_retriever(similarity_top_k=3)
llm = OpenAI(api_key=<redacted>("LLM_API_KEY"), base_url=os.getenv("LLM_BASE_URL"))
app = FastAPI(title="知识库问答服务")
# 请求体格式:{"question": "..."},FastAPI 会自动校验
class AskRequest(BaseModel):
question: str
@app.get("/health")
def health():
return {"status": "ok"}
@app.post("/ask")
def ask(req: AskRequest):
nodes = retriever.retrieve(req.question)
context = "\n\n".join(n.get_content() for n in nodes)
resp = llm.chat.completions.create(
model=os.getenv("LLM_MODEL"),
messages=[
{"role": "system", "content": "根据资料回答,资料里没有就说不知道。\n资料:\n" + context},
{"role": "user", "content": req.question},
])
return {"answer": resp.choices[0].message.content,
"sources": [round(n.score, 3) for n in nodes]}启动方式和上一阶段一样:
bash
uvicorn 13_api:app --port 8000五、实测
打开 http://localhost:8000/docs,用 Try it out 提问"什么时候可以保修":
- 返回 200;
- 回答:签收 7 天内可退换,8~30 天可换货或维修,12 个月内可维修;
sources是[0.618, 0.492, 0.386]。
接口和第十二阶段一模一样,只是数据换成了从 Qdrant 读取,调用方完全感觉不到变化。
下一阶段预告
第十四阶段:用 Dockerfile 和 docker compose 把 API 服务和 Qdrant 打包在一起,一条命令同时启动。