一、LlamaIndex-RAG

  • 使用 LlamaIndex 实现 RAG 的一些常见操作案例

1、PDF解析-MD

  • 把 PDF 解析为 MD 格式,使用 LlamaParse 实现
  • 前提:
    • 注册 LlamaCloud 账号并且配置环境变量 LLAMA_CLOUD_API_KEY
    • LlamaCloud 一个月的免费额度为 10000 点
import os
from llama_parse import LlamaParse

# 从环境变量读取 API Key
api_key = os.getenv("LLAMA_CLOUD_API_KEY")
if not api_key:
    raise ValueError("请设置环境变量 LLAMA_CLOUD_API_KEY")

# 初始化 LlamaParse 解析器
parser = LlamaParse(
    api_key=api_key,
    result_type="markdown",  # 输出格式: markdown / text / json
    num_workers=4,           # 并行 worker 数
    verbose=True,            # 打印详细日志
    language="ch_sim",        # 文档语言: 简体中文
    # 自定义解析指令(可选)
    system_prompt="""
    这是一篇关于 RAG 评估框架 Ragas 的中文技术文档。
    请保留所有表格结构、代码块和层级标题。
    对于数学公式,请尽量保留原始格式。
    """,
)

# 指定要解析的 PDF 文件路径
pdf_path = os.path.join(os.path.dirname(__file__), "19.Ragas评估.pdf")

print(f"正在解析 PDF: {pdf_path}")
print("=" * 60)

# 方式1: load_data() — 直接返回 Document 对象列表
# documents = parser.load_data(pdf_path)
# for i, doc in enumerate(documents):
#     print(f"文档片段 {i}: {doc.text[:200]}...")

# 获取 Markdown 格式的完整结果
json_result = parser.get_json_result(pdf_path)
if not json_result:
    raise RuntimeError("PDF 解析失败,返回结果为空。请检查 API Key 是否有效、PDF 文件是否损坏。")

# json_result 结构: [{"pages": [{"page": 1, "md": "...", "text": "..."}, ...], ...}]
# 拼接所有页面的 Markdown 内容
pages = json_result[0]["pages"]
md_text = "\n\n".join(page["md"] for page in pages)

# 保存解析结果到 Markdown 文件
output_path = pdf_path.replace(".pdf", "_解析结果.md")
with open(output_path, "w", encoding="utf-8") as f:
    f.write(md_text)

print(f"解析完成! 结果已保存到: {output_path}")
print("=" * 60)
print(f"解析内容预览(前 500 字符):\n{md_text[:500]}")

2、PDF 解析后构建索引存储-内存

  • PDF 解析后构建索引存储在内存中
import os
from llama_parse import LlamaParse
from llama_index.core import Settings
from llama_index.embeddings.dashscope import DashScopeEmbedding
from llama_index.llms.openai_like import OpenAILike
from llama_index.core import VectorStoreIndex
from llama_index.core.node_parser import MarkdownElementNodeParser

# 配置 Embedding 模型(用 DashScope)
Settings.embed_model = DashScopeEmbedding(
    model_name="qwen3.7-text-embedding",
    api_key=os.getenv("DASHSCOPE_API_KEY"),
    embed_batch_size=10,  # DashScope embedding 单次最大 25,设小一点避免超限
)

# 配置 LLM(用 DashScope 兼容模式)
Settings.llm = OpenAILike(
    model="qwen3.7-flash",
    api_key=os.getenv("DASHSCOPE_API_KEY"),
    api_base="https://dashscope.aliyuncs.com/compatible-mode/v1",
    is_chat_model=True,
)


# Step 1: 解析文档
api_key = os.getenv("LLAMA_CLOUD_API_KEY")
if not api_key:
    raise ValueError("请设置环境变量 LLAMA_CLOUD_API_KEY")
parser = LlamaParse(
    api_key=api_key,
    result_type="markdown",  # 输出格式: markdown / text / json
    num_workers=4,  # 并行 worker 数
    verbose=True,  # 打印详细日志
    language="ch_sim",  # 文档语言: 简体中文
    # 自定义解析指令(可选)
    system_prompt="""
    这是一篇关于 RAG 评估框架 Ragas 的中文技术文档。
    请保留所有表格结构、代码块和层级标题。
    对于数学公式,请尽量保留原始格式。
    """,
)
pdf_path = os.path.join(os.path.dirname(__file__), "19.Ragas评估.pdf")

documents = parser.load_data(pdf_path)

# Step 2: 构建节点解析器(保留 Markdown 结构)
node_parser = MarkdownElementNodeParser()
nodes = node_parser.get_nodes_from_documents(documents)

