Refly Auto Model 智能路由架构解析:虚拟模型层设计与五级路由引擎实战
2026/9/16 17:40:48 网站建设 项目流程

Refly Auto Model 智能路由架构解析:虚拟模型层设计与五级路由引擎实战

【免费下载链接】reflyThe first open-source agent skills builder. Define skills by vibe workflow, run on Claude Code, Cursor, Codex & more. Build Clawdbot 🦞· APIs for Lovable · Bots for Slack & Lark/Feishu · Skills are infrastructure, not prompts.项目地址: https://gitcode.com/GitHub_Trending/re/refly

Refly 通过 Auto Model(自动模型)为用户构建了一层虚拟模型层:用户在界面上始终看到并选择 "Auto",系统则根据场景、工具集、用户上下文与可用的真实模型,在后台静默路由到最合适的 LLM。本文以specs/current/auto-model/AUTO_MODEL.md为核心骨架,结合apps/api/src/modules/provider/下的路由服务源码与packages/utils/src/auto-model.ts工具实现,完整拆解 Auto 虚拟 Provider 的数据结构、五级路由优先级链、试用期计数、显示与计费的解耦设计,以及auto_model_routing_rulesauto_model_routing_results两张数据表的使用方式,帮助读者掌握如何通过环境变量和数据库规则低成本地控制 Refly 的模型调度策略。

一、设计理念:为什么需要 Auto Routing

Auto Model Routing 的本质是虚拟模型层(virtual model layer):它把"用户选择的模型"与"实际执行请求的模型"彻底解耦。用户看到的是固定的 "Auto",系统按规则、场景、工具集和用户上下文静默选择真实模型。这样做带来的收益包括:

  • 成本优化而不暴露细节:Refly 可以在后端灵活切换低成本/高性价比模型,用户无需感知底层模型变化;
  • 按场景、按条件路由:无需用户重新配置,即可在不同场景(Agent、Copilot)或不同工具集(如图像生成)下选择不同模型;
  • UI 稳定:后台模型升级、轮换时前端界面保持不变,"Auto" 始终位于模型列表首位;
  • 新用户体验钩子:通过试用期(trial period)规则,把新用户的前 N 次 Auto 调用路由到更强模型,形成体验正反馈。

从源码结构看,整个路由体系被拆成三个层次:路由引擎(auto-model-router.service.ts)、试用期服务(auto-model-trial.service.ts)和通用工具(auto-model.ts),三者职责单一、通过依赖注入组合,便于独立测试与演进。

二、虚拟 Provider 与 Item:Auto 如何"假装"成一个普通模型

Auto 模型不是一个特殊分支,而是一个真实存在于数据库中的provider_item,由系统内置 provider 支撑,因此它完整参与现有的模型查询管道,无需任何特殊 case。

字段
providers.provider_idpr-system
providers.provider_keysystem
provider_items.item_idpi-asnj62w0h0acxi34lkfg4ijn
provider_items.nameAuto
config.modelIdauto
config.tooltipSmart Routing
config.contextLimit200000
config.capabilities.visiontrue
credit_billing.inputCost80credits/1M tokens
credit_billing.outputCost400credits/1M tokens
UI 位置列表首位(名称Auto字典序排在最前)

需要特别注意credit_billing的设计含义:Auto 虚拟项本身带有 80/400 的计费费率,这个费率正是"路由后按 Auto 费率计费"机制的落点(详见第七章)。

全代码库通过isAutoModel()工具识别该虚拟项,实现在 packages/utils/src/auto-model.ts:它接受字符串或对象形式的ProviderItemConfig,先safeParseJSON解析字符串,再检查'modelId' in config && config.modelId === 'auto'。核心常量AUTO_MODEL_ID = 'auto'定义在同一文件的第 8 行,路由引擎、计费注入(originalModelId: AUTO_MODEL_ID)都引用它,保证单点定义。

三、默认模型配置:环境变量与优先级链

每个场景(scene)的默认模型由环境变量控制:

