RAG 问答 Agent 跑多轮对话:三层分类、三种检索策略的 Key 用 TaoToken
发布时间:2026/9/16 18:07:39 作者:尧图编辑部 阅读量:1,286

1. 组装多轮对话 Agent模型通道先统一再谈策略在把 RAG 问答 Agent 跑多轮对话的能力真正串起来时我发现最花时间的不是 Layer 1 分类阈值也不是多路检索的去重而是散落在 llm_factory、HYDE、Multi-Query 节点里的模型 Key。每一轮对话可能触发三到五次 LLM 调用每处指向的厂商通道还不一样。TaoToken 帮我解决的是接入层统一的问题打开 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 创建一把 Key把 llm_factory 的 base_url 指向 https://taotoken.net/api三层分类中的大模型策略层、HYDE 假设文档生成、Multi-Query 改写以及两路生成全部复用同一套认证。原文的架构本身不复杂Layer 0 规则层做关键词秒判Layer 1 用 MiniLM 做二分类Layer 2 才把拿不准的问题交给大模型。问题在于Layer 2 的每一次判断都要调模型而 vague 类型要额外生成一段假设文档broad 类型要额外拆成 3 到 5 个子问题最后生成答案时又要根据检索置信度走高置信或低置信两条路。这些节点散落在 backend/agents/qa/nodes.py、backend/rag/retriever.py 和 backend/core/llm_factory.py 里每一处都可能自己持有 Key 和 Endpoint。多轮对话一旦跑起来这几个节点会在同一轮里被反复触发密钥管理就成了比检索逻辑更先爆发的瓶颈。所以这次改造不是要动分类规则也不是要重写检索逻辑而是把 llm_factory 变成唯一持有 Key 的地方。所有模型调用都从 llm_factory.get_lm(qa, ...) 拿实例而 llm_factory 内部统一使用 TaoToken 的兼容通道。这样 RAG 链路不需要为 HYDE、Multi-Query、高置信度生成分别准备多套 Key也不用担心某个节点漏配导致请求打到旧地址。1.1 为什么不是每个节点单独配 Key如果只在 classify_query_node 里配了 Key而 hyde_node 忘了配vague 类型的问题就会在检索前悄悄失败。多轮对话还有一个更隐蔽的坑Multi-Query 会并发发起多个检索子任务生成子问题本身也需要一次模型调用。各节点如果各自带 Key限流、超时、认证报错会混在一起日志看起来像随机故障。统一接入之后llm_factory 是全局单例Key 和 Base URL 只在初始化时设置一次。后续无论哪一层用 get_lm 拿模型拿到的都是同一个网关。这样日志里能明确区分两类问题一类是模型通道报错例如 401、404、模型 ID 不存在另一类是 Agent 逻辑本身的问题例如 HYDE 生成的假设文档没传给 retriever、Multi-Query 后处理没做导致子问题列表为空。前者可以单独去 TaoToken 控制台对用量后者回到代码里调提示词或状态字段排查路径清楚很多。1.2 本次改造涉及的节点清单按原文的章节顺序需要统一到 TaoToken 的模型调用点包括第 3.4 节 classify_query_nodeLayer 2 大模型策略层负责选择 precise / vague / broad第 4.2 节 hyde_node为 vague 类型问题生成假设文档第 4.3 节 multi_query_node把 broad 类型问题拆成多个子问题第 5.1 节 generate_rag_high_node高置信度时严格基于检索内容生成回答第 5.2 节低置信度回答与联网搜索补充这些模型调用点有的写在 llm_factory 里有的在节点函数里临时获取模型。改造原则是不要把 Key 复制到每个文件只改 llm_factory 和它依赖的环境变量。下面进入具体配置。2. 把 Key 和 Base URL 准备好准备材料阶段除了原来的依赖库还需要在 TaoToken 上创建一个 API Key。你不需要为每个模型厂商单独维护运行时凭证但需要理解 TaoToken 在这里扮演的是统一接入通道而不是替代你的 Agent 逻辑。2.1 在 TaoToken 上创建 API Key打开 TaoToken注册登录后进入控制台的 API Keys 页面。创建一把 Key复制后先放在本地环境变量里不要直接写进 Python 源码避免误提交到 Git。export TAOTOKEN_API_KEYYOUR_API_KEY export TAOTOKEN_BASE_URLhttps://taotoken.net/api注意 Base URL 末尾不要加 /v1。很多 SDK 会在请求路径里自动追加 /v1如果你手动再加请求会变成 https://taotoken.net/api/v1/v1 或者路径拼接错误。TaoToken 的接口地址以 https://taotoken.net/api 为准填环境变量、写 Python 代码、在工具里配置供应商时都保持一致。2.2 llm_factory 的接入配置示例原文没有贴 llm_factory 的具体实现只提出了 get_lm(qa, temperature0.0) 这样的接口。你可以把它理解成一个带模型名注册表的工厂类。改造后的核心逻辑如下# backend/core/llm_factory.py import os from langchain_openai import ChatOpenAI class LLMFactory: def __init__(self): self.base_url os.getenv(TAOTOKEN_BASE_URL, https://taotoken.net/api) self.api_key os.getenv(TAOTOKEN_API_KEY, YOUR_API_KEY) def get_lm(self, role: str qa, **kwargs): model_id os.getenv(QA_MODEL_ID) if not model_id: raise ValueError(QA_MODEL_ID 未配置请从 TaoToken 模型广场复制模型 ID) return ChatOpenAI( modelmodel_id, base_urlself.base_url, api_keyself.api_key, **kwargs, ) llm_factory LLMFactory()这里 QA_MODEL_ID 不要自己猜版本号或日期后缀。打开 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 的模型广场看当时模型列表里有哪些可用 ID复制完整 ID 填到环境变量。同一把 Key 能调多个模型但模型 ID 必须与模型广场上架的完全一致否则会 404。3. classify_query_node三层分类里的大模型策略层三层分类中Layer 0 规则层只做关键词匹配Layer 1 MiniLM 在本地 CPU 跑都不消耗 API。只有 Layer 2 真正调用大模型这是第一个需要确认已接入 TaoToken 的位置。3.1 大模型策略层的调用方式原文 classify_query_node 在规则层和 MiniLM 都没命中时会调用 llm_factory.get_lm(qa, temperature0.0) 让大模型判断检索策略。这段代码的核心逻辑可以保留只需要确认 get_lm 实例来自已经指向 TaoToken 的工厂。# backend/agents/qa/nodes.py async def classify_query_node(state: QAState) - dict: ... strategy_lm llm_factory.get_lm(qa, temperature0.0) prompt RAG_STRATEGY_PROMPT.format(queryclean_query) response await strategy_lm.ainvoke([(human, prompt)]) llm_choice parse_strategy_output(response.content) strategy llm_choice if llm_choice in {precise, vague, broad} else fast_strategy return { original_query: query, clean_query: clean_query, enable_web_search: enable_web, query_type: strategy, session_summary: session_summary, }注意 temperature 保持 0.0。策略选择是稳定的分类任务温度太高会让模型在三种策略之间随机摇摆。之前遇到过 temperature0.7 时同一个问题两次运行分别返回 vague 和 precise导致检索路径不稳定。走 TaoToken 通道时temperature 参数同样透传给模型不会因为换了接入层就失效。3.2 结构化输出的容错parse_strategy_output 是拿到大模型文本之后的第一步。因为模型输出的是自然语言可能出现空格、换行、引号甚至整句话。这里建议对输出做一次白名单校验def parse_strategy_output(content: str) - str: text content.strip().lower() for item in (precise, vague, broad): if item in text: return item return 白名单校验看起来简单但能避免 LLM 偶尔输出 “ 我认为应该是broad策略” 时解析失败。失效时回退到 fast_strategy这是原文设计里的合理兜底。TaoToken 统一通道不会改变输出格式问题所以解析逻辑要保留。4. HYDE 与 Multi-Query两个检索改写节点的通道配置三种检索策略里precise 最直接不需要额外模型调用。vague 和 broad 在检索前各多了一次 LLM 调用这两次调用都走 llm_factory因此只需要在工厂层面接好 TaoToken节点代码几乎不用动。4.1 vague 类型HYDE 假设文档生成原文 hyde_node 调用 llm_factory.get_lm(qa, temperature0.7)生成 200 到 300 字的假设文档。温度调高是为了让文本有变化避免生成过于模板化的内容影响向量召回。这个温度值应该作为参数传进来而不是写死在工厂里。# backend/agents/qa/nodes.py async def hyde_node(state: QAState) - dict: history_text format_history_for_prompt(state[messages][-6:]) hyde_lm llm_factory.get_lm(qa, temperature0.7) prompt HYDE_PROMPT.format(querystate[clean_query], historyhistory_text) resp await hyde_lm.ainvoke([(human, prompt)]) hyde_doc resp.content.strip() return {hyde_document: hyde_doc}这里最需要注意的是生成的 hyde_document 只用来做检索不能直接当作最终答案返回。检索端取 query 时要优先读状态里的 hyde_document如果这一行漏掉vague 类型会退回用原始模糊 query 检索效果等同于没做 HYDE。排查时如果发现假设文档生成了但检索结果仍很差先看 retrieve_node 里是否真的把 query_text 替换成了 hyde_document而不是去怀疑模型通道。4.2 broad 类型Multi-Query 改写与后处理multi_query_node 的调用点同样是 llm_factory.get_lm(qa, temperature0.5)提示词要求模型输出若干行子问题每行一个。由于模型对“一行一个”的遵守程度不稳定原文已经做了后处理去序号、过滤空行、最多保留 3 个、失败回退原始 query。这套逻辑不能省。# backend/agents/qa/nodes.py async def multi_query_node(state: QAState) - dict: last_answer extract_last_answer(state[messages]) multi_lm llm_factory.get_lm(qa, temperature0.5) prompt MULTI_QUERY_PROMPT.format( querystate[clean_query], last_answerlast_answer or 无上下文, ) resp await multi_lm.ainvoke([(human, prompt)]) lines resp.content.strip().splitlines() queries [] for line in lines: cleaned line.strip().lstrip(0123456789.-、 ) if len(cleaned) 3: queries.append(cleaned) queries (queries or [state[clean_query]])[:3] return {multi_queries: queries}多轮对话中last_answer 来自 state[messages][-2] 这条 AIMessage。如果消息列表里存在 LangChain 新版 content 列表格式直接用 str() 转换会得到 dict 结构文本所以要用 get_message_content 提取。这与模型通道无关但在调试 Multi-Query 输出时很容易误判成模型问题。4.3 多路并发检索与共享 Key原文 retrieve_broad 用 asyncio.gather 并发调检索目的是加速多子问题召回。这个阶段本身不直接调用生成模型但如果你的 Embedding 服务也需要 API Key记得把 Embedding 的 base_url 一并指到 TaoToken 控制台里对应的模型而不是只改 Chat 模型的地址。实际交付时常见情况是Chat 通道换到了 TaoTokenEmbedding 还指向旧配置结果 broad 类型检索结果突然变差。这不是多路并发的问题而是 Embedding 的 Endpoint 漏改了。5. 答案生成高置信度和低置信度两路生成答案生成是多轮对话里最后一次大模型调用。原文把生成路径分为高置信度 RAG 回答、低置信度回答加分 general 直接回答三者都从 llm_factory 取模型。通道统一后这里不需要写第二个 Key。5.1 高置信度回答严格基于检索内容generate_rag_high_node 会把检索到的文档拼成 context再把当前用户问题和 context 一起交给模型。这一步的提示词和组装逻辑保持不变只确认 llm 实例来自已经指向 TaoToken 的 llm_factory。# backend/agents/qa/nodes.py async def generate_rag_high_node(state: QAState) - dict: context_parts [ f参考{i}:\n{doc[content]} for i, doc in enumerate(state[retrieved_docs], 1) ] context \n\n.join(context_parts) sources_list \n.join(f- {s} for s in state.get(retrieved_sources, [])) gen_lm llm_factory.get_lm(qa, temperature0.3, streamingTrue) messages build_rag_messages( system_promptbuild_system_prompt(summarystate.get(session_summary)), historystate[messages][:-1], rag_promptRAG_HIGH_CONF_PROMPT.format( contextcontext, querystate[clean_query], sources_listsources_list, ), ) resp await gen_lm.ainvoke(messages) return { answer: resp.content.strip(), answer_mode: rag_high, need_new_summary: True, }注意 messages[:-1] 这个切片必须保留。最后一条消息是当前用户问题需要单独放进 RAG 专用提示词里而不是作为普通历史消息传给模型。如果漏掉这个切片模型会在历史里看到一次当前问题又在提示词里看到一次回答时可能重复或混淆。5.2 低置信度回答与联网补充当 retrieve_confidence 小于 0.75 时流程走低置信度路径。如果 enable_web_search 为真则把联网搜索结果拼进上下文否则直接让模型结合自身知识回答并说明内容不来自课程资料。这块的模型调用同样来自 llm_factory不需要为“低置信度”单独配一个模型。唯一建议是根据模型上下文长度控制拼接内容量避免检索结果太多时把系统提示词和历史消息挤出窗口。5.3 历史、时间与摘要工具函数build_system_prompt、get_current_time_str、sliding_window_prune 这些函数和模型通道无关。它们影响的是模型看到的上下文质量不是请求能否成功。多轮对话中时间注入和会话摘要能减少模型误解历史提问但如果你发现某轮回答与摘要矛盾先检查 load_session_summary 是否真的返回了摘要而不是去改 TaoToken 的模型 ID。6. 跑一轮多轮对话验证所有 LLM 调用都从 TaoToken 返回配置完成后需要实际跑一轮多轮对话验证不能只发一个问题就结束。分类、检索改写、生成在不同轮次里触发的概率不一样只有把 general、precise、vague、broad 四种类型都覆盖到才能确认全链路已经打通。6.1 给 llm_factory 加一段调用日志在 llm_factory.get_lm 返回的模型对象外做一个简单包装每次模型调用时打印 role、model_id、base_url 和耗时。或者更粗粒度地在几个关键 await 前后加 logger.info。import time async def log_model_call(lm, messages): start time.time() logger.info(model_call_start, modellm.model_name, base_urllm.base_url) resp await lm.ainvoke(messages) logger.info(model_call_done, latencyround(time.time() - start, 2)) return resp跑一轮对话时观察日志里每次 model_call 的 base_url 是否都等于 https://taotoken.net/api。如果某一跳还是旧厂商地址说明那处代码没有走全局 llm_factory而是局部 new 了一个客户端。这种漏网之鱼在代码评审阶段很难发现日志验证最直接。6.2 最少覆盖四种类型的问题按原文的三层分类建议按以下顺序发四个问题通用问题“你好你叫什么名字” 预期走 general直接 LLM不检索precise 问题“LSTM 的输入维度是多少” 预期直接检索vague 问题“那个东西再讲一下我没懂” 预期走 HYDEbroad 问题“给我讲讲微服务整体架构” 预期走 Multi-Query 并多路检索最后一个问题最好放在第二轮再发用来触发多轮历史消息的拼接。发完之后看日志general 和 precise 通常只有 1 到 2 次 LLM 调用vague 会比 precise 多一次 HYDE 生成broad 会多一次 Multi-Query 改写随后还有一次高置信或低置信生成所有这些调用都应从 llm_factory 的 base_url 进出。确认日志里的 model 字段与模型广场 ID 完全一致。6.3 在控制台核对本轮调用记录跑完这一轮后回到 TaoToken 官网 的控制台看用量列表。此时应该能看到刚才多轮对话消耗的所有请求时间点和本地日志里的耗时能对上。这一步用来验证统一通道是否真的统一控制台里应当是连续的一串请求而不是像以前那样分散在多个平台。如果用量列表里只有部分记录说明有节点请求走的不是 T 家后端需要按日志逐一排查。7. 易错点多轮对话里 Key 容易漏配的位置原文第 7 节整理了一大张易错点速查表。结合本次改造真正与模型通道有关的错误集中在三个地方另外两个逻辑坑和通道问题经常同时出现。7.1 llm_factory 缓存了旧实例如果 llm_factory 在模块加载时创建单例改完环境变量之后不重启进程旧实例会一直持有原来的 base_url 和 Key。热重载在这种情况下不可靠因为模块级单例的初始化只发生一次。改完配置后重启服务再跑验证轮次。7.2 Base URL 多了 /v1TaoToken 的接口统一使用 https://taotoken.net/api 末尾不要加 /v1。SDK 通常会在请求路径里自动拼接 /v1。手动加上的话请求会打到不存在的路径返回 404 或者路由错误。这个问题在.env 文件里尤其常见因为很多开源项目模板自带.../v1后缀。7.3 模型 ID 不照模型广场填同一把 Key 可以调多个模型但模型 ID 必须与 TaoToken 模型广场上架的 ID 完全一致。不要在代码里自己推导版本名、随便加日期后缀或者使用某个模型厂商官网的内部命名。正确做法是打开 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 查看模型广场列表把完整 ID 写入 QA_MODEL_ID 环境变量。如果模型广场的列表会更新部署脚本里最好读配置中心而不是硬编码。另外两个与通道无关、但经常被误判成通道问题的坑HYDE 节点必须把生成的 doc 传给 retriever.retrieve 的 query_text而不是用 clean_query。否则 vague 类型问题等于白做 HYDE检索结果和直接检索原始 query 一样差。Multi-Query 的 post_process 需要去序号、过滤空行、失败回退。TaoToken 通道返回的也是模型自由文本不是结构化 JSON不做后处理会拿到空列表后续 asyncio.gather 只能等一个空任务。8. 跑通之后去 TaoToken 控制台对一下这次的调用记录到这里多轮 RAG 问答 Agent 的模型通道已经统一走 TaoToken三层分类中的大模型策略层、HYDE、Multi-Query、高置信度生成、低置信度生成全部都在 llm_factory 的同一套 base_url 上。建议再多跑几轮不同真实问题重点观察 vague 和 broad 类型确认额外触发的模型调用都计入了 TaoToken 用量。配置保存后先用同一把 Key 在 TaoToken 模型对话 里发一条测试消息确认模型 ID 和 Base URL 没填错。若要长期跑 RAG Agent可以打开 Coding Plan 看套餐是否够用新的 Key 在 控制台 API Keys 创建。如果之后想把同一把 Key 配到 Claude Code 环境里可以直接参考 Claude Code 接入文档 里的环境变量对照。