
大多数开发者花费数周时间从零开始构建检索系统,与向量数据库和嵌入模型搏斗,却发现他们的LLM仍然会产生幻觉。
讽刺的是什么?虽然每个人都将RAG视为将AI响应基于真实数据的解决方案,但实现复杂性往往完全背离了其目的。
LlamaIndex 改变了这种局面,它将一个过去需要数月工程的项目,浓缩为几十行代码,同时不牺牲使检索增强生成真正发挥作用的复杂性。你无需构建基础设施,而是可以专注于重要的事情:将公司的知识转化为智能、准确的响应,让你的用户可以信任。
理解LlamaIndex和RAG在高级搜索应用中的作用
LLM擅长生成文本,但它们可能会犯错误或使用过时信息。RAG通过将语言模型连接到最新、相关的数据来解决这个问题。
LlamaIndex简介:构建RAG管道的工具包
如果RAG是构建更智能LLM应用的策略,那么LlamaIndex就是实现它的工具包。构建RAG系统就像准备一道美食:你需要优质的食材(你的数据)、合适的工具(来处理和理解数据)以及一份食谱(工作流程)。LlamaIndex提供了所有这些。
LlamaIndex原名GPT Index,是一个开源框架,它简化了RAG系统的构建。它提供管理数据旅程的组件,从数据摄取到索引再到与LLM集成以生成答案。
LlamaIndex RAG系统的核心组件:从原始数据到智能答案
使用LlamaIndex将原始文档转换为上下文感知的答案需要经过五个明确的阶段:
- 加载: 将您的数据导入系统。LlamaIndex支持多种文档加载器,可以轻松导入PDF、文本文件、网站、API等。
- 索引: 将加载的文档转换为向量嵌入,即捕捉文本含义的数值表示。这有助于查找相关信息,即使查询使用了不同的词语。
- 存储: 保存您的索引和元数据,避免每次启动应用程序时都重新处理大型数据集。
- 查询: 当用户提出问题时,LlamaIndex将查询转换为嵌入,并在索引数据中搜索最相关的文本块。它还可以应用高级搜索策略来改进结果。
- 生成: 检索到的上下文和用户查询发送到LLM,LLM根据您的特定数据生成人性化的答案。
此工作流程确保LLM始终能获取精确、相关的信息。
使用LlamaIndex进行RAG开发的主要优势
使用LlamaIndex进行RAG项目可以简化开发。它通过抽象数据连接、转换和检索的细节来简化复杂性。这使得开发人员能够用更少的样板代码构建复杂的RAG应用程序。
LlamaIndex的模块化设计允许你根据需求和预算更换组件——嵌入模型、向量存储或LLM。这种灵活性对于AI技术的发展至关重要。
对于技术领导者和开发人员而言,这意味着更快的迭代、更可靠的应用程序以及提供智能、数据驱动体验的能力。
构建你的基础LlamaIndex RAG管道:分步指南
让我们构建一个LlamaIndex RAG系统,该系统使用您自己的文档回答问题。我们将从项目设置到提出您的第一个查询,一步一步地进行。本指南使用Meilisearch文档文件作为示例,但您可以将这些步骤应用于任何文档。
为LlamaIndex RAG项目设置开发环境
首先准备一个Node.js环境。确保您已安装 Node.js 18或更高版本(建议20+)。使用以下命令检查您的版本:
node -v
创建一个新的项目目录并进行初始化
mkdir rag-tutorial cd rag-tutorial npm init -y
这将创建一个名为 rag-tutorial
的文件夹,进入该文件夹,并设置一个基本的 Node.js 项目。
更新你的 package.json
文件以使用现代 JavaScript 模块和 TypeScript。它应包含:
"type": "module"
用于 ES 模块语法- 使用
tsx
运行 TypeScript 文件的脚本
示例
{ "name": "rag-tutorial", "version": "1.0.0", "type": "module", "main": "rag-llamaindex.ts", "scripts": { "start": "tsx rag-llamaindex.ts", "interactive": "tsx interactive-rag.ts", "test-setup": "tsx test-setup.ts" } }
安装必要的 npm 包
npm install llamaindex @llamaindex/openai @llamaindex/readers dotenv tsx
从 Node.js v23.6.0 开始,开发者可以直接运行 TypeScript 文件,无需手动转译。此功能在 v24.1.0 中成为默认。
每个包裹都有其作用:
llamaindex
: 核心 LlamaIndex.TS 库@llamaindex/openai
: 连接 OpenAI 模型进行文本生成和嵌入@llamaindex/readers
: 用于从各种来源读取数据的工具,包括SimpleDirectoryReader
dotenv
: 从.env
文件加载环境变量,以确保API密钥安全tsx
: 直接在 Node.js 中运行 TypeScript 文件
通过在项目根目录中创建一个 .env
文件来设置你的 OpenAI API 密钥:
OPENAI_API_KEY=your_actual_openai_key_here
用你的真实密钥替换占位符,以保护凭据的私密性。
为您的数据和索引存储创建文件夹
mkdir data storage
data
将保存你的文档storage
将保存处理后的索引
添加 tsconfig.json
文件用于 TypeScript 配置
{ "compilerOptions": { "target": "ES2022", "module": "ESNext", "moduleResolution": "node", "allowSyntheticDefaultImports": true, "esModuleInterop": true, "strict": true, "skipLibCheck": true } }
完成此设置后,您的环境已准备就绪。接下来,导入您的数据。
使用LlamaIndex高效摄取和加载多样化数据源
现在,向您的LlamaIndex RAG系统提供一些知识。本教程中,我们使用Meilisearch文档中的Markdown(.mdx
)文件作为示例,但您可以使用PDF、网页或数据库条目。
将您的 .mdx
文件复制到 data
目录。例如:
# Copy documentation files to data directory # find ../documentation -name "*.mdx" -exec cp {} ./data/ ;
确保你的 data/
文件夹包含你想要索引的文件。
使用 SimpleDirectoryReader
从 @llamaindex/readers/directory
读取这些文件。在你的主 TypeScript 文件(例如,rag-llamaindex.ts
)中,像这样导入和使用它:
import "dotenv/config"; import { SimpleDirectoryReader } from "@llamaindex/readers/directory"; const reader = new SimpleDirectoryReader(); const documents = await reader.loadData("./data"); console.log(`📄 Loaded ${documents.length} documents.`);
每个文件都成为一个包含其内容和元数据的 Document
对象。
LlamaIndex支持多种数据格式。您可以将多个来源合并到一个索引中,以创建全面的知识库。
加载完文档后,您就可以开始索引了。
优化数据检索的索引:创建你的第一个LlamaIndex向量存储
让你的文档可以按语义搜索,而不仅仅是关键词。向量索引使用捕捉语义含义的数值向量来组织文本。
这样设置:
import { VectorStoreIndex, storageContextFromDefaults, Settings } from "llamaindex"; import { openai, OpenAIEmbedding } from "@llamaindex/openai";
VectorStoreIndex
: 管理向量索引storageContextFromDefaults
: 处理索引的保存位置Settings
: 配置语言和嵌入模型openai
和OpenAIEmbedding
: 设置 OpenAI 的 LLM 和嵌入模型
配置你的模型
Settings.llm = openai({ model: "gpt-4o", temperature: 0.0, maxTokens: 1024 }); Settings.embedModel = new OpenAIEmbedding({ model: "text-embedding-ada-002", });
此设置确保事实性、有重点的答案和高效的嵌入。
使用以下命令构建你的索引:
console.log("🔄 Creating vector embeddings... (this may take a few minutes)"); const storageContext = await storageContextFromDefaults({ persistDir: './storage' }); const index = await VectorStoreIndex.fromDocuments(documents, { storageContext }); console.log("💾 Index built and saved to storage.");
这个过程:
- 将文档分割成更小的块,以便更好地检索
- 使用嵌入模型将每个块转换为向量
- 将向量和文本块存储在索引中
- 将索引保存到
./storage
目录以供将来使用
根据您的数据量,这可能需要几分钟。完成后,您的系统就可以回答问题了。
正在寻找城里最好的搜索体验?提供闪电般的搜索结果,让您的用户保持参与度,并提高您的转化率。探索 Meilisearch Cloud
用你的基本LlamaIndex RAG实现查询引擎并生成响应
数据索引完成后,提出问题并获得智能答案。LlamaIndex的查询引擎能顺畅处理这一切。
获取您的 VectorStoreIndex
并创建一个查询引擎
const queryEngine = index.asQueryEngine({ similarityTopK: 5 });
similarityTopK
参数控制引擎每次查询检索的相关块的数量。
像这样提问:
const question = "What is Meilisearch and what are its main features?"; const result = await queryEngine.query({ query: question }); console.log("📖 Answer:"); console.log(result.toString());
幕后:
- 你的问题会转换成一个向量
- 引擎会找到最相关的文档片段
- 这些片段构成了上下文
- LlamaIndex 构建一个包含你的问题和上下文的提示
- LLM 根据您的数据生成答案
- 答案被返回并显示
以下是针对Meilisearch文档的最终结果示例:
这确保了答案准确且基于您的文档,而不仅仅是LLM的预训练知识。
现在你已经拥有了一个工作的LlamaIndex RAG管道。接下来,我们来提高重复使用的效率。
持续化并高效地重用LlamaIndex进行后续RAG操作
LlamaIndex 可以持久化索引,从而节省时间和成本。索引涉及分块文档和进行 API 调用以获取嵌入,这可能需要大量资源。您不希望每次运行应用程序时都重复此操作。
持久性使用 storageContext
和 loadOrBuildIndex
函数。构建索引时:
const storageContext = await storageContextFromDefaults({ persistDir: './storage' }); const index = await VectorStoreIndex.fromDocuments(documents, { storageContext });
这会将索引保存到 ./storage
。
在后续运行中,首先尝试加载已保存的索引:
try { const storageContext = await storageContextFromDefaults({ persistDir: './storage' }); const index = await VectorStoreIndex.init({ storageContext }); console.log("✅ Loaded existing index from storage."); } catch (error) { console.log("📚 Building new index from documentation files..."); // ...build logic }
如果存在已保存的索引,它会立即加载——无需重新处理文档或嵌入。
好处包括:
- 节省成本,避免重复的嵌入API调用
- 节省时间,因为加载比重建快
- 效率高,因为繁重的处理只发生一次
- 自动管理,只需最少的努力
这种持久性使得您的LlamaIndex RAG系统实用高效,确保您的初始处理投资随着时间的推移获得回报。
通过这些步骤,您已经构建了一个基础的LlamaIndex RAG管道:从设置和数据摄取到索引、查询和高效复用。这个基础已为随着您的需求增长而进一步增强做好了准备。
优化您的LlamaIndex RAG以实现生产级性能和相关性
您已经构建了基础的LlamaIndex RAG管道。现在,优化从数据准备到提供洞察的每个步骤,以最大限度地提高性能、相关性和效率。这个过程需要理解所有组件如何协同工作,以创建一个不仅智能而且真正有效的系统。
掌握高级数据分块策略,以实现卓越的LlamaIndex RAG上下文
将您的文档想象成浩瀚的图书馆,而“块”则是您的LLM阅读的页面或段落。给LLM一整本书来回答一个问题会让人不知所措,但一个脱离上下文的句子又不足够。分块的目标是找到“恰到好处”的片段,以实现有效的理解。
总结检索
默认的分块(大约512个token)是一个不错的开始,但生产系统需要更细微的调整。一个有效的方法是解耦检索和合成块:
- 嵌入每个特性部分的简洁摘要,用于广泛检索。
- 当查询与摘要匹配时,从该部分提取详细的句子级块,供LLM合成答案。
这种方法就像使用目录来找到正确的章节,然后阅读其中的特定段落。
结构化检索
结构化检索增加了另一层。如果您的数据包含元数据——例如产品类别、版本号或作者标签——请在语义搜索开始之前使用它来缩小搜索范围。例如,如果用户询问“版本3.0”中的某个功能,则只筛选v3.0文档。这种预过滤使向量搜索更快、更专注。
此外,使用动态块检索。不同的问题需要不同量的上下文:
- 简单的事实查询可能只需要几个小块。
- 复杂的比较问题需要更多或更大的块。
根据查询复杂性调整检索到的块的数量和大小,确保LLM获得最佳上下文,从而改进答案并减少token使用。
通过重新排序和混合搜索技术增强LlamaIndex RAG检索
使用向量相似度(通常由similarityTopK
控制)检索候选块后,这个过程并未完成。向量搜索会找到语义相似的内容,但“相似”并不总是意味着“最相关”。
混合搜索结合了语义搜索和基于关键词的搜索。例如,如果用户搜索特定的API错误代码,如“ERR_CONN_RESET_005”,纯向量搜索可能会找到一般的连接错误文档。混合方法会使用精确的错误代码作为关键词来精确定位包含该字符串的文档,然后应用语义相似度来理解上下文。这种组合通常会产生更精确的结果,特别是对于包含特定实体或代码的查询。
对检索到的初始块集进行优化也很重要。在获取前K个块后,您可以根据以下条件对其进行重新排序:
- 直接的关键词重叠。
- 来自一个更小、更快的模型评估问题相关性的得分。
这确保LLM获得最佳上下文。
“多步搜索”进一步改进了检索。初始查询识别一个宽泛的主题或文档集,然后一个集中的后续查询(可能由LLM生成)深入到该子集。这种从粗到细的方法提高了最终上下文的质量。
为你的特定LlamaIndex RAG用例选择最佳嵌入模型和LLM
选择合适的嵌入模型和LLM,就像为汽车选择发动机和变速器一样——它影响性能、成本和适用性。没有普遍的最佳选择;这取决于你的需求。
尝试不同的嵌入模型和维度。OpenAI的text-embedding-ada-002
模型在性能和成本之间取得了很好的平衡。
但是,如果您的数据是专业化的——例如法律或医学文本——领域特定的微调模型可能会表现更好。更高维的嵌入捕获更多细微差别,但会增加计算和存储成本,因此平衡是关键。
对于生成式LLM,gpt-4o
在成本和质量方面提供了强大的组合。对于高吞吐量、低延迟的聊天机器人,更小、更快、更便宜的LLM可能更合适,即使它们牺牲了一些响应的细微差别。对于复杂的研发或分析,投资于顶级模型是值得的。调整诸如temperature
(例如,事实问答为0.0
)和maxTokens
等设置,以微调行为并控制成本。
考虑一个电商例子:
- 对于简单的产品问题,使用快速、经济实惠的LLM。
- 对于复杂的比较,使用更强大的模型和更高的
similarityTopK
。
条件模型选择优化了用户体验和运营成本。
使用关键指标和基准测试评估您的LlamaIndex RAG管道以取得成功
在优化分块、检索和模型选择之后,定期评估您的系统以确保其良好运行。
关注以下几个关键领域:
- 检索质量:检索到的块是否相关?使用人工评估来审查样本查询的前K个文档,并检查它们是否与主题相关且信息丰富。
- 生成质量:LLM的答案是否准确、连贯且忠实于上下文?避免幻觉。人工判断在早期至关重要。
- 端到端性能:系统响应速度如何?包括嵌入、检索和生成时间。延迟对于面向用户的应用程序至关重要。
- 成本效益:跟踪嵌入和生成的API调用。检查分块或
similarityTopK
调整等优化是否能在不影响质量的情况下降低成本。
例如,一个帮助开发人员浏览代码库的 RAG 系统如果满足以下条件,则可能成功:
- 开发者花费更少时间搜索信息。
- 技术问题的答案更准确。
- 用户提供积极反馈。
- 提交给人类同事的后续问题更少。
对照人工验证过的问答对的“黄金数据集”进行基准测试,以衡量进度。调整嵌入模型、块大小或提示等组件应能改进这些基准。
你在RAG革命中的下一步
构建一个生产就绪的LlamaIndex RAG系统,将彻底改变您的应用程序处理知识检索和生成的方式。它将使应用程序从静态响应转向与数据进行动态的、上下文感知的交互。
LlamaIndex 简化的开发体验与强大的生产模式相结合,为从原型到企业部署的扩展奠定了基础。