在大语言模型技术飞速发展的当下,基于文档的智能检索与问答系统成为企业和开发者的重要需求。本文将详细介绍如何利用 LlamaIndex 框架,结合阿里云千问(DashScope)大模型与 Qdrant 向量数据库,构建一套高效的 PDF 文档检索系统,实现对本地 PDF 文档的精准语义检索。

Qdrant简介

Qdrant是一个开源的向量搜索引擎,由Qdrant团队开发。它提供了高性能的向量检索和相似度搜索功能,支持多种向量类型和距离度量方法。Qdrant还提供了丰富的查询语法和灵活的配置选项,使得用户可以根据自己的需求进行定制化的搜索。

Qdrant 因其易用性和用户友好的开发者文档,面世不久即获得关注。

Qdrant 以 Rust 语言构建,提供 Rust、Python、Golang 等客户端 API,能够满足当今主流开发人员的需求。

一、技术栈选型与核心依赖

构建该检索系统的核心技术栈围绕 LlamaIndex 生态展开,同时结合了阿里云千问大模型与 Qdrant 向量存储,具体依赖如下:

# 核心框架与工具
pip install llama-index llama-index-embeddings-dashscope llama-index-llms-dashscope
# PDF解析与环境配置
pip install pymupdf python-dotenv
# 兼容OpenAI接口的LLM适配
pip install llama-index-llms-openai-like
# Qdrant向量存储
pip install llama-index-vector-stores-qdrant
  • LlamaIndex:作为核心的检索增强生成(RAG)框架,提供文档加载、切分、向量化、检索等全流程能力;
  • DashScope(千问):阿里云推出的大模型服务,提供高质量的文本嵌入与大语言模型能力;
  • PyMuPDF:高效的 PDF 文件解析工具,支持精准提取 PDF 中的文本内容;
  • Qdrant:轻量级向量数据库,支持快速的向量相似度检索;
  • python-dotenv:用于加载环境变量,安全管理 API 密钥等敏感信息。

二、系统实现流程

1. 环境配置与密钥管理

首先通过环境变量加载千问模型的 API 密钥,避免硬编码敏感信息,需提前在.env文件中配置DASHSCOPE_API_KEY=你的密钥

import os
from dotenv import load_dotenv

# 加载环境变量
load_dotenv()
api_key = os.getenv("DASHSCOPE_API_KEY")

2. 千问模型配置

系统核心依赖千问的两大能力:文本嵌入(用于将文档转为向量)和大语言模型(用于后续问答生成),通过 LlamaIndex 的适配层快速接入:

from llama_index.embeddings.dashscope import DashScopeEmbedding
from llama_index.llms.openai_like import OpenAILike

# 配置千问嵌入模型(文本向量化)
embed_model = DashScopeEmbedding(
    model_name="text-embedding-v2",  # 千问轻量级嵌入模型,输出维度1536
    api_key=api_key,
    timeout=30  # 超时时间保障
)

# 配置千问大语言模型(回答生成)
llm = OpenAILike (
    model="qwen-plus",  # 千问增强版模型
    api_base="https://dashscope.aliyuncs.com/compatible-mode/v1",  # 兼容OpenAI接口的地址
    api_key=api_key,
    is_chat_model=True  # 标记为对话模型
)

3. PDF 文档加载与解析

通过 LlamaIndex 的SimpleDirectoryReader结合PyMuPDFReader解析器,批量加载指定目录下的 PDF 文件,提取文本内容:

from llama_index.core import SimpleDirectoryReader
from llama_index.readers.file import PyMuPDFReader

# 加载指定目录下的所有PDF文件
documents = SimpleDirectoryReader(
    input_dir="./data",  # 本地PDF文档存放目录
    required_exts=[".pdf"],  # 仅加载PDF格式文件
    file_extractor={".pdf": PyMuPDFReader()}  # 指定PDF解析器
).load_data()

4. 文档切分:适配模型上下文长度

原始 PDF 文本通常较长,需按 Token 粒度切分为小片段(Node),保证向量化和检索的精准性:

from llama_index.core.node_parser import TokenTextSplitter

# 按Token切分文档
node_parser = TokenTextSplitter(
    chunk_size=300,    # 每个文本块300个Token
    chunk_overlap=50   # 块间重叠50个Token,避免语义断裂
)
nodes = node_parser.get_nodes_from_documents(documents)

5. Qdrant 向量库初始化与索引构建

将切分后的文本块通过千问嵌入模型转为向量,存储到 Qdrant 向量库中,构建可检索的向量索引:

from qdrant_client import QdrantClient
from qdrant_client.models import VectorParams, Distance
from llama_index.core import StorageContext, VectorStoreIndex
from llama_index.vector_stores.qdrant import QdrantVectorStore