环境变量场景说明
DEFAULT_MODEL_AGENTagent设为auto(或 Auto 的 itemId)即可让新建 Agent 节点默认使用 Auto 模型
DEFAULT_MODEL_COPILOTcopilot设为auto(或 Auto 的 itemId)即可让 Copilot 默认使用 Auto 模型
DEFAULT_MODEL_CHATchat已废弃,不要设为Auto

findDefaultProviderItem()是默认模型的解析入口,实现在 provider.service.ts,其解析优先级为:

  1. 管理员覆盖:用户偏好中的defaultModelOverride(需defaultModelOverrideEnabled === true),按场景字段(chat/copilot/agent/titleGeneration/queryAnalysis/image/video/audio)取对应itemId
  2. 用户偏好:用户偏好中的defaultModel,同样按场景字段解析;
  3. 全局环境变量默认值:通过configService.get('defaultModel')读取,匹配规则同时支持itemIdmodelId两种写法(源码中体现为item.itemId === defaultModel.agent || config.modelId === defaultModel.agent这类双匹配);
  4. 用户 LLM 列表首个可用项:兜底保证永远有默认模型。

此外,prepareModelProviderMap()(同文件第 951 行起)会为chatcopilotagenttitleGenerationqueryAnalysisimagevideoaudio八个场景分别调用findDefaultProviderItem,产出一份完整的"场景 → provider item"映射表,供路由与执行使用。

四、模块地图:路由体系的代码布局

文件职责
apps/api/src/modules/provider/auto-model-router.service.ts核心路由引擎:规则缓存、规则匹配、工具路由、回退链、结果持久化
apps/api/src/modules/provider/auto-model-trial.service.ts跟踪每个用户前 N 次 Auto 调用,支撑试用期条件
apps/api/src/modules/provider/provider.service.tsprepareModelProviderMap()findDefaultProviderItem(),为路由提供 provider item
apps/api/src/modules/skill/skill.service.ts路由调用点:构建RoutingContext、调用AutoModelRoutingService.route()、向 config 注入routedData
apps/api/src/modules/skill/skill-invoker.service.ts读取routedData用于 token 用量统计与计费
packages/utils/src/auto-model.tsisAutoModel()AUTO_MODEL_IDAUTO_MODEL_ROUTING_PRIORITY、环境变量读取器
packages/utils/src/models.tsgetModelSceneFromMode():将AgentMode映射为场景字符串

路由引擎的行为有对应的单元测试覆盖,见 apps/api/src/modules/provider/auto-model-router.service.spec.ts,测试通过 mock 的ProviderItemModel(如 Claude Sonnet、Gemini Flash)验证路由选择逻辑,可作为理解各优先级行为的辅助阅读材料。

五、调用链:一次 Auto 请求的完整旅程

用户在界面选择 Auto 模型后,一次请求经历以下流程(以 skill 调用为例,代码位于 skill.service.ts 的skillInvokePreCheck):

用户提交任务(选择了 Auto 模型) ↓ skill.service.ts: skillInvokePreCheck() 1. Fetch llmItems(用户可用的 LLM provider items) 2. autoModelTrialService.checkAndUpdateTrialStatus(uid) → inAutoModelTrial 3. 构建 RoutingContext { llmItems, userId, actionResultId, mode, inputPrompt, toolsets, inAutoModelTrial } 4. providerService.prepareModelProviderMap(user, param.modelItemId) → 为每个场景解析 originalProviderItem 5. param.modelItemId 保持不变(Auto itemId),用于 UI 展示 6. autoModelRoutingService.route(originalProviderItem, routingContext) → 返回 routedProviderItem(真实模型) 7. 将 routedData = { isRouted: true, originalItemId, originalModelId: 'auto' } 注入 routedProviderItem.config ↓ skill-invoker.service.ts: 用 routedProviderItem 调用 LLM - token 用量记录包含 routedData,用于计费计算 ↓ 计费:使用 Auto 模型的费率(80/400),而非真实模型的费率 ↓ action_results.provider_item_id = Auto itemId → 前端展示 "Auto" action_results.model_name = 真实模型 ID → Langfuse 追踪 auto_model_routing_results 写入一行 → 可观测性