# Step 3: 创建索引
index = VectorStoreIndex(nodes)

# Step 4: 创建查询引擎
query_engine = index.as_query_engine(
    similarity_top_k=3,  # 返回最相关的 3 个片段
    verbose=True,
)

# Step 5: 提问
response = query_engine.query("Ragas评估体系概述")
print(f"回答: {response}")

3、PDF解析后构建索引存储-chromadb

  • PDF 解析后构建索引存储在 Chromadb
import os

import chromadb
from llama_parse import LlamaParse
from llama_index.core import Settings, VectorStoreIndex, StorageContext
from llama_index.core.node_parser import MarkdownElementNodeParser
from llama_index.embeddings.dashscope import DashScopeEmbedding
from llama_index.llms.openai_like import OpenAILike
from llama_index.vector_stores.chroma import ChromaVectorStore

# 1. 配置 Embedding 模型
Settings.embed_model = DashScopeEmbedding(
    model_name="qwen3.7-text-embedding",
    api_key=os.getenv("DASHSCOPE_API_KEY"),
    embed_batch_size=10,
)

# 2. 配置 LLM
Settings.llm = OpenAILike(
    model="qwen3.7-flash",
    api_key=os.getenv("DASHSCOPE_API_KEY"),
    api_base="https://dashscope.aliyuncs.com/compatible-mode/v1",
    is_chat_model=True,
)

# 3. 初始化 Chroma
# Chroma 数据持久化目录
chroma_client = chromadb.PersistentClient(
    path="./chroma_db"
)

# 创建 / 获取 collection
chroma_collection = chroma_client.get_or_create_collection(
    name="ragas_docs"
)

# 将 Chroma collection 包装成 LlamaIndex VectorStore
vector_store = ChromaVectorStore(
    chroma_collection=chroma_collection
)

# 创建 StorageContext
storage_context = StorageContext.from_defaults(
    vector_store=vector_store
)

# 4. 解析 PDF
llama_cloud_api_key = os.getenv("LLAMA_CLOUD_API_KEY")

if not llama_cloud_api_key:
    raise ValueError(
        "请设置环境变量 LLAMA_CLOUD_API_KEY"
    )

parser = LlamaParse(
    api_key=llama_cloud_api_key,
    result_type="markdown",
    num_workers=4,
    verbose=True,
    language="ch_sim",
    system_prompt="""
    这是一篇关于 RAG 评估框架 Ragas 的中文技术文档。
    请保留所有表格结构、代码块和层级标题。
    对于数学公式,请尽量保留原始格式。
    """,
)

pdf_path = os.path.join(
    os.path.dirname(__file__),
    "19.Ragas评估.pdf"
)

documents = parser.load_data(pdf_path)

# 5. Markdown → Nodes
node_parser = MarkdownElementNodeParser()

nodes = node_parser.get_nodes_from_documents(
    documents
)

# 6. 创建 Chroma 向量索引
index = VectorStoreIndex(
    nodes,
    storage_context=storage_context,
)

# 7. 创建 Query Engine
query_engine = index.as_query_engine(
    similarity_top_k=3,
    verbose=True,
)

# 8. 查询
response = query_engine.query(
    "Ragas是什么?"
)

print(f"回答: {response}")

4、读取Charomadb数据回答

import os

import chromadb
from llama_index.core import VectorStoreIndex, StorageContext, Settings
from llama_index.embeddings.dashscope import DashScopeEmbedding
from llama_index.llms.openai_like import OpenAILike
from llama_index.vector_stores.chroma import ChromaVectorStore

# 1. 模型配置
Settings.embed_model = DashScopeEmbedding(
    model_name="qwen3.7-text-embedding",
    api_key=os.getenv("DASHSCOPE_API_KEY"),
    embed_batch_size=10,
)

Settings.llm = OpenAILike(
    model="qwen3.7-flash",
    api_key=os.getenv("DASHSCOPE_API_KEY"),
    api_base="https://dashscope.aliyuncs.com/compatible-mode/v1",
    is_chat_model=True,
)

# 2. Chroma配置
CHROMA_PATH = "./chroma_db"

COLLECTION_NAME = "ragas_docs"

chroma_client = chromadb.PersistentClient(
    path=CHROMA_PATH
)

collection = chroma_client.get_or_create_collection(
    COLLECTION_NAME
)

vector_store = ChromaVectorStore(
    chroma_collection=collection
)

storage_context = StorageContext.from_defaults(
    vector_store=vector_store
)

# 3. 加载已有知识库
index = VectorStoreIndex.from_vector_store(
    vector_store
)