# 初始化Qdrant客户端(内存模式,也可配置为持久化)
client = QdrantClient(location=":memory:")
collection_name = "demo"
# 创建向量集合,指定向量维度(text-embedding-v2输出1536维)和相似度计算方式(余弦相似度)
client.create_collection(
    collection_name=collection_name,
    vectors_config=VectorParams(size=1536, distance=Distance.COSINE)
)

# 关联Qdrant向量存储
vector_store = QdrantVectorStore(client=client, collection_name=collection_name)
storage_context = StorageContext.from_defaults(vector_store=vector_store)
# 构建向量索引:将文本块向量化并存储到Qdrant
index = VectorStoreIndex(
    nodes, 
    storage_context=storage_context,
    embed_model=embed_model  # 指定千问嵌入模型
)

6. 语义检索:实现精准内容查找

基于构建好的向量索引,创建检索器并指定返回最相似的 Top-K 结果,实现对目标问题的语义检索:

# 创建检索器,返回Top2相似结果
vector_retriever = index.as_retriever(similarity_top_k=2)
# 检索与“所获荣誉”相关的内容
results = vector_retriever.retrieve("所获荣誉")
# 输出最相似的检索结果
print(results[0])

三、核心优势与扩展方向

1. 核心优势

  • 轻量化部署:Qdrant 支持内存模式运行,无需复杂的数据库部署,适合快速开发与测试;
  • 本土化适配:基于阿里云千问模型,无需访问海外服务,稳定性和合规性更优;
  • 精准语义检索:结合余弦相似度计算与 Token 级文档切分,检索精度远高于传统关键词匹配;
  • 灵活扩展:LlamaIndex 框架支持多类型文档(Word、Markdown 等)和多向量库(Milvus、Chroma 等)适配。

2. 扩展方向

  • 持久化存储:将 Qdrant 从内存模式改为磁盘持久化,避免重启后索引丢失;
  • 问答生成:结合千问 LLM,在检索结果基础上生成自然语言回答,实现端到端的问答系统;
  • 批量处理与增量索引:支持新增文档的增量索引构建,无需重复处理全部文档;
  • Web 化部署:结合 FastAPI/Flask 搭建 Web 接口,提供可视化的检索界面。

四、总结

本文基于 LlamaIndex 框架,完整实现了从 PDF 文档加载、解析、切分、向量化,到基于 Qdrant 向量库的语义检索全流程,核心依赖阿里云千问模型提供的嵌入能力,构建了一套轻量、高效的本地文档检索系统。该方案无需复杂的基础设施,适合开发者快速搭建私有化文档检索服务,也可基于此扩展为完整的 RAG 问答系统,满足企业知识库、个人文档管理等多场景需求。

关键点回顾

  1. 系统核心流程为:文档加载→切分→向量化→向量存储→语义检索,基于 LlamaIndex 实现全流程串联;
  2. 千问模型提供嵌入和 LLM 双重能力,通过 OpenAILike 适配层快速接入 LlamaIndex;
  3. Qdrant 向量库支持高效的相似度检索,结合余弦相似度计算实现精准的语义匹配。

完整代码如下

#pip install llama-index llama-index-embeddings-dashscope llama-index-llms-dashscope pymupdf python-dotenv
#pip install llama-index-llms-openai-like、
#pip install llama-index-vector-stores-qdrant

import os
from dotenv import load_dotenv
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader, StorageContext
from llama_index.core.node_parser import TokenTextSplitter
from llama_index.readers.file import PyMuPDFReader
from llama_index.embeddings.dashscope import DashScopeEmbedding
from llama_index.llms.openai_like import OpenAILike
from llama_index.vector_stores.qdrant import QdrantVectorStore

# 1. 加载环境变量(提前在 .env 文件配置 DASHSCOPE_API_KEY=你的密钥)
load_dotenv()
api_key = os.getenv("DASHSCOPE_API_KEY")

# 2. 配置千问嵌入模型(用于文本向量化)
embed_model = DashScopeEmbedding(
    model_name="text-embedding-v2",  # 千问嵌入模型版本
    api_key=api_key,
    timeout=30  # 超时时间
)

# 3. 配置千问大语言模型(用于生成回答)
llm =OpenAILike (
    model="qwen-plus",
    api_base="https://dashscope.aliyuncs.com/compatible-mode/v1",
    api_key=os.getenv("DASHSCOPE_API_KEY"),
    is_chat_model=True
)

# 4. 加载PDF文档(可替换为其他格式)
documents = SimpleDirectoryReader(
    input_dir="./data",  # 本地文档目录
    required_exts=[".pdf"],  # 只加载PDF文件
    file_extractor={".pdf": PyMuPDFReader()}  # PDF解析器
).load_data()

