从入门到实战:LlamaIndex构建RAG知识库问答系统学习路径
先说个真实感受:LlamaIndex 这名字听着像个“索引工具”,但真正用起来才发现,它其实是一整套为 RAG(检索增强生成)量身打造的应用框架。我最早接触的时候也以为它只是 LangChain 的附庸,后来才意识到自己错得离谱——在知识库问答、文档解析、复杂检索这些场景里,LlamaIndex 的抽象设计反而更顺手。如果你正在纠结怎么系统入门 RAG、想搞明白“向量数据库之外还需要什么”,或者已经在用 LangChain 但被各种概念绕晕,这篇学习路径应该能帮你把思路彻底捋顺。
1. 核心概念:先搞懂 LlamaIndex 到底解决了什么问题
先说结论:LlamaIndex 解决的是“大模型怎么和你自己的数据对话”这件事。ChatGPT 再聪明,它也没见过你公司的内部文档、你电脑里的 PDF、你数据库里的业务数据。LlamaIndex 做的事情就是把这些私有数据组织起来,让 LLM 能够按需检索、精准引用、稳定回答。
它和 LangChain 最大的区别,我个人的体会是:LangChain 更像瑞士军刀,什么都能干,但什么都要自己组装;LlamaIndex 则是一条完整的流水线,从数据摄入到问答生成都替你考虑好了默认方案。如果你主要做知识库问答、文档分析这类场景,LlamaIndex 的上手曲线明显更平滑。
1.1 五大核心概念一次讲清
学习任何框架,第一步永远是建立正确的抽象认知。LlamaIndex 的抽象层级很清晰,我建议你按这个顺序理解:
- Document(文档):数据的原始载体。一个 PDF、一个网页、一段文本,都可以是一个 Document。它是最粗粒度的数据单元,通常包含元数据(文件名、来源 URL、作者等)。
- Node(节点):Document 被切分后的最小语义单元。一个 10 页的 PDF 可能被切成 30 个 Node,每个 Node 包含文本内容、元数据、以及在原文档中的位置信息。Node 是索引和检索的基本单位。
- Index(索引):Node 集合的存储与组织结构。最常用的是 VectorStoreIndex,把每个 Node 的文本向量化后存入向量数据库。还有其他类型,后面会详细讲。
- Retriever(检索器):根据用户查询从 Index 中召回相关 Node 的组件。它是 RAG 的“找”环节。
- QueryEngine(查询引擎):把检索和生成串起来的顶层组件。它接收用户提问,调用 Retriever 拿到相关 Node,再把这些 Node 作为上下文交给 LLM 生成最终回答。
这五个概念是贯穿整个 LlamaIndex 学习过程的主线。我见过很多新手上来就写query_engine = index.as_query_engine(),然后以为 LlamaIndex 就是“把 PDF 喂进去,就能问答”的黑盒。等出了问题,完全不知道怎么排查。所以我在带团队的时候,第一课永远是让他们把这五个词的含义背下来,并且能说出每个环节的数据流向。
1.2 为什么 LlamaIndex 会特别强调“索引”这个概念
因为这个框架的骨髓里,是传统信息检索(IR)的那套思维。不就是“建索引 + 查索引 + 排序返回”吗?只不过检索的粒度从文档变成了语义块,排序从 BM25 变成了向量相似度。
想通这一点,你就明白为什么 LlamaIndex 里会有那么多 Index 类型了。VectorStoreIndex 只是最基础的,还有SummaryIndex(适合整篇文档问答)、TreeIndex(按层级结构组织)、KeywordTableIndex(靠关键词匹配)、KnowledgeGraphIndex(构建实体关系图)。它们的底层逻辑都是:用不同的方式组织 Node,用不同的策略匹配查询。所以你学习的时候,不要只盯着向量索引用,理解每种索引的适用场景,才能在复杂需求面前游刃有余。
2. 学习路线规划:从“跑通最小 Demo”到“掌控工作流”
实操派的学习方式,我强烈建议“先跑通、再深挖”。不需要把官方文档从头读到尾,那太枯燥也太慢了。按我总结的这条路走,大概一周时间就能达到“基本能独立做项目”的水平。
2.1 第一阶段:10 分钟跑通最小问答系统
这个阶段的目标不是理解原理,而是建立信心。你需要准备好:Python 3.9+ 环境、一个 OpenAI API Key(或者任何兼容的 LLM API)。
最基础的代码就这么多:
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader # 读取当前目录下的所有 txt/md/pdf 文档 documents = SimpleDirectoryReader("data").load_data() # 建索引(默认调用 OpenAI embedding 接口) index = VectorStoreIndex.from_documents(documents) # 创建查询引擎 query_engine = index.as_query_engine() # 提问 response = query_engine.query("这份文档里提到的最重要的结论是什么?") print(response)你可能会惊讶:就这么点代码?对,LlamaIndex 的默认设计就是让你用最少的代码感受完整的 RAG 流程。但这个阶段暴露出一个问题:很多新手在这里就停止了,觉得“已经会了”。其实远远不够。
这个阶段我必须补充几个 Key Point:第一,如果你没有 OpenAI Key,可以去硅基流动或者阿里云百炼申请一个便宜甚至免费的兼容 API,用Settings.llm替换默认配置。第二,SimpleDirectoryReader默认支持 PDF、Word、Markdown、CSV 等常见格式,但 PDF 里的复杂表格它处理得并不好,这个坑后面详细说。第三,中文场景下索引效果出问题,大概率是 embedding 模型选择不对,这点至关重要。
2.2 第二阶段:吃透文档加载与节点切分
跑通 Demo 之后,你马上会碰到第一个真正的挑战:处理各种奇葩格式的文档。LlamaIndex 对数据加载的抽象是Reader和Connector。
from llama_index.readers.web import SimpleWebPageReader # 加载网页内容 documents = SimpleWebPageReader(html_to_text=True).load_data([ "https://example.com/articles/llama-index-guide" ]) # 也可以加载 PDF、CSV 甚至 Notion 数据库 from llama_index.readers.notion import NotionReader在实际项目里,我最常用的是 PDF 加载 + Markdown 加载组合。但要注意,PDF 是个大坑:扫描版 PDF 需要 OCR;文字版 PDF 如果排版本复杂,解析出来可能顺序错乱。LlamaIndex 的 PDFReader 底层用的是 PyPDF2,对付简单文档没问题,到了工程化阶段,我建议直接换成 LlamaParse 或者 Apache PDFBox 前置解析,原因后面细说。
接下来是切分策略。这是 RAG 项目效果好坏的最关键环节之一,甚至比选什么模型还重要。LlamaIndex 默认使用SentenceSplitter,按 1024 token 切分,重叠 20 token。这个默认参数对英文效果还不错,但中文场景下,它会把句子拆得四分五裂。我建议一上来就自定义切分器:
from llama_index.core.node_parser import SentenceSplitter splitter = SentenceSplitter( chunk_size=512, chunk_overlap=64, separator=" " )注意几个经验值:chunk_size 在 200-500 token 之间通常效果最好,太小了上下文不全,太大了检索精度下降。chunk_overlap 控制在 10%-15% 之间,保证跨块的关键信息有连续性。如果文档里存在大量长段落,用SemiSplitter或MarkdownNodeParser会更合适,后者的思路是“按 Markdown 结构切分”,比如一个##标题下的内容作为一个 Node,保留层级结构,能显著提高检索命中率。
2.3 第三阶段:学会嵌入选型与索引的“增删改查”
embedding 选型的重要性我再强调一遍:它决定了你的 RAG 系统“语义理解的基底”。在 LlamaIndex 里配置 embedding 很方便:
from llama_index.core import Settings from llama_index.embeddings.huggingface import HuggingFaceEmbedding Settings.embed_model = HuggingFaceEmbedding( model_name="BAAI/bge-large-zh-v1.5" )中文场景下,这几类模型你可以直接抄作业:
- 通用效果均衡:
BAAI/bge-large-zh-v1.5(1024 维,效果稳定,资源占用友好) - 更好的语义匹配:
BAAI/bge-m3(支持 8192 长文本,中文效果更强) - 海外场景:
text-embedding-3-small/text-embedding-3-large(OpenAI,英文效果好) - 本地开发测试:
sentence-transformers/all-MiniLM-L6-v2(快但中文不推荐)
这里有个实际经验要提一下:embedding 模型和 LLM 模型是两回事。很多人误解“用 GPT-4 做问答,效果一定好”,但如果 embedding 不匹配,检索召回的内容就是错的,再强的生成模型也救不回来。我见过一个项目,用text-embedding-ada-002处理中文保险条款,召回的结果毫无相关性,换用 bge 系列之后效果翻倍。嵌入模型选型这件事,中文场景千万别偷懒。
索引的增删改查也是必经之路。每次全量重建索引太重了,LlamaIndex 支持增量插入和删除:
# 插入新节点 index.insert_nodes([new_node]) # 删除节点 index.delete_nodes(["node_id_123"]) # 合并索引 combined_index = VectorStoreIndex.from_index(existing_index)你注意观察就会发现,LlamaIndex 的 Index 对象本质上是对向量存储的封装,不做持久化的话,重启就丢了。工程上一定要index.storage_context.persist到磁盘,或者直接用 PGVector、Chroma 等外部向量库,后面会专门讲存储层怎么搞。
3. 实战进阶:从“能回答”到“答得好”——Query Engine 与检索策略调优
跑通基础流程后,你很快会遭遇两个典型问题:一是“回答得太空泛,没有引用原文”;二是“问一句‘帮我总结下这个文档的核心观点’,它给我把整篇文档都塞进去”。这两个问题都要靠 QueryEngine 来调。
3.1 QueryEngine 的三种典型模式和选择逻辑
LlamaIndex 的as_query_engine()背后,是一套可插拔的模块组合。我按“你的问题类型”来推荐模式:
RetrieverQueryEngine:最基础的检索问答,适合“这个文档里对于 XX 是怎么定义的”这类事实型问题。它先用 retriever 召回 top-k 节点,再拼接 prompt 交给 LLM 生成。RouterQueryEngine:适合“你手里有多个索引,需要路由到不同索引”的场景。比如你同时有“产品手册索引”和“客服 FAQ 索引”,系统会根据问题内容判断应该调用哪个索引。CitationQueryEngine:如果你需要回答中带上引用来源(页码、文件名),用这个模式,它会强制 LLM 在回答中插入引用标记并输出来源列表。
RouterQueryEngine 的核心价值在于“分流”。传统做法是拿一个问题去所有索引里做向量检索取最大相似度,但那是“模糊匹配”,容易把两类文档强行混合。路由是个更聪明的方案:先做一个轻量的意图判断,再去对应领域检索,精度会明显提高。在项目设计阶段提前规划好几个索引的领域划分,后面维护会轻松很多。
有了这个认识,再实操一遍“带引用的问答”:
from llama_index.core.query_engine import CitationQueryEngine query_engine = CitationQueryEngine.from_args( index=index, citation_chunk_size=512, similarity_top_k=3, ) response = query_engine.query("公司对病假的规定是什么?")响应里会带source_nodes列表,每个 Node 包含metadata(比如页码page=12)。你可以在前端模板里把它渲染成“引用 [来自第 12 页]”的样子。我做的知识库产品就是这么干的,用户满意度提升了不少,因为“大模型 + 引用”才真正有可信度。
3.2 检索器的核心参数:Top-K 与相似度阈值的平衡
这是最容易被忽略、但对效果影响极大的一个参数组合。
similarity_top_k控制召回多少个节点。默认是 2,但实际项目里我建议至少设成 4。原因很简单:召回太少,回答可能缺关键信息;召回太多,又会把不相关内容灌进上下文,干扰生成。根据我的经验,500-2000 字的文档,top_k 设 4-6 最合适;超过 20 页的长文档可以到 8。
相似度阈值(similarity_cutoff)控制“低于多少分的节点直接不要”。它是个过滤器,但设置要谨慎。默认不启用,如果你启用了,建议用 0.3-0.5 区间。这里有个坑:各嵌入模型算出来的相似度分数区间不一致,OpenAI 的 embedding 分数普遍偏低(0.6 已经算很高),而 bge 系列普遍偏高(0.8 常见)。所以最好先跑 50 个真实问题,统计一下你的模型给出的相似度分布,再定阈值。千万别拿别人的阈值直接抄,那个没有意义。
我踩过最惨的一次坑:把阈值设成 0.7,用的是 OpenAI embedding,结果 90% 的查询召回为空,整个客服系统“废”了一天。后来才知道 OpenAI 的分数普遍在 0.5-0.7 之间。这个问题不调试一次,你根本就不会意识到。
3.3 Metadata 过滤:被低估的检索提效手段
很多人忽视这一点。想象一个场景:你的知识库里有 2022 年和 2024 年两版合同模板,用户问“今年的付款条款是什么”,如果不过滤,系统很可能会优先召回 2022 年的旧条款,因为向量相似度可能更高。这时候 Metadata 过滤能从根本上解决问题。
在 LlamaIndex 中,你可以在 Node 的 metadata 里存year、category、author等字段,然后在查询时动态过滤:
from llama_index.core.vector_stores import MetadataFilters, ExactMatchFilter filters = MetadataFilters( filters=[ ExactMatchFilter(key="year", value="2024"), ExactMatchFilter(key="category", value="contract"), ] ) query_engine = index.as_query_engine(filters=filters)还可以结合MetadataFilter的子类做范围过滤(GreaterThanFilter、LessThanFilter)。这是一个活用场景:当你的业务文档存在“版本”“日期”“级别”这类硬性字段时,Metadata 过滤的价值就远远大于向量检索本身——它解决的是“结构化的约束”,向量解决的是“语义的相关性”。两者配合,效果才真正靠谱。
我在一个保险产品知识库里实践过:预先给每份条款标注了product_type和effective_date,用户问“重疾险的等待期是多久”,系统只检索同类产品的最新版本,回答在业务层面天生正确。做知识库的兄弟一定要记住这一招。
4. 进阶必杀技:Workflow 编排,RAG 应用开发的“最终形态”
如果只是做单轮问答,LlamaIndex 的四层抽象已经够用。但真实业务往往是复杂的:先判断意图、再查数据库、再调 API、再检查答案质量……这时候你需要工作流(Workflow)能力。LlamaIndex 在 0.10.x 版本后引入的 Workflow 机制,正是为了应对这种编排需求。
4.1 为什么要用 Workflow,而不是硬堆 if-else
我之前就是硬堆 if-else 的受害者。业务流程一旦超过三步,代码就变成“面条代码”:每个分支都要手动维护状态,加上几个异步调用,多层嵌套基本没法维护。后来迁移到 Workflow,本质就是把业务拆成若干个“步骤对象”,每个步骤只依赖入参和上下文,步骤之间通过事件通信,整个链路清晰了十倍。
Workflow 的核心机制如下:
Workflow:一个编排容器,内部管理多个Step。Step:一个函数/类,用@step修饰,接收特定事件,处理完后输出新事件。Event:步骤之间的消息载体,可以携带任意数据。Context:工作流运行时的共享状态容器。
学习 Workflow 的关键在于转变思维:不要把它当成“智能代理”,它本质是“有向无环图(DAG)+ 消息传递”。你定义好哪些步骤之间能传递什么事件,它自动完成串行或并行调度。我最初被那么多概念绕晕了,后来套用“消息队列”的思路才彻底想通。每个 step 就是一个消费者,也是生产者。
4.2 一个极简但完整的 Workflow 实例
我们做一个“检索 + 反思”工作流:先检索,如果 LLM 觉得上下文不够,就换一个 query 再检索一次。这个看似简单的逻辑,在纯 QueryEngine 里没法直接实现,但 Workflow 可以优雅解决。
import os from llama_index.core.workflow import ( Context, Event, StartEvent, StopEvent, Workflow, step ) from llama_index.core.indices.vector_store import VectorStoreIndex from llama_index.core.schema import Document class DocIndex: """模拟一个固定知识库的索引""" def __init__(self, texts: list[str]): self.index = VectorStoreIndex.from_documents( [Document(text=t) for t in texts] ) class RetrieveEvent(Event): query: str class EmptyEvent(Event): query: str class AnswerEvent(Event): text: str retrieval_count: int = 1 class ReflectionWorkflow(Workflow): def __init__(self, doc_index: DocIndex, llm=None): super().__init__() self.index = doc_index.index self.llm = llm @step async def analyze_query(self, ctx: Context, ev: StartEvent) -> RetrieveEvent: query = ev.query return RetrieveEvent(query=query) @step async def retrieve(self, ctx: Context, ev: RetrieveEvent) -> AnswerEvent | EmptyEvent: retriever = self.index.as_retriever(similarity_top_k=4) nodes = retriever.retrieve(ev.query) context_text = "\n\n".join([n.text for n in nodes]) can_answer = await ctx.get("should_check", self._can_answer)( context_text, ev.query ) if can_answer: return AnswerEvent(text=self._generate(context_text, ev.query), retrieval_count=1) return EmptyEvent(query=f"请基于以下上下文重新回答:{ev.query}\n\n上下文:{context_text}") async def _can_answer(self, context_text: str, query: str) -> bool: # 简化:上下文长度大于 100 就直接回答,否则认为信息不足 return len(context_text) > 100 def _generate(self, context_text: str, query: str) -> str: return f"基于检索结果,我的回答是:{query[:10]}...(实际项目这里调 LLM)" @step async def retry(self, ctx: Context, ev: EmptyEvent) -> StopEvent: return StopEvent(result=f"信息不足,第二次尝试如下:{ev.query[:30]}...") @step async def final(self, ctx: Context, ev: AnswerEvent) -> StopEvent: return StopEvent(result=ev.text) async def main(): texts = [ "LLaMA 模型训练需要大量 GPU 资源,一般推荐 A100 以上。", "RAG 系统可以显著提高大模型对私有知识的回答准确性。", ] wf = ReflectionWorkflow(doc_index=DocIndex(texts)) result = await wf.run(query="RAG 和微调的区别?为什么用 RAG?") print(result) if __name__ == "__main__": import asyncio asyncio.run(main())这个 Demo 虽然简化了很多逻辑(比如_can_answer没真正调用 LLM),但它完整展示了 Workflow 的“事件驱动 + 步骤编排”风格。在真实项目里,你可以把retrieve步骤换成“向量召回 + Bing 搜索 + SQL 查询”的多路召回,把_can_answer换成“调用 LLM 判断上下文是否足够回答”,把_generate换成“携带引用的结构化 prompt + LLM”。整个系统会变成一个极其灵活的知识问答机器人。
每次跑 Workflow,LlamaIndex 都会自动记录各阶段耗时和输入输出,这是它的Context对象自带的能力。排查链路慢的问题,可以直接打印ctx.collect_events来定位瓶颈,这一点非常好用。
5. 工程化落地:向量存储、评测调优、上线避坑
走到这一步,说明你已经不是“会写 Demo”的阶段了,而是打算把 RAG 系统真正放到生产环境里。这里面的门道比前面所有阶段加起来都多。
5.1 向量存储选型:不要用默认的,不要用默认的,不要用默认的
LlamaIndex 默认的向量存储是内存里的SimpleVectorStore,这玩意儿一重启就丢,只适合刚上手前两小时的体验。生产环境必须用外部存储。我个人用下来比较顺手的几套:
| 存储方案 | 优点 | 适合场景 | 注意事项 |
|---|---|---|---|
| Chroma | 纯本地、无需外部依赖、API 简单 | 单机小规模(<100万条) | 数据量太大时性能一般 |
| PGVector | PostgreSQL 扩展,和业务数据同库 | 中型项目、已有 PG 集群 | 需要安装 pgvector 插件 |
| Qdrant | Rust 编写,性能好,支持过滤 | 中大规模、要求高性能 | 需要单独部署服务 |
| Milvus | 分布式,万亿级扩展 | 大规模企业级 | 运维成本高 |
配置 PGVector 的示例代码:
import psycopg2 from llama_index.vector_stores.postgres import PGVectorStore connection_string = "postgresql://user:pass@localhost:5432/ragdb" vector_store = PGVectorStore.from_params( database="ragdb", host="localhost", password="pass", port=5432, user="user", table_name="llama_index_demo", embed_dim=1024, # 必须和 embedding 模型的维度一致 )注意这个embed_dim必须和你的 embedding 模型输出维度严格一致,否则写入时会直接报错。bge-large-zh 是 1024,OpenAI 的 text-embedding-3-small 是 1536,别搞混。
5.2 评测:RAG 系统最大的坑是“感觉效果不错”
“感觉”不可靠,必须量化评测。我强烈建议你使用RAGAS这套评测框架,它和 LlamaIndex 集成得相当好。
需要评测的核心指标:
- 忠实度(Faithfulness):回答是否严格基于检索到的上下文,有没有幻觉。
- 答案相关性(Answer Relevance):回答是否切题。
- 上下文相关性(Context Relevance):检索到的节点是否真的包含关键信息。
- 准确率(Precision/Recall):涉及多文档检索时,有没有漏掉关键文档。
评测代码长这样:
from ragas import evaluate from ragas.metrics import faithfulness, answer_relevancy, context_relevancy from datasets import Dataset # 准备评测数据:每个样本包含 question, answer, contexts, ground_truth dataset = Dataset.from_dict({ "question": ["公司年假规定是什么?"], "answer": ["根据员工手册,工作满一年可享受 5 天年假。"], "contexts": [["员工手册第一条规定:工作满一年,年假 5 天。"]], "ground_truth": ["工作满一年可休 5 天年假"], }) result = evaluate(dataset, metrics=[faithfulness, answer_relevancy, context_relevancy]) print(result)跑完评测才能知道瓶颈在检索还是生成。我自己的经验是:数据集至少要准备 100 个真实业务问题,分成 80/20 做迭代测试和最终验收。盲目上线,效果“翻车”是必然的。
5.3 上线避坑清单:这些都是流量进去之后才暴露的问题
第一,延迟控制。RAG 的完整链路 = 嵌入(通常 50-200ms)+ 检索(1-10ms)+ LLM 生成(3-10s)。瓶颈几乎永远在 LLM。如果对实时性要求高(例如在线客服),你需要考虑对回答做流式输出(stream=True),或者用更小的模型做首轮回答再异步精修。LlamaIndex 支持:
response = query_engine.stream_query("什么是 RAG?") for chunk in response.response_gen: print(chunk)第二,并发控制。直接用 OpenAI 的 API 做 embedding,并发跑到几十 QPS 就会被限流。你最好在中间加一层缓存。如果用户问“苹果好吃吗”和“苹果好吃不”这种近乎重复的问题,别浪费一次 embedding 调用。我建了个简单的 Redis 缓存,key 是 query 的哈希值,value 是检索结果,命中率大概在 15% 左右,成本省了不少。
第三,数据更新策略。知识库文档永远在变。不要每次全量重建索引,建议用“增量更新 + 定时全量备份”的组合。增量更新用index.insert_nodes(),注意给 Node 打上doc_id作为幂等键。否则同样的文档重复插入,检索时会返回重复内容,回答质量惨不忍睹。
第四,权限控制。企业内部知识库往往涉及权限。我的方案是:Metadata 里存permission_level,查询的时候根据当前用户动态生成过滤器。这个我在前面 Metadata 过滤那节讲过,在工程化阶段更是刚需。别等到合规审查来敲门才补,那就晚了。
6. 常见问题速查表:从入门到放弃前的最后一道防线
学习 LlamaIndex 的过程中,有几个典型问题几乎每个人都会遇到。我直接整理成表格,方便你对号入座排查。
| 现象 | 根本原因 | 解决方案 |
|---|---|---|
Connection error/ 无法调用 OpenAI API | 网络链路或 API Key 失效 | 检查 Key,检查网络。国内务必用 API 代理/中转地址,但注意合规性。 |
| 检索结果与问题完全无关 | embedding 模型不适合中文/领域 | 换成 bge-m3 或 bge-large-zh,重新调 chunk_size |
| 回答包含幻觉内容 | 上下文不充分 | 调大 top_k,增加 chunk_overlap,把“无法从文中找到答案”写进 prompt |
| 回答引用页码不对 | Node metadata 中 page 信息丢失 | 加载时用SimpleDirectoryReader(required_exts=[".pdf"])并确保底层 reader 保留页码 |
| 索引文件过大,构建太慢 | 全量嵌入耗时过长 | 用批量嵌入接口、升级 embedding 模型硬件、分批构建 |
| 结果中有重复段落 | 同一文档被重复插入索引 | 设置doc_id幂等键;清理旧数据再插入 |
| 重启后索引消失 | 未持久化 vector store | 设置storage_context.persist()或改用 PGVector/Chroma |
| 流式输出时内容断裂 | LLM 供应商流式接口兼容问题 | 检查基础 LLM 是否支持 stream;测试输出是否走原厂 SDK |
这里再单独啰嗦一句:最后一个“流式输出断裂”问题,我在对接一个国产大模型时踩过。它的流式接口是 WebSocket 而不是 SSE,LlamaIndex 默认实现接不上,导致流式输出一直卡住。解决办法是写一个自定义CustomLLM类,重写stream_chat方法,把对方的 WebSocket 协议封装成标准接口。这个如果你碰到了再深研究,没碰到可以先留着印象。
7. 关于学习资源与路线图,最后补充一点私货
市面上关于 LlamaIndex 的中文资料乱象丛生,有些还停留在 0.9 甚至 0.8 版本,接口早就变了。我建议你以官方文档 v2 分支为准(api reference 优先),配合动手做一个小项目来巩固。
具体来说,我给团队成员建议的项目实训是:用 LlamaIndex 搭建一个“简历筛选助手”。输入一堆 PDF 简历,让系统能回答“有哪些候选人熟悉 React 并且有三年以上经验”。这个项目短小精悍,但覆盖了加载、解析、切分、索引、检索、带引用回答、基础评测全流程。做完它,你对框架的理解就能超过绝大多数“只会跑官方 Demo”的人。
学习这件事,最怕的就是陷入“文档—示例—再看文档”的死循环。我见过太多人看了两周文档,却连一个能跑通的、属于自己的查询引擎都没写出来。LlamaIndex 最有魅力的地方在于:它让你用不到 50 行代码,就能实现一个曾经需要数周开发的问答系统雏形。但真正拉开差距的,永远是你对自己业务数据的理解,以及对检索生成链路上每个环节的调优深度。
现在,你值得打开终端,把第一个 Demo 跑起来。等你把第一个query_engine.query()跑通,再回来读一遍这篇文章,一定会有完全不一样的感受。