# 4. 查询
question = "什么是忠诚度?"

query_engine = index.as_query_engine(
    similarity_top_k=3,
    verbose=True
)

response = query_engine.query(
    question
)
print(response)

5、使用本地向量化模型-存储

import os

import chromadb
from llama_index.embeddings.huggingface import HuggingFaceEmbedding
from llama_index.vector_stores.chroma import ChromaVectorStore
from llama_parse import LlamaParse
from llama_index.core import Settings, StorageContext
from llama_index.llms.openai_like import OpenAILike
from llama_index.core import VectorStoreIndex
from llama_index.core.node_parser import MarkdownElementNodeParser

# 1. 配置 Embedding 模型
Settings.embed_model = HuggingFaceEmbedding(
    model_name=r"G:\models\bge-base-zh-v1.5",
    model_kwargs={
        "local_files_only": True,
    },
)

# 2. 配置 LLM
Settings.llm = OpenAILike(
    model="qwen3.7-flash",
    api_key=os.getenv("DASHSCOPE_API_KEY"),
    api_base="https://dashscope.aliyuncs.com/compatible-mode/v1",
    is_chat_model=True,
)

# 3. 初始化 Chroma
# Chroma 数据持久化目录
chroma_client = chromadb.PersistentClient(
    path="./chroma_db"
)

# 创建 / 获取 collection
chroma_collection = chroma_client.get_or_create_collection(
    name="dldh_docs"
)

# 将 Chroma collection 包装成 LlamaIndex VectorStore
vector_store = ChromaVectorStore(
    chroma_collection=chroma_collection
)

# 创建 StorageContext
storage_context = StorageContext.from_defaults(
    vector_store=vector_store
)

# 4. 解析 PDF
llama_cloud_api_key = os.getenv("LLAMA_CLOUD_API_KEY")

if not llama_cloud_api_key:
    raise ValueError(
        "请设置环境变量 LLAMA_CLOUD_API_KEY"
    )

parser = LlamaParse(
    api_key=llama_cloud_api_key,
    result_type="markdown",
    num_workers=4,
    verbose=True,
    language="ch_sim",
    system_prompt="""
    这是一篇关于 RAG 评估框架 Ragas 的中文技术文档。
    请保留所有表格结构、代码块和层级标题。
    对于数学公式,请尽量保留原始格式。
    """,
)

pdf_path = os.path.join(
    os.path.dirname(__file__),
    "11.多轮对话.pdf"
)

documents = parser.load_data(pdf_path)

# 5. Markdown → Nodes
node_parser = MarkdownElementNodeParser()

nodes = node_parser.get_nodes_from_documents(
    documents
)

# 6. 创建 Chroma 向量索引
index = VectorStoreIndex(
    nodes,
    storage_context=storage_context,
)

# 7. 创建 Query Engine
query_engine = index.as_query_engine(
    similarity_top_k=3,
    verbose=True,
)

# 8. 查询
response = query_engine.query(
    "KV缓存指的是什么?"
)

print(f"回答: {response}")

6、使用本地向量化模型-推理+自定义提示词+检索内容打印

import os
from llama_index.core.prompts import ChatPromptTemplate
import chromadb
from llama_index.core import VectorStoreIndex, StorageContext, Settings
from llama_index.embeddings.huggingface import HuggingFaceEmbedding
from llama_index.llms.openai_like import OpenAILike
from llama_index.vector_stores.chroma import ChromaVectorStore

# 1. 模型配置
Settings.embed_model = HuggingFaceEmbedding(
    model_name=r"G:\models\bge-base-zh-v1.5",
    model_kwargs={
        "local_files_only": True,
    },
)
Settings.llm = OpenAILike(
    model="qwen3.7-flash",
    api_key=os.getenv("DASHSCOPE_API_KEY"),
    api_base="https://dashscope.aliyuncs.com/compatible-mode/v1",
    is_chat_model=True,
)

# 2. Chroma配置
CHROMA_PATH = "./chroma_db"

COLLECTION_NAME = "dldh_docs"

chroma_client = chromadb.PersistentClient(
    path=CHROMA_PATH
)

collection = chroma_client.get_or_create_collection(
    COLLECTION_NAME
)

vector_store = ChromaVectorStore(
    chroma_collection=collection
)

storage_context = StorageContext.from_defaults(
    vector_store=vector_store
)

# 3. 加载已有知识库
index = VectorStoreIndex.from_vector_store(
    vector_store
)

# 4. 查询
question = "短期记忆是什么?"

