☰
Semantic Router v0.3 Preference Signal 学习型偏好路由:配置、对比学习原理与源码剖析
2026/10/15 1:56:55 网站建设 项目流程
  • 后端
  • API网关
  • 模型推理服务
  • AI Agent

【免费下载链接】semantic-router

An open, programmable decision layer for models and compute.

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

导读

preference是 Semantic Router(vSR)中的一类学习型信号(learned signal):它从一组人工编写的示例(examples)与分类器设置中推断出用户的回答风格偏好(如“想要简洁回答”或“想要详尽推演”),并把推断结果作为具名路由输入暴露给决策引擎。本文以 v0.3 Preference Signal 文档 为骨架,完整讲解它的配置方式、use_contrastive默认行为、prototype_scoring参数矩阵,并结合 preference_classifier.go、contrastive_preference_classifier.go、prototype_bank.go 等源码揭示对比学习匹配的完整链路。读完本文,你将能够:在 v0.3 配置中声明偏好信号、用示例锚定风格类别、理解对比模式与外部 LLM 模式的取舍,并把偏好信号接入路由决策条件。

什么是 Preference Signal

解决的问题:同一话题,不同风格

即使用户询问的是相同主题,不同人也往往想要截然不同的回答方式:有人希望“一句话讲完”,有人要求“逐步推演、不要跳过任何步骤”。如果这种风格偏好只在生成链路的下游处理(例如由模型提示词临时体现),那么路由层就无法据此选择更合适的模型或插件栈——因为路由发生在模型调用之前。

preference信号解决的就是这个问题:把推断出的风格偏好作为命名路由输入(named routing input)暴露出来,让决策引擎在模型选择(model choice)与插件选择(plugin choice)两个层面都看到“这位用户想要什么风格”的事实。

关键优势

  • 不硬编码用户状态:偏好检测结果独立于决策产出,无需在路由逻辑中手工维护“用户 A 喜欢简洁”之类的状态机;
  • 检测与路由解耦:偏好检测只负责产出信号,不关心最终路由结果,二者可以独立演进;
  • 示例驱动的风格检测:支持“简洁 vs 详尽”这类语义风格判断,而不是简单的关键词匹配;
  • 策略复用:一条偏好策略可以被多个决策(decision)同时引用,一处定义、多处生效。

从源码结构看,这一设计体现在 classifier_signal_preference_support.go 中:信号计算函数只负责产出preference:<名称>这一命名信号及其置信度/边界值,写入SignalResults后,路由决策层再按规则消费(见recordPreferenceSignalValues)。

何时使用

  • 一部分用户偏好极简回答,另一部分用户需要高细节输出;
  • 路由行为需要适配稳定的风格偏好(而非单次请求的临时倾向);
  • 希望偏好检测在多个决策之间保持可复用;
  • 风格信号需要同时影响模型选择与插件选择。

配置总览

1. 在 routing.signals.preferences 声明偏好规则

源片段族位于 config/fragments/signal/preference/,仓库自带的可复用示例 power-user.yaml 内容如下:

routing: signals: preferences: - name: terse_answers description: Users who prefer short, direct responses. examples: - keep it concise - bullet points only - answer in one paragraph threshold: 0.7

字段说明(对应 signal_config.go 中的PreferenceRule结构):

字段类型说明
namestring规则名称,也是信号名称,路由条件里用type: preference, name: <name>引用
descriptionstring规则语义描述;在对比模式下会连同 examples 一起参与嵌入
examples[]string训练锚点(training anchors),非字面关键词规则
thresholdfloat32该规则的相似度阈值;匹配得分低于阈值时判定为不匹配

⚠️重要语义:把examples视作偏好检测器的训练锚点,而不是字面关键词规则。例如keep it concise这类例子在嵌入空间里“语义靠近”的请求(如 “Please keep your reply short and to the point”)才会命中,而不是要求用户请求里出现这几个单词。

threshold的默认值语义可参考源码:在 contrastive_preference_classifier.go 中,若threshold > 0且BestScore < threshold,分类器返回ErrPreferenceBelowThreshold(“preference below threshold”),信号视为不匹配;当threshold未设置(0)时,该检查被跳过。在 e2e/profiles/preference-routing/values.yaml 的实战配置中,terse_answers与detailed_answers都使用了threshold: 0.6,并用margin_threshold: 0.05追加一道“歧义否决”门槛。

2. 可选:在 global.model_catalog 覆盖偏好分类器行为

global: model_catalog: modules: classifier: preference: use_contrastive: false # optional override; default is true prototype_scoring: enabled: true cluster_similarity_threshold: 0.9 max_prototypes: 8 best_weight: 0.75 top_m: 2 margin_threshold: 0.05

对应源码类型为 model_config_types.go 中的PreferenceModelConfig(UseContrastive、EmbeddingModel、PrototypeScoring)以及 prototype_scoring_config.go 中的PrototypeScoringConfig。

3. use_contrastive 默认值:v0.3 的新行为

