LlamaIndex 多文件 RAG 实现

2026-02-03 LlamaIndex RAG 多文件检索

概述

本项目演示如何使用 LlamaIndex 构建一个基于多文档的智能问答系统,采用 ReAct 智能体框架,结合检索工具进行问答。

核心功能:从 docs 目录加载多种格式文档,创建 ReAct 智能体,结合检索工具进行多步推理问答。

技术架构

技术组件 功能描述 在本例中的作用
VectorStoreIndex 向量存储索引 高效相似性搜索
SimpleDirectoryReader 简单目录读取器 支持多种文档格式加载
Settings 全局设置对象 配置 LLM 和嵌入模型
StorageContext 存储上下文 持久化/加载索引
ReActAgent ReAct 框架智能体 推理+行动循环问答
FunctionTool 函数工具包装器 将检索功能封装为工具
DashScope 大语言模型接口 使用 DeepSeek-V3 模型
DashScopeEmbedding 嵌入模型接口 文本向量化

Pydantic 兼容性处理

def _patch_dashscope_response_for_pydantic():
    """
    兼容 pydantic 的 isinstance/hasattr 检查,
    避免 DashScopeResponse 抛 KeyError
    """
    try:
        from dashscope.api_entities import dashscope_response as dsr
        target_cls = dsr.DashScopeAPIResponse
    except Exception:
        return

    if getattr(target_cls, "_pydantic_safe_getattr", False):
        return

    original_getattr = target_cls.__getattr__

    def __getattr__(self, attr):
        if attr.startswith("__pydantic_"):
            raise AttributeError(attr)
        return original_getattr(self, attr)

    target_cls.__getattr__ = __getattr__
    target_cls._pydantic_safe_getattr = True

这是一个重要的兼容性补丁,解决 DashScope 响应对象与 Pydantic 检查的冲突问题。

LLM 和 Embedding 配置

def setup_llm_and_embedding():
    # 兼容 pydantic 检查
    _patch_dashscope_response_for_pydantic()

    # 从环境变量获取 API 密钥
    api_key = os.getenv('DASHSCOPE_API_KEY')
    if not api_key:
        raise ValueError("请设置环境变量 DASHSCOPE_API_KEY")

    # 配置 DashScope 语言模型
    llm = DashScope(
        model="deepseek-v3",  # 使用 DeepSeek v3 模型
        api_key=api_key,
        temperature=0.7,      # 温度参数,控制生成随机性
        top_p=0.8,           # 核采样参数,控制输出多样性
    )

    # 配置文本嵌入模型
    embed_model = DashScopeEmbedding(
        model_name="text-embedding-v1",
        api_key=api_key,
    )

    return llm, embed_model

配置语言模型和嵌入模型,温度和 top_p 参数用于控制生成的随机性和多样性。

文档加载与索引创建

def load_documents_and_create_index(file_dir: str = './docs'):
    persist_dir = "./storage"

    # 尝试从存储中加载已存在的索引
    if os.path.exists(persist_dir):
        try:
            storage_context = StorageContext.from_defaults(
                persist_dir=persist_dir)
            index = load_index_from_storage(storage_context)
            print("从存储加载索引成功")
            return index
        except Exception as e:
            print(f"加载索引失败: {e},将重新创建索引")

    # 检查文档目录是否存在
    if not os.path.exists(file_dir):
        print(f"文档目录 {file_dir} 不存在")
        return None

    # 使用 SimpleDirectoryReader 读取所有文档
    # 自动支持多种格式:txt, pdf, docx, html, md 等
    reader = SimpleDirectoryReader(file_dir)
    documents = reader.load_data()

    if not documents:
        print("没有找到任何文档")
        return None

    print(f"加载了 {len(documents)} 个文档")

    # 创建向量存储索引
    index = VectorStoreIndex.from_documents(documents)

    # 持久化索引到本地
    index.storage_context.persist(persist_dir=persist_dir)
    print(f"索引已保存到 {persist_dir}")

    return index