# 自定义提示词模板
CUSTOM_QA_PROMPT = ChatPromptTemplate.from_messages([
    (
        "system",
        """
            你是一个知识渊博的助手,请根据以下上下文信息回答问题。
            上下文信息:
                {context_str}
            规则:
                1. 仅根据上下文信息回答,不要编造内容
                2. 如果上下文不足以回答问题,请明确说"根据现有资料无法回答"
                3. 回答要简洁、准确,使用中文
        """,
    ),
    ("user", "问题:{query_str}"),
])

# 方式1: 通过 text_qa_template 传入自定义提示词
query_engine = index.as_query_engine(
    similarity_top_k=3,
    text_qa_template=CUSTOM_QA_PROMPT,  # 自定义 QA 提示词
    verbose=True,
)

# 5. 查询(一次检索 + 生成,通过 response.source_nodes 查看检索结果)
response = query_engine.query(question)

print("=" * 60)
print("检索到的文档片段:")
print("=" * 60)
for i, node in enumerate(response.source_nodes):
    print(f"\n--- 片段 {i + 1} (相关性: {node.score:.4f}) ---")
    print(f"来源: {node.metadata}")
    print(f"内容:\n{node.text[:500]}...")
    print("-" * 40)

print("\n" + "=" * 60)
print("生成回答:")
print("=" * 60)
print(f"回答: {response}")

7、使用本地向量化模型-自定义提示词赋值

import os
from llama_index.core.prompts import ChatPromptTemplate
import chromadb
from llama_index.core import VectorStoreIndex, StorageContext, Settings
from llama_index.embeddings.huggingface import HuggingFaceEmbedding
from llama_index.llms.openai_like import OpenAILike
from llama_index.vector_stores.chroma import ChromaVectorStore

# 1. 模型配置
Settings.embed_model = HuggingFaceEmbedding(
    model_name=r"G:\models\bge-base-zh-v1.5",
    model_kwargs={
        "local_files_only": True,
    },
)
Settings.llm = OpenAILike(
    model="qwen3.7-flash",
    api_key=os.getenv("DASHSCOPE_API_KEY"),
    api_base="https://dashscope.aliyuncs.com/compatible-mode/v1",
    is_chat_model=True,
)

# 2. Chroma配置
CHROMA_PATH = "./chroma_db"

COLLECTION_NAME = "dldh_docs"

chroma_client = chromadb.PersistentClient(
    path=CHROMA_PATH
)

collection = chroma_client.get_or_create_collection(
    COLLECTION_NAME
)

vector_store = ChromaVectorStore(
    chroma_collection=collection
)

storage_context = StorageContext.from_defaults(
    vector_store=vector_store
)

# 3. 加载已有知识库
index = VectorStoreIndex.from_vector_store(
    vector_store
)

# 4. 模拟对话历史记录
chat_history = [
    {"role": "user", "content": "我的名字叫cc,今年29岁"},
    {"role": "assistant", "content": "你好cc,很高兴认识你"},
    {"role": "user", "content": "我是一名华清远见成都中心的AI讲师"},
    {"role": "assistant", "content": "好滴"},
]

# 将历史记录转为文本
history_text = "\n".join(
    [f"{msg['role']}: {msg['content']}" for msg in chat_history]
)

# 5. 自定义提示词模板(含 {history} 占位符)
CUSTOM_QA_PROMPT = ChatPromptTemplate.from_messages([
    (
        "system",
        """
            你是一个知识渊博的助手,请根据以下上下文信息回答问题。
            上下文信息:
                {context_str}
            对话历史:
                {history}
            规则:
                1. 仅根据上下文信息回答,不要编造内容
                2. 如果上下文不足以回答问题,请明确说"根据现有资料无法回答"
                3. 回答要简洁、准确,使用中文
        """,
    ),
    ("user", "问题:{query_str}"),
])

# 6. 主动为 {history} 赋值,{context_str} 和 {query_str} 由引擎自动填充
prompt_with_history = CUSTOM_QA_PROMPT.partial_format(history=history_text)

# 7. 创建查询引擎,传入已赋值的提示词
query_engine = index.as_query_engine(
    similarity_top_k=3,
    text_qa_template=prompt_with_history,
    verbose=True,
)

# 8. 当前问题
question = "cc是谁?"

print(f"当前问题: {question}")
print(f"历史记录数: {len(chat_history)} 条")
print("=" * 60)

# 5. 查询(一次检索 + 生成,通过 response.source_nodes 查看检索结果)
response = query_engine.query(question)

