第十四节:LlamaIndex框架介绍和入门1(基本用法、提示词、节点等)
一. LlamaIndex介绍
1. 简介
2. LlamaIndex 和 LangChain 对比
(1) LlamaIndex 核心目标就是:RAG(数据检索增强),专注于从大量的非结构化数据(如文档、网页等)中提取信息并提供自然语言查询的能力,帮助开发者轻松地将大型数据集转化为可以用自然语言查询的知识库,让LLM更好地理解你的数据和快速找到你要的数据。
【LlamaIndex更适合构建企业级知识库】
(2) LangChain核心目标:Chain和工作流(Agent),它不仅限于处理文本数据或提供查询功能,还支持创建复杂的对话代理(Chatbots)、自动化任务执行(Agents)、以及与其他服务集成等,让LLM更好地执行复杂任务
【LangChain更适合多工具协作的Agent】

总结:
如果你想让LLM“读懂”你的文档,用LlamaIndex;
如果你想让LLM“像人一样做事+查文档+查天气+计算”,用LangChain。
二. 快速入门
1. 大模型的基本调用
代码分享:
"""
入门使用
需要安装的镜像有
pip install llama-index # 核心
pip install llama-index-embeddings-huggingface # 使用本地的embedding模型
pip install llama-index-llms-dashscope # 使用千问的模型
pip install llama-index-llms-ollama
"""
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader
from llama_index.core import Settings
from llama_index.embeddings.huggingface import HuggingFaceEmbedding
from llama_index.llms.dashscope import DashScope
from llama_index.llms.ollama import Ollama
from dotenv import load_dotenv
import os
load_dotenv()
model = os.getenv("model1")
api_key = os.getenv("DASHSCOPE_API_KEY")
api_base_url = os.getenv("DASHSCOPE_BASE_URL")
# 1. 基本的LLM调用
# 1.1.初始化千问模型(设置成默认)
Settings.llm = DashScope(model_name=model, api_key=api_key, api_base_url=api_base_url)
# 1.2 调用模型
res = Settings.llm.complete("总结一下笑傲江湖,100字以内")
print(res.text)
2. 快速构建RAG
代码分享:
# 2.简单的RAG用法
# 2.1 加载本地的embedding模型
Settings.embed_model = HuggingFaceEmbedding(r'E:\LLM\bge-large-zh-v1.5')
# 2.2 加载文档
documents = SimpleDirectoryReader(input_files=["data/公司规章制度.txt"]).load_data()
# 2.3 创建索引-默认内存存储
index = VectorStoreIndex.from_documents(documents)
# 2.4 创建查询引擎
engine = index.as_query_engine()
# 2.5 查询
print(engine.query("公司的上下班时间?"))
3. Agent
-
将复杂的问题分解成更小的问题
-
选择要使用的外部工具 + 提取调用该工具的参数
-
规划一系列任务
-
将之前完成的任务存储在记忆模块中
三. 模板
1. RichPromptTemplate
通过利用 Jinja 语法,可以构建包含变量、逻辑、解析对象等的提示模板。
- 该 {% chat %}块用于将消息格式化为聊天消息并设置角色 {% endchat %} 用于结束聊天消息块
- 循环{% for %}用于迭代multi_modal_data传入的列表
- 该{{ image_path | image }}语法用于将图像路径格式化为图像内容块。此处,|用于对变量应用“过滤器”,以帮助将其识别为图像。
# ====================================================================
# 案例1:基础文本提示词模板
# 功能:演示 RichPromptTemplate 的基本用法(纯文本变量 + format/format_messages)
# ====================================================================
# 上下文信息
context_str1 = """
【企业基础信息】
公司全称:杭州深度求索人工智能基础技术研究有限公司
成立时间:2023年7月17日(工商注册日期)
核心技术:数据蒸馏技术(用于优化大语言模型训练数据)
股东背景:由幻方量化(知名私募机构)孵化
注册地址:浙江省杭州市拱墅区环城北路169号汇金国际大厦西1幢1201室
法定代表人:裴湉
核心业务:大语言模型(LLM)研发、技术服务、软件开发、技术转让
【补充说明】
1. 公司成立后6个月内完成首轮融资,估值超10亿人民币;
2. 数据蒸馏技术为公司核心专利,已应用于多款自研大模型。
"""
question1 = 'DeepSeek公司的工商注册成立年份是哪一年?请仅给出数字答案'
template = RichPromptTemplate(
"""
# 任务说明
你是企业信息问答助手,需严格基于提供的上下文信息回答问题,不得编造内容。
# 上下文信息
---------------------
{{ context_str }}
---------------------
# 待回答问题
{{ query_str }}
# 回答要求
1. 严格按照问题要求的格式回答;
2. 仅使用上下文里的信息,不添加额外解释;
3. 若上下文无相关信息,回复:"未查询到相关信息"。
"""
)
# 1. 格式化为纯字字符串你(非聊天型大模型/API)
prompt_str1 = template.format(context_str=context_str1, query_str=question1)
print("=== 格式化后的纯字符串Prompt ===")
print(prompt_str1)
print("=" * 50)
# 2. 格式化为聊天消息列表(使用于各种聊条大模型)
chatMsgList1 = template.format_messages(context_str=context_str1, query_str=question1)
print("\n=== 格式化后的聊天消息列表 ===")
# print(chatMsgList1)
for msg in chatMsgList1:
print(f"角色:{msg.role}")
print(f"内容:{msg.content}")
print("\n")
print("=" * 50)
print("=" * 100)
# ====================================================================
# 案例1:基础文本提示词模板
# 功能:演示 RichPromptTemplate 的基本用法(纯文本变量 + format/format_messages)
# ====================================================================
# 上下文信息
context_str1 = """
【企业基础信息】
公司全称:杭州深度求索人工智能基础技术研究有限公司
成立时间:2023年7月17日(工商注册日期)
核心技术:数据蒸馏技术(用于优化大语言模型训练数据)
股东背景:由幻方量化(知名私募机构)孵化
注册地址:浙江省杭州市拱墅区环城北路169号汇金国际大厦西1幢1201室
法定代表人:裴湉
核心业务:大语言模型(LLM)研发、技术服务、软件开发、技术转让
【补充说明】
1. 公司成立后6个月内完成首轮融资,估值超10亿人民币;
2. 数据蒸馏技术为公司核心专利,已应用于多款自研大模型。
"""
question1 = 'DeepSeek公司的工商注册成立年份是哪一年?请仅给出数字答案'
template = RichPromptTemplate(
"""
# 任务说明
你是企业信息问答助手,需严格基于提供的上下文信息回答问题,不得编造内容。
# 上下文信息
---------------------
{{ context_str }}
---------------------
# 待回答问题
{{ query_str }}
# 回答要求
1. 严格按照问题要求的格式回答;
2. 仅使用上下文里的信息,不添加额外解释;
3. 若上下文无相关信息,回复:"未查询到相关信息"。
"""
)
# 1. 格式化为纯字字符串你(非聊天型大模型/API)
prompt_str1 = template.format(context_str=context_str1, query_str=question1)
print("=== 格式化后的纯字符串Prompt ===")
print(prompt_str1)
print("=" * 50)
# 2. 格式化为聊天消息列表(使用于各种聊条大模型)
chatMsgList1 = template.format_messages(context_str=context_str1, query_str=question1)
print("\n=== 格式化后的聊天消息列表 ===")
# print(chatMsgList1)
for msg in chatMsgList1:
print(f"角色:{msg.role}")
print(f"内容:{msg.content}")
print("\n")
print("=" * 50)
print("=" * 100)
2. 函数映射
代码分享:
""" ======================================= 函数映射(Function Mapping)示例 ======================================= 核心原理: RichPromptTemplate 支持将模板中的变量映射到自定义函数, 在渲染时自动调用函数生成内容,实现动态提示词构建。 应用场景: - 敏感信息脱敏(姓名、身份证等) - 上下文格式美化 - 动态内容生成(如检索相关示例) ======================================= """ from llama_index.core.prompts import RichPromptTemplate import re def hide_sensitive_info(text: str) -> str: """ 敏感信息脱敏函数 参数: text: 原始文本 返回: str: 脱敏后的文本(姓名、身份证已隐藏) """ # 隐藏姓名(匹配 "姓名:XXX" 格式) text = re.sub(r'姓名:[^\n\r]+', '姓名:[已隐藏]', text) # 隐藏身份证号码(支持15位或18位) text = re.sub(r'身份证:\d{15}(\d{2}[0-9Xx])?', '身份证:[已隐藏]', text) # 隐藏其他格式的身份证(兼容中英文冒号、空格) text = re.sub(r'身份证[::]\s*\d+[0-9Xx]*', '身份证:[已隐藏]', text) return text def format_context_fn(**kwargs) -> str: """ 上下文格式化函数 - 映射到模板中的 {{ context_str }} 处理流程: 1. 提取上下文字符串 2. 脱敏处理(隐藏敏感信息) 3. 按空行分段 4. 过滤空段落 5. 添加项目符号格式 参数: **kwargs: 关键字参数,包含 "context_str" 返回: str: 格式化后的上下文(每行以 "- " 开头) """ # 1. 提取上下文字符串(带默认值防止KeyError) context_str = kwargs.get("context_str", "") # 2. 脱敏处理 context_str = hide_sensitive_info(context_str) # 3. 按空行分割成段落 paragraphs = context_str.split("\n\n") # 4. 过滤空段落并去除首尾空白 valid_paragraphs = [p.strip() for p in paragraphs if p.strip()] # 跳过空段落 # 5. 添加项目符号格式 formatted = "\n\n".join([f"- {p}" for p in valid_paragraphs]) return formatted # ==================== 主流程 ==================== # 提示词模板 template_str = """ 上下文信息如下。 --------------------- {{ context_str }} --------------------- 给定上下文信息而不是先验知识,回答查询。 查询:{{ query_str }} 答案: """ # 待处理的上下文(包含敏感信息) context_str2 = """ 姓名:张三 身份证:123456798123456 这项工作中,我们开发并发布了 Llama 2,这是一组经过预训练和微调的大型语言模型 (LLM),其规模从 70 亿到 700 亿个参数不等。 我们经过微调的 LLM 称为 Llama 2-Chat,针对对话用例进行了优化。 在我们测试的大多数基准测试中,我们的模型都优于开源聊天模型,并且根据我们对有用性和安全性的人工评估,它们可能是闭源模型的合适替代品。 """ # 创建模板并注册函数映射 prompt_tmpl = RichPromptTemplate(template_str, function_mappings={"context_str": format_context_fn}) # 渲染模板(自动调用 format_context_fn 处理 context_str) fmt_prompt = prompt_tmpl.format(context_str=context_str2, query_str="llama2有多少参数?") # 输出结果 print(fmt_prompt)
3. 部分格式化
partial_format)允许部分格式化提示,填写一些变量,同时将其他变量留待以后填写。这是一个很好的便利功能,因此不必一直维护所有必需的提示变量format,可以在它们进入时进行部分格式化。代码分享:
from llama_index.core.prompts import RichPromptTemplate prompt_tmpl = RichPromptTemplate( """ 上下文信息如下。 --------------------- {{ context_str }} --------------------- 给定上下文信息而不是先验知识,回答查询。 请以 {{ tone_name }} 的格式写出答案 查询: {{ query_str }} 答案: """ ) # 1 partial_format-部分格式化:先填充一部分,剩下的后续再填充 partial_prompt_tmpl = prompt_tmpl.partial_format(tone_name="莎士比亚") print(partial_prompt_tmpl) print("*" * 80) # 2 format-格式化:将所有占位符都填充好 fmt_prompt = partial_prompt_tmpl.format( context_str="在这项工作中,我们开发并发布了 Llama 2,这是一组经过预训练和微调的大型语言模型 (LLM),其规模从 70 亿到 700 亿个参数不等", query_str="llama 2 有多少个参数", ) print(fmt_prompt) print("*" * 80) # 3 format_messages-格式化为聊天消息列表 fmt_prompt = partial_prompt_tmpl.format_messages( context_str="在这项工作中,我们开发并发布了 Llama 2,这是一组经过预训练和微调的大型语言模型 (LLM),其规模从 70 亿到 700 亿个参数", query_str="llama 2 有多少个参数", ) print(fmt_prompt) print("*" * 80)
4. 动态少样本
代码分享:
""" ================================================================================ 案例:动态少样本提示(Dynamic Few-Shot Prompting) ================================================================================ 核心思路: 传统少样本:手动写死几个示例 → 不够灵活,示例可能与当前问题无关 动态少样本:根据用户问题,自动从示例库中检索"最相似"的示例 → 精准匹配 执行流程: 1. 准备示例文档 → 存入向量索引(VectorStoreIndex) 2. 用户提问 → get_examples_fn 从索引中检索最相关的示例 3. 示例注入模板 → LLM 在相关示例的引导下生成答案 本例场景: 故事生成 —— 根据用户想讲的故事类型,自动匹配相似的故事示例作为参考 ================================================================================ """ from llama_index.core import Settings, VectorStoreIndex from llama_index.core.schema import TextNode from llama_index.embeddings.huggingface import HuggingFaceEmbedding from llama_index.core.prompts import RichPromptTemplate from llama_index.llms.dashscope import DashScope from dotenv import load_dotenv import os # ============================================================ # 第1步:加载环境变量 & 配置 LLM 和嵌入模型 # ============================================================ load_dotenv() model = os.getenv("model1") # 模型名称,如 qwen-turbo api_key = os.getenv("api_key") # API密钥 api_base_url = os.getenv("base_url") # API基础地址 print(api_base_url, model, api_key) Settings.llm = DashScope(model_name=model, api_key=api_key, api_base=api_base_url, is_chat_model=True) # 标记为聊天模型 # 嵌入模型:用于将文本转换为向量,进行语义相似度检索 Settings.embed_model = HuggingFaceEmbedding(model_name=r'E:\LLM\bge-large-zh-v1.5') # ============================================================ # 第2步:定义提示词模板 # ============================================================ # 注意:模板中有两个占位符 # {{ examples }} → 对应 function_mappings 中的 "examples",由函数动态生成 # {{ query_str }} → 普通的字符串变量,传入时直接替换 text_to_sql_prompt_tmpl_str = """\ 你是一个故事生成专家 下面是一些例子: 示例: {{ examples }} 现在轮到你了. 问题: {{ query_str }} 答案: """ # ============================================================ # 第3步:准备示例文档并构建向量索引(示例库) # ============================================================ # 每个 TextNode 存储一对 "问题+答案" 作为示例 example_nodes = [ TextNode(text="Query: 请生成一个笑傲江湖的故事,输出20字符\n令狐冲练成独孤九剑,打败任我行。"), TextNode(text="Query: 请生成一个白雪公主的故事,输出20字符\n白雪公主被后妈害,七矮人救她,王子吻醒了她。"), ] # 构建向量索引:将示例转为向量,存入向量数据库(内存中) index = VectorStoreIndex(nodes=example_nodes) # 创建检索器:similarity_top_k=1 表示每次只取最相似的1个示例 retriever = index.as_retriever(similarity_top_k=1) # ============================================================ # 第4步:定义动态示例获取函数(核心!) # ============================================================ def get_examples_fn(**kwargs): """ 动态少样本的核心函数 —— 根据用户问题自动检索最相关的示例 作用(关键理解): 模板中的 {{ examples }} 不是一个静态字符串,而是调用这个函数动态生成的。 当 RichPromptTemplate 渲染模板时, 遇到 {{ examples }} 就会自动调用 get_examples_fn(query_str="用户问题") 处理流程: 用户问题 "西游记故事" → 转为向量 → 在索引中查找最相似的示例 → 找到 "白雪公主的故事"(同为童话类) → 返回该示例文本,填入 {{ examples }} 占位符 为什么叫"动态"少样本: 传统做法:模板里写死 examples = "小红帽示例 + 白雪公主示例" 动态做法:模板只写 {{ examples }},由函数实时检索决定用哪些示例 → 用户问童话 → 检索到童话示例 → 用户问科幻 → 检索到科幻示例 → 示例随问题变化,更精准 参数: **kwargs: 由 RichPromptTemplate 自动传入, 至少包含 "query_str"(用户的原始问题) 返回: str: 检索到的示例文本,多个示例用空行分隔 """ # 1. 从模板变量中取出用户的原始问题 query = kwargs["query_str"] # 2. 用问题去向量索引中检索最相似的示例 # retrieve() 将问题转为向量,计算与示例库中各示例的余弦相似度 # 返回相似度最高的 top_k 个结果(这里 top_k=1) examples = retriever.retrieve(query) # 3. 将检索到的示例拼接为文本,用空行分隔(多个示例时) return "\n\n".join(node.text for node in examples) # ============================================================ # 第5步:创建 RichPromptTemplate,注册函数映射 # ============================================================ # function_mappings 的作用: # 将模板中的 {{ examples }} 占位符 映射到 get_examples_fn 函数 # 渲染时,{{ examples }} → 自动调用 get_examples_fn() → 替换为返回结果 prompt_tmpl = RichPromptTemplate( text_to_sql_prompt_tmpl_str, function_mappings={"examples": get_examples_fn}, ) # ============================================================ # 第6步:渲染模板 + 调用 LLM 生成 # ============================================================ # 传入 query_str = "请生成一个西游记的故事" # 渲染过程: # ① {{ query_str }} → 直接替换为 "请生成一个西游记的故事" # ② {{ examples }} → 调用 get_examples_fn(query_str="请生成一个西游记的故事") # → 检索到最相似的示例(如白雪公主示例) # → 替换为示例文本 prompt = prompt_tmpl.format(query_str="请生成一个西游记的故事") print("prompt=>", prompt) # 将渲染好的完整提示词发给大模型 response = Settings.llm.complete(prompt) print("response.text=>", response.text)
四. 节点
1. 说明
Document 和 Node 对象是 Llama Index 中的核心。
文档(Document)是包含任何数据源(例如 PDF、API 输出或从数据库检索的数据)的通用容器。它们可以手动构建,也可以通过我们的数据加载器自动创建。默认情况下,文档会存储文本以及其他一些属性。其中一些属性如下所示。
-
metadata- 可以附加到文本的注释词典。 -
relationships- 包含与其他文档/节点的关系的字典。
手动构建节点--代码分享
from llama_index.core.schema import TextNode, NodeRelationship, RelatedNodeInfo def bind_nodes(a: TextNode, b: TextNode, a_note: str, b_note: str): """ 给两个 TextNode 建立双向的 NEXT/PREVIOUS 关系,并添加描述元数据 Args: a: 第一个文本节点(作为「前序节点」) b: 第二个文本节点(作为「后序节点」) a_note: 从a指向b的关系描述(a的NEXT关系元数据) b_note: 从b指向a的关系描述(b的PREVIOUS关系元数据) """ # 第一步:给节点a添加「NEXT(下一个)」关系 → 指向节点b # 意思是:a的下一个节点是b,并用metadata记录这个关系的描述 a.relationships[NodeRelationship.NEXT] = RelatedNodeInfo( node_id=b.node_id, metadata={"desc": a_note} # 关联的目标节点ID(b的唯一标识) # 关系的描述元数据,方便后续追溯 ) # 第二步:给节点b添加「PREVIOUS(上一个)」关系 → 指向节点a # 意思是:b的上一个节点是a,形成双向绑定,元数据记录描述 b.relationships[NodeRelationship.PREVIOUS] = RelatedNodeInfo( node_id=a.node_id, metadata={"desc": b_note} # 关联的目标节点ID(a的唯一标识) # 关系的描述元数据 ) # 3.创建两个节点 node1 = TextNode(text="deepseek", id_="1") # 节点1:文本内容"deepseek",ID为"1" node2 = TextNode(text="chatgpt", id_="2") # 节点2:文本内容"chatgpt",ID为"2" # 4.调用函数,建立节点间的关系 bind_nodes(node1, node2, a_note="这是节点2", b_note="这是节点1") # 5. 打印节点关系 nodes = [node1, node2] print(nodes)
2. 元数据提取
代码分享
""" ======================================= 元数据自动提取(Metadata Extraction) ======================================= 功能说明: 使用 LLM 自动从文档节点中提取元数据,包括: - TitleExtractor:提取文档标题 - QuestionsAnsweredExtractor:提取该内容能回答的问题 - SummaryExtractor:提取摘要(未使用) - EntityExtractor:提取实体(未使用) 应用场景: - 提升 RAG 检索效果:通过提取的问题增强检索匹配 - 文档概览:快速了解文档主题 - 构建知识图谱:提取实体关系 ======================================= """ from llama_index.core.extractors import ( TitleExtractor, # 标题提取器 QuestionsAnsweredExtractor, # 问题提取器 ) from llama_index.core.node_parser import TokenTextSplitter # 文本分割器 from llama_index.core import SimpleDirectoryReader, Settings from llama_index.embeddings.huggingface import HuggingFaceEmbedding from llama_index.llms.dashscope import DashScope from dotenv import load_dotenv import os import asyncio # ============================================================ # 第1步:加载环境变量 & 配置 LLM 和嵌入模型 # ============================================================ load_dotenv() model = os.getenv("model1") # 模型名称,如 qwen-turbo api_key = os.getenv("api_key") # API密钥 api_base_url = os.getenv("base_url") # API基础地址 # 配置 LLM(大模型,用于生成元数据) Settings.llm = DashScope(model_name=model, api_key=api_key, api_base=api_base_url, is_chat_model=True) # 配置嵌入模型(用于向量检索) Settings.embed_model = HuggingFaceEmbedding(model_name=r'E:\LLM\bge-large-zh-v1.5') # ============================================================ # 第2步:加载文档 & 分割文本 # ============================================================ documents = SimpleDirectoryReader(input_files=["data/小说.txt"]).load_data() # 读取小说文件 # 文本分割器配置 # - chunk_size: 每个片段的 token 数(512) # - chunk_overlap: 相邻片段的重叠 token 数(128),保证上下文连贯 text_splitter = TokenTextSplitter(chunk_size=512, chunk_overlap=128) # ============================================================ # 第3步:配置元数据提取器 # ============================================================ # 【问题提取器】- 生成该内容能回答的问题 # 这些问题有什么用? # → 检索时可以用问题匹配,比直接匹配正文更容易找到相关内容 question_prompt_template = """ 以下是参考内容: {context_str} 请根据上述上下文信息,生成 {num_questions} 个该内容能够具体回答的问题, 这些问题的答案最好是该内容独有的,不容易在其他地方找到。 请用中文输出! """ qa_extractor = QuestionsAnsweredExtractor( questions=3, prompt_template=question_prompt_template, num_workers=5 # 每个节点生成3个问题 # 并行提取的线程数 ) # 【标题提取器】- 提取文档片段的标题 title_extractor = TitleExtractor( nodes=5, # 合并相邻5个节点生成一个标题 node_template="请为以下文档生成一个简洁的标题: {context_str}", # 提取提示词模板 num_workers=5, # 并行提取的线程数 ) # ============================================================ # 第4步:主函数 - 执行元数据提取 # ============================================================ async def main(): """ 执行流程: 1. 将文档分割成节点 2. 并行提取标题和问题 3. 将提取的元数据回填到节点 """ # ① 将文档分割成节点(取前3个节点测试) nodes = text_splitter.get_nodes_from_documents(documents)[:3] print(f"文档已分割为 {len(nodes)} 个节点") # ② 并行提取元数据(使用异步提高效率) titles = await title_extractor.aextract(nodes) # 提取标题 qas = await qa_extractor.aextract(nodes) # 提取问题 # ③ 将提取的元数据回填到节点 metadata 中 for node, t, q in zip(nodes, titles, qas): node.metadata.update(t) # 加入 document_title node.metadata.update(q) # 加入 questions_answered # ④ 打印结果 for i, node in enumerate(nodes): print(f"\n===== 节点 {i+1} 的元数据 =====") print(f"标题: {node.metadata.get('document_title', '无')}") print(f"问题: {node.metadata.get('questions_answered', '无')}") # ============================================================ # 程序入口 # ============================================================ if __name__ == "__main__": asyncio.run(main())
!
- 作 者 : Yaopengfei(姚鹏飞)
- 博客地址 : http://www.cnblogs.com/yaopengfei/
- 声 明1 : 如有错误,欢迎讨论,请勿谩骂^_^。
- 声 明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。

浙公网安备 33010602011771号