CH1_02_llamaindex_example

2054 字
10 分钟
CH1_02_llamaindex_example

相比 LangChain 更像一个通用的 LLM 编排工具,LlamaIndex 更专注于 “数据索引与检索” 这个 RAG 核心场景,开箱即用。

1. 库导入#

import os
# os.environ['HF_ENDPOINT']='https://hf-mirror.com'
from dotenv import load_dotenv
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader, Settings
from llama_index.embeddings.huggingface import HuggingFaceEmbedding
from llama_index.llms.deepseek import DeepSeek
load_dotenv()

1.1 核心库解析#

导入语句作用
from dotenv import load_dotenv读取 .env 文件中的环境变量(如 API Key)
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader, Settings三大核心组件,见下方详解
from llama_index.embeddings.huggingface import HuggingFaceEmbedding使用 HuggingFace 上的嵌入模型,将文本转为向量
from llama_index.llms.deepseek import DeepSeekLlamaIndex 对 DeepSeek 大语言模型的封装,用于回答问题

1.2 🧩 核心函数详解#

SimpleDirectoryReader — 数据加载器

  • 从指定文件(或文件夹)中读取文档内容
  • 支持 Markdown、PDF、Word、TXT 等多种格式
  • 返回一个 Document 对象列表(每个文件/页面是一个 Document)

Settings — 全局配置

  • 一个全局单例配置对象,设置后整个项目自动生效
  • Settings.llm:指定用哪个大模型来回答问题
  • Settings.embed_model:指定用哪个模型将文本转为向量(embedding)