print("=" * 60)
print("检索到的文档片段:")
print("=" * 60)
for i, node in enumerate(response.source_nodes):
    print(f"\n--- 片段 {i + 1} (相关性: {node.score:.4f}) ---")
    print(f"来源: {node.metadata}")
    print(f"内容:\n{node.text[:500]}...")
    print("-" * 40)

print("\n" + "=" * 60)
print("生成回答:")
print("=" * 60)
print(f"回答: {response}")

8、使用本地向量化模型-重排序

import os
from llama_index.core.prompts import ChatPromptTemplate
import chromadb
from llama_index.core import VectorStoreIndex, StorageContext, Settings, get_response_synthesizer
from llama_index.embeddings.huggingface import HuggingFaceEmbedding
from llama_index.llms.openai_like import OpenAILike
from llama_index.vector_stores.chroma import ChromaVectorStore
from llama_index.postprocessor.flag_embedding_reranker import FlagEmbeddingReranker

# 1. 模型配置
Settings.embed_model = HuggingFaceEmbedding(
    model_name=r"G:\models\bge-base-zh-v1.5",
    model_kwargs={
        "local_files_only": True,
    },
)
Settings.llm = OpenAILike(
    model="qwen3.7-flash",
    api_key=os.getenv("DASHSCOPE_API_KEY"),
    api_base="https://dashscope.aliyuncs.com/compatible-mode/v1",
    is_chat_model=True,
)

# 2. Chroma配置
CHROMA_PATH = "./chroma_db"

COLLECTION_NAME = "dldh_docs"

chroma_client = chromadb.PersistentClient(
    path=CHROMA_PATH
)

collection = chroma_client.get_or_create_collection(
    COLLECTION_NAME
)

vector_store = ChromaVectorStore(
    chroma_collection=collection
)

storage_context = StorageContext.from_defaults(
    vector_store=vector_store
)

# 3. 加载已有知识库
index = VectorStoreIndex.from_vector_store(
    vector_store
)

# 4. 模拟对话历史记录
chat_history = [
    {"role": "user", "content": "我的名字叫cc,今年29岁"},
    {"role": "assistant", "content": "你好cc,很高兴认识你"},
    {"role": "user", "content": "我是一名华清远见成都中心的AI讲师"},
    {"role": "assistant", "content": "好滴"},
]

# 将历史记录转为文本z
history_text = "\n".join(
    [f"{msg['role']}: {msg['content']}" for msg in chat_history]
)

# 5. 自定义提示词模板(含 {history} 占位符)
CUSTOM_QA_PROMPT = ChatPromptTemplate.from_messages([
    (
        "system",
        """
            你是一个知识渊博的助手,请根据以下上下文信息回答问题。
            上下文信息:
                {context_str}
            对话历史:
                {history}
            规则:
                1. 仅根据上下文信息回答,不要编造内容
                2. 如果上下文不足以回答问题,请明确说"根据现有资料无法回答"
                3. 回答要简洁、准确,使用中文
        """,
    ),
    ("user", "问题:{query_str}"),
])

# 6. 创建 Retriever
retriever = index.as_retriever(
    similarity_top_k=15
)

# 7. 配置重排序模型(Cross-Encoder,比向量检索更精准)
reranker = FlagEmbeddingReranker(
    model="G:\\models\\bge-reranker-large_v1",  # 中文重排序模型,首次运行自动下载
    top_n=3,  # 从候选集中精选出最相关的 3 条
    use_fp16=True,
)

# 8. 主动为 {history} 赋值,{context_str} 和 {query_str} 由引擎自动填充
prompt_with_history = CUSTOM_QA_PROMPT.partial_format(history=history_text)

# 9. 创建查询引擎(检索 + 重排序 + 生成)
query_engine = index.as_query_engine(
    similarity_top_k=15,  # 第一阶段:向量检索召回 15 条候选
    text_qa_template=prompt_with_history,
    node_postprocessors=[reranker],  # 第二阶段:重排序精选 top_n=3 条
    verbose=True,
)

# 10. 当前问题
question = "长期记忆是什么?"
print(f"当前问题: {question}")
print(f"历史记录数: {len(chat_history)} 条")
print("=" * 60)

# 10. 向量召回
retrieved_nodes = retriever.retrieve(question)
print("=" * 60)
print("第一阶段:向量数据库召回")
print("=" * 60)

print(f"召回数量:{len(retrieved_nodes)}")

for i, node in enumerate(retrieved_nodes, 1):
    print(f"\n--- 召回结果 {i} ---")
    print(f"向量相似度:{node.score}")
    print(f"Node ID:{node.node.node_id}")
    print(f"Metadata:{node.metadata}")
    print(f"内容:\n{node.text}")
    print("-" * 60)


