LlamaIndex RAG实战:从模型加载到检索器构建的完整链路

📅 2026/7/11 7:11:20
LlamaIndex RAG实战:从模型加载到检索器构建的完整链路
1. 项目概述为什么一个“基础RAG应用”值得花一整天认真搭一遍你有没有过这种体验手头有一堆PDF、Word、网页文本想快速从中找出某段话的出处或者让AI基于这些材料回答专业问题但每次提问都像在大海捞针不是答非所问就是胡编乱造。这时候RAG检索增强生成就不是个时髦词而是你真正能用上的生产力工具。而LlamaIndex就是目前把这件事做得最“顺手”的框架——它不强迫你写几十行向量数据库操作代码也不要求你手动拼接prompt模板而是用几行Python就把数据加载、切块、嵌入、索引、检索、生成这一整条链路串起来了。我第一次用它跑通一个本地知识库问答从下载模型到得到第一句准确回答只用了47分钟。这背后没有魔法只有清晰的设计逻辑和对开发者真实痛点的深刻理解。本项目标题里说的“加载模型和构建检索器”看似只是两个动作实则涵盖了RAG系统最核心的初始化阶段模型是大脑检索器是眼睛。没有合适的模型再好的检索结果也生成不出人话没有高效的检索器再强的模型也只能对着错误的上下文瞎猜。所以这不是一个“Hello World”式的玩具项目而是你后续所有RAG实战的基石。无论你是刚接触AI的业务分析师还是想快速验证想法的工程师只要你想把私有数据变成可对话的知识资产这个基础结构就必须亲手搭一遍、调一遍、踩一遍坑。接下来的内容我会完全按照一个资深从业者的真实工作流来展开不讲虚的原理只告诉你每一步为什么这么写、参数怎么选、哪里最容易出错、以及我踩过的那些坑是怎么被一行日志或一个配置项救回来的。2. 核心设计思路拆解LlamaIndex为何能成为RAG开发的“瑞士军刀”2.1 从LangChain到LlamaIndex不是替代而是聚焦网上关于“LlamaIndex和LangChain区别”的讨论铺天盖地但很多答案都停留在表面。作为一个同时用过两者搭建过生产级知识库的人我的体会是LangChain像一个功能齐全的“工具箱”里面扳手、螺丝刀、电钻一应俱全但你要自己决定先拧哪颗螺丝、再接哪根线而LlamaIndex更像一把为“数据连接”这一个任务深度打磨的“万用钳”它把90%的RAG通用流程都预设好了最优路径你只需要提供数据和模型剩下的“怎么切、怎么存、怎么找、怎么问”它都替你规划得明明白白。比如LangChain里你要分别引入DocumentLoader、TextSplitter、Embeddings、VectorStore、Retriever、LLM再手动把它们串起来而在LlamaIndex里SimpleDirectoryReader自动处理各种文件格式SentenceSplitter默认按语义切分VectorStoreIndex一键完成嵌入与索引as_query_engine()直接返回一个能回答问题的对象。这种设计不是偷懒而是源于一个深刻认知在绝大多数RAG场景中“如何高效地把我的数据喂给大模型”才是核心瓶颈而不是“如何实现一个通用的Agent调度框架”。所以LlamaIndex把全部精力放在了数据管道上它的Node、Document、Index、QueryEngine这几个核心概念每一个都直指数据流动中的关键节点。当你看到index.as_query_engine()这行代码时它背后封装的其实是查询文本的嵌入计算、向量相似度搜索、相关文档的排序与重排、上下文的动态拼接、以及最终prompt的构造与发送。你不需要知道Milvus的search_config里nprobe参数是什么意思LlamaIndex已经为你选了一个在精度和速度间平衡的默认值。这种“默认即合理”的哲学正是它能快速上手的根本原因。2.2 RAG架构的三层抽象数据层、索引层、查询层一个健壮的RAG系统绝不是把文档扔进数据库然后查一下那么简单。LlamaIndex的精妙之处在于它用三层清晰的抽象把复杂性层层隔离数据层Data Layer这是你的原始材料PDF、TXT、Markdown、甚至数据库里的记录。LlamaIndex通过Reader如SimpleDirectoryReader,PandasCSVReader统一入口把不同来源、不同格式的数据都转换成内部标准的Document对象。每个Document不仅包含text还自带metadata文件名、页码、作者等这为后续的精准过滤打下了基础。我曾经处理过一份带大量表格的财务报告PDF用默认的PyMuPDFReader读出来全是乱码换成UnstructuredReader后表格内容和文字结构都完美保留。这说明数据层的质量直接决定了整个RAG系统的天花板。索引层Index Layer这是RAG的“记忆中枢”。Document经过NodeParser如SentenceSplitter被切成更小的语义单元Node每个Node再通过EmbeddingModel如OpenAIEmbedding转换成高维向量最后存入VectorStore如MilvusVectorStore,ChromaVectorStore。这里的关键在于LlamaIndex把“向量化”和“存储”解耦了。你可以用OpenAI的API做嵌入但把向量存在本地的Chroma里也可以用开源的BAAI/bge-small-en-v1.5模型做嵌入再存到云上的Zilliz Cloud。这种灵活性让你能根据数据敏感性、预算、性能要求自由组合技术栈。比如客户明确要求数据不出内网我就用llama-cpp-python加载一个量化后的nomic-embed-text-v1.5模型配合Qdrant向量库整个流程都在本地完成连网络请求都不发一次。查询层Query Layer这是用户直接交互的“前台”。QueryEngine是这一层的核心它接收自然语言问题执行完整的RAG流程问题嵌入 → 向量检索 → 文档重排Rerank→ 上下文注入 → LLM生成。LlamaIndex提供了多种QueryEngineRetrieverQueryEngine适合简单问答SubQuestionQueryEngine能自动把复杂问题拆成子问题并行检索CondenseQuestionQueryEngine则会先把你模糊的问题“浓缩”成一个更精准的查询词再进行检索。选择哪个取决于你的场景。我给一个法律咨询公司做的知识库就用了CondenseQuestionQueryEngine因为律师提问往往很口语化“那个去年签的合同违约金怎么算”直接检索效果很差先浓缩成“合同违约金计算条款”再搜准确率提升了60%。这三层抽象让LlamaIndex既足够简单新手几分钟就能跑通又足够强大专家可以深入每一层定制。它不是一个黑盒而是一个透明的、可插拔的流水线。2.3 “加载模型”与“构建检索器”的本质一次初始化两次绑定回到项目标题“加载模型和构建检索器”听起来是两个独立动作但在LlamaIndex的语境下它们是一次初始化过程中的两个关键绑定环节。这里的“模型”其实包含两类嵌入模型Embedding Model负责把文本无论是文档还是问题转换成向量。它决定了“什么内容是相关的”这个根本判断。LlamaIndex默认使用OpenAI的text-embedding-ada-002但你完全可以换成HuggingFace上的开源模型比如BAAI/bge-base-en-v1.5。选择依据很简单看你的数据语言和领域。处理中文法律文书bge-zh-v1.5的效果远超英文模型处理代码codegeex2的嵌入空间更能捕捉函数签名和变量关系。大语言模型LLM负责最终的文本生成。它决定了“如何把检索到的信息组织成一句人话”。LlamaIndex支持几乎所有主流LLM接口从OpenAI、Anthropic到本地运行的llama.cpp、Ollama、vLLM。关键点在于LLM的选择必须与你的硬件和延迟要求匹配。我在一台RTX 3090上部署Qwen2-7B推理速度尚可但如果换成Qwen3.5-9B显存就会爆掉必须用4-bit量化。而Qwen2-1.5B在CPU上都能跑虽然生成质量稍逊但胜在稳定和低成本。“构建检索器”则是将这两个模型与你的数据索引绑定的过程。VectorStoreIndex.from_documents(documents, storage_contextstorage_context)这行代码就是把documents数据、storage_context存储位置含向量库配置、以及隐式使用的EmbeddingModel由全局设置或Settings指定三者关联起来。而index.as_query_engine()则是把Index检索能力和LLM生成能力最终组装成一个可用的服务。所以这不是简单的“加载”和“构建”而是一次精密的“系统集成”。3. 核心细节解析与实操要点从零开始搭建你的第一个RAG3.1 环境准备与依赖安装避开那些“看似成功”的坑别急着写代码环境配置是第一步也是最容易翻车的地方。我见过太多人卡在pip install llama-index这一步报一堆pydantic版本冲突的错误。这是因为LlamaIndex的主干包llama-index和它的生态包llama-index-vector-stores-milvus对依赖版本要求非常严格。我的经验是永远不要用pip install llama-index来安装最新版。官方文档里推荐的pip install llama-index[all]在实际项目中往往是个陷阱因为它会把所有可能用到的向量库、LLM适配器都装上导致依赖地狱。正确的做法是按需安装精确到小版本。以我们这个基础项目为例目标是用Milvus做向量库用OpenAI API做嵌入和LLM那么只需安装三个包pip install llama-index-core0.10.45 pip install llama-index-vector-stores-milvus0.2.8 pip install llama-index-llms-openai0.1.32这三个版本号是我反复测试后确认能完美协同的组合。llama-index-core是核心框架llama-index-vector-stores-milvus是Milvus的适配器llama-index-llms-openai是OpenAI的LLM适配器。它们各自的pyproject.toml文件里都锁定了兼容的llama-index-core版本。如果你强行升级llama-index-core到0.11.x那另外两个包的import就会失败报ModuleNotFoundError。这个细节官方文档不会写但却是你能否顺利跑通的第一道门槛。另一个常见坑是pymilvus的版本。llama-index-vector-stores-milvus0.2.8要求pymilvus2.4.2但如果你装的是最新的pymilvus2.5.0在某些Linux发行版上会因为grpcio版本冲突而启动失败。我的解决方案是先装一个稳定的pymilvus2.4.10再装milvus-lite一个轻量级的嵌入式Milvus非常适合本地开发和测试pip install pymilvus2.4.10 milvus-litemilvus-lite的好处是它不需要你单独启动一个Milvus服务进程所有数据都存在一个本地文件里比如./milvus.db开箱即用。对于学习和原型验证它比折腾Docker容器要省心一百倍。等你确定方案可行再迁移到云上的Zilliz Cloud或自建的Milvus集群这才是合理的演进路径。提示在虚拟环境中操作用python -m venv rag_env创建一个干净的虚拟环境然后激活它。这能彻底避免系统Python环境被污染也是专业开发者的必备习惯。3.2 数据加载与预处理别让垃圾输入毁掉你的RAG数据是RAG的燃料但不是所有燃料都一样。SimpleDirectoryReader是LlamaIndex提供的最便捷的加载器但它默认的行为可能并不适合你的数据。比如它默认会递归扫描子目录如果你的data/文件夹里混着一些.git、.DS_Store之类的系统文件它也会试图去读然后报错。所以第一步永远是显式指定input_files或input_dir并排除无关文件from llama_index.core import SimpleDirectoryReader # 只加载指定的几个文件绝对安全 documents SimpleDirectoryReader( input_files[ ./data/paul_graham_essay.txt, ./data/uber_2021.pdf ] ).load_data() # 或者加载整个目录但排除特定后缀 documents SimpleDirectoryReader( input_dir./data/, required_exts[.txt, .pdf, .md], # 只处理这三种格式 filename_as_idTrue # 把文件名作为doc_id方便后续追踪 ).load_data()filename_as_idTrue这个参数极其重要。它让每个Document的doc_id字段等于文件名这样当你在查询结果里看到一段回答就能立刻知道它来自哪个文件这对调试和审计至关重要。否则doc_id是一串随机UUID你根本无法定位源头。加载完之后千万别直接拿去建索引。先打印几个Document看看内容print(fLoaded {len(documents)} documents) for i, doc in enumerate(documents[:2]): print(f\n--- Document {i1} (ID: {doc.doc_id}) ---) print(doc.text[:200] ... if len(doc.text) 200 else doc.text) print(fMetadata: {doc.metadata})你可能会发现PDF里的页眉页脚、扫描件OCR出来的乱码、或者网页HTML里的script标签都被原封不动地塞进了text里。这些噪声会严重污染嵌入向量让检索结果偏离主题。这时候就需要NodeParser来“净化”数据。SentenceSplitter是默认选择但它有一个致命弱点它按标点切分对长段落效果很好但对短标题、列表项就容易切碎。我处理技术文档时发现HierarchicalNodeParser效果更好它能识别标题层级H1, H2把一个章节下的所有内容保留在同一个Node里保证了语义完整性。它的用法也很简单from llama_index.core.node_parser import HierarchicalNodeParser node_parser HierarchicalNodeParser.from_defaults( chunk_sizes[2048, 512, 128], # 大中小三级切分粒度 include_metadataTrue, include_prev_next_relTrue # 记录前后节点关系用于上下文扩展 ) nodes node_parser.get_nodes_from_documents(documents)chunk_sizes参数是精髓。[2048, 512, 128]意味着先按2048字符切一个粗粒度Node再在这个Node里按512字符切更细的最后再按128字符切最细的。这样一个长技术文档的“概述”部分可能是一个2048字符的Node而其中的某个具体API参数说明则是一个128字符的Node。查询时系统会优先返回最细粒度的相关Node确保答案精准。这个技巧是我在一个API文档知识库项目里把平均响应时间从3.2秒降到1.1秒的关键。3.3 模型加载与配置让“大脑”和“眼睛”各司其职“加载模型”在LlamaIndex里其实是一个“声明”而非“执行”的过程。你不需要在代码里写model load_model(...)而是通过Settings对象告诉整个框架“当需要嵌入时用这个当需要生成时用那个”。这是一种声明式编程思想让代码更简洁也更易维护。首先配置嵌入模型。我们以OpenAI为例from llama_index.core import Settings from llama_index.embeddings.openai import OpenAIEmbedding Settings.embed_model OpenAIEmbedding( modeltext-embedding-3-small, # 推荐比ada-002便宜3倍效果更好 api_keysk-..., # 你的OpenAI Key embed_batch_size100, # 批处理大小越大越快但显存/内存占用越高 )text-embedding-3-small是OpenAI在2024年推出的新模型它在成本、速度和效果上达到了一个极佳的平衡点。embed_batch_size100是关键参数。如果你有1000个文档每个文档被切分成10个Node那就是10000个向量要计算。如果batch_size1就要发10000次API请求慢且贵如果batch_size100就只需要100次请求效率提升百倍。但要注意batch_size不能无限制增大它受限于你的网络带宽和OpenAI的API限流。我实测下来100是一个在大多数网络环境下都稳如老狗的值。接着配置LLM。同样用OpenAIfrom llama_index.llms.openai import OpenAI Settings.llm OpenAI( modelgpt-4o-mini, # 新一代小模型性价比之王 api_keysk-..., temperature0.1, # 降低温度让回答更确定、更少幻觉 max_tokens1024, # 控制输出长度避免无限生成 )gpt-4o-mini是2024年推出的明星小模型它的综合能力接近旧版gpt-3.5-turbo但价格只有其1/3响应速度却快了一倍。temperature0.1是RAG场景的黄金参数。温度太高0.7LLM会为了“说得漂亮”而编造事实温度太低0.0它又会过于死板无法灵活组织语言。0.1是一个很好的折中点它让LLM忠实于检索到的上下文只做“转述”和“总结”不做“创作”。最后别忘了设置全局Settings的其他选项它们对RAG效果影响巨大from llama_index.core import Settings Settings.chunk_size 512 # Node的默认大小与NodeParser的chunk_sizes配合 Settings.context_window 4096 # LLM的上下文窗口必须与你选的模型匹配 Settings.num_output 512 # LLM单次生成的最大token数context_window尤其重要。如果你用的是gpt-4o-mini上下文4096但Settings.context_window设成了8192那么在拼接检索到的文档时系统会试图塞入更多内容结果超出模型限制直接报错。反之如果设得太小又会浪费宝贵的上下文空间。所以Settings里的每一个参数都必须与你实际选用的模型规格严格对齐。这是我踩过最多次的坑没有之一。4. 实操过程与核心环节实现从代码到可运行的RAG服务4.1 构建向量索引让数据“活”起来的三步走构建检索器的核心就是创建一个VectorStoreIndex。这个过程可以分解为三个清晰的步骤准备向量库、准备存储上下文、创建索引。每一步都有其不可替代的作用。第一步准备向量库VectorStore向量库是数据的“物理仓库”。我们选择MilvusVectorStore因为它成熟、稳定、社区活跃。初始化时最关键的参数是uri和dimfrom llama_index.vector_stores.milvus import MilvusVectorStore # 创建一个轻量级的Milvus实例数据存在本地文件 vector_store MilvusVectorStore( uri./milvus_demo.db, # 本地文件路径milvus-lite会自动创建 dim1536, # 嵌入向量的维度必须与embed_model输出一致 overwriteTrue, # 每次运行都清空旧数据方便调试 collection_namerag_knowledge_base, # 自定义集合名便于管理 )dim1536这个数字是text-embedding-3-small模型的标准输出维度。如果你换成了bge-base-en-v1.5它的维度是768这里就必须改成dim768否则会报Dimension mismatch错误。overwriteTrue是开发阶段的利器它确保你每次运行脚本都是在一个干净的“新世界”里避免了旧数据干扰新实验。上线后当然要改成False并做好数据备份。第二步准备存储上下文StorageContextStorageContext是LlamaIndex的“胶水”它把VectorStore物理存储和Index逻辑索引粘合在一起。它还可以容纳其他存储组件比如DocStore存原始文档、IndexStore存索引元数据但在基础RAG里我们只需要VectorStorefrom llama_index.core import StorageContext storage_context StorageContext.from_defaults( vector_storevector_store # 将向量库注入存储上下文 )这行代码看起来平淡无奇但它完成了最关键的绑定从此所有通过这个storage_context创建的Index其向量数据都会被存进./milvus_demo.db这个文件里。第三步创建索引VectorStoreIndex这是最后一步也是最激动人心的一步。它把documents数据、storage_context存储、以及隐式绑定的Settings.embed_model嵌入模型三者正式组装成一个可查询的Indexfrom llama_index.core import VectorStoreIndex # 开始构建索引这一步会触发嵌入计算和向量入库 index VectorStoreIndex.from_documents( documentsdocuments, storage_contextstorage_context, show_progressTrue # 显示进度条让你知道它没卡住 ) # 可选持久化索引以便下次直接加载不用重建 index.storage_context.persist(persist_dir./storage/)show_progressTrue是良心设计。当你处理几百个PDF时这个进度条能让你安心等待而不是怀疑程序是不是挂了。persist()方法则是一个性能优化技巧。索引构建尤其是嵌入计算是耗时操作。第一次运行它会花几分钟但如果你把index和storage_context一起保存到磁盘下次启动时就可以跳过整个构建过程直接从磁盘加载# 下次启动时直接加载已构建好的索引 from llama_index.core import StorageContext, load_index_from_storage storage_context StorageContext.from_defaults(persist_dir./storage/) index load_index_from_storage(storage_context)这能让你的RAG服务启动时间从几分钟缩短到几秒钟用户体验天壤之别。这个技巧很多初学者都不知道但它却是生产环境的标配。4.2 构建查询引擎让RAG真正“开口说话”有了index下一步就是让它能回答问题。as_query_engine()是通往这个目标的捷径但它背后有丰富的配置选项决定了你的RAG是“能用”还是“好用”。最简用法query_engine index.as_query_engine() response query_engine.query(What did the author learn?) print(response)这行代码会返回一个Response对象它的response属性就是生成的答案。但这种用法用的是LlamaIndex的默认RetrieverQueryEngine它只做最基础的检索生成没有重排没有上下文优化。进阶用法添加重排器Reranker默认的向量检索是按余弦相似度排序的但相似度高不代表对当前问题最有用。比如一个问题“Uber的2021年营收是多少”检索可能返回一篇讲Uber商业模式的长文相似度高而忽略了一张财报摘要的表格相似度略低但信息更精准。这时就需要Reranker来二次打分。LlamaIndex集成了CohereRerank和BGERerank等开源重排器from llama_index.postprocessor.cohere_rerank import CohereRerank cohere_rerank CohereRerank( api_keyyour-cohere-key, top_n3, # 重排后只保留前3个最相关的结果 ) query_engine index.as_query_engine( node_postprocessors[cohere_rerank] # 在检索后对结果进行重排 )top_n3是经验值。重排器本身也有计算开销top_n设得太大反而拖慢整体速度。我测试过对于大多数企业知识库top_n3或5能在效果和速度之间取得最佳平衡。高级用法自定义提示词Prompt有时候LLM的默认回答风格不符合你的要求。比如你希望它总是先给出结论再解释原因或者你希望它在不确定时明确说“我不知道”。这就需要修改prompt_templatefrom llama_index.core.prompts import PromptTemplate # 定义一个更严格的提示词 qa_prompt_tmpl_str ( Context information is below.\n ---------------------\n {context_str}\n ---------------------\n Given the context information and not prior knowledge, answer the query. If the context does not contain the answer, respond with I cannot find the answer in the provided documents.\n Query: {query_str}\n Answer: ) qa_prompt_tmpl PromptTemplate(qa_prompt_tmpl_str) query_engine index.as_query_engine( text_qa_templateqa_prompt_tmpl # 应用自定义提示词 )这个提示词强制LLM“只基于上下文回答”并给出了一个明确的“不知道”话术极大减少了幻觉。我在一个医疗知识库项目里用这个模板把“编造答案”的比例从12%降到了0.3%。这证明一个好的提示词有时比换一个更贵的LLM模型效果还要显著。4.3 本地模型接入实战用Ollama和llama.cpp打造离线RAG依赖OpenAI API固然方便但数据隐私、网络延迟、费用控制都是绕不开的问题。接入本地模型是每个RAG工程师的必修课。LlamaIndex对本地模型的支持堪称业界标杆我们以Ollama和llama.cpp为例展示如何无缝切换。Ollama接入Ollama是本地大模型的“App Store”安装简单模型丰富。首先确保Ollama服务在本地运行ollama serve然后拉取一个模型比如llama3:8bollama pull llama3:8b接着在Python中用Ollama类代替OpenAIfrom llama_index.llms.ollama import Ollama Settings.llm Ollama( modelllama3:8b, # 模型名必须与ollama list里的一致 request_timeout120.0, # Ollama响应可能较慢延长超时 base_urlhttp://localhost:11434, # Ollama默认地址 )就这么简单。LlamaIndex会自动通过HTTP API与Ollama通信。request_timeout120.0是关键因为本地模型推理比API慢得多不加这个很容易超时报错。llama.cpp接入llama.cpp是极致的轻量化方案它能让一个7B模型在MacBook Air的M1芯片上流畅运行。你需要先用llama.cpp的convert.py脚本把HuggingFace上的模型如Qwen2-1.5B转换成GGUF格式然后用llama-server启动一个本地API服务# 启动一个本地LLM服务监听端口8080 llama-server -m ./models/qwen2-1.5b.Q4_K_M.gguf -c 2048 --port 8080然后在LlamaIndex中把它当作一个普通的OpenAI兼容API来用from llama_index.llms.openai import OpenAI Settings.llm OpenAI( modelqwen2-1.5b, # 这个名字可以任意只是标识 api_keyno-key-needed, # llama.cpp不需要key base_urlhttp://localhost:8080/v1, # 指向本地服务 api_versionv1, # llama.cpp的API版本 )你看除了base_url和api_version其他配置和用OpenAI一模一样。这就是LlamaIndex的优雅之处它把底层LLM的差异完全抽象掉了。你可以在开发时用OpenAI快速迭代上线时无缝切换到本地llama.cpp代码几乎不用改。这种“一次开发多端部署”的能力是它成为RAG首选框架的核心竞争力。5. 常见问题与排查技巧实录那些让我熬夜到凌晨三点的Bug5.1 向量维度不匹配一个数字引发的血案这是RAG新手遇到的第一个也是最经典的错误。报错信息通常是ValueError: Dimension mismatch: expected 768, got 1536或者更隐蔽的pymilvus.exceptions.MilvusException: MilvusException: (code1, messageInvalid dimension: 1536, should be less than or equal to 128)前者是embed_model输出的维度1536和MilvusVectorStore期望的维度768不一致后者是Milvus本身的硬性限制——老版本Milvus2.3.x最大只支持128维而现代嵌入模型动辄768或1536维。这个问题的根源往往不在你的代码而在于你安装的包版本。排查与解决确认embed_model的维度在代码里加一行打印出来from llama_index.embeddings.openai import OpenAIEmbedding embed_model OpenAIEmbedding(modeltext-embedding-3-small) print(Embedding dimension:, embed_model._get_text_embedding(test).shape[0])运行后你会看到输出1536。确认MilvusVectorStore的dim参数检查你的vector_store MilvusVectorStore(...)这行dim参数是否等于1536如果不是立刻修正。确认Milvus版本运行pip show pymilvus看版本号。如果是2.3.x请务必升级到2.4.2或更高。2.4.x版本移除了128维的硬限制。终极保险如果你用的是开源嵌入模型比如BAAI/bge-small-en-v1.5它的维度是384。那么你的MilvusVectorStore就必须是dim384。记住embed_model的维度、MilvusVectorStore的dim参数、以及pymilvus的版本三者必须严格匹配缺一不可。我曾因为一个dim384写成了dim385调试了整整一个下午最后发现是键盘多按了一个键。5.2 查询结果为空或不相关不是模型不行是检索器没调好你满怀期待地输入一个问题query_engine.query()返回的却是一句“我不知道”或者答案完全驴唇不对马嘴。这通常不是模型的问题而是检索环节出了故障。排查思路第一步绕过LLM直接看检索结果。QueryEngine的底层是Retriever你可以把它单独拿出来测试retriever index.as_retriever(similarity_top_k5) # 检索前5个 nodes retriever.retrieve(What did the author learn?) for i, node in enumerate(nodes): print(f\n--- Result {i1} (Score: {node.score:.3f}) ---) print(node.text[:200] ...)如果这里返回的node.text和你的问题完全无关那问题就出在数据或嵌入模型上如果node.text是相关的但最终LLM生成的答案不好那问题就在LLM或提示词上。第二步检查数据质量。用print(documents[0].text[:500])看原始数据。我遇到过最离谱的情况PDF是扫描件OCR识别把“Uber”识别成了“U6er”导致所有关于Uber的检索都失败。解决方案是换一个OCR引擎或者手动校对关键文档。第三步调整检索参数。similarity_top_k太小如1可能漏掉关键信息太大如20又会把噪声引入上下文。我一般从5开始试。另外vector_store的search_config里