HuggingFaceEmbedding — 嵌入模型

  • 调用 HuggingFace 上的文本嵌入模型(这里是 BAAI 的 bge-small-zh-v1.5
  • 作用:把一段文字转换成一个固定长度的向量(数字数组),用于相似度搜索
  • bge-small-zh-v1.5 是一个轻量级的中文嵌入模型,速度快且效果不错

DeepSeek — LLM 封装

  • 将 DeepSeek 的 API 封装成 LlamaIndex 能调用的 LLM 接口
  • model 参数指定模型名,api_key 从环境变量读取

VectorStoreIndex — 向量索引(核心!)

  • 将 Document 列表构建成一个可检索的向量索引,内部自动完成:
    1. 文本分块(把长文档切成小段)
    2. 向量化(用 embed_model 给每个块生成向量)
    3. 存入内存向量存储(默认的内存索引)

2. 模型传入#

Settings.llm = DeepSeek(
model="deepseek-v4-flash",
api_key=os.getenv("DEEPSEEK_API_KEY"),
)
Settings.embed_model = HuggingFaceEmbedding("BAAI/bge-small-zh-v1.5")

指定回答问题的大模型与向量嵌入模型。

docs = SimpleDirectoryReader(input_files=["../../data/C1/markdown/easy-rl-chapter1.md"]).load_data()
index = VectorStoreIndex.from_documents(docs)
query_engine = index.as_query_engine()

这里我们用 .as_query_engine() 查询引擎将 index 包装为一个问答引擎,调用的时候会自动执行检索 + 生成的全过程。

它的内部流程大致是:用户问题 → 向量检索 Top-K 相关段落 → 拼到 Prompt 中 → 发给 LLM 回答

3. 输出#

print(query_engine.get_prompts())
print(query_engine.query("文中举了哪些例子?"))

输出为:

{'response_synthesizer:text_qa_template': SelectorPromptTemplate(metadata={'prompt_type': <PromptType.QUESTION_ANSWER: 'text_qa'>}, template_vars=['context_str', 'query_str'], kwargs={}, output_parser=None, template_var_mappings={}, function_mappings={}, default_template=PromptTemplate(metadata={'prompt_type': <PromptType.QUESTION_ANSWER: 'text_qa'>}, template_vars=['context_str', 'query_str'], kwargs={}, output_parser=None, template_var_mappings=None, function_mappings=None, template='Context information is below.\n---------------------\n{context_str}\n---------------------\nGiven the context information and not prior knowledge, answer the query.\nQuery: {query_str}\nAnswer: '), conditionals=[(<function is_chat_model at 0x000001E63D7F40E0>, ChatPromptTemplate(metadata={'prompt_type': <PromptType.CUSTOM: 'custom'>}, template_vars=['context_str', 'query_str'], kwargs={}, output_parser=None, template_var_mappings=None, function_mappings=None, message_templates=[ChatMessage(role=<MessageRole.SYSTEM: 'system'>, additional_kwargs={}, blocks=[TextBlock(block_type='text', text="You are an expert Q&A system that is trusted around the world.\nAlways answer the query using the provided context information, and not prior knowledge.\nSome rules to follow:\n1. Never directly reference the given context in your answer.\n2. Avoid statements like 'Based on the context, ...' or 'The context information ...' or anything along those lines.")]), ChatMessage(role=<MessageRole.USER: 'user'>, additional_kwargs={}, blocks=[TextBlock(block_type='text', text='Context information is below.\n---------------------\n{context_str}\n---------------------\nGiven the context information and not prior knowledge, answer the query.\nQuery: {query_str}\nAnswer: ')])]))]), 'response_synthesizer:refine_template': SelectorPromptTemplate(metadata={'prompt_type': <PromptType.REFINE: 'refine'>}, template_vars=['query_str', 'existing_answer', 'context_msg'], kwargs={}, output_parser=None, template_var_mappings={}, function_mappings={}, default_template=PromptTemplate(metadata={'prompt_type': <PromptType.REFINE: 'refine'>}, template_vars=['query_str', 'existing_answer', 'context_msg'], kwargs={}, output_parser=None, template_var_mappings=None, function_mappings=None, template="The original query is as follows: {query_str}\nWe have provided an existing answer: {existing_answer}\nWe have the opportunity to refine the existing answer (only if needed) with some more context below.\n------------\n{context_msg}\n------------\nGiven the new context, refine the original answer to better answer the query. If the context isn't useful, return the original answer.\nRefined Answer: "), conditionals=[(<function is_chat_model at 0x000001E63D7F40E0>, ChatPromptTemplate(metadata={'prompt_type': <PromptType.CUSTOM: 'custom'>}, template_vars=['context_msg', 'query_str', 'existing_answer'], kwargs={}, output_parser=None, template_var_mappings=None, function_mappings=None, message_templates=[ChatMessage(role=<MessageRole.USER: 'user'>, additional_kwargs={}, blocks=[TextBlock(block_type='text', text="You are an expert Q&A system that strictly operates in two modes when refining existing answers:\n1. **Rewrite** an original answer using the new context.\n2. **Repeat** the original answer if the new context isn't useful.\nNever reference the original answer or context directly in your answer.\nWhen in doubt, just repeat the original answer.\nNew Context: {context_msg}\nQuery: {query_str}\nOriginal Answer: {existing_answer}\nNew Answer: ")])]))])}
文中举了选择餐馆、做广告、挖油和玩游戏的例子。

上面的一整段输出中其实就包含两个 prompt 模板:

  1. response_synthesizer:text_qa_template主要问答模板,你可以在开头找到它;
  2. response_synthesizer:refine_template精炼模板,在这一大段输出中间。

我们接着来一个个看里面的参数:

  • text_qa_template问答主模板,这是 LLM 首次回答时用的 prompt,在第一个长长的 context_str 前后。
    • context_str :检索到的 相关文本片段,拼到这里;
    • query_str :用户的 原始问题
    • Answer :留给 LLM 输出答案的位置。
Context information is below.
---------------------
{context_str}
---------------------
Given the context information and not prior knowledge, answer the query.
Query: {query_str}
Answer:

这里的 “Given the …” 提示词是 query_engine 内置的,因为我们在创建查询引擎 query_engine = index.as_query_engine() 的时候没有传入任何参数,所以它内部自动使用了 LlamaIndex 的默认 prompt 模板。

如果想要创建自己的模板或 prompt,应该类似:

from llama_index.core import PromptTemplate
custom_qa_prompt = PromptTemplate(
"请根据以下内容回答问题。\n"
"内容:{context_str}\n"
"问题:{query_str}\n"
"答案:"
)
query_engine = index.as_query_engine(
text_qa_template=custom_qa_prompt
)
  • is_chat_modelChat 模型条件分支。因为 Deepseek 是 Chat 模型,所以实际用的是下面这个版本。
    • role=SYSTEM :系统角色指令,约束回答风格;
    • role=USER :包含上下文和用户问题的消息。
System: You are an expert Q&A system...
1. Never directly reference the given context in your answer.
2. Avoid statements like 'Based on the context, ...'
User: Context information is below.
---------------------
{context_str}
---------------------
Given the context information and not prior knowledge, answer the query.
Query: {query_str}
Answer:
  • refine_template精炼模板。当文档被切分成多个块,依次检索时,除了第一块用上面的主模板,后续块用这个模板精炼答案。
    • query_str :原始问题;
    • existing_answer :上一轮已有的答案(需要被精炼);
    • context_msg :新的补充上下文(当前检索到的另一块文本);
    • Refined Answer: :LLM 输出优化后答案的位置。
# Chat 分支版本,带 Sytem/User 角色
System: ...1. **Rewrite** an original answer using the new context.
2. **Repeat** the original answer if the new context isn't useful.
Never reference the original answer or context directly...
User: New Context: {context_msg}
Query: {query_str}
Original Answer: {existing_answer}
New Answer:

综上,完整的流程如下图:

用户问:"文中举了哪些例子?"
向量检索 Top-K 个相关块
┌─────┴─────┐
│ 第 1 块 │ ──→ text_qa_template ──→ LLM → 初步答案 A1
└─────┬─────┘
┌─────┴─────┐
│ 第 2 块 │ ──→ refine_template (A1 + 新块) → 精炼答案 A2
└─────┬─────┘
┌─────┴─────┐
│ 第 3 块 │ ──→ refine_template (A2 + 新块) → 最终答案 A3
└───────────┘

其他元数据补充:

  • prompt_type :模板类型标识,这里是 QUESTION_ANSWER(问答)和 REFINE(精炼);
  • template_vars :模板中用到的变量名列表
  • conditionals :条件分支 —— 根据模型类型(是否是 Chat 模型)选择不同的模板格式
  • default_template :文本补全模型的默认模板(text-davinci-003 风格)

3.1 什么是 Chat 模型?#

在 LlamaIndex 的语境里,is_chat_model 的判断对应的是 LLM 的两种 API 调用方式:

1️⃣ Chat 模型(如 DeepSeek、GPT-4o、GPT-3.5-turbo、Claude 等)#

  • API 格式:接受 System + User + Assistant 角色的消息列表(Messages)
  • 特点:支持多轮对话、角色分离、指令遵循能力强
  • LlamaIndex 对 Chat 模型使用 ChatPromptTemplate,如我们看到的带 role=SYSTEMrole=USER 的模板

2️⃣ 文本补全模型(Text Completion / 原始 LLM)#

  • API 格式:接受一段连续文本字符串,模型接着往后生成
  • 代表:OpenAI 早期的 text-davinci-003text-curie-001gpt-3.5-turbo-instruct
  • 特点:没有 System/User 角色之分,全靠 Prompt 本身的措辞来引导

LlamaIndex 对这类模型使用 default_template,就是一个纯文本模板:

Context information is below.
---------------------
{context_str}
---------------------
Given the context information and not prior knowledge, answer the query.
Query: {query_str}
Answer:

3.1.3 区别对比#

维度Chat 模型文本补全模型
输入格式消息列表([System, User, Assistant]纯文本字符串
角色区分✅ 有 System / User 角色❌ 无角色,全部混合在文本中
指令位置System 消息放系统指令指令写在 Prompt 开头/结尾
代表模型DeepSeek, GPT-4o, Claude, Geminitext-davinci-003, gpt-3.5-turbo-instruct
LlamaIndex 模板ChatPromptTemplatePromptTemplate(即 default_template

3.1.4 实际效果差异#

对于同一个意图,模型实际收到的内容分别是:

文本补全模型(纯文本)→

Context information is below.
---------------------
文中提到选择餐馆...
---------------------
...
Answer:

Chat 模型(消息列表)→

System: You are an expert Q&A system...
1. Never directly reference the given context...
User: Context information is below.
---------------------
文中提到选择餐馆...
---------------------
...
Answer:

Chat 模型多了 System message 这个专门放指令的位置,指令不会被用户问题”淹没”,所以 LlamaIndex 在 Chat 模型的模板里加了更多细粒度的约束(如”不要提到 ‘根据上下文‘“等),让回答更自然。

现在几乎所有主流 API 都是 Chat 模型了,OpenAI 也在 2024 年彻底弃用了 text-davinci-003 等补全模型。所以我们大概率只会用到 Chat 模型分支,default_template 更多是历史兼容用途。

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

CH1_02_llamaindex_example
https://datawhalechina.github.io/all-in-rag/#/chapter1/03_get_start_rag
作者
HAC
发布于
2026-06-15
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
HAC
观之非易,行且克难
Greetings
欢迎来到我的博客!这里主要分享我的学习笔记与兴趣爱好。
音乐
封面

音乐

暂未播放

0:00 0:00
暂无歌词
分类
标签
站点统计
文章
32
分类
5
标签
13
总字数
79,889
运行时长
0
最后活动
0 天前

文章目录