
1. 本地文件问答为什么总卡在模型接入这一步很多人第一次用 LlamaIndex 搭本地文件问答卡住的地方往往不是切分参数也不是向量库选型而是模型服务怎么接。LlamaIndex 默认走 OpenAI 的接口环境变量里塞一个OPENAI_API_KEY就能跑但真到落地阶段你会遇到几个很现实的问题密钥散落在不同脚本里、换模型要改一堆代码、团队里几个人共用一套额度不好管、想对比不同模型效果还得来回改配置。我试过把 key 硬编码在 notebook 里结果文件一多、脚本一多自己都记不清哪个 key 对应哪个项目。后来改成统一走一个兼容 OpenAI 协议的通道把 Base URL、Key、Model ID 三件套集中管理LlamaIndex 侧只需要改Settings或OpenAI类的初始化参数检索链路本身完全不用动。这篇就按这个思路从文档加载、切分、向量索引一路写到查询引擎把本地文件问答应用跑通并核对引用来源。先说清楚这套东西适合谁手里有一堆 PDF、Markdown、TXT 想做成能问答的知识库或者你在做企业内部文档助手需要可控的模型接入方式再或者你只是想快速验证 RAG 链路不想在密钥管理上耗时间。核心检索词就三个LlamaIndex、本地文件问答应用、统一 Key 接入。下面所有代码都可以直接复制路径和参数按你自己的环境改一下即可。LlamaIndex 的定位是「数据框架」它把非结构化文档转成可检索的索引再让 LLM 基于检索结果回答。整个链路拆开就是加载器读文件 → 切分器切块 → 嵌入模型转向量 → 向量库存储 → 查询引擎检索 合成答案。这里面只有嵌入和合成两步需要调模型服务也就是我们要接的那部分。把这两步的通道统一了剩下的都是本地计算。2. TaoToken 前置准备统一 Key 与 API 通道怎么配在写 LlamaIndex 代码之前先把模型服务的接入信息准备好。TaoToken 提供的是兼容 OpenAI 协议的 API 通道也就是说 LlamaIndex 里所有基于 OpenAI 接口的类都能直接用不需要额外写适配层。你需要拿到三样东西Base URL、API Key、Model ID。Base URL 用https://taotoken.net/api注意这个地址不带任何查询参数。API Key 在控制台的 API Keys 页面创建建议一个项目建一个 key方便后面按项目统计用量和吊销。Model ID 就是你实际要调的模型标识比如对话模型和嵌入模型各选一个。这三件套后面会反复出现建议先写进环境变量别硬编码。创建 key 的入口在这里https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。进去之后点新建复制出来的 key 只显示一次记得存好。如果你还没决定用哪个模型可以先到模型对话页面看看有哪些可用模型https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。环境变量建议这样设Linux/macOS 用 exportWindows 用 set 或写进系统环境变量export TAOTOKEN_API_KEY你的key export TAOTOKEN_BASE_URLhttps://taotoken.net/api export TAOTOKEN_LLM_MODEL你的对话模型ID export TAOTOKEN_EMBED_MODEL你的嵌入模型ID注意Base URL 结尾不要多加/v1或斜杠LlamaIndex 的 OpenAI 兼容类会自己拼接路径。多写一层路径是最常见的 404 来源。为什么强调「统一 Key」因为 LlamaIndex 里至少有两个地方要调模型嵌入模型负责把文本块转成向量对话模型负责基于检索结果生成答案。如果这两步走不同供应商、不同 key配置会散成两处。统一走一个通道后你只需要维护一份 Base URL 和一份 Key换模型时改一个 Model ID 就行。对于本地文件问答这种需要反复调参的场景这个收敛很关键。另外提一句 Coding Plan如果你后面要把这个问答应用长期跑起来、或者接成 Agent 持续调用按量计费可能不如套餐划算可以到 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 看看额度方案。前期验证阶段用按量就够了别一上来就买套餐。3. 可复制配置LlamaIndex 索引构建与查询片段这一节是核心直接给能跑的代码。先装依赖pip install -U llama-index llama-index-vector-stores-chroma chromadbLlamaIndex 新版本把很多集成拆成了独立包向量库、嵌入、LLM 都可能要单独装。上面这套组合覆盖了本地 Chroma 持久化 OpenAI 兼容接入。装完先确认版本python -c import llama_index.core; print(llama_index.core.__version__)接下来是全局配置。LlamaIndex 用Settings统一管理 LLM 和嵌入模型这是最省事的写法import os from llama_index.core import Settings from llama_index.llms.openai_like import OpenAILike from llama_index.embeddings.openai import OpenAIEmbedding BASE_URL os.environ[TAOTOKEN_BASE_URL] API_KEY os.environ[TAOTOKEN_API_KEY] Settings.llm OpenAILike( modelos.environ[TAOTOKEN_LLM_MODEL], api_baseBASE_URL, api_keyAPI_KEY, is_chat_modelTrue, temperature0.1, ) Settings.embed_model OpenAIEmbedding( modelos.environ[TAOTOKEN_EMBED_MODEL], api_baseBASE_URL, api_keyAPI_KEY, )这里用OpenAILike而不是OpenAI是因为前者对自定义 Base URL 的兼容性更稳is_chat_modelTrue明确告诉它走 chat 接口。temperature0.1是为了问答场景答案更贴文档减少发挥。然后是加载和切分。假设你的文件放在./docs目录混合了 md 和 pdffrom llama_index.core import SimpleDirectoryReader from llama_index.core.node_parser import SentenceSplitter documents SimpleDirectoryReader( input_dir./docs, recursiveTrue, required_exts[.md, .txt, .pdf], ).load_data() splitter SentenceSplitter(chunk_size512, chunk_overlap64) nodes splitter.get_nodes_from_documents(documents) print(f文档数 {len(documents)}切分后节点数 {len(nodes)})chunk_size512是个稳妥起点中文文档可以适当放大到 768。chunk_overlap64保证跨块的句子不被切断。切完打印节点数如果节点数是 0说明文件没被读到先查路径和扩展名。接着建索引并持久化到 Chromaimport chromadb from llama_index.core import VectorStoreIndex, StorageContext from llama_index.vector_stores.chroma import ChromaVectorStore chroma_client chromadb.PersistentClient(path./chroma_db) collection chroma_client.get_or_create_collection(local_qa) vector_store ChromaVectorStore(chroma_collectioncollection) storage_context StorageContext.from_defaults(vector_storevector_store) index VectorStoreIndex( nodes, storage_contextstorage_context, show_progressTrue, ) index.storage_context.persist(persist_dir./storage)PersistentClient会把向量落盘到./chroma_db下次启动不用重新嵌入。persist额外存了 docstore 和 index 元数据重建时能省一次嵌入开销。查询引擎配置重点是让它返回引用来源from llama_index.core.query_engine import RetrieverQueryEngine from llama_index.core.retrievers import VectorIndexRetriever from llama_index.core.response_synthesizers import get_response_synthesizer retriever VectorIndexRetriever(indexindex, similarity_top_k4) synthesizer get_response_synthesizer(response_modecompact) query_engine RetrieverQueryEngine( retrieverretriever, response_synthesizersynthesizer, ) response query_engine.query(这个项目的安装步骤是什么) print(response) for node in response.source_nodes: print(node.metadata.get(file_name), node.score)similarity_top_k4是召回块数文档多可以调到 6。response_modecompact会把召回的块压缩后再喂给模型省 token。source_nodes就是引用来源file_name来自加载器自动写入的元数据score是相似度用来判断召回质量。如果你想把配置写成文件而不是散在代码里可以用一个settings.json{ base_url: https://taotoken.net/api, api_key_env: TAOTOKEN_API_KEY, llm_model: 你的对话模型ID, embed_model: 你的嵌入模型ID, chunk_size: 512, chunk_overlap: 64, top_k: 4 }代码里读这个 json 再初始化Settings团队协作时改配置不用动代码。注意api_key_env存的是环境变量名而不是 key 本身避免密钥进版本库。4. 验证请求跑通样例问答并核对引用来源配置写完必须验证不然你不知道是检索没召回还是模型没接上。准备一个样例文件./docs/readme.md内容随便写几段比如项目介绍、安装命令、常见问题。然后按顺序跑。第一步单独验证嵌入通道。这一步不涉及检索只确认 Base URL 和 Key 能通from llama_index.embeddings.openai import OpenAIEmbedding import os emb OpenAIEmbedding( modelos.environ[TAOTOKEN_EMBED_MODEL], api_baseos.environ[TAOTOKEN_BASE_URL], api_keyos.environ[TAOTOKEN_API_KEY], ) vec emb.get_text_embedding(测试文本) print(len(vec))打印出向量维度比如 1536 或 1024就说明嵌入通道正常。如果这里报 401直接跳到第 5 节排查。第二步单独验证对话通道from llama_index.llms.openai_like import OpenAILike import os llm OpenAILike( modelos.environ[TAOTOKEN_LLM_MODEL], api_baseos.environ[TAOTOKEN_BASE_URL], api_keyos.environ[TAOTOKEN_API_KEY], is_chat_modelTrue, ) print(llm.complete(用一句话说明什么是向量检索))能返回一句通顺的话说明对话通道也通了。这两步分开验证的好处是出问题时能立刻定位是嵌入挂了还是对话挂了不用在完整链路里猜。第三步跑完整问答并核对来源。用第 3 节的query_engine问一个只有样例文件里才有答案的问题response query_engine.query(安装依赖的命令是什么) print(答案, response.response) print(引用来源) for i, node in enumerate(response.source_nodes): print(f[{i}] 文件{node.metadata.get(file_name)} 分数{node.score:.4f}) print( 片段, node.text[:120].replace(\n, ))成功的结果长这样答案里包含你写在样例文件里的安装命令source_nodes里至少有一个节点的file_name是readme.mdscore在 0.7 以上。如果答案对但来源为空说明response_mode或检索器配置有问题如果来源对但答案跑偏多半是temperature太高或召回块数不够。实测下来similarity_top_k从 4 调到 6召回覆盖率会明显提升但 token 消耗也上去。建议先用 4 跑通再根据答案质量微调。核对来源这个动作别省它是判断 RAG 是否真的在工作、而不是模型在瞎编的唯一依据。你可以故意问一个文档里没有的问题看它是否老实说「文档中没有相关信息」如果它硬编一个答案说明提示词或response_mode需要收紧。5. 本篇常见报错排查401、local proxy failed、reading choices、OAuth这一节按真实报错来遇到哪个查哪个。401 Unauthorized / invalid api key。最常见。先确认环境变量真的被读到了在 Python 里print(os.environ.get(TAOTOKEN_API_KEY))如果打印 None说明 export 没生效或写在了别的 shell。再确认 key 没有多余空格复制时容易带上换行。最后确认 Base URL 是https://taotoken.net/api没有多写/v1。三件套里 Base URL、Key、Model ID 任何一个错都会 401 或 404。local proxy failed / connection error。这类报错通常是网络层没通或者本地有残留的代理环境变量指向了不可用地址。检查HTTP_PROXY、HTTPS_PROXY是否被设成了奇怪的值清掉再试。另外确认你的运行环境能正常访问外网 API公司内网可能需要走特定出口。注意这里说的是正常的网络连通性排查不涉及任何绕过网络管理的手段。Error reading choices / KeyError choices。这个报错说明请求发出去了、也返回了但返回体结构不是预期的 OpenAI 格式。常见原因是 Model ID 填成了嵌入模型或者填了一个不支持 chat 的模型。对话模型和嵌入模型要分开填Settings.llm用对话模型Settings.embed_model用嵌入模型。另外OpenAILike的is_chat_model要设 True否则它可能按 completion 接口发请求返回结构对不上。OAuth / authentication 相关报错。如果你用的是某些需要 OAuth 的客户端或 CLI报错里出现 OAuth 字样通常是客户端自己的鉴权流程没走完和 API Key 通道是两回事。LlamaIndex 这套代码走的是 API Key不涉及 OAuth。如果你在 Codex 或 Claude Code 这类工具里配置注意它们的配置文件格式不同Codex 用auth.jsonClaude Code 用settings.jsonCline 用 MCP 配置。不管哪个核心都是 Base URL Key Model ID 三件套缺一不可。Chroma 相关报错。get_or_create_collection如果报维度不匹配说明你换了嵌入模型但复用了旧 collection。嵌入模型一换向量维度就变旧库必须删掉重建。直接删./chroma_db目录重新跑索引即可。另外PersistentClient的 path 不要指向已有文件要指向目录。节点数为 0。文件没被读到。检查input_dir路径、required_exts是否包含你的文件后缀、文件编码是否是 UTF-8。PDF 扫描件没有文字层加载器读出来是空的需要先做 OCR。排障时建议把Settings初始化、嵌入验证、对话验证、完整问答分成四步单独跑哪步挂了一眼就能看出来。别一上来就跑完整链路报错信息会混在一起。6. 把链路固定下来从验证到长期使用的接入建议跑通之后建议把配置和代码固化成一个可复用的小项目而不是留在 notebook 里。目录结构可以这样config/settings.json放非敏感配置环境变量放密钥ingest.py负责加载切分建索引query.py负责查询docs/放源文件chroma_db/和storage/是生成物、加进.gitignore。日常使用就两条命令文档更新后跑python ingest.py重建索引提问时跑python query.py 你的问题。如果文档量大、重建慢可以只对新增文件做增量嵌入LlamaIndex 的IngestionPipeline配合 docstore 能跳过已处理的节点。关于模型接入再强调一次三件套的写法不管你后面用 Cline、Codex 还是 Claude Code配置逻辑都一样Base URL 填https://taotoken.net/apiKey 填控制台创建的 keyModel ID 填实际模型。Claude Code 的settings.json里对应字段是base_url、api_key、modelCodex 的auth.json里是api_base、api_key、modelCline 的 MCP 配置里是baseUrl、apiKey、model。字段名不同含义一致。如果你要把这个问答应用接成长期跑的 Agent或者团队多人共用建议看一下 Coding Plan 的额度方案比按量计费更可控https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 里面有各客户端的完整配置示例。想先试模型效果直接去模型对话页面发几条消息感受一下https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。最后给个实用技巧把similarity_top_k和chunk_size做成命令行参数每次调参不用改代码。再写一个小的评估脚本准备 10 个已知答案的问题跑一遍看命中率和来源准确率这样换模型、换切分参数时能快速对比而不是凭感觉。本地文件问答的价值在于答案可溯源来源核对这一步永远别省。