两个实现细节值得注意:

  • 只路由主模型:源码中仅将modelProviderMap[primaryScene]替换为routedProviderItem,其余辅助模型(titleGeneration、queryAnalysis、image、video、audio)保持原样;
  • routedData 注入条件providerItem.itemId !== param.modelItemId时才注入routedData,即真实模型与 Auto 项不同才标记为已路由(见 skill.service.ts)。随后actualProviderItemIdisAutoModelRouted会被用于信用计费预检查与结果记录。

六、路由引擎:五级优先级链

AutoModelRoutingService.route()(auto-model-router.service.ts)实现了五级优先级链:

1. 规则路由(Rule-based routing) ← DB 规则,按场景 + 条件匹配 2. 工具路由(Tool-based routing) ← 针对特定工具集 key 的环境变量覆盖 3. 随机选择(Random selection) ← AUTO_MODEL_ROUTING_RANDOM_LIST 环境变量 4. 内置优先级列表(Built-in priority) ← AUTO_MODEL_ROUTING_PRIORITY 硬编码列表 5. 首个可用模型(First available) ← context.llmItems[0]

若输入不是 Auto 模型(isAutoModel()返回 false),则原样返回该项,不执行任何路由——这是整个引擎的短路保护。

路由前会通过buildModelMap()(同文件第 668-683 行)构建候选模型索引:同时以config.modelIditem.itemId作为 key 建 Map,并且排除capabilities.reasoning === true的推理模型(推理模型不参与自动路由)。双 key 索引意味着路由规则中的目标既可以是模型 ID,也可以是 provider item ID,对模型版本升级更鲁棒。

每次成功路由都会以genRoutingResultID()生成结果 ID,并在每个优先级命中后调用saveRoutingResult()异步写入auto_model_routing_results表(fire-and-forget,写失败仅记日志不阻塞主流程)。

6.1 优先级 1:规则路由(主路径)

规则从auto_model_routing_rules表加载,按scene过滤且enabled=true,按priority DESC排序(同优先级按ruleId ASC稳定排序,见RuleCache.fetchAndCacheorderBy)。

规则匹配(所有已定义条件必须同时满足——AND 逻辑,见RuleRouter.matchCondition):

  • condition.toolsetInventoryKeys:请求中任一激活工具集的 key 出现在该列表中即命中(按toolset.toolset.key匹配);
  • condition.inAutoModelTrial:用户必须处于前 N 次 Auto 调用的试用期内。

规则目标决定目标模型(target 内部优先级):

  1. target.model— 固定单一模型 ID(字符串)
  2. target.models— 从数组中随机选取
  3. target.weights— 按权重加权随机,来自[{ model, weight }]

对应的选择逻辑在RuleRouter.selectModelFromTarget:固定路由直接查 Map;随机路由会先过滤掉modelMap中不存在的模型再随机;加权路由则过滤weight <= 0或不可用的项后,按"随机数落在累积权重区间"的经典加权随机算法选择。所有目标模型 ID 都基于context.llmItems构建的modelMap匹配。

规则缓存RuleCache):按场景做内存缓存,5 分钟 TTL + 3 分钟后台刷新;缓存过期时返回旧值并在后台异步刷新(stale-while-revalidate),刷新失败回退到已有缓存或空数组;首次访问才阻塞查询。NODE_ENV=development时完全绕过缓存、每次直查数据库,方便开发调试。服务启动时会预热agentcopilot两个常用场景,且定时器unref()不会阻止进程退出。

6.2 优先级 2:工具路由(已废弃)

完全由环境变量控制,且仅当mode === 'node_agent'(即场景为agent)时生效(见routeByTools的前置判断)。命中逻辑为:请求工具集 key 与TARGET_TOOLS有交集 → 使用MATCHED_MODEL_ID;否则使用UNMATCHED_MODEL_ID

环境变量说明
AUTO_MODEL_ROUTING_TOOL_BASED_ENABLEDtrue时启用
AUTO_MODEL_ROUTING_TOOL_BASED_TARGET_TOOLS逗号分隔的工具集 inventory keys,命中则触发匹配模型
AUTO_MODEL_ROUTING_TOOL_BASED_MATCHED_MODEL_ID存在任一目标工具时使用的模型 ID
AUTO_MODEL_ROUTING_TOOL_BASED_UNMATCHED_MODEL_ID不存在目标工具时使用的模型 ID

