news 2026/10/15 16:02:28

Semantic Router Elo 算法指南:基于 Bradley-Terry 的在线反馈模型选择与评分

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Semantic Router Elo 算法指南:基于 Bradley-Terry 的在线反馈模型选择与评分
  • 后端
  • API网关
  • 模型推理服务
  • AI Agent

【免费下载链接】semantic-router

An open, programmable decision layer for models and compute.

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

elo是 Semantic Router(v0.3 文档体系)中一类反馈驱动的模型选择算法:它用 Elo 式的评分系统对候选模型进行两两比较并持续更新排名。本文以 elo.md 为主体,结合 pkg/selection/elo.go 的源码实现,完整讲解算法原理、选择流程、反馈 API、持久化机制与全部配置参数,读完即可在路由决策中配置并使用algorithm.type: elo实现"越用越准"的在线模型排序。

概述:elo 是什么

elo是一种基于历史成对比较(pairwise comparison)反馈的模型选择算法。它不再依赖一次性启发式规则,而是把用户/评估者每一次"哪个模型表现更好"的反馈沉淀为可复用的评分:候选模型通过 Elo 评分系统被持续排序,评分随新反馈不断演化。

该算法在源码中作为一类独立的选择方法存在,其方法标识定义于 pkg/selection/selector.go:

// MethodElo uses Elo rating system with Bradley-Terry model // Models are scored based on pairwise comparisons using preference feedback MethodElo SelectionMethod = "elo"

其理论依据是RouteLLM: Simple and Effective LLM Routing(arXiv:2406.18665)一文,核心是利用Bradley-Terry 模型做成对偏好学习;在 pkg/selection/selector.go 的包注释中同样明确标注了该论文引用。

核心优势

  • 复用历史反馈:不只看当前请求的即时启发式信号,而是利用历史积累的成对偏好数据。
  • 在线学习:随着更多比较反馈到达,评分持续更新,无需重新训练模型。
  • 类别加权:支持按决策类别维护独立评分,不同工作负载互不干扰。
  • 可配置时间衰减:可逐步遗忘过时比较,适应模型能力漂移。
  • 成本感知可选:在质量与价格之间做可配置的权衡。

算法原理:Bradley-Terry 模型与 Elo 更新

Elo 评分建立在Bradley-Terry 模型之上,该模型估计"模型 A 优于模型 B"的概率:

$$P(A \succ B) = \frac{1}{1 + 10^{(R_B - R_A) / 400}}$$

每次成对比较后,评分按如下规则更新:

$$R_A' = R_A + K \cdot (S_A - E_A)$$

其中:

  • $K$ 为学习率(配置项k_factor,默认 32,值越大评分波动越剧烈);
  • $S_A$ 为实际结果(胜 1.0、负 0.0、平 0.5);
  • $E_A = P(A \succ B)$ 为期望得分。

源码中这条公式被完整实现于 pkg/selection/elo.go 的applyPairwiseFeedbackLocked:

// Calculate expected scores using Bradley-Terry model // E_a = 1 / (1 + 10^((R_b - R_a) / 400)) expectedWinner := 1.0 / (1.0 + math.Pow(10, (loserRating.Rating-winnerRating.Rating)/400.0)) expectedLoser := 1.0 - expectedWinner // Determine actual scores actualWinner, actualLoser := 1.0, 0.0 if feedback.Tie { actualWinner = 0.5 actualLoser = 0.5 } // Update ratings: R' = R + K * (actual - expected) winnerRating.Rating += e.config.KFactor * (actualWinner - expectedWinner) loserRating.Rating += e.config.KFactor * (actualLoser - expectedLoser)

注意平局(tie)时,胜负双方实际得分各取 0.5,更新后评分总和守恒。每次比较同时累加Comparisons,并根据是否平局分别统计Wins/Losses/Ties。

当开启category_weighted时,每个决策维护独立的按类别评分(categoryRatings以decision name → model → rating的映射存储,见 elo.go),因此模型在 "math" 类别的表现不会影响其 "coding" 类别评分。

源码实现:EloSelector 的构成与初始化

配置结构与默认值

EloConfig结构体定义于 pkg/selection/elo.go,字段与 YAML 配置一一对应;其默认值来自 DefaultEloConfig 与顶层常量:

常量值含义
DefaultEloRating1500.0新模型的初始评分
EloKFactor32.0默认学习率
EloMinRatingFromScore1000.0静态分数(0–1)转换 Elo 的基准
EloRatingRange1000.0分数区间(0–1 映射到 1000–2000)

默认配置为InitialRating=1500、KFactor=32、CategoryWeighted=true、DecayFactor=0.0、MinComparisons=5、CostScalingFactor=0.0。