# 11. Reranker 重排序 -- 它接收的就是上面 retrieved_nodes
reranked_nodes = reranker.postprocess_nodes(
    retrieved_nodes,
    query_str=question,
)

print("\n")
print("=" * 60)
print("第二阶段:Reranker 重排序")
print("=" * 60)

print(f"重排序后数量:{len(reranked_nodes)}")

for i, node in enumerate(reranked_nodes, 1):

    print(f"\n--- 重排序结果 {i} ---")
    print(f"Reranker 分数:{node.score}")
    print(f"Node ID:{node.node.node_id}")
    print(f"Metadata:{node.metadata}")
    print(f"内容:\n{node.text}")

    print("-" * 60)

# 12. 创建 Response Synthesizer
response_synthesizer = get_response_synthesizer(
    text_qa_template=prompt_with_history,
    llm=Settings.llm,
)


# 13. 直接使用重排序后的 Node 生成回答
response = response_synthesizer.synthesize(
    question,
    nodes=reranked_nodes,
)

print("\n" + "=" * 60)
print("生成回答:")
print("=" * 60)
print(f"回答: {response}")

9、使用本地向量化模型-BM25检索

import os
from llama_index.core.prompts import ChatPromptTemplate
import chromadb
from llama_index.core import VectorStoreIndex, StorageContext, Settings, get_response_synthesizer
from llama_index.embeddings.huggingface import HuggingFaceEmbedding
from llama_index.llms.openai_like import OpenAILike
from llama_index.vector_stores.chroma import ChromaVectorStore
from llama_index.postprocessor.flag_embedding_reranker import FlagEmbeddingReranker
from llama_index.retrievers.bm25 import BM25Retriever

# 1. 模型配置
Settings.embed_model = HuggingFaceEmbedding(
    model_name=r"G:\models\bge-base-zh-v1.5",
    model_kwargs={
        "local_files_only": True,
    },
)
Settings.llm = OpenAILike(
    model="qwen3.7-flash",
    api_key=os.getenv("DASHSCOPE_API_KEY"),
    api_base="https://dashscope.aliyuncs.com/compatible-mode/v1",
    is_chat_model=True,
)

# 2. Chroma配置
CHROMA_PATH = "./chroma_db"

COLLECTION_NAME = "dldh_docs"

chroma_client = chromadb.PersistentClient(
    path=CHROMA_PATH
)

collection = chroma_client.get_or_create_collection(
    COLLECTION_NAME
)

vector_store = ChromaVectorStore(
    chroma_collection=collection
)

storage_context = StorageContext.from_defaults(
    vector_store=vector_store
)

# 3. 加载已有知识库
index = VectorStoreIndex.from_vector_store(
    vector_store
)

# 4. 模拟对话历史记录
chat_history = [
    {"role": "user", "content": "我的名字叫cc,今年29岁"},
    {"role": "assistant", "content": "你好cc,很高兴认识你"},
    {"role": "user", "content": "我是一名华清远见成都中心的AI讲师"},
    {"role": "assistant", "content": "好滴"},
]

# 将历史记录转为文本z
history_text = "\n".join(
    [f"{msg['role']}: {msg['content']}" for msg in chat_history]
)

# 5. 自定义提示词模板(含 {history} 占位符)
CUSTOM_QA_PROMPT = ChatPromptTemplate.from_messages([
    (
        "system",
        """
            你是一个知识渊博的助手,请根据以下上下文信息回答问题。
            上下文信息:
                {context_str}
            对话历史:
                {history}
            规则:
                1. 仅根据上下文信息回答,不要编造内容
                2. 如果上下文不足以回答问题,请明确说"根据现有资料无法回答"
                3. 回答要简洁、准确,使用中文
        """,
    ),
    ("user", "问题:{query_str}"),
])

# 6. 从 ChromaDB 获取所有已存储的节点,构建 BM25 索引
# ChromaDB 中存储了文档内容和元数据,提取出来构建 BM25
from llama_index.core.schema import TextNode

# 获取 ChromaDB 中所有文档
all_docs = collection.get()
nodes = []
for i, (doc_id, doc_text, doc_meta) in enumerate(
    zip(all_docs["ids"], all_docs["documents"], all_docs["metadatas"])
):
    if doc_text:  # 跳过空文档
        nodes.append(TextNode(
            id_=doc_id,
            text=doc_text,
            metadata=doc_meta if doc_meta else {},
        ))

print(f"ChromaDB 中共有 {len(nodes)} 条文档,已加载到 BM25 索引")