6.3 优先级 3:随机选择(已废弃)

AUTO_MODEL_ROUTING_RANDOM_LIST配置逗号分隔的模型 ID 列表,每次调用随机选一个。底层实现是selectAutoModel()(auto-model.ts):解析环境变量、按逗号切分、Math.random()取随机下标;列表为空返回null,从而让位给下一优先级。

6.4 优先级 4:内置优先级列表(已废弃)

硬编码在 packages/utils/src/auto-model.ts 的AUTO_MODEL_ROUTING_PRIORITY

global.anthropic.claude-opus-4-5-20251101-v1:0 ← 主选 global.anthropic.claude-sonnet-4-5-20250929-v1:0 ← 回退 1 us.anthropic.claude-sonnet-4-5-20250929-v1:0 ← 回退 2

按顺序在用户可用项中查找,第一个在modelMap中存在的模型胜出

6.5 优先级 5:首个可用(最终回退)

直接取context.llmItems[0]。只要用户至少拥有一个 LLM item 就必定成功;若llmItems为空,则抛出ProviderItemNotFoundError("Auto model routing failed: no model available")。

七、Auto Model Trial:新用户体验钩子

AutoModelTrialService(auto-model-trial.service.ts)为规则路由提供inAutoModelTrial条件:新用户的前 N 次 Auto 调用可被一条带inAutoModelTrial: true条件的规则路由到更强大的模型。

其实现要点(checkAndUpdateTrialStatus):

  • 计数器存 Redis,30 天 TTL:key 前缀为auto-model-trial:+ userId;DB 仅在缓存未命中时查询,查得后写回 Redis(setex)避免后续穿透;
  • 阈值由AUTO_MODEL_TRIAL_COUNT控制,默认20,读取逻辑见getAutoModelTrialCount()
  • 计数器 fire-and-forget 自增:通过redis.incr(cacheKey, ttlSeconds)原子完成INCR + EXPIRE,不阻塞主链路;
  • DB 查询用上限截断findMany只取trialCount + 1条记录(按createdAt ASC)来判定是否超出阈值,避免全表count(*)
  • Redis 出错时默认inTrial = false(安全降级):即使试用计数不可用,也绝不阻塞正常请求,路由会继续走向其余优先级。

注意一个细微语义:currentCount表示"本次请求之前的累计次数",inTrial = currentCount < trialCount,即第 N 次请求时currentCount === N-1,仍在试用期内。

八、场景映射:AgentMode 到 Scene

getModelSceneFromMode(mode)(models.ts)将AgentMode映射为场景字符串:

modescene
copilot_agentcopilot
node_agentagent
其他chat

场景有两个用途:

  1. 过滤 DB 中的路由规则(auto_model_routing_rules.scene列);
  2. 决定modelProviderMap中哪一项参与路由——只有主场景模型会被路由;titleGeneration、queryAnalysis、image、video、audio 等辅助模型永不路由,保证标题生成、查询分析等内部调用使用固定低成本模型。

九、显示与计费分离:关键不变量

整个体系最重要的不变量是:param.modelItemId从不被修改,始终持有 Auto itemId。这样前端展示与后台计费可以完全解耦:

字段用途
action_results.provider_item_idAuto itemId前端展示——始终显示 "Auto"
action_results.model_name真实模型 IDLangfuse 追踪、可观测性
routedData.originalItemIdAuto itemId计费时用于取 Auto 的信用费率
routedData.originalModelId'auto'Langfuse 标记,区分路由流量
routedData.isRoutedtrue计费分支触发器

计费规则:当routedData.isRouted为 true 时,计费使用Auto 模型的creditBilling(输入 80 / 输出 400,每 1M tokens),而不是真实模型的费率;token 数量则来自真实模型的实际用量。该逻辑在 skill-invoker.service.ts 中体现:读取config.routedData,命中isRouted后取originalItemId对应的 Auto 项费率计费。这意味着运营商可以预先锁定 Auto 的固定费率,把底层模型切换带来的成本波动吸收在路由层。