# 5. 文档切分(按token分块,适配千问上下文长度)
node_parser = TokenTextSplitter(
    chunk_size=300,    # 每个块300个token
    chunk_overlap=50   # 块之间重叠50个token,保证语义连贯
)
nodes = node_parser.get_nodes_from_documents(documents)
from qdrant_client import QdrantClient
from qdrant_client.models import VectorParams, Distance

client = QdrantClient(location=":memory:")
collection_name = "demo"
collection = client.create_collection(
    collection_name=collection_name,
    vectors_config=VectorParams(size=1536, distance=Distance.COSINE)
)

vector_store = QdrantVectorStore(client=client, collection_name=collection_name)
# 创建 storage: 指定存储空间
storage_context = StorageContext.from_defaults(vector_store=vector_store)
# 创建 index: 通过 Storage Context 关联到自定义的 Vector Store
index = VectorStoreIndex(nodes, storage_context=storage_context,   embed_model=embed_model)
# 获取 retriever
vector_retriever = index.as_retriever(similarity_top_k=2)
# 检索
results = vector_retriever.retrieve("所获荣誉")
print(results[0])

多轮对话

#pip install llama-index llama-index-embeddings-dashscope llama-index-llms-dashscope pymupdf python-dotenv
#pip install llama-index-llms-openai-like、
#pip install llama-index-vector-stores-qdrant

import os
from dotenv import load_dotenv
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader, StorageContext
from llama_index.core.node_parser import TokenTextSplitter
from llama_index.readers.file import PyMuPDFReader
from llama_index.embeddings.dashscope import DashScopeEmbedding
from llama_index.llms.openai_like import OpenAILike
from llama_index.vector_stores.qdrant import QdrantVectorStore

# 1. 加载环境变量(提前在 .env 文件配置 DASHSCOPE_API_KEY=你的密钥)
load_dotenv()
api_key = os.getenv("DASHSCOPE_API_KEY")

# 2. 配置千问嵌入模型(用于文本向量化)
embed_model = DashScopeEmbedding(
    model_name="text-embedding-v2",  # 千问嵌入模型版本
    api_key=api_key,
    timeout=30  # 超时时间
)

# 3. 配置千问大语言模型(用于生成回答)
llm =OpenAILike (
    model="qwen-plus",
    api_base="https://dashscope.aliyuncs.com/compatible-mode/v1",
    api_key=os.getenv("DASHSCOPE_API_KEY"),
    is_chat_model=True
)

# 4. 加载PDF文档(可替换为其他格式)
documents = SimpleDirectoryReader(
    input_dir="./data",  # 本地文档目录
    required_exts=[".pdf"],  # 只加载PDF文件
    file_extractor={".pdf": PyMuPDFReader()}  # PDF解析器
).load_data()

# 5. 文档切分(按token分块,适配千问上下文长度)
node_parser = TokenTextSplitter(
    chunk_size=300,    # 每个块300个token
    chunk_overlap=50   # 块之间重叠50个token,保证语义连贯
)
nodes = node_parser.get_nodes_from_documents(documents)
from qdrant_client import QdrantClient
from qdrant_client.models import VectorParams, Distance

client = QdrantClient(location=":memory:")
collection_name = "demo"
collection = client.create_collection(
    collection_name=collection_name,
    vectors_config=VectorParams(size=1536, distance=Distance.COSINE)
)

vector_store = QdrantVectorStore(client=client, collection_name=collection_name)
# 创建 storage: 指定存储空间
storage_context = StorageContext.from_defaults(vector_store=vector_store)
# 创建 index: 通过 Storage Context 关联到自定义的 Vector Store
index = VectorStoreIndex(nodes, storage_context=storage_context,   embed_model=embed_model)
#单轮对话
#qa_engine = index.as_query_engine(llm)
#response = qa_engine.query("所获荣誉有哪些?")
#print(response)

#流式
#qa_engine = index.as_query_engine(llm,streaming=True)
#response = qa_engine.query("所获荣誉有哪些?")
#response.print_response_stream()

# 8. 构建 Chat Engine(显式指定千问 LLM,核心解决 OpenAI 密钥报错)
chat_engine = index.as_chat_engine(
    llm=llm,  # 关键:指定千问模型为聊天引擎的 LLM
    chat_mode="context",  # 基于文档上下文的对话模式(推荐)
    similarity_top_k=2,    # 检索Top2相似文档作为上下文
    streaming=False        # 非流式输出,如需流式可设为True
)


# 9. 测试多轮对话(无 OpenAI 密钥也能运行)
# 第一轮对话
response1 = chat_engine.chat("所获荣誉有哪些?")
print("第一轮回复:", response1)

# 第二轮对话(上下文关联)
response2 = chat_engine.chat("分几类?")
print("第二轮回复:", response2)

# 可选:清空对话历史
chat_engine.reset()

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