# 创建 BM25 检索器(关键词匹配,无需 embedding)
retriever = BM25Retriever(
    nodes=nodes,
    similarity_top_k=15,  # 召回 15 条候选
    language="chinese",   # 中文分词
)

# 7. 配置重排序模型(Cross-Encoder,比向量检索更精准)
reranker = FlagEmbeddingReranker(
    model="G:\\models\\bge-reranker-large_v1",  # 中文重排序模型,首次运行自动下载
    top_n=3,  # 从候选集中精选出最相关的 3 条
    use_fp16=True,
)

# 8. 主动为 {history} 赋值,{context_str} 和 {query_str} 由引擎自动填充
prompt_with_history = CUSTOM_QA_PROMPT.partial_format(history=history_text)

# 9. 当前问题
question = "长期记忆是什么?"
print(f"当前问题: {question}")
print(f"历史记录数: {len(chat_history)} 条")
print("=" * 60)

# 10. BM25召回
retrieved_nodes = retriever.retrieve(question)
print("=" * 60)
print("第一阶段:BM25 关键词召回")
print("=" * 60)

print(f"召回数量:{len(retrieved_nodes)}")

for i, node in enumerate(retrieved_nodes, 1):
    print(f"\n--- 召回结果 {i} ---")
    print(f"BM25 分数:{node.score}")
    print(f"Node ID:{node.node.node_id}")
    print(f"Metadata:{node.metadata}")
    print(f"内容:\n{node.text}")
    print("-" * 60)


# 11. Reranker 重排序 -- 它接收的就是上面 retrieved_nodes
reranked_nodes = reranker.postprocess_nodes(
    retrieved_nodes,
    query_str=question,
)

print("\n")
print("=" * 60)
print("第二阶段:Reranker 重排序")
print("=" * 60)

print(f"重排序后数量:{len(reranked_nodes)}")

for i, node in enumerate(reranked_nodes, 1):

    print(f"\n--- 重排序结果 {i} ---")
    print(f"Reranker 分数:{node.score}")
    print(f"Node ID:{node.node.node_id}")
    print(f"Metadata:{node.metadata}")
    print(f"内容:\n{node.text}")

    print("-" * 60)

# 12. 创建 Response Synthesizer
response_synthesizer = get_response_synthesizer(
    text_qa_template=prompt_with_history,
    llm=Settings.llm,
)


# 13. 直接使用重排序后的 Node 生成回答
response = response_synthesizer.synthesize(
    question,
    nodes=reranked_nodes,
)

print("\n" + "=" * 60)
print("生成回答:")
print("=" * 60)
print(f"回答: {response}")

10、使用本地向量化模型-混合检索

import os
from llama_index.core.prompts import ChatPromptTemplate
import chromadb
from llama_index.core import VectorStoreIndex, StorageContext, Settings, get_response_synthesizer
from llama_index.embeddings.huggingface import HuggingFaceEmbedding
from llama_index.llms.openai_like import OpenAILike
from llama_index.vector_stores.chroma import ChromaVectorStore
from llama_index.postprocessor.flag_embedding_reranker import FlagEmbeddingReranker
from llama_index.retrievers.bm25 import BM25Retriever
from llama_index.core.retrievers import QueryFusionRetriever
from llama_index.core.retrievers.fusion_retriever import FUSION_MODES
from llama_index.core.schema import TextNode

# 1. 模型配置
Settings.embed_model = HuggingFaceEmbedding(
    model_name=r"G:\models\bge-base-zh-v1.5",
    model_kwargs={
        "local_files_only": True,
    },
)
Settings.llm = OpenAILike(
    model="qwen3.7-flash",
    api_key=os.getenv("DASHSCOPE_API_KEY"),
    api_base="https://dashscope.aliyuncs.com/compatible-mode/v1",
    is_chat_model=True,
)

# 2. Chroma配置
CHROMA_PATH = "./chroma_db"

COLLECTION_NAME = "dldh_docs"

chroma_client = chromadb.PersistentClient(
    path=CHROMA_PATH
)

collection = chroma_client.get_or_create_collection(
    COLLECTION_NAME
)

vector_store = ChromaVectorStore(
    chroma_collection=collection
)

storage_context = StorageContext.from_defaults(
    vector_store=vector_store
)

# 3. 加载已有知识库
index = VectorStoreIndex.from_vector_store(
    vector_store
)

# 4. 模拟对话历史记录
chat_history = [
    {"role": "user", "content": "我的名字叫cc,今年29岁"},
    {"role": "assistant", "content": "你好cc,很高兴认识你"},
    {"role": "user", "content": "我是一名华清远见成都中心的AI讲师"},
    {"role": "assistant", "content": "好滴"},
]