十、数据库表:规则存储与路由观测

10.1auto_model_routing_rules

存放路由规则,规则变更通过 DB 管理,无需发版部署代码

类型说明
rule_idString唯一规则 ID
rule_nameString可读名称
sceneString"copilot""agent"
conditionJSON匹配条件(AND 逻辑)。空{}匹配一切
targetJSON目标模型选择配置
priorityInt越大越先评估
enabledBoolean软禁用,无需删除

Condition schema

{ "toolsetInventoryKeys": ["fal_image", "fal_video"], "inAutoModelTrial": true }

Target schema(target 内部优先级:model > models > weights):

{ "model": "claude-sonnet-4-5-20250929" } { "models": ["claude-sonnet-4-5", "gemini-flash"] } { "weights": [{ "model": "claude-haiku", "weight": 70 }, { "model": "gemini-flash", "weight": 30 }] }

本地开发库当前状态:无任何规则(表为空),所有路由均落到环境变量回退链。

巡检查询—— 列出已启用的规则,并输出人类可读的模型名(同时覆盖三种 target 类型,权重按大小降序拼接):

WITH weight_labels AS ( SELECT r.pk, string_agg( COALESCE(pi_item.name, pi_model.name, elem->>'model') || ' (' || (elem->>'weight') || ')', ', ' ORDER BY (elem->>'weight')::int DESC ) AS label FROM refly.auto_model_routing_rules r, jsonb_array_elements(r.target::jsonb->'weights') AS elem LEFT JOIN refly.provider_items pi_item ON pi_item.item_id = elem->>'model' LEFT JOIN refly.provider_items pi_model ON pi_model.config::jsonb->>'modelId' = elem->>'model' AND pi_model.deleted_at IS NULL WHERE r.target::jsonb ? 'weights' GROUP BY r.pk ), models_labels AS ( SELECT r.pk, string_agg( COALESCE(pi_item.name, pi_model.name, elem#>>'{}'), ', ' ) AS label FROM refly.auto_model_routing_rules r, jsonb_array_elements(r.target::jsonb->'models') AS elem LEFT JOIN refly.provider_items pi_item ON pi_item.item_id = elem#>>'{}' LEFT JOIN refly.provider_items pi_model ON pi_model.config::jsonb->>'modelId' = elem#>>'{}' AND pi_model.deleted_at IS NULL WHERE r.target::jsonb ? 'models' GROUP BY r.pk ) SELECT r.rule_id, r.rule_name, r.scene, r.priority, r.enabled, r.condition, CASE WHEN r.target::jsonb ? 'model' THEN COALESCE(pi_item.name, pi_model.name, r.target::jsonb->>'model') WHEN r.target::jsonb ? 'weights' THEN wl.label WHEN r.target::jsonb ? 'models' THEN ml.label END AS target_model FROM refly.auto_model_routing_rules r LEFT JOIN refly.provider_items pi_item ON pi_item.item_id = r.target::jsonb->>'model' LEFT JOIN refly.provider_items pi_model ON pi_model.config::jsonb->>'modelId' = r.target::jsonb->>'model' AND pi_model.deleted_at IS NULL LEFT JOIN weight_labels wl ON wl.pk = r.pk LEFT JOIN models_labels ml ON ml.pk = r.pk WHERE r.enabled = true ORDER BY r.scene, r.priority DESC;

10.2auto_model_routing_results

每次 Auto 模型调用写一行记录,异步(非阻塞)写入,为运营与可观测性提供完整的路由决策审计。

说明
routing_result_id唯一结果 ID(前缀rrt-
user_id触发调用的用户
action_result_id关联action_results,可全链路追踪
action_result_versionaction result 版本号
scene"copilot""agent"
routing_strategy使用的策略(见下)
matched_rule_id/matched_rule_name命中的规则(仅 rule_based)
original_item_id/original_model_idAuto item ID +"auto"
selected_item_id/selected_model_id实际路由到的模型
created_at时间戳

路由策略枚举值(与RoutingStrategy枚举一一对应):

  • rule_based— 命中 DB 规则
  • tool_based— 命中环境变量工具路由
  • fallback_random_selection— 从AUTO_MODEL_ROUTING_RANDOM_LIST随机
  • fallback_built_in_priority— 命中AUTO_MODEL_ROUTING_PRIORITY列表
  • fallback_first_available— 取llmItems[0]

唯一约束:(action_result_id, action_result_version)——一次执行只有一次路由决策。同时该表还承担了试用期计数的 DB 来源:试用服务缓存未命中时按(userId, createdAt)索引统计该表记录数。

十一、环境变量汇总

变量默认值说明
DEFAULT_MODEL_AGENTagent 场景的默认 item ID 或模型 ID
DEFAULT_MODEL_COPILOTDEFAULT_MODEL_CHATcopilot 场景的默认值
DEFAULT_MODEL_CHATchat 基础默认值(已废弃,勿设 Auto)
AUTO_MODEL_TRIAL_COUNT20视为"试用期"的前 N 次调用数
AUTO_MODEL_ROUTING_RANDOM_LIST随机回退的逗号分隔模型 ID 列表
AUTO_MODEL_ROUTING_TOOL_BASED_ENABLEDfalse启用工具路由
AUTO_MODEL_ROUTING_TOOL_BASED_TARGET_TOOLS触发匹配模型的工具集 keys
AUTO_MODEL_ROUTING_TOOL_BASED_MATCHED_MODEL_ID存在目标工具时的模型
AUTO_MODEL_ROUTING_TOOL_BASED_UNMATCHED_MODEL_ID不存在目标工具时的模型

十二、当前实现状态与路线图

截至文档记录的 2026-02-28,各能力状态如下:

功能状态
虚拟 Auto provider + DB provider_item✅ 已完成
isAutoModel()检测工具✅ 已完成
prepareModelProviderMap()集成✅ 已完成
param.modelItemId保留用于展示✅ 已完成
规则路由引擎 + 缓存✅ 已完成
工具路由(环境变量控制)✅ 已完成
随机选择回退✅ 已完成
内置优先级列表回退✅ 已完成
Auto 试用服务(Redis 计数器)✅ 已完成
auto_model_routing_results持久化✅ 已完成
routedData计费注入✅ 已完成
计费使用 Auto 费率(而非真实模型费率)✅ 已完成
生产环境 DB 路由规则✅ 通过 DB 管理
本地开发 DB 路由规则⚠️ 表为空——落到环境变量回退链
规则目标使用itemId(升级鲁棒)❌ 当前为modelId——升级期间脆弱
语义路由❌ 未实现(中期路线图)
ML 路由❌ 未实现(长期路线图)
级联路由 / 失败重试❌ 未实现

值得关注的一点:buildModelMap已经同时索引了itemIdmodelId,这意味着路由规则的 target 字段在技术上可以填itemId(规则目标查找会同时命中两种 key),文档所指的"规则 target 使用 itemId"改造更多是规范层面的收尾工作。

结语

Refly 的 Auto Model 是一套"以小博大"的工程方案:不引入独立的路由中间件,而是把 Auto 注册为一个普通 provider item,复用既有模型查询与计费管道;路由决策本身则收敛为一条五级回退链,从 DB 规则到环境变量再到硬编码列表逐级兜底,保证任何配置缺失时都有可用模型。对于希望在自己的 Refly 部署中落地智能模型调度、控制模型成本的开发者而言,核心操作路径非常清晰:通过DEFAULT_MODEL_AGENT/DEFAULT_MODEL_COPILOT把默认模型指向 Auto,通过AUTO_MODEL_TRIAL_COUNT调节新用户体验阈值,再通过auto_model_routing_rules表按场景和工具集精细控制路由目标——全程无需改代码、无需重新部署,这正是该设计最大的工程价值。

【免费下载链接】reflyThe first open-source agent skills builder. Define skills by vibe workflow, run on Claude Code, Cursor, Codex & more. Build Clawdbot 🦞· APIs for Lovable · Bots for Slack & Lark/Feishu · Skills are infrastructure, not prompts.项目地址: https://gitcode.com/GitHub_Trending/re/refly

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

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

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

立即咨询