news 2026/10/13 1:44:34

semantic-router Reask 信号深度解析:用语义相似度检测用户重复提问并驱动模型路由升级

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
semantic-router Reask 信号深度解析:用语义相似度检测用户重复提问并驱动模型路由升级
  • 后端
  • API网关
  • 模型推理服务
  • AI Agent

【免费下载链接】semantic-router

An open, programmable decision layer for models and compute.

项目地址:https://gitcode.com/gh_mirrors/sem/semantic-router
点击查看免费下载

本指南系统讲解 semantic-router 的reask信号:它如何在同一会话内比较当前用户轮次与最近的用户轮次,通过语义相似度识别"用户反复重述同一问题"这一隐式不满模式,并产出可配置的likely_dissatisfied、persistently_dissatisfied等信号供路由决策消费。读完本文,你将掌握routing.signals.reasks的完整配置方法、余弦相似度与decision.v1原生判断两种评分模式的取舍,以及 reask 信号在升级路由决策(如切换到更强模型)时的接入方式与限制边界。

概览:什么是 Reask 信号

在同一个多轮会话中,用户常常因为上一次的回答没有解决问题而重述同一个问题。这种重复是"隐式不满"——用户不会直接说 "this is wrong" 或 "this is not helpful",而是换一种说法再问一遍。单轮分类器(只看当前这一条消息)无法捕捉这种模式,因为抱怨是隐含在会话上下文里的。

reask信号正是为解决这个问题而生:它把当前用户轮次与同一会话中最近的用户轮次做语义对比,当最近的连续几轮都保持语义相似时,暴露一个可配置的"不满意/重复提问"信号。在配置中,reask 规则定义在routing.signals.reasks下:

routing: signals: reasks: - name: likely_dissatisfied description: Current user turn closely repeats the immediately previous user turn. threshold: 0.8 lookback_turns: 1 - name: persistently_dissatisfied description: Current user turn repeats the last two user turns in a row. threshold: 0.8 lookback_turns: 2

这个信号家族是learned(学习型)的:它复用路由器共享的语义 embedding 路径(shared semantic embedding path),把当前用户轮次与历史用户轮次投影到同一语义空间后再比较,而不需要单独训练或部署第二个模型面。

它解决什么问题:单轮分类器的盲区

考虑下面的会话:

  1. 用户:请解释一下 TCP 三次握手。
  2. 助手:给出一段不完整的解释。
  3. 用户:我不太明白,TCP 握手到底是什么流程?

第三轮消息单独看是一条普通的求知提问,没有任何显式的负面情绪词。但如果把它和第一轮对比,语义上几乎是同一件事。一个只看单条消息的分类器会给出"这是一条正常的求知请求",从而把请求路由到轻量模型——这恰恰是用户最不满意的结果。

reask的做法是:把最新用户轮次与最近几个用户轮次逐一对比,当这个"滑动窗口"内的每一轮都超过相似度阈值时,就判定为重复提问并给出可配置的 dissatisfaction 信号。这解决了单轮分类器"只见树木不见森林"的结构性盲区。

核心优势

从设计意图上看,reask 信号有四个关键优势:

  • 捕捉隐式不满:不需要用户说出 "this is wrong" 之类的显式短语,仅凭语义重复即可感知不满意;
  • 区分"一次重复"与"持续不满":lookback_turns: 1只要求上一轮相似,lookback_turns: 2则要求连续两轮都相似,可以把"偶然再问一次"与"反复追问、强烈不满"区分开,做差异化处理;
  • 决策基于会话历史而非单条消息:路由升级可以建立在"最近几轮"之上,而不是孤立的当前消息;
  • 复用现有语义相似度栈:直接使用路由器已有的 embedding 基础设施,不引入第二个模型面,部署与运维成本更低。

何时使用 Reask

适合使用 reask 信号的场景包括:

  • 重复问题需要升级到更强模型:当用户在同一会话里第二次、第三次提出同一问题时,把它路由给能力更强的模型或更长的思考模式;
  • 单次重复与多次重复需要不同处理:一次重复可能只是没看清楚,连续重复则说明前几次回答都失败了,两种情况的应对策略应当不同;
  • 显式反馈稀少,但重复轮次仍然重要:用户很少直接说"你答错了",但反复追问本身就是一个高价值信号;
  • 路由决策需要依赖同会话用户历史:而不是只依赖当前单条消息。

配置详解

配置位置与字段