评分数据结构

每个模型维护一条ModelRating记录(elo.go),包含评分与胜负平统计,同时作为持久化 JSON 结构:

type ModelRating struct { Model string `json:"model"` Rating float64 `json:"rating"` Comparisons int `json:"comparisons"` Wins int `json:"wins"` Losses int `json:"losses"` Ties int `json:"ties"` }

注册与工厂装配

elo通过 pkg/selection/factory.go 的Factory.Create()创建:case MethodElo分支先构造NewEloSelector(f.cfg.Elo),再调用InitializeFromConfig(f.modelConfig, f.categories)用模型配置初始化评分与成本;在CreateAll()中还会注册到全局Registry(factory.go),便于运行时按方法名取出。

InitializeFromConfig(elo.go)承担"冷启动种子"职责:

  • 为所有配置中的模型建立全局评分,初始值取InitialRating;
  • 从模型配置的Pricing.PromptPer1M读取成本,供成本感知选择使用;
  • 若开启category_weighted,则把每个类别下的静态分数(ModelScores,0–1)线性映射为 Elo 评分:rating = 1000 + score * 1000。

关于"学习到的评分不应被静态种子覆盖"这一点,elo_category_seed_test.go 专门做了回归测试:从存储恢复出的已学习评分(如 1740)在InitializeFromConfig之后保持不变,而缺失的模型才使用静态分数作为种子(见 TestInitializeFromConfigSeedsMissingCategoryRating)。

选择流程(Select)

整体选择流程如下:

Select的源码实现(elo.go)与流程图中"按评分排名"的直观描述略有差异,实际采用概率化加权选择:

  1. 取评分:getRatingsForCandidates按决策名优先查类别评分,缺失则回退全局评分,再回退到initial_rating(对应流程图中"Enough comparisons?"的 fallback 分支)。
  2. 计算选择概率:以标准 Elo 概率尺度P_i = 10^(R_i/400) / Σ 10^(R_j/400)归一化为分数(这是 Bradley-Terry 模型的等价形式)。
  3. 成本调整:若CostScalingFactor > 0且SelectionContext.CostWeight > 0,调用applyCostAdjustment(见下节)。
  4. 选出最优:取分数最高的模型,并基于评分稳定性计算置信度。

成本感知调整

applyCostAdjustment(elo.go)先把各候选成本归一化到 0–1(0 为最便宜,1 为最贵),再对便宜模型加成分数:

normalizedCost := (cost - minCost) / (maxCost - minCost) costBonus := (1.0 - normalizedCost) * costWeight * e.config.CostScalingFactor scores[model] *= (1.0 + costBonus)

当所有候选成本相同(maxCost == minCost)时不做任何调整;costWeight来自请求上下文,cost_scaling_factor决定整体权衡强度。

置信度计算

calculateConfidence(elo.go)用 sigmoid 函数把比较次数映射到 0–1 置信度,min_comparisons即阈值:

k := 0.2 // Steepness threshold := float64(e.config.MinComparisons) confidence := 1.0 / (1.0 + math.Exp(-k*(float64(rating.Comparisons)-threshold)))

比较次数达到阈值后,置信度快速趋近于 1;评分尚不稳定时置信度较低。选中的SelectionResult会携带Method: MethodElo、Score、Confidence以及形如Selected based on Elo rating 1568.0 (win rate: 20/30)的Reasoning(类别加权模式下还会加上Category 'xxx':前缀)。

GetLeaderboard(elo.go)则按评分降序返回全部模型的排行榜,可用于调试与监控。

反馈流程(Feedback)

反馈是elo学习的唯一数据来源,整体流程如下:

反馈 API

通过 HTTP 接口提交成对反馈以更新 Elo 评分:

curl -X POST http://localhost:8000/api/v1/feedback \ -H "Content-Type: application/json" \ -d '{ "query": "Solve: 2x + 5 = 15", "winner_model": "gpt-4", "loser_model": "llama-3.2-3b", "decision_name": "math_reasoning" }'

反馈字段说明:

字段必填说明
query是原始查询文本
winner_model是更优的模型名
loser_model否被否定的模型(用于成对比较)
tie否两个模型表现相当
decision_name否类别上下文(用于类别加权 Elo)
user_id否用户标识(按用户追踪)
confidence否反馈置信度(0.0–1.0)

源码中的三种反馈模式

Feedback结构定义于 pkg/selection/selector.go,除了文档表格列出的字段,还包含response(响应文本)、session_id、conversation_id、feedback_type(取值如satisfied、need_clarification、wrong_answer、want_different)与timestamp,供隐式反馈与多轮会话场景使用。