# 将历史记录转为文本z
history_text = "\n".join(
    [f"{msg['role']}: {msg['content']}" for msg in chat_history]
)

# 5. 自定义提示词模板(含 {history} 占位符)
CUSTOM_QA_PROMPT = ChatPromptTemplate.from_messages([
    (
        "system",
        """
            你是一个知识渊博的助手,请根据以下上下文信息回答问题。
            上下文信息:
                {context_str}
            对话历史:
                {history}
            规则:
                1. 仅根据上下文信息回答,不要编造内容
                2. 如果上下文不足以回答问题,请明确说"根据现有资料无法回答"
                3. 回答要简洁、准确,使用中文
        """,
    ),
    ("user", "问题:{query_str}"),
])

# 6. 构建两个检索器:向量检索 + BM25 关键词检索

# 6a. 向量检索器(语义匹配)
vector_retriever = index.as_retriever(similarity_top_k=15)

# 6b. BM25 检索器(关键词匹配)
all_docs = collection.get()
bm25_nodes = []
for doc_id, doc_text, doc_meta in zip(
    all_docs["ids"], all_docs["documents"], all_docs["metadatas"]
):
    if doc_text:
        bm25_nodes.append(TextNode(
            id_=doc_id,
            text=doc_text,
            metadata=doc_meta if doc_meta else {},
        ))

bm25_retriever = BM25Retriever(
    nodes=bm25_nodes,
    similarity_top_k=15,
    language="chinese",
)

print(f"ChromaDB 中共有 {len(bm25_nodes)} 条文档")

# 6c. 混合检索器:RRF 融合向量检索 + BM25 检索
# RRF (Reciprocal Rank Fusion):对两个检索器的排名取倒数求和,无需调权重
hybrid_retriever = QueryFusionRetriever(
    retrievers=[vector_retriever, bm25_retriever],
    mode=FUSION_MODES.RECIPROCAL_RANK,  # RRF 融合算法
    similarity_top_k=15,                 # 融合后返回 15 条
)

# 7. 配置重排序模型(Cross-Encoder,比向量检索更精准)
reranker = FlagEmbeddingReranker(
    model="G:\\models\\bge-reranker-large_v1",  # 中文重排序模型,首次运行自动下载
    top_n=3,  # 从候选集中精选出最相关的 3 条
    use_fp16=True,
)

# 8. 主动为 {history} 赋值,{context_str} 和 {query_str} 由引擎自动填充
prompt_with_history = CUSTOM_QA_PROMPT.partial_format(history=history_text)

# 9. 当前问题
question = "长期记忆是什么?"
print(f"当前问题: {question}")
print(f"历史记录数: {len(chat_history)} 条")
print("=" * 60)

# 10. 混合检索
retrieved_nodes = hybrid_retriever.retrieve(question)
print("=" * 60)
print("第一阶段:混合检索(向量 + BM25,RRF 融合)")
print("=" * 60)

print(f"召回数量:{len(retrieved_nodes)}")

for i, node in enumerate(retrieved_nodes, 1):
    print(f"\n--- 召回结果 {i} ---")
    print(f"RRF 融合分数:{node.score:.4f}")
    print(f"Node ID:{node.node.node_id}")
    print(f"Metadata:{node.metadata}")
    print(f"内容:\n{node.text[:200]}...")
    print("-" * 60)


# 11. Reranker 重排序 -- 它接收的就是上面 retrieved_nodes
reranked_nodes = reranker.postprocess_nodes(
    retrieved_nodes,
    query_str=question,
)

print("\n")
print("=" * 60)
print("第二阶段:Reranker 重排序")
print("=" * 60)

print(f"重排序后数量:{len(reranked_nodes)}")

for i, node in enumerate(reranked_nodes, 1):

    print(f"\n--- 重排序结果 {i} ---")
    print(f"Reranker 分数:{node.score}")
    print(f"Node ID:{node.node.node_id}")
    print(f"Metadata:{node.metadata}")
    print(f"内容:\n{node.text}")

    print("-" * 60)

# 12. 创建 Response Synthesizer
response_synthesizer = get_response_synthesizer(
    text_qa_template=prompt_with_history,
    llm=Settings.llm,
)

# 13. 直接使用重排序后的 Node 生成回答
response = response_synthesizer.synthesize(
    question,
    nodes=reranked_nodes,
)

print("\n" + "=" * 60)
print("生成回答:")
print("=" * 60)
print(f"回答: {response}")