reask 规则位于routing.signals.reasks下,是一个规则列表。每条规则支持四个字段,对应的配置结构定义在 signal_config.go:

字段类型必填默认值说明
namestring是无规则名称,供路由决策(如 DSL 中的reask("likely_dissatisfied"))引用
descriptionstring否无规则语义说明,便于维护与可观测
thresholdfloat否0.8相似度阈值,取值范围 0~1
lookback_turnsint否1需要往前回溯并逐一比较的最近用户轮次数

需要特别注意的是默认值的处理:在源码中,threshold == 0时会被替换为0.8,lookback_turns == 0时会被替换为1(见 signal_config.go 的WithDefaults()方法)。这意味着如果你显式把threshold设为0,它不会保持为 0,而是被当作"未设置"回退到0.8。

规则匹配语义

每条规则把当前用户轮次与"最近的lookback_turns个用户轮次"逐一比较。只有当该连续段中的每一轮都超过配置的相似度阈值时,规则才算匹配。以默认配置为例:

  • likely_dissatisfied(lookback_turns: 1):当前轮次与紧邻的上一轮用户轮次相似度 ≥ 0.8 即命中;
  • persistently_dissatisfied(lookback_turns: 2):当前轮次与最近两轮用户轮次都相似度 ≥ 0.8 才命中。

因此persistently_dissatisfied命中的条件比likely_dissatisfied更严苛,可以视为更高等级的 dissatisfaction 信号。

配置校验规则

配置加载时会执行严格的校验(见 validator_reask.go):

  • name不能为空;
  • threshold必须在 0 到 1 之间;
  • lookback_turns必须 ≥ 1。

如果违反上述任何一条,路由器会直接拒绝该配置并返回对应的错误信息(如routing.signals.reasks[%q]: threshold must be between 0 and 1)。

仓库中的真实配置示例

仓库根目录的 config.yaml 就内置了完整的 reask 配置段(两条规则likely_dissatisfied与persistently_dissatisfied),与本文开头的示例完全一致,可作为开箱即用的参考。此外,官方维护的片段文件 dissatisfaction.yaml 也提供了同样的配置;内置食谱 mom-v1/config.yaml 中则以lookback_turns: 1、threshold: 0.8定义了一条名为repeat的 reask 规则。

工作原理:源码级剖析

reask 的核心实现位于 reask_classifier.go,入口是ReaskClassifier.ClassifyContext(ctx, currentUserTurn, priorUserTurns)。整体流程分三步:

第一步:预处理与短路判断

当前用户轮次会被TrimSpace处理;若当前轮次为空、没有配置规则、或没有任何历史用户轮次,直接返回空匹配。接下来,完全相同的非空完整轮次会被短路处理——如果某条历史轮次与当前轮次字符串完全相等,直接记相似度1,完全不需要调用 embedding 模型。这是源码中两种评分模式共有的优化路径。

第二步:按评分模式计算相似度

reask 支持两种评分模式,通过配置选择:

模式 A:余弦相似度(默认)

在computeSimilarities中(reask_classifier.go),实现细节如下:

  • 从最近的用户轮次由近到远倒序遍历历史轮次;
  • 对每个非空历史轮次,先查字符串级缓存(map[string][]float32),命中则复用,未命中才调用embedFullText计算 embedding;
  • 当前轮次的 embedding 只计算一次,之后复用;
  • 相似度通过cosineSimilarity计算;
  • 早停优化:一旦某个历史轮次的相似度低于所有规则中的最低阈值(minimumReaskThreshold),立即停止继续回溯——因为更早的轮次不可能再组成"连续段"。

模式 B:decision.v1原生重复意图判断

默认评分始终是余弦相似度,即使配置了默认决策模型也不会改变。只有当配置中显式存在routing.model_bindings.reask且其contract: decision.v1时,才切换到原生判断模式。这个判定逻辑在 reask_execution.go 的ReaskUsesDecisionTask()中:只有EffectiveModelBindings(...)["reask"].Contract == DecisionTaskContract才返回 true。

在判断模式下,semanticSimilarities(reask_classifier.go)会:

  • 对去重后的每条历史轮次,向决策模型提问"当前请求与历史请求是否寻求相同的信息或动作"(request parts 为{"current": ..., "prior": ...});
  • 通过modelservice.Fan并发发起所有判断请求;
  • 完全相同的轮次直接记1,不调用模型;
  • 任何一个判断请求失败都会让整个 reask 评估返回 unknown 错误(reask pair %d is unknown),而不是给出负匹配。