UpdateFeedback(elo.go)的处理路径是:先NormalizeFeedback规范化并校验,再更新全局评分,若开启category_weighted且提供了decision_name则同步更新类别评分,最后persistRatings。其内部updateRatingLocked(elo.go)支持三种模式:

  1. 成对比较(WinnerModel与LoserModel都设置):标准 Elo 更新,走applyPairwiseFeedbackLocked;
  2. 正向单模型反馈(仅WinnerModel):applyWinnerOnlyFeedbackLocked给予K * 0.1的小幅奖励(约为 K 因子的 10%),用于信号驱动、只知道"某个模型表现好"的场景;
  3. 负向单模型反馈(仅LoserModel):applyLoserOnlyFeedbackLocked施加同等幅度的小幅惩罚。

反馈校验契约

feedback.go 定义了严格的反馈契约与对应错误:

  • 反馈对象为空 →ErrFeedbackRequired;
  • winner_model与loser_model均为空 →ErrFeedbackModelRequired;
  • 两个模型名相同(含去空格后相同)→ErrFeedbackSelfComparison(禁止自我比较);
  • confidence超出 [0,1]、为 NaN 或无穷 →ErrFeedbackConfidenceRange。

NormalizeFeedback还会对所有状态键(模型名、决策名、用户/会话/对话 ID、反馈类型)做TrimSpace规范化,feedback_test.go 与 TestValidateFeedbackRejectsInvalidContracts 覆盖了上述全部边界情况。

持久化与自动保存

elo的评分是本地学习状态。配置storage_path后,评分通过FileEloStorage(pkg/selection/storage.go)持久化为 JSON 文件,包含version、last_updated、global_ratings与category_ratings四个部分(StoredRatings结构,见 storage.go)。

几个值得注意的实现细节:

  • 原子写入:saveToFile先写临时文件(path + ".tmp")再os.Rename原子替换,避免中途崩溃产生半截文件(storage.go)。
  • 防御性加载:文件不存在或为空时返回全新状态;文件损坏时先备份为path + ".corrupted"再报错(storage.go)。
  • 数据清洗:sanitizeRatings会把评分钳制在100–3000合理区间,并把负数的胜负平统计归零(storage.go)。
  • 启动加载:NewEloSelector配置了存储路径后,会先loadFromStorage()恢复已有评分再启动后台自动保存(elo.go)。
  • 自动保存:StartAutoSave以后台 goroutine 定期把"脏"状态(MarkDirty标记)写入磁盘,关闭时做最后一次保存(storage.go)。auto_save_interval若缺失或非法,会回退到默认的30s(auto_save_interval.go),因此文档示例中的1m仅是合法取值之一。

多副本状态告警

:::warning Multi-replica state

Elo 评分无论是保存在内存还是本地storage_path,都属于本地学习状态。在 Helm 部署下,只要某个活跃决策使用了algorithm.type: elo,多副本 router 渲染默认会被拒绝——因为各副本的本地评分会各自演化、产生不一致的选择结果。请保持单一 router 副本,或将选择器状态外部化(如共享存储/外部服务),再考虑关闭该安全护栏。

:::

配置详解

完整配置片段如下(对应ModelSelectionConfig.Elo,解析于 pkg/selection/factory.go):

algorithm: type: elo elo: initial_rating: 1500 # 新模型的起始评分 k_factor: 32 # 学习率(越高波动越大) category_weighted: true # 启用按类别独立评分 decay_factor: 0.0 # 旧比较的时间衰减(0 = 不衰减) min_comparisons: 5 # 评分稳定所需的最小比较次数 cost_scaling_factor: 0.0 # 成本权重(0 = 忽略成本) storage_path: /var/lib/vsr/elo_ratings.json # 评分持久化文件路径 auto_save_interval: 1m # 自动保存频率

参数一览

参数类型默认值说明
initial_ratingfloat1500新模型的起始 Elo 评分
k_factorfloat32评分波动性(建议范围 1–100)
category_weightedbooltrue启用按类别独立评分
decay_factorfloat0.0旧比较的时间衰减(0–1)
min_comparisonsint5评分稳定所需的最小比较次数
cost_scaling_factorfloat0.0每 1M tokens 成本惩罚(0 = 忽略)
storage_pathstring—Elo 评分的持久化文件路径
auto_save_intervalstring1m自动保存间隔(如5m、30s;源码默认回退 30s)