实现索引的持久化存储,首次运行创建索引并保存,后续运行直接加载,提升启动速度。支持多种文档格式的自动识别和加载。

ReAct 智能体创建

创建检索器
封装检索工具
定义系统提示
创建 ReAct 智能体
def create_agent(index, llm):
    # 创建检索器,返回最相似的 5 个文档片段
    retriever = index.as_retriever(similarity_top_k=5)
    
    # 创建查询引擎
    query_engine = index.as_query_engine(similarity_top_k=5)

    # 定义系统提示词
    system_instruction = '''你是一个乐于助人的AI助手。
你可以从给定的文档中检索相关信息来回答用户的问题。
你总是用中文回复用户。'''

    # 创建检索工具函数
    def retrieve_documents(query: str) -> str:
        """从文档中检索相关信息的工具函数"""
        response = query_engine.query(query)
        return str(response)

    # 将检索函数包装为 LlamaIndex 工具
    retrieve_tool = FunctionTool.from_defaults(fn=retrieve_documents)

    # 创建 ReAct 框架智能体
    agent = ReActAgent(
        tools=[retrieve_tool],          # 可用工具列表
        llm=llm,                        # 使用的 LLM 模型
        verbose=True,                   # 输出详细执行过程
        system_prompt=system_instruction,
        streaming=False,                # 禁用流式输出
    )

    return agent, retriever

ReAct 框架允许智能体通过推理(Reasoning)和行动(Acting)循环解决问题,可以进行多步推理和工具调用。

异步主函数

async def main():
    # 配置 LLM 和 Embedding 模型
    llm, embed_model = setup_llm_and_embedding()
    Settings.llm = llm
    Settings.embed_model = embed_model

    # 加载文档并创建向量索引
    index = load_documents_and_create_index()
    if index is None:
        print("无法创建索引,程序退出")
        return

    # 创建智能体和检索器
    agent, retriever = create_agent(index, llm)

    # 定义查询示例
    query = "介绍下雇主责任险"
    print(f"\n用户查询: {query}\n")

    # 展示检索过程和结果
    print("\n===== 召回的文档内容 =====")
    retrieved_nodes = retriever.retrieve(query)
    if retrieved_nodes:
        for i, node in enumerate(retrieved_nodes):
            print(f"\n文档片段 {i+1}:")
            print(f"内容: {node.text[:200]}...")
            print(f"元数据: {node.metadata}")
            if hasattr(node, 'score'):
                print(f"相似度分数: {node.score}")
    else:
        print("没有召回任何文档内容")
    print("===========================\n")

    # 使用智能体回答问题
    print("\n===== 智能体回复 =====")
    result = await agent.run(query)
    if hasattr(result, "response") and hasattr(result.response, "content"):
        print(result.response.content)
    else:
        print(result)
    print("======================\n")


if __name__ == "__main__":
    asyncio.run(main())

使用 asyncio 实现异步执行,提升性能。展示完整的问答流程,包括文档召回和智能体回复。

核心特点

1. 多格式文档支持

SimpleDirectoryReader 自动支持多种文档格式:txt、pdf、docx、html、md 等,无需手动指定加载器。

2. 索引持久化

首次运行创建索引并保存到 ./storage 目录,后续运行直接加载,避免重复计算。

3. ReAct 智能推理

智能体可以进行多步推理,根据需要调用检索工具,处理复杂查询更加智能。

4. 详细日志输出

展示召回的文档片段、相似度分数和元数据,便于调试和理解检索过程。

应用场景

这种实现方式适合:

  • 企业知识库问答系统
  • 技术文档智能检索
  • 学术论文问答助手
  • 法律文档查询系统
  • 客服知识库应用

扩展建议

可以进一步优化:

  • 添加更多工具(搜索、计算器、代码执行等)
  • 实现对话历史管理,支持多轮对话
  • 添加文档更新和增量索引功能
  • 集成更多检索策略(混合检索、重排序等)
  • 添加评估指标,监控系统性能

标签