文档明确说明:如果global.model_catalog.modules.classifier.preference.use_contrastive被省略,vSR 默认将其视为true。这意味着一个像 config/recipes/balance/config.yaml 这样的 profile,只要声明了偏好信号,就无需额外追加全局 classifier 块——除非你想显式禁用对比模式。

源码层面的执行契约选择逻辑在 preference_execution.go 中:

  • PreferenceUsesPrototypes():规则带 examples、显式指定了embedding_model、或显式use_contrastive: true时返回 true(走向量原型对比路径);
  • PreferenceUsesDecisionTask():description-only 规则默认走原生 decision-task 判定;显式外部分类器保留自己的执行契约;
  • 优先级关系见 preference_execution_test.go 的测试矩阵:"explicit prototypes override external"、"native binding overrides external"等用例保证了多种配置组合下的契约选择一致性。

初始化链路在 classifier_preference_lifecycle.go:先判断是否走 decision task,否则读取PreferenceModel.WithDefaults(),把UseContrastive指向PreferenceUsesPrototypes()的判定结果,再调用NewPreferenceClassifierWithProvider实例化对应实现(对比模式默认嵌入模型为mmbert,可远程嵌入后端)。

对比模式(Contrastive)工作原理

在对比模式下,路由器的处理流程是:嵌入 → 原型压缩 → 相似度打分 → 阈值/边界裁决。

第一步:预加载规则嵌入

contrastive_preference_embeddings.go 中的preloadRuleEmbeddings会收集每条规则的 description + 全部 examples(见 contrastive_preference_scoring.go 的collectExamples),然后并发地(worker 数为runtime.NumCPU() * 2)把它们嵌入为向量,日志输出[Preference Contrastive] preloaded N/M example embeddings using model=...。

第二步:原型库(prototype bank)压缩

嵌入完成后调用rebuildRuleBanks(),对每条规则构建一个prototypeBank。构建逻辑在 prototype_bank.go:

  1. 对嵌入后的例子做去重(dedupePrototypeExamples);
  2. 计算例子之间的余弦相似度矩阵(prototype_clustering.go);
  3. 以cluster_similarity_threshold(默认 0.9)为阈值做贪心聚类,把语义冗余的例子合并;
  4. 每个簇选出medoid(最代表该簇的例子)作为原型,并按ClusterSize、AvgSimilarity降序、文本序兜底排序;
  5. 若原型数超过max_prototypes(默认 8),截断保留前 N 个。

若prototype_scoring.enabled: false,则跳过聚类压缩,把每个例子原样作为原型(uncompressedPrototypeRepresentatives)。

第三步:打分公式

查询请求被嵌入后,与每条规则的每个原型计算余弦相似度,打分逻辑在 prototype_scoring.go:

score = best_weight × best + (1 - best_weight) × support
  • best:与规则原型库的最高相似度;
  • support:Top-M 个最高相似度的平均值(top_m默认 2);
  • best_weight:默认 0.75,权重越大越依赖“最强单原型”,越小越依赖“整体支持度”(取值会被夹在 [0, 1])。

最终规则得分即PreferenceRuleScore.Score,排序取最高者为BestRule,次高者为RunnerUp,Margin = BestScore - RunnerUpScore(contrastive_preference_scoring.go)。

第四步:阈值与 margin 双重裁决

contrastive_preference_classifier.go 的Classify依次检查:

  1. 最佳规则得分低于其threshold→ 拒绝(ErrPreferenceBelowThreshold);
  2. margin_threshold > 0且Margin < margin_threshold→ 拒绝“勉强获胜”的模糊结果。

文档强调的margin_threshold作用正在于此:拒绝模糊赢家,而不是硬凑一个弱匹配。在 e2e/profiles/preference-routing/values.yaml 中注释给出了实战理由:“Off by default; set explicitly so an ambiguous query that narrowly favors one rule over the other still counts as 'no match' rather than a weak, coincidental win.”

信号产出与观测

命中后,classifier_signal_preference_support.go 会把结果写进SignalResults:

  • preference:<name>:匹配规则名(同时进入MatchedPreferenceRules);
  • SignalValues["preference:<name>"]:置信度;
  • SignalValues["preference:<name>:margin"]:胜出边界;
  • 以及每条规则的:best、:support、:prototype_count明细,便于调试与观测。

信号提取延迟与置信度会写入results.Metrics.Preference,供监控与调参使用。

路由决策中的使用方式

偏好信号在routing.decisions[].rules.conditions中以type: preference引用。e2e 配置 e2e/profiles/preference-routing/values.yaml 给出了两个完整的对照决策:

routing: decisions: - name: terse_preference_routing description: Route requests whose preference signal matches terse_answers. priority: 100 rules: operator: AND conditions: - type: preference name: terse_answers modelRefs: - model: general-expert use_reasoning: false - name: detailed_preference_routing description: Route requests whose preference signal matches detailed_answers. priority: 90 rules: operator: AND conditions: - type: preference name: detailed_answers modelRefs: - model: general-expert use_reasoning: false - name: default-route description: Fallback route for requests matching no preference rule. priority: 10 rules: operator: AND conditions: [] modelRefs: - model: general-expert use_reasoning: false