参数背后的实现影响

  • initial_rating:决定新模型冷启动时的起点,也用于"无记录"时的评分回退(getRatingsForCandidates的最后兜底分支);decay_factor目前作为配置契约保留,默认 0 表示不衰减。
  • k_factor:直接缩放每次更新的步长,updateRatingLocked系列函数与单模型反馈(K * 0.1)都依赖它。
  • category_weighted:控制Select优先查类别评分还是全局评分(elo.go),也决定反馈是否同步更新类别评分。
  • min_comparisons:作为置信度 sigmoid 的阈值,比较次数越过该值后置信度快速上升。
  • cost_scaling_factor:只有大于 0 且请求携带CostWeight时才启用成本调整。
  • storage_path/auto_save_interval:控制评分生命周期的两个关键参数——前者决定是否启用文件持久化,后者决定落盘频率;不配置storage_path时评分仅存内存。

它解决了什么问题?

当模型质量随时间漂移时,静态优先级列表与一次性启发式会迅速过时。elo让路由从成对反馈中持续学习,使候选排名反映的是观测到的实际胜负,而不是冻结的假设。源码中的 selector_elo_test.go 直观演示了这一演化过程:通过 10 轮"强胜中、中胜弱、强胜弱"的模拟反馈,strong-model/medium-model/weak-model的评分严格按预期排序(强 > 中 > 弱),排行榜顺序与胜负统计(强 20 胜、弱 20 负)均被断言验证——这就是"评分随反馈演化"的最小闭环。

何时使用

  • 你能够收集路由级别(route-level)的反馈或质量比较;
  • 排名需要随着更多比较反馈持续提升;
  • 某条路由存在可重复的工作负载,评分体系能沉淀价值;
  • 你希望在线学习,而不想为每次模型切换重新训练。

已知限制

  • 需要足够的成对比较:评分在稳定之前需要足够样本,由min_comparisons控制;
  • 冷启动问题:新模型一律从initial_rating起步,与真实能力无关(不过可借助类别的静态ModelScores种子加速收敛);
  • 无语义理解:elo纯粹由反馈驱动,不分析查询语义。

进一步探索

想深入验证或改造elo行为,可以围绕以下源码文件继续阅读:

  • 核心实现:pkg/selection/elo.go(选择、反馈更新、成本调整、置信度、排行榜)
  • 方法注册与装配:pkg/selection/factory.go、pkg/selection/selector.go
  • 反馈校验契约:pkg/selection/feedback.go
  • 持久化实现:pkg/selection/storage.go、pkg/selection/auto_save_interval.go
  • 测试用例:selector_elo_test.go、elo_category_seed_test.go、feedback_test.go
  • 指标观测:pkg/selection/metrics.go(ModelEloRating、ModelComparisons、ModelWinRate等指标随选择与反馈自动上报)

其中elo在源码的算法分级测试中被归类为TierSupported(生产可用)(tier_test.go),无需额外外部依赖即可运行,适合作为反馈闭环型路由的落地起点。

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

【免费下载链接】semantic-router

An open, programmable decision layer for models and compute.

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

相关推荐

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

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

跨平台Markdown编辑器DBeditor:JavaScript方案的架构与工程实践

简介:DBeditor 是一款面向 Linux、macOS、Windows 等主流系统的跨平台 Markdown 编辑器,主打简洁扁平、大量留白的 UI 设计,去掉繁杂菜单,适合需要轻量写作与代码环境一致的开发者和内容创作者。压缩包共 61 个文件,主…

作者头像 李华
网站建设 2026/10/15 16:01:35

存量HART仪表如何低成本接入以太网上位机:仓储物位改造实践

去年年底接手了一个仓储改造项目,现场情况很典型:十几个储料仓和液位罐,雷达物位计、超声波料位计、压力式液位变送器都有,品牌还不统一,但大部分都带HART协议。问题是过去只用了4-20mA进PLC做联锁,变送器里…

作者头像 李华
网站建设 2026/10/15 15:59:49

systemd启动流程深度解析:从单元依赖到系统排障与优化

每次按下电源键,从内核交出控制权到系统进入可交互的登录界面,这一整段旅程的调度核心就是systemd。作为几乎所有主流Linux发行版默认的init程序,systemd以一号进程的身份接管了用户态的全部启动工作——从挂载文件系统、拉起设备管理服务&am…

作者头像 李华
网站建设 2026/10/15 15:59:10

Apollo-11实战:基于WebSocket与protobuf的多人实时同步架构解析

1. 项目全貌:为什么叫Apollo-11拿"Apollo-11"当项目代号,多少带点浪漫主义。阿波罗11号是人类第一次成功登月的任务代号,代表着"目标明确、过程波折、最终落地"。我们这个项目也是一样:要做的事不算复杂&…

作者头像 李华