ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

RAG系列:从零搭建企业级RAG系统实战——用TaoToken统一Key打通检索与生成链路

RAG系列:从零搭建企业级RAG系统实战——用TaoToken统一Key打通检索与生成链路 1. 企业级 RAG 从零搭建为什么第一步不是选模型而是统一 Key企业级 RAG 系统落地时最容易被低估的环节不是向量库选型也不是 Prompt 调优而是多模型调用的鉴权管理。一个完整的 RAG 链路至少涉及三类模型Embedding 负责把文档和 Query 转成向量Rerank 负责对召回结果精排生成模型负责基于上下文产出答案。如果每类模型都单独申请 Key、单独维护 Base URL代码里就会散落一堆OPENAI_API_KEY、COHERE_API_KEY、DASHSCOPE_API_KEY环境变量越堆越多换模型时改到怀疑人生。我试过在一个知识库项目里同时接了三个厂商的接口结果测试环境和生产环境的 Key 混用排查了半天才发现是 Embedding 走了旧 Key 导致维度对不上。这类问题在企业级场景里非常致命因为 RAG 的检索命中和生成质量强依赖链路一致性——Embedding 模型换了向量库里的历史向量就全废了。TaoToken 在这里的价值就很明确它提供统一的 API 通道和 Key 管理把 Embedding、Rerank、生成模型的调用收敛到同一个 Base URL 和同一套鉴权体系下。你只需要维护一个 Key就能在检索和生成两个阶段调用不同模型切换模型时只改 Model ID不动鉴权逻辑。这对企业级 RAG 的持续迭代非常关键。这篇文章面向的是有 Python 基础、准备从零搭一套企业知识库问答系统的开发者。我会用 LangChain Qdrant 作为基础框架把 Embedding、Rerank、生成三段链路全部接到 TaoToken 的统一通道上给出可直接复制的环境变量、配置片段和端到端验证代码。读完之后你应该能跑通一次完整的「文档入库 → 检索召回 → 精排 → 生成答案」流程并且确认检索命中的内容和最终生成结果是一致的。核心检索词先明确企业级 RAG 实战、TaoToken 统一 Key、Embedding 与生成模型打通、LangChain RAG 配置。下面从环境准备开始。2. TaoToken 前置准备统一 Key 与 Base URL 的接入配置在写任何 RAG 代码之前先把 TaoToken 的接入信息准备好。这一步的目标是拿到一个 API Key并确认 Base URL 和可用模型列表。TaoToken 的 API 入口是https://taotoken.net/api注意这个地址在代码里作为base_url使用时通常需要带上版本路径具体以接入文档为准。先访问 API Keys 管理页面创建 Keyhttps://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentrag_apikey创建完成后你会得到一串以sk-开头的 Key。把它写进环境变量不要硬编码在代码里。企业级项目建议用.env文件配合python-dotenv管理生产环境走密钥管理服务。# .env 文件 TAOTOKEN_API_KEYsk-你的实际Key TAOTOKEN_BASE_URLhttps://taotoken.net/api接下来确认模型可用性。TaoToken 的模型列表和接入文档在这里https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentrag_doc你需要确认三类模型的 Model ID一个 Embedding 模型比如text-embedding-3-small或bge-m3对应的托管版本、一个 Rerank 模型、一个生成模型比如gpt-4o或claude-3-5-sonnet。Model ID 的命名要和文档里保持一致写错会直接报 404 或 model not found。如果你想先在对话界面里验证 Key 是否可用可以打开模型对话页面发一条测试消息https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentrag_chat确认能正常返回后再进入代码环节。这里有个细节LangChain 的OpenAIEmbeddings和ChatOpenAI都支持自定义base_url和api_key所以我们可以用同一套环境变量驱动所有模型调用。这样做的最大好处是当你要把生成模型从 GPT-4o 换成 Claude 时只需要改一个 Model ID 字符串鉴权部分完全不动。安装依赖pip install langchain langchain-openai langchain-community langchain-qdrant qdrant-client python-dotenv如果你打算用本地 Qdrant可以用 Docker 起一个单机实例docker run -d -p 6333:6333 -v $(pwd)/qdrant_storage:/qdrant/storage qdrant/qdrant到这里前置准备就完成了。你手里应该有一个可用的 TaoToken Key、确认过的 Base URL、三类模型的 Model ID以及一个跑起来的 Qdrant 实例。下一节进入可复制的配置代码。3. 可复制配置用统一 Base URL 串起 Embedding、Rerank 与生成模型这一节是整篇文章的核心。我会给出完整的配置片段包括环境变量加载、Embedding 初始化、向量库连接、Rerank 调用和生成模型初始化。所有模型调用都走 TaoToken 的统一通道Key 和 Base URL 只出现一次。先看配置加载部分。创建一个config.pyimport os from dotenv import load_dotenv load_dotenv() TAOTOKEN_API_KEY os.getenv(TAOTOKEN_API_KEY) TAOTOKEN_BASE_URL os.getenv(TAOTOKEN_BASE_URL, https://taotoken.net/api) # 三类模型的 Model ID按接入文档填写 EMBEDDING_MODEL text-embedding-3-small RERANK_MODEL rerank-multilingual-v3 GENERATION_MODEL gpt-4o # Qdrant 配置 QDRANT_URL http://localhost:6333 COLLECTION_NAME enterprise_knowledge_base然后是 Embedding 和向量库的初始化。这里用 LangChain 的OpenAIEmbeddings把base_url指向 TaoTokenfrom langchain_openai import OpenAIEmbeddings from langchain_qdrant import QdrantVectorStore from qdrant_client import QdrantClient from config import ( TAOTOKEN_API_KEY, TAOTOKEN_BASE_URL, EMBEDDING_MODEL, QDRANT_URL, COLLECTION_NAME ) embeddings OpenAIEmbeddings( modelEMBEDDING_MODEL, api_keyTAOTOKEN_API_KEY, base_urlTAOTOKEN_BASE_URL, check_embedding_ctx_lengthFalse, ) client QdrantClient(urlQDRANT_URL) vectorstore QdrantVectorStore( clientclient, collection_nameCOLLECTION_NAME, embeddingembeddings, )注意check_embedding_ctx_lengthFalse这个参数。LangChain 默认会对输入做 token 预检某些托管模型的 tokenizer 和 OpenAI 不一致时会误报超长关掉可以避免不必要的报错。接下来是文档入库。假设你有一批 Markdown 格式的内部文档用DirectoryLoader加载后切块from langchain_community.document_loaders import DirectoryLoader, TextLoader from langchain.text_splitter import RecursiveCharacterTextSplitter loader DirectoryLoader( ./docs, glob**/*.md, loader_clsTextLoader, loader_kwargs{encoding: utf-8}, ) documents loader.load() splitter RecursiveCharacterTextSplitter( chunk_size800, chunk_overlap150, separators[\n\n, \n, 。, , ], ) chunks splitter.split_documents(documents) vectorstore.add_documents(chunks) print(f已入库 {len(chunks)} 个文本块)切块参数针对中文做了调整separators里加了中文句号chunk_size用 800 字符而不是 1000 token因为中文的字符密度更高。这些参数需要根据你的文档类型微调技术文档可以小一点政策类文档可以大一点。Rerank 部分TaoToken 如果提供兼容的 Rerank 接口可以用 HTTP 直接调用。下面是一个封装示例import requests from config import TAOTOKEN_API_KEY, TAOTOKEN_BASE_URL, RERANK_MODEL def rerank(query: str, documents: list[str], top_n: int 3): resp requests.post( f{TAOTOKEN_BASE_URL}/rerank, headers{ Authorization: fBearer {TAOTOKEN_API_KEY}, Content-Type: application/json, }, json{ model: RERANK_MODEL, query: query, documents: documents, top_n: top_n, }, timeout30, ) resp.raise_for_status() return resp.json()生成模型初始化同样走统一通道from langchain_openai import ChatOpenAI from config import TAOTOKEN_API_KEY, TAOTOKEN_BASE_URL, GENERATION_MODEL llm ChatOpenAI( modelGENERATION_MODEL, api_keyTAOTOKEN_API_KEY, base_urlTAOTOKEN_BASE_URL, temperature0.2, )到这里三类模型全部接入了同一个 Base URL 和同一个 Key。你可以把这段配置理解成 RAG 系统的「鉴权总线」——所有模型调用都从这里分发换模型只改config.py里的 Model ID。如果你打算长期跑编码类 Agent 或者高频调用生成模型可以了解一下 Coding Plan它在持续调用场景下更划算https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentrag_codingplan配置写完后下一节做端到端验证。4. 端到端验证一次问答确认检索命中与生成结果一致配置写完不代表链路通了。企业级 RAG 最容易出问题的地方是「检索到的内容和生成的内容对不上」——要么召回为空但模型硬编答案要么召回了 A 文档但生成引用了 B 文档。这一节用一个完整的问答流程来验证一致性。先构造一个检索函数把向量召回和 Rerank 串起来def retrieve(query: str, top_k: int 10, top_n: int 3): # 第一阶段向量召回 candidates vectorstore.similarity_search(query, ktop_k) if not candidates: return [] # 第二阶段Rerank 精排 docs_text [doc.page_content for doc in candidates] rerank_result rerank(query, docs_text, top_ntop_n) # 按 rerank 分数重排 ranked [] for item in rerank_result.get(results, []): idx item[index] ranked.append({ content: candidates[idx].page_content, metadata: candidates[idx].metadata, score: item[relevance_score], }) return ranked然后是生成函数把检索结果拼进 Promptdef generate_answer(query: str): hits retrieve(query) if not hits: return 知识库中未找到相关内容请补充文档后重试。, [] context \n\n.join( f[片段{i1} | 来源: {h[metadata].get(source, unknown)}]\n{h[content]} for i, h in enumerate(hits) ) prompt f你是一个企业知识库助手。请严格基于以下检索片段回答问题。 如果片段中没有相关信息直接回答「知识库中未找到」不要编造。 检索片段 {context} 用户问题{query} 回答要求 1. 先给出结论 2. 再列出依据的片段编号 3. 不要引入检索片段之外的信息 response llm.invoke(prompt) return response.content, hits现在跑一次真实问答query 流水线 npm run build 报 JavaScript heap out of memory 怎么处理 answer, hits generate_answer(query) print( 检索命中 ) for i, h in enumerate(hits): print(f[{i1}] score{h[score]:.4f} source{h[metadata].get(source)}) print(h[content][:120]) print(---) print( 生成答案 ) print(answer)预期输出应该满足三个条件第一检索命中的片段里包含NODE_OPTIONS或max_old_space_size相关的内容第二生成答案里明确引用了片段编号第三答案中的解决方案和检索片段一致没有出现片段里没有的参数或命令。如果检索命中为空说明 Embedding 或向量库有问题如果检索命中了但生成答案跑偏说明 Prompt 约束不够或者生成模型温度太高。这两种情况的排查方式不同下一节展开。验证通过后你可以把retrieve和generate_answer封装成 FastAPI 接口对外提供/ask端点。到这里一条完整的「统一 Key → 检索 → 精排 → 生成」链路就跑通了。5. 常见报错排查401、local proxy failed、reading choices 与 OAuth 问题企业级 RAG 搭建过程中报错集中在鉴权、网络和响应解析三类。下面按真实报错逐条排查。401 Unauthorized / invalid api key这是最常见的。先确认.env里的 Key 没有多余空格或换行然后确认base_url没有写错。一个典型错误是把base_url写成了https://taotoken.net而漏掉了/api路径。LangChain 的OpenAIEmbeddings会在base_url后面自动拼/embeddings所以base_url必须是https://taotoken.net/api这种带版本路径的形式。排查命令curl -X POST https://taotoken.net/api/embeddings \ -H Authorization: Bearer $TAOTOKEN_API_KEY \ -H Content-Type: application/json \ -d {model:text-embedding-3-small,input:test}如果这条命令返回 401说明 Key 本身有问题去 API Keys 页面重新生成。如果返回 200说明 Key 没问题是代码里的配置写错了。local proxy failed / connection refused这个报错通常出现在 Qdrant 连接上不是 TaoToken 的问题。检查 Qdrant 容器是否在运行docker ps | grep qdrant curl http://localhost:6333/healthz如果 Qdrant 没起来QdrantClient会报连接拒绝。另一个可能是QDRANT_URL写成了localhost但代码跑在容器里容器内的localhost指向容器自身而不是宿主机需要改成宿主机的内网 IP 或 Docker 网络别名。reading choices / KeyError choices这个报错说明响应体里没有choices字段通常是模型返回了错误信息但 HTTP 状态码是 200。常见原因有三个Model ID 写错导致返回了错误对象请求体格式不对被网关拦截Rerank 接口和 Chat 接口的响应结构不同代码里混用了。排查方式是把原始响应打出来import logging logging.basicConfig(levellogging.DEBUG)或者在调用处加一层resp llm.invoke(prompt) print(type(resp), resp)如果resp是字符串而不是AIMessage说明 LangChain 解析失败需要检查base_url是否指向了兼容 OpenAI 协议的端点。OAuth / authentication_error如果你用的是 Claude Code 或 Codex 这类工具接入可能会遇到 OAuth 相关报错。这类工具通常需要三件套配置齐全Base URL、API Key、Model ID。缺任何一个都会报鉴权失败。以 Claude Code 为例配置文件里需要同时写{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_API_KEY: sk-你的Key, ANTHROPIC_MODEL: claude-3-5-sonnet } }注意ANTHROPIC_BASE_URL不要带/v1后缀具体以接入文档为准。Codex 的auth.json类似需要同时填base_url、api_key和model。Cline MCP 场景下如果 MCP Server 里调用了 Embedding 或生成模型也要把 TaoToken 的 Base URL 和 Key 传进去不能只配一半。向量维度不匹配这个报错不会直接提示维度问题而是表现为检索结果全为空或相似度异常。原因是入库时用的 Embedding 模型和查询时用的不一致。比如入库用了text-embedding-3-small1536 维查询时换成了bge-m31024 维向量库会拒绝查询或返回垃圾结果。解决办法是固定 Embedding 模型换模型时重建整个集合。排查清单可以整理成一张表报错关键词可能原因排查动作401 UnauthorizedKey 错误或 base_url 缺 /apicurl 测试 检查 .envlocal proxy failedQdrant 未启动或地址错误docker ps curl healthzreading choicesModel ID 错误或响应格式不符打印原始响应OAuth / authentication_error三件套缺项补全 Base URL Key Model ID检索结果为空Embedding 模型不一致确认入库与查询同模型把这张表存下来下次遇到报错直接对照。排障过程中如果需要确认模型是否可用可以回到模型对话页面发一条测试消息快速区分是 Key 问题还是代码问题。6. 把统一 Key 沉淀为 RAG 基础设施跑通一次问答只是起点。企业级 RAG 真正难的是持续迭代文档每天在更新模型每季度在换代业务方随时要求换一个更便宜的生成模型。如果每次换模型都要改鉴权代码系统根本维护不下去。TaoToken 统一 Key 的价值就在这里体现。把 Base URL 和 Key 收敛到config.py一个文件Embedding、Rerank、生成三类模型全部从这里读取配置。换模型时只改 Model ID 字符串鉴权逻辑零改动。更进一步你可以把模型配置做成数据库表或配置中心按业务线分配不同的模型组合但底层仍然共用同一套 Key 和通道。实际落地时还有几个细节值得注意。第一Embedding 模型一旦确定就不要轻易换换模型意味着全量重建向量库成本很高。第二Rerank 模型可以独立于 Embedding 更换因为它只影响排序不影响向量存储。第三生成模型可以按场景切换比如内部问答用便宜模型对外客服用好模型但都走同一个 Base URL。如果你准备把这套 RAG 系统接入 Claude Code 或 Cline 做开发辅助记得把三件套配全Base URL 用https://taotoken.net/apiKey 用同一个Model ID 按文档填。接入文档在这里https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentrag_doc_end需要新建或轮换 Key 时回到控制台https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentrag_apikey_end最后留一个实用建议在retrieve函数里加一层日志把每次查询的 top_k 召回内容、rerank 分数、最终生成答案的引用片段编号都记下来。上线后每周抽检一批日志看检索命中和生成结果是否一致。这个习惯能帮你在业务方反馈「答得不对」之前自己先发现问题。RAG 系统的质量不是靠一次调优而是靠持续观测和迭代。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进