该模式产出的分数是"重复意图的概率",与余弦相似度不在同一刻度上,因此必须针对所选模型单独校准概率阈值——把余弦模式下的0.8直接搬过来是没有意义的。

第三步:连续段评估与结果归并

无论哪种评分模式,得到相似度数组后,都由evaluateReaskStreak(reask_classifier.go)按规则逐一评估:

  • 从最近轮次开始,只要相似度 ≥ 阈值就累计streak,遇到低于阈值的立即中断;
  • 只有当streak >= lookback_turns时规则才匹配;
  • 匹配结果的MinSimilarity记录的是该连续段内的最小相似度(即最保守的那个值);
  • 最后retainMaxLookbackReaskMatches(reask_classifier.go)会只保留 lookback 最大的一组匹配——当likely_dissatisfied与persistently_dissatisfied同时命中时,只输出更严重的后者,避免信号冗余。

信号结果如何被记录

匹配结果由 classifier_signal_rule_evaluators.go 中的evaluateReaskSignalContext写入信号结果:

  • SignalConfidences["reask:<rule_name>"]记录该规则的最小相似度(置信度);
  • SignalValues["reask:<rule_name>"]记录命中的连续轮次数(MatchedTurns);
  • 同时记录执行耗时(ExecutionTimeMs)、信号匹配与提取的遥测指标;
  • 若评估出错(例如输入超限导致 unknown),会按reask_evaluation_failed错误码记录规则级错误。

另外需要注意:applicableReaskRuleNames(classifier_signal_rule_evaluators.go)会先统计非空历史用户轮次数,只有当历史轮次数 ≥ 规则的lookback_turns时才真正调用分类器执行该规则——会话历史不足时规则自动跳过,不会产生误报。

把 Reask 信号接入路由决策

reask 信号本身不直接改变路由,它的价值在于被决策引擎(decision engine)消费,触发模型升级等动作。

决策规则中的引用

决策引擎支持以reask:<rule_name>作为条件类型。在 DSL 食谱中可以看到真实用法,例如 recipe.dsl 中的WHEN ... (user_feedback("wrong_answer") OR keyword("correction_feedback_markers") OR reask("likely_dissatisfied")) ...——reask 信号与显式反馈、纠正标记一样,都是"回答失败"判定的输入;而 recipe.dsl 则在NOT (... reask("likely_dissatisfied") ...)中把 reask 作为否定条件,用于排除疑似不满的请求。内置食谱 mom-v1/recipe.dsl 同样定义了SIGNAL reask repeat并把它与user_feedback("wrong_answer")一起加权合成回答满意度投影。

决策引擎的测试验证

engine_reask_test.go 用两个测试精确验证了"按信号升级"的语义:

  • TestDecisionEngine_EvaluateDecisionsWithReaskLikelyEscalation:reask("likely_dissatisfied")命中时,repeat_question_escalation决策(Priority 10)被选中;
  • TestDecisionEngine_EvaluateDecisionsWithReaskPersistentEscalation:reask("likely_dissatisfied")命中不会触发只要求persistently_dissatisfied的决策,而reask("persistently_dissatisfied")命中才会——这证明了两种级别的 dissatisfaction 在路由上是可区分的。

也就是说,你可以设计两条决策:一条在"单轮重复"时升级到次强模型,另一条在"连续两轮重复"时升级到最强模型(或触发人工介入),这正是 reask 信号的核心使用模式。

DSL 方式配置 Reask

除了 YAML,semantic-router 的 DSL 也支持声明 reask 信号。compiler_signals.go 的compileReaskSignal会把SIGNAL reask <name>声明(含description、threshold、lookback_turns字段)编译为config.ReaskRule并追加到routing.signals.reasks。因此你可以用 DSL 以声明式方式表达相同的配置,并在WHEN子句中直接引用。

依赖与限制

对共享 embedding 路径的依赖

reask 复用路由器的共享语义 embedding 路径。从 embedding_needs.go 可以看出,只有当存在 reask 规则且没有切换到decision.v1判断模式时(len(cfg.ReaskRules) > 0 && !cfg.ReaskUsesDecisionTask()),配置解析才会把主 embedding 模型标记为必需。因此:

  • 余弦模式下,配置了远程 embedding provider 时,recent user turns 会被发送到远程服务计算向量;
  • 切换到decision.v1判断模式后,则不再需要 embedding 模型,改由决策模型承担判断任务。

