RAG Agent 调用 LLM 报 401?TaoToken 这样设置 Base URL RAGAgent 在_generate_answer里用 openai 库调用 LLM突然抛 401这种问题最容易误判成向量库或知识库权限。其实多数时候检索已经返回了文档prompt 也拼好了卡住的是对外那一次chat.completions请求。把api_key换成从 TaoToken 官网 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentrag_agent_401_lead 创建的 Key再把 OpenAI 客户端的base_url设为https://taotoken.net/api注意末尾不带/v1RAG 问答的 LLM 调用通常就能通401 也会消失。下面按排障顺序走一遍先定位 401 出现在哪一层再改 RAGAgent最后用本地脚本和控制台把结果对清楚。1. RAGAgent._generate_answer 报 401先分清检索层和 LLM 出口1.1 401 出现的位置openai 客户端初始化而不是向量库RAG 流程一般拆成三段retriever.search(question)从知识库召回片段_build_prompt()把片段拼成上下文_generate_answer()调 LLM 生成答案。401 属于 HTTP 认证失败它不会从向量库抛出来通常从openai库的AuthenticationError抛出来。也就是说知识库可能已经命中了文档Agent 也已经拿到上下文只是最后一步出示的 Key 或请求地址不对。排查时先看 traceback 的最后一层。如果栈里出现client.chat.completions.create、httpx、Authorization就不要再去调 chunk size 或 embedding 模型。此时最值得打印的是客户端实际使用的base_url而不是 Key 本身。Key 一旦进了日志后面还要轮换反而更麻烦。可以临时加一行import os from openai import OpenAI client OpenAI( api_keyos.environ.get(TAOTOKEN_API_KEY, YOUR_API_KEY), base_urlhttps://taotoken.net/api, ) print(client.base_url)如果打印结果是https://taotoken.net/api/v1或者把官网落地页当成了接口地址或者环境变量没加载导致 Key 为空401 就会出现。base_url填https://taotoken.net/api就行末尾不要带/v1官网落地页只用于注册、创建 Key、看模型广场和用量不要填进OpenAI()。1.2 用最小请求确认 base_url 和 Key 是否配对不要一上来就改 RAGAgent 的整条链路。先写一个最小请求只保留 Key、base_url、模型 ID 和一条用户消息。这个脚本通了再回到_generate_answer里查环境变量和初始化顺序。最小请求可以这样写from openai import OpenAI client OpenAI( api_keyYOUR_API_KEY, base_urlhttps://taotoken.net/api, ) resp client.chat.completions.create( modelYOUR_MODEL_ID, messages[{role: user, content: 只回复pong}], ) print(resp.choices[0].message.content)这里的YOUR_API_KEY要从 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentrag_agent_401_minimal 创建YOUR_MODEL_ID以模型广场当时列表为准不要凭记忆写一个带日期后缀的名字。最小请求返回 401说明 Key 或base_url至少有一个没对上返回 200说明 RAGAgent 里还有另一套旧配置比如某个子模块自己 new 了一个OpenAI()或者.env读取晚于客户端初始化。2. 企业知识大脑里的 Harness 层为什么要把 LLM 出口统一到 TaoToken2.1 知识库、Agent Harness 和 LLM 通道的边界企业知识大脑通常由两层组成知识库负责“记住什么”AI Agent Harness 负责“怎么用”。知识库做切分、索引、召回、重排Harness 做工具编排、会话记忆、失败重试、模型路由、结果评测。LLM 通道是 Harness 向外拨出的那一通电话401 就是这通电话没被接起来。把知识库和 LLM 通道混在一起排查会在向量维度、相似度阈值上绕很久最后还是回到 Key 和base_url。更稳的做法是把 LLM 出口收口到 Harness 层的一个LLMGatewayRAGAgent 只依赖这个网关不直接到处创建OpenAI()。这样某个 Agent 出现 401只需要检查网关的 Key、base_url和模型 ID而不是翻遍每个工具类。知识库仍然负责召回Harness 仍然负责编排TaoToken 只承担统一 API 兼容通道这一层边界清楚排障也快。2.2 TaoToken 在这个架构里承担什么统一 API 兼容通道TaoToken 的定位是统一 API、兼容通道、一站接入。对 RAGAgent 来说改动只有两处api_key换成从官网创建的YOUR_API_KEYbase_url填https://taotoken.net/api。模型 ID 去模型广场看当时列表不同模型切换时通常只需要改环境变量。落地页用于注册、创建 Key、看模型广场和看用量地址是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentrag_agent_401_arch 不要把落地页和接口地址混用。这种收口对 Harness Engineering 也有好处。比如白天用便宜模型跑召回总结晚上用更强模型跑复杂问答路由规则写在网关里RAGAgent 不感知。再比如某个模型临时报错网关可以按策略重试或切到备用模型业务代码仍然只调_generate_answer。401 这类认证问题也会集中在网关一层暴露不会散落到每个知识库工具里。3. 准备 Key 与模型 ID在 TaoToken 官网完成原文里的注册和控制台动作3.1 打开 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 创建 API Key原文里需要打开官网、注册登录、进控制台、申请或复制 API Key 的步骤在这里统一改成打开 TaoToken 完成。注册登录后进入控制台创建一个 API Key复制出来先放在本地环境变量里代码里只写占位符YOUR_API_KEY。不要把 Key 提交到 Git也不要把 Key 写进前端页面或公开的 notebook。建议单独建一个.env文件只在本机使用并加入.gitignore。如果团队共用一套 RAG 服务Key 应该放在服务端环境变量或密钥管理里由后端读取。前端只调用你们自己的业务接口不直接拿这个 Key 去请求模型。401 排障时确认 Key 没有多余空格没有换行没有把 Key ID 当成 Secret 复制。3.2 模型广场选模型以当时列表为准别硬编码记忆里的名字同一个落地页里可以进入模型广场查看当前可用模型和对应 ID。写代码时不要凭记忆写一个gpt-5或随手加日期后缀的名字模型 ID 以 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentrag_agent_401_models 模型广场当时列表为准。可以先在模型对话里发一条测试消息确认这个模型 ID 和 Key 能配合使用再写进 RAGAgent。配置可以先用环境变量TAOTOKEN_API_KEYYOUR_API_KEY TAOTOKEN_BASE_URLhttps://taotoken.net/api TAOTOKEN_MODELYOUR_MODEL_ID注意TAOTOKEN_BASE_URL只写https://taotoken.net/api不要加/v1也不要加任何 UTM 参数。UTM 只用于官网落地页的访问归因不能混进接口地址。RAGAgent 读取这三个变量即可模型切换时只改TAOTOKEN_MODEL。4. 改写 RAGAgent把 base_url 设为 https://taotoken.net/api4.1 环境变量与 .env 的推荐写法如果项目用python-dotenv可以在入口最早处加载.envfrom dotenv import load_dotenv load_dotenv()然后在 RAGAgent 初始化时读取import os api_key os.environ[TAOTOKEN_API_KEY] base_url os.environ.get(TAOTOKEN_BASE_URL, https://taotoken.net/api) model os.environ[TAOTOKEN_MODEL]这里最容易出的问题是加载顺序。比如某个模块在load_dotenv()之前就创建了OpenAI()它读到的TAOTOKEN_API_KEY是空字符串401 就会出现。还有一种情况是旧代码里写死了base_urlhttps://api.openai.com/v1即使环境变量已经改对客户端仍然往旧地址发请求。排障时全局搜OpenAI(、base_url、OPENAI_API_KEY把旧入口一个个收掉。4.2 _generate_answer 的可复制实现下面这段代码把原来的_generate_answer改成从环境变量读 Key、base_url和模型 IDbase_url明确写https://taotoken.net/api末尾不带/v1import os from openai import OpenAI class RAGAgent: def __init__(self, retriever, api_keyNone, base_urlNone, modelNone): self.retriever retriever self.client OpenAI( api_keyapi_key or os.environ[TAOTOKEN_API_KEY], base_urlbase_url or os.environ.get( TAOTOKEN_BASE_URL, https://taotoken.net/api ), ) self.model model or os.environ[TAOTOKEN_MODEL] def _build_prompt(self, question, contexts): context_text \n\n.join( f[{i 1}] {ctx} for i, ctx in enumerate(contexts) ) return f你是一个企业知识库问答助手。请只根据下面资料回答并在句末标注来源编号。 若资料不足请说“资料中没有找到明确答案”。 资料 {context_text} 问题{question} def _generate_answer(self, question: str) - str: contexts self.retriever.search(question, top_k5) prompt self._build_prompt(question, contexts) response self.client.chat.completions.create( modelself.model, messages[ {role: system, content: 你负责基于企业知识库资料回答不要编造。}, {role: user, content: prompt}, ], temperature0.2, ) return response.choices[0].message.content这段代码里没有出现任何具体模型名YOUR_MODEL_ID只存在于环境变量。Key 从官网创建base_url只填https://taotoken.net/api。如果你们的 Harness 层已经有重试和日志可以把chat.completions.create再包一层但不要改变这两个核心参数。4.3 用 LLMGateway 包一层方便以后切模型如果 RAGAgent 只是众多 Agent 之一建议加一个LLMGateway把 LLM 调用收口class LLMGateway: def __init__(self, api_key, base_url, model): self.client OpenAI(api_keyapi_key, base_urlbase_url) self.model model def chat(self, messages, **kwargs): return self.client.chat.completions.create( modelself.model, messagesmessages, **kwargs, )RAGAgent 改成依赖注入class RAGAgent: def __init__(self, retriever, gateway): self.retriever retriever self.gateway gateway def _generate_answer(self, question: str) - str: contexts self.retriever.search(question, top_k5) prompt self._build_prompt(question, contexts) response self.gateway.chat( messages[ {role: system, content: 你负责基于企业知识库资料回答不要编造。}, {role: user, content: prompt}, ], temperature0.2, ) return response.choices[0].message.content这样 401 只会在网关初始化时出现一次不会每个 Agent 各报一遍。模型路由、重试、降级也都在网关里做知识库和业务代码保持干净。5. 本地验证跑一轮问答看 401 是否消失5.1 用 pytest 或脚本跑通最小问答先用一个假的 retriever 验证 LLM 出口不要连生产知识库。这样能把 401 和检索问题彻底分开class FakeRetriever: def search(self, question, top_k5): return [TaoToken 的 API Base URL 是 https://taotoken.net/api。] if __name__ __main__: agent RAGAgent(FakeRetriever()) print(agent._generate_answer(TaoToken 的 Base URL 应该填什么))如果这段脚本返回了正常文本说明 Key、base_url、模型 ID 三件事已经对齐。如果仍然 401先检查.env是否被当前 shell 读到再检查有没有旧进程缓存了空环境变量。可以在脚本开头打印bool(os.environ.get(TAOTOKEN_API_KEY))只打印布尔值不打印 Key 内容。确认 Key 存在后再看base_url是否真的是https://taotoken.net/api。5.2 用 curl 看状态码和返回体想更直接地看 HTTP 状态可以用 curl 发一条最小请求curl -i https://taotoken.net/api/chat/completions \ -H Authorization: Bearer YOUR_API_KEY \ -H Content-Type: application/json \ -d { model: YOUR_MODEL_ID, messages: [{role: user, content: ping}] }这里接口地址没有 UTM 参数Key 也用占位符。返回 401 时看响应体里的错误信息通常能区分 Key 无效、缺少认证头、地址不对。返回 404 时不要当成 401 处理404 更可能是路径写错比如多了一层/v1或少了一段/chat/completions。返回 200 后再把同样的参数搬回 Python 代码。5.3 回控制台对一下用量本地脚本跑通后打开 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentrag_agent_401_usage 看这次调用有没有记上。控制台里能看到请求记录和用量确认 RAGAgent 的调用确实走到了同一个 Key 上。如果脚本成功但控制台没有记录检查是不是还有其他旧客户端在用另一个地址。企业知识大脑一旦上线建议给不同环境用不同 Key排障时更容易区分测试流量和线上流量。6. 401 排障对照表Key、/v1、模型 ID、环境变量6.1 仍然 401 的四种高概率原因现象可能原因处理方式AuthenticationError401Key 为空、复制错、已失效去 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 重新创建YOUR_API_KEY401base_url指向旧地址或官方地址改成https://taotoken.net/api末尾不要/v1401把官网落地页填进OpenAI()落地页只用于注册、创建 Key、看模型广场接口地址只用https://taotoken.net/api401环境变量没加载代码读到空字符串在入口最早处load_dotenv()打印布尔值确认存在还有一种不常见但很容易漏的情况多个子模块各自创建OpenAI()你只改了 RAGAgent 这一处另一个工具类仍然用旧 Key。全局搜索OpenAI(把初始化收口到LLMGateway或工厂函数。401 不是“模型不回答”而是“请求没通过认证”所以重点永远在 Key、认证头、base_url三件事上。6.2 404 与 401 的区别别把路径错误当认证错误401 是认证失败404 是路径不存在。把base_url写成https://taotoken.net/api/v1后有的客户端会把路径拼成/api/v1/chat/completions可能报 404把官网落地页当base_url可能报 401 或 404取决于服务端怎么处理。排障时先确认client.base_url的字符串再确认请求路径。base_url是https://taotoken.net/api客户端会自动拼上/chat/completions不要自己再补/v1。如果错误信息里出现model not found那通常是模型 ID 不对不是 401。模型 ID 以模型广场当时列表为准复制完整 ID不要加空格。如果错误信息里出现invalid api key才回到 Key 和认证头检查。把不同错误分开处理排障速度会快很多。6.3 安全边界Key 不进仓库不贴日志Key 只放在服务端环境变量或密钥管理里.env加入.gitignore。日志里不要打印完整 Key最多打印前几位和后几位做标识。RAGAgent 的异常日志可以打印base_url和模型 ID但不要把Authorization头原样输出。团队协作时测试环境和生产环境用不同 Key控制台里更容易对账。7. 把知识库和 AI Agent Harness 接到同一条通道上7.1 多模型路由与降级在 Harness 层做不在业务代码里散落企业知识大脑上线后RAGAgent 只是其中一个消费者。可能还有摘要 Agent、工单分类 Agent、SQL 解释 Agent。每个都直接创建OpenAI()Key 和base_url就会散落各处改一次要全局搜索。把 LLM 出口收到 Harness 的LLMGateway后路由规则可以按任务类型选模型失败重试和降级也在这里做。模型 ID 仍然以模型广场当时列表为准代码里只保留环境变量名。这样做还有一个好处401 只会出现在网关初始化或网关调用处不会在十几个业务类里重复出现。RAGAgent 的_generate_answer只负责拼 prompt 和解析结果知识库只负责召回。知识库、Harness、LLM 通道三层各管各的排障边界清楚后续换模型也不用动业务代码。7.2 文末下一步去模型对话、Coding Plan 和控制台配置保存后先在 TaoToken 模型对话 里用同一把 Key 发一条测试消息确认模型 ID 和 Base URL 没填错。如果 RAG 问答会长期跑量可以打开 Coding Plan 看套餐是否够用Key 仍然在 控制台 API Keys 创建和管理。先把测试消息发出去再回控制台看这次 RAG 问答有没有记上账。