注意两点:

  • 决策按priority从高到低匹配,未命中任何偏好规则的请求会落到default-route(conditions 为空)这样的兜底决策;
  • 在真实部署中,每个偏好决策还可以挂不同的modelRefs(如简洁偏好走低成本快速模型、详尽偏好走高成本强推理模型)以及不同的插件栈,这正是文档所说“风格信号影响模型选择与插件选择”的落地方式。

e2e 验证用例

preference_routing.go 注册了名为preference-routing的端到端用例,数据来自 preference_routing_cases.json,三个用例完整覆盖了三种结果:

用例查询期望信号是否命中
Terse“Please keep your reply short and to the point, no need to explain further.”terse_answers→terse_preference_routing命中
Detailed“Please explain your full reasoning and go through this step by step, don't leave anything out.”detailed_answers→detailed_preference_routing命中
No match“What's the capital of France?”无(不应命中任何规则)不命中

用例通过响应头x-vsr-matched-preference断言实际命中的信号名(见 signal_routing_helpers.go),用“纯事实性查询没有风格提示词、不应命中任何偏好规则”验证了阈值与 margin 双重门槛确实生效。

三种执行模式与选择建议

结合 classifier_preference_lifecycle.go 与 preference_execution_test.go,偏好信号共有三类执行契约:

模式触发条件行为
对比原型(contrastive / prototypes)规则带 examples、显式embedding_model、或use_contrastive: true嵌入 + 原型聚类 + 相似度打分,本地或远程嵌入后端均可
原生决策任务(decision task)仅有 description(description-only),且未绑定外部分类器由决策模型以原生 judgment 判定,概率式打分
外部 LLM 分类器(external)配置了model_role: preference的外部模型由外部 LLM 按 system/user prompt 判定(classifyExternal)

选择建议:

  • 追求可解释、可离线验证、低延迟:默认的对比原型模式最合适,examples 就是可审计的锚点;
  • 只有描述、不想维护 examples:可以只写 description,让其自动走决策任务模式;
  • 需要强语义判定或跨语言泛化:可显式绑定外部 LLM 分类器,但注意其延迟与成本;
  • 明确禁用对比:显式写use_contrastive: false;省略即默认true,这是 v0.3 的关键行为变更。

调参实战指南

prototype_scoring的默认值在 prototype_scoring_config.go 的WithDefaults()中固化,可直接作为调参起点:

参数默认值调参含义
enabledtrue是否启用原型聚类压缩;设为false则每个例子独立参与打分
cluster_similarity_threshold0.9聚类合并阈值,越高越严格、保留更多原型;越低压缩越激进
max_prototypes8每个规则保留的最大原型数,超出截断
best_weight0.75最强原型在最终得分中的权重(0~1);越接近 1 越偏向单点最强匹配
top_m2计算 support 时取前 M 个最高相似度
margin_threshold0(<0 时归零)最佳与次佳的得分差下限;>0 时拒绝歧义匹配

典型调参路径:

  1. 先用默认参数跑通,观察prototype_count(每个规则实际保留的原型数);
  2. 若 examples 语义高度重合、prototype_count偏多,可下调cluster_similarity_threshold或上调max_prototypes限制;
  3. 若出现“弱命中”(得分刚好过 threshold 但语义牵强),调大margin_threshold(如 0.05)拒绝歧义;
  4. 若发现某类请求被误判为其他偏好,检查该类别的 examples 是否过少,适当补充语义多样的锚点例子;
  5. 每条规则独立的threshold用于控制该规则的严格程度——整体偏好检测过松/过紧时优先调整它,而不是全局参数。

完整的多规则实战样例可参考 e2e/profiles/preference-routing/values.yaml(双规则 + margin 门槛 + 兜底路由),单规则可复用片段见 power-user.yaml,参考基线配置见 config/config.yaml。

总结

preference信号把“用户的回答风格偏好”变成了一个可路由、可观测、可复用的命名输入:声明routing.signals.preferences下的规则,用 description + examples 作为训练锚点;v0.3 默认启用对比模式,把每个规则压缩成原型库,用余弦相似度 + threshold + margin 双重门槛做裁决;决策层通过type: preference条件选择最合适的模型与插件组合。从 Preference Signal 文档 到 classifier_preference_lifecycle.go、contrastive_preference_classifier.go 与 e2e 用例,整条链路既保留了示例驱动的可解释性,又提供了工程化的调参与验证手段——这是 v0.3 让“同一话题、不同风格”的差异化路由变得可行、可运维的关键能力。

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

【免费下载链接】semantic-router

An open, programmable decision layer for models and compute.

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

相关推荐

上一篇:CloudCompare插件生态系统:如何选择和配置最佳插件组合
下一篇:Attendize自定义票务模板:QR码、Logo和品牌化设计终极指南

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询