完整输入覆盖(complete input coverage)要求

非完全相同的轮次要求完整输入覆盖,这是 reask 最容易踩坑的限制:

  • 如果 embedding 模型是截断型的(超长输入被静默截断),或端点无法建立其输入覆盖范围,那么 reask 对超长轮次的评估会返回unknown 信号,而不是一个"负匹配"——路由器宁可不知道,也不给出可能错误的低相似度结论;
  • OpenAI 兼容的 embedding 端点必须拒绝超出其输入上限的请求(返回错误),而不是悄悄截断。

这一行为有专门的测试保障:reask_input_coverage_test.go 中的TestReaskEmbeddingCoverageIsNotAFalseMatch验证了三种情形——"无法证明覆盖"返回ErrCapability、"截断型 provider"返回ErrInputLimit、"覆盖检查出错"返回对应错误,且任何情况下都不产生匹配;TestReaskEmbeddingReceivesCompleteDifferentTails则验证 embedding 端收到的一定是完整输入(包含共享前缀的完整文本),绝不允许裁剪后的片段。内置食谱的探针 probes.yaml 也明确记载了这一行为:当前轮次超过 embedding 输入上限时,reask 及其依赖的信号会以reask_evaluation_failed失败。

语义边界:重复 ≠ 不满

重复提问可能是有意的:用户可能在补充信息、强调重点,或在不同语境下追问细节。因此官方指引是把 reask 用于升级(escalation)而非惩罚(punishment)——信号的意义在于让更强的模型再回答一次,而不是对用户做负面处理。

阈值校准

余弦模式的0.8只是经验默认值,实际效果取决于 embedding 模型对领域文本的分布。建议结合真实会话数据观察相似度分布后再微调;切换decision.v1判断模式时,必须针对所选决策模型重新校准概率阈值,余弦阈值不可迁移。

最佳实践小结

  1. 成对配置两级规则:用lookback_turns: 1捕获"可能不满意",用lookback_turns: 2捕获"持续不满意",分别路由到不同等级的模型;
  2. 让决策组合多个信号:把reask与user_feedback、keyword纠正标记等信号一起做 AND/OR 组合(参考 recipe.dsl),提高判定鲁棒性;
  3. 关注 embedding 输入覆盖:部署 OpenAI 兼容 embedding 端点时确认其对超限输入返回错误而非截断,避免超长轮次静默产生错误相似度;
  4. 按用途而非惩罚接入:reask 命中后的动作应是模型升级或内容澄清,而不是拒绝服务;
  5. 运行时观测:通过信号遥测指标(匹配规则名、置信度、命中的连续轮次数、执行耗时)监控 reask 的命中率与延迟,据此迭代阈值。

通过 dissatisfaction.yaml 与根目录 config.yaml 的完整配置,再结合 reask_classifier.go 的实现与 engine_reask_test.go 的验证,你可以在自己的路由器上快速落地一套"重复提问感知"的模型升级机制。

  • 后端
  • API网关
  • 模型推理服务
  • AI Agent

【免费下载链接】semantic-router

An open, programmable decision layer for models and compute.

项目地址:https://gitcode.com/gh_mirrors/sem/semantic-router
点击查看免费下载

相关推荐

上一篇:depgraph-maven-plugin 常见问题解决方案
下一篇:用 Video2X 把模糊视频放大到 4K:模型选择、命令参数与常见卡点

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/13 1:39:33

MAS 激活器:三步免费永久激活 Windows 11 与 Office

MAS 激活器&#xff1a;三步免费永久激活 Windows 11 与 Office 【免费下载链接】Microsoft-Activation-Scripts Open-source Windows and Office activator featuring HWID, Ohook, TSforge, and Online KMS activation methods, along with advanced troubleshooting. 项目…

作者头像 李华
网站建设 2026/10/13 1:38:32

java项目实战滑雪场管理系统[文档+开题+PPT

&#x1f345;选题推荐——以防找不到我们&#xff0c;点击上方订阅专栏✌✌\ Java毕设实战项目 Python毕设项目源代码 asp.net毕业设计项目 Uniapp安卓毕业设计项目 node.js毕业设计项目 python毕业设计 微信小程序毕业设计项目 php毕业设计 &#x1f447;&#x1f3fb;&#…

作者头像 李华