news 2026/9/10 7:40:25

OpenHuman 模型层迁移 Phase 3:RouterProvider 到 crate ModelRegistry 的设计、验证与落地全解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
OpenHuman 模型层迁移 Phase 3:RouterProvider 到 crate ModelRegistry 的设计、验证与落地全解

OpenHuman 模型层迁移 Phase 3:RouterProvider 到 crate ModelRegistry 的设计、验证与落地全解

【免费下载链接】openhumanOpenHuman is an open source personal AI for Mac, Windows and Linux — local-first memory, agent orchestration, and deep research.项目地址: https://gitcode.com/GitHub_Trending/op/openhuman

导读

本文以 OpenHuman 仓库中的历史设计文档《Phase 3 — RouterProvider → crate ModelRegistry: design & ground truth》为核心骨架,剖析一次"看似需要上游改动、实际已在接缝层完成"的模型注册表迁移:它解释了为什么assemble_turn_harness已经把主模型与各工作负载 tier 路由注册进 tinyagents crate 的ModelRegistry,以及迁移真正剩余的 host 侧工作(Motion A 与 Motion B)是什么。读完本文,你将掌握TurnModels的结构与访问器设计、TurnModelSource/build_turn_models_crate的构建链路、OH_WORKLOAD_ROUTER的声明式路由与能力门控,以及 provider-string 语法在 crate 原生模型构建中的实际作用,并能在当前仓库源码中逐行印证这些设计。


1. 文档定位:一份"被取代但仍具历史价值"的设计理据

该文档(docs/tinyagents-phase3-router-registry-design.md)的状态标注非常特殊:Status: superseded on 2026-07-22,被 docs/tinyagents-migration-plan-2026-07-22.md 取代。文档本身明确指出:

Phase 3's client cutover landed in #4783/#4784; this document is retained as historical design rationale, not current status.

即 Phase 3 的客户端切换已经在 #4783/#4784 两个 PR 中完成,本文档仅作为历史设计理据保留。它并非当前状态的描述,而是研究以下三块内容后沉淀的"ground truth":

  • crate 侧:vendor/tinyagents/src/harness/modelharness/agent_loopharness/retryregistry/
  • host 接缝层:src/openhuman/agent/tinyagents/{mod,routes,model}.rs
  • 推理层:inference/provider/{router,factory}.rs

同时它关联了两份姊妹文档:docs/tinyagents-inference-migration-plan.md(Phase 3 计划)与 docs/tinyagents-drift-ledger.md(P1-9 漂移台账),并对应 issue #4249。因此,阅读本文的正确姿势是:把它当作"设计结论与实现路径"的说明书,再结合当前仓库源码验证其落地状态。


2. 前提纠正:注册表迁移"其实已经完成了"

这是全文最重要的洞见。原计划把 Phase 3 表述为"在vendor/tinyagents中实现 RouterProvider → crate ModelRegistry"(即以为需要给上游 crate 补功能),但调查结果显示:注册表迁移已经在接缝层完成,crate 本身已经具备 host 路由器所需的全部能力,不存在硬性的上游缺口

证据集中在assemble_turn_harness(当前仓库中实现于 src/openhuman/agent/tinyagents/mod_part_04.rs),它已经完成以下四件事:

  1. 注册主模型与全部工作负载 tier 路由到 crateModelRegistry主模型通过harness.register_model(model, primary)+set_default_model(model)成为 registry 默认与分发目标;每条 workload-tier 路由通过循环harness.register_model(name, route_model)追加为附加注册项(见mod_part_04.rscapability_registry.replace_model/register_model的对应逻辑)。

  2. 跨路由 fallback 以 crate 的RunPolicy.fallback承载通过routes::route_fallback_policy(model)生成同族 fallback 链并写入policy.fallback,由agent_loop::invoke_model_resolving原生遍历。当前实现见 src/openhuman/agent/tinyagents/routes.rs 与mod_part_04.rspolicy.fallback = route_fallback.clone()的装配。

  3. 视觉能力门控由 crate 完成RequiredCapabilitiesMiddlewareimage_in盖戳到ModelRequest.required_capabilities上,crate 的resolve_request再用ModelProfile::satisfies过滤命名候选模型——不满足能力需求的模型在分发前即被拒绝。

  4. 发出FallbackSelected对等事件通过FallbackObserverMiddleware把 crate 内部的静默 fallback 转换变成 OpenHuman 进度/可观测性桥接上可见的AgentEvent::FallbackSelected(详见下文第 6 节)。

2.1 crate 缺失的两件事,host 其实都不需要

按 crate 审计,crate 确实缺少两个能力,但host 都不需要

crate 缺少的能力host 为何不需要
基于能力的选择(扫描 registry 找一个有能力的模型)host 从不这样做——调用方在上游就选好 tiersubagent_runner/ops/graph.rs在图片轮次直接设model = vision-v1;中间件只做校验/强制执行,不做选择
能力感知的fallbackhost 的 fallback 链是手工构造且保证能力安全的(routes::same_family_fallbacksvision-v1 → [],同族文本备选全部具备文本能力),所以 crate 不做能力过滤的next_after在这里是无害的

结论:RouterProvider 其实已经被投影到 crate registry 上了。剩余工作全部在 host 侧,且不需要发布新的 crate 版本


3. 真正存活的东西:RouterProvider 的角色收缩

RouterProvider(位于inference/provider/router.rs)作为宿主Provider存活的唯一理由变成了被包裹 Provider 内部的"按调用 BYOK 别名解析器":分发时把 tier 别名(reasoning-v1等)映射为具体的(provider, model)。这一层之所以必要,是因为 issue #2079 指出:原始别名在 OpenAI/DeepSeek 上会返回 400 错误。

注册进 harness 的 tier 模型仍然是包装了宿主ProviderProviderModel。所以 harness 持有Provider只有一个原因:build_turn_models需要用同一个Provider句柄构建"每轮主模型 + 路由 + 摘要器"的ProviderModel集合(对应tinyagents/mod.rs:1139-1175routes::build_route_models)。

由此得出两个独立的后续动作,按优先级排序:

Motion A — Harness 持有 crateChatModel(当前 Phase,仅 host 侧改动)

build_turn_models的构造位置从 harness 轮次路径(agent/harness/graph.rs::run_channel_turn_via_graph、session 轮次路径)上移到生产者/工厂边界,让agent/只持有 crate 模型类型、不再持有Provider。harness 轮次路径今天在构建前读取的四个Provider方法,全部已可脱离 trait 获得:

harness 现在读取的内容crate 原生来源
provider.supports_native_tools()TurnModels.primary.profile().tool_calling
provider.supports_vision()…profile().modalities.image_in
provider.effective_context_window(model).await构建时解析 →…profile().max_input_tokens
provider.telemetry_provider_id()新增TurnModels.provider_id: String

于是TurnModels成为 harness 持有的唯一单元。设计要点如下:

  1. 扩展TurnModels:新增provider_id: String以及小型访问器(native_tools()supports_vision()context_window()),全部读取primary.profile()。这删除了 harness 图中所有对裸Provider的读取。
  2. 新增工厂入口inference::provider::factory::create_turn_models(role_or_model, config, temperature) -> anyhow::Result<TurnModels>:内部走既有create_chat_provider路径构建Provider,解析异步的effective_context_window,计算telemetry_provider_id,再调用接缝层build_turn_models。所有Provider命名留在inference/provider/与接缝层。
  3. 生命周期build_turn_models(provider, model)粒度构建,harness 今天就是每轮构建全新的TurnModels。保持这个行为——由生产者(channels processor、session 轮次入口、subagent runner)在轮次请求组装时构建TurnModels,而不是 harness 图来做。error_slot保持每轮一个(这是正确的——它只恢复本轮的 provider 错误)。
  4. 类型替换AgentTurnRequest.provider: Arc<dyn Provider>改为携带构建好的TurnModels(连同已有的model/provider_name);Agent/AgentBuilder.provider同理。run_channel_turn_via_graph与 session 的turn/graph.rs接收TurnModels,通过访问器读能力位,直接传给run_turn_via_tinyagents_shared(它本来就已经接收TurnModels)。IntelligentRoutingProvider仍然是Provider实现(provider 栈成员);工厂在build_turn_models之前把它包起来。

退出条件:没有任何agent/文件再命名Providertrait;ProviderModel只会在接缝层(model.rs/build_turn_models)构建,且只能通过工厂入口触达。行为零变化(同样的ProviderModel、同样的 registry/fallback 装配)。

Motion B — 注册模型改为 crate 原生(后续,最大的 LOC 收益)

用 crate 的providers::openai客户端替换ProviderModel包装器——这些客户端直接由配置构建(BYOK slugs、Ollama/LM Studio 的 base URL),把每 tier 的客户端直接注册进 registry,从而让RouterProvider的按调用别名解析消失(每个 tier 就是它自己的已注册客户端)。

这对应推理计划文档的Phase 2(客户端切换,删除compatible*.rs)加 Phase 3 的剩余部分,同时把 bespoke providers(managed backend、claude-code、codex)保留为 host 的ChatModel实现(Phase 4)。Motion B 不在 Motion A 范围内,但被 Motion A 解除阻塞。

值得注意:从 docs/tinyagents-migration-plan-2026-07-22.md 的审计记录看,Motion B 所描绘的"客户端层反转"后来确实发生了——managed backend、wire-equivalent BYOK slugs、openai/codex/custom slugs 都已成为 crate 原生ChatModel,crateModelRouter被采纳,compatible*.rs已删除并合并为legacy_provider.rs门面(对应 PR #4769、#4780、#4782、#4783、#4784)。


4. 当前源码中的落地证据(Motion A 已闭合)

4.1TurnModels:结构、访问器与语义

当前仓库中TurnModels定义于 src/openhuman/agent/tinyagents/mod_part_03.rs,与设计文档 Motion A 的构想完全一致:

pub(crate) struct TurnModels { /// 本轮有效/主模型(registry 默认 + 分发目标) primary: TurnChatModel, /// 附加工作负载 tier 路由(registry 名 → 模型),不含主模型; /// crate registry 跨它们解析 fallback/selection routes: TierRoutes, /// 上下文窗口摘要器模型(独立适配器实例, /// 其 provider 错误不触碰本轮的 error_slot) summarizer: TurnChatModel, /// 失败时恢复主模型原始(可 downcast 的)provider 错误 error_slot: crate::openhuman::agent::tinyagents::model::ModelErrorSlot, /// 提供方遥测 id(Langfuse 中为 {provider_id}.{model}), /// 构建时从源 Provider 捕获——harness 轮次路径不再读裸 Provider provider_id: String, /// 主模型有效上下文窗口(驱动上下文窗口摘要步骤), /// 由生产者/工厂在构建前解析——harness 图不再发起异步调用 context_window: Option<u64>, /// 源 provider 是否原生工具调用——仅用于选择历史后缀分发器 native_tools: bool, /// 源 provider 是否视觉能力——用于门控多模态占位符恢复 supports_vision: bool, }

对应的四个访问器provider_id()context_window()native_tools()supports_vision()全部是行为中立的只读方法(mod_part_03.rs第 55-75 行),印证了"seam-internal; behavior-neutral"的设计要求。

4.2TurnModelSource:构建链路与上下文窗口解析

src/openhuman/agent/tinyagents/mod_part_03.rs 的TurnModelSource是 Motion A 的关键抽象——它把Provider隔离在接缝层:

  • new_crate_native(role, config):crate 原生来源,build()时走build_turn_models_crate而非包装 provider;
  • new_crate_native_from_string(role, provider_string, config):triage 路径的 #1257 强制托管覆盖(build_remote_provider选出有效字符串),主模型用显式 provider-string 构建;
  • effective_context_window(&self, model):解析模型的有效上下文窗口——这是驱动上下文窗口摘要步骤的值。它在build()之前解析,使 harness 图不再发起任何异步Provider调用;解析时对本地运行时(Ollama / LM Studio)走context_window_for_model_with_local_fallback带回退;
  • build(model, temperature, context_window):组装TurnModels。crate 原生分支里,provider_id的推导规则是:openhuman/空/cloud"managed",否则取 provider-string 的:前缀(如ollamalmstudio),native_toolssupports_vision对本地 provider 为false
  • build_summarizer(model, temperature):构建独立的摘要器ChatModel(自带 error slot),供主轮次之外的摘要调用使用,调用方无需命名Providertrait。

4.3build_turn_models_crate:P3-B 的 crate 原生构建

build_turn_models_crate(mod_part_03.rs)是设计文档 Motion B 方向在 seam 层的早期投影:不再为每个 tier 包装一个宿主Provider,而是通过factory::create_turn_chat_model系列把每个 tier 构建成 crate 原生ChatModel(managed →OpenHumanBackendModel,local/cloud → crateOpenAiModel)。TurnModels的形状与build_turn_models完全一致,所以assemble_turn_harness无需改动。error_slot使用全新空槽——crate 原生模型直接暴露TinyAgentsError,没有可 downcast 的anyhow需要保留(Sentry 抑制不受影响)。

4.4 harness 图:读取访问器而非裸 Provider

src/openhuman/agent/harness/graph.rs 的run_channel_turn_via_graph现在接收TurnModelSource,构建流程完全符合 Motion A 的设计:

let context_window = source.effective_context_window(model).await; let turn_models = source.build(model, temperature, context_window)?; let native_tools = turn_models.native_tools(); let provider_id = turn_models.provider_id().to_string(); // 视觉能力门控多模态占位符恢复 if turn_models.supports_vision() && has_image_placeholders(&prepared) { … }

随后把turn_modelsprovider_idcontext_window直接传给共享接缝run_turn_via_tinyagents_shared——其中native_tools用于选择"原生 envelope vs prompt-guided text"的历史后缀分发器,supports_vision用于多模态占位符恢复与[IMAGE:…]/[FILE:…]标记展开。这正是文档表格中四行"harness reads → crate-native source"的代码级落地。


5. 可选的 crate 优化:能力感知的 fallback(非必需)

如果未来想要能力感知的 fallback(让"手工构造的链"不再是唯一安全网),crate 侧只需一行改动:在invoke_model_resolving中对FallbackPolicy::next_after的目标重新应用model_eligible过滤(对应路径vendor/tinyagents/src/harness/agent_loop/model_call.rs:186-204)。

设计文档给出的建议是:仅当 Motion B 引入能力分叉的 fallback 链时,才把它作为独立的小型上游 PR 提交。对 Motion A 而言完全不需要——因为当前 host 的 fallback 链(见下节)天然能力安全。


6. 声明式路由表与能力门控:OH_WORKLOAD_ROUTER

作为纵深补充,当前仓库已经把文档提到的"手工same_family_fallbacks+turn_required_capabilities"整合为一份声明式路由表OH_WORKLOAD_ROUTER(src/openhuman/agent/tinyagents/routes.rs),它是一个基于 crateModelRouter的静态表,同时回答route_fallback_policyturn_required_capabilities两个问题:

tier 路由fallback 链能力门控
chat-v1burst-v1
burst-v1chat-v1
reasoning-v1agentic-v1
agentic-v1reasoning-v1
coding-v1agentic-v1
summarization-v1chat-v1
vision-v1无(primary-only)要求image_in
hint:vision无(primary-only)要求image_in(同 gate)

关键设计语义(与文档第 1 节的能力安全论证一一对应):

  • 轻量对话兄弟chat-v1 ⇄ burst-v1、重型推理/agentic 兄弟reasoning-v1 ⇄ agentic-v1互为同族备选,全部文本能力安全;
  • coding-v1 → agentic-v1(coding 工具密集、与 agentic 相邻);
  • summarization-v1 → chat-v1(摘要搭乘通用聊天模型);
  • vision-v1image_in门控且 primary-only——文本 fallback 无法满足该门控,所以链为空,这正好是文档"vision-v1 → []"的声明式表达。

6.1RequiredCapabilitiesMiddleware:分发前拒绝不合格模型

src/openhuman/agent/tinyagents/routes.rs 的wrap_model实现:当request.required_capabilities为空时,用本轮推导出的能力集(今天仅视觉)盖戳request.with_required_capabilities(...)。这样在vision-v1轮次中,只有携带image_in能力的模型可以被选为分发目标,不合格的备选在分发前就被过滤——"校验/强制执行、不选择"的角色定位与文档完全吻合。

6.2FallbackObserverMiddleware:让静默 fallback 可见

crate 的 registry-backedRunPolicy.fallback遍历是静默的——不发出AgentEvent::FallbackSelectedFallbackObserverMiddleware(routes.rs)包装模型解析核心:成功时比较响应中的resolved_model与轮次主模型名,若不同即发生 fallback,于是发出对等事件并记录[fallback]日志。它从不重新发起调用,因此不增加额外 provider 分发(无双重 fallback)。该中间件仅在存在 fallback 链时安装(mod_part_04.rsif route_fallback.is_some()),并配合UsageCarryMiddleware完成每调用的成本用量捕获。


7. 首次实现切片(Motion A 的六步执行清单)

设计文档给出了精确到步骤的实现切片:

  1. TurnModels扩展:增加provider_idnative_tools()/supports_vision()/context_window()访问器(seam 内部、行为中立)——已在当前源码中完成。
  2. create_turn_models(...)工厂入口:包裹create_chat_provider+ 异步上下文窗口解析 +build_turn_models
  3. 裁剪run_channel_turn_via_graph:改为接收TurnModels并通过访问器读能力位(删除 4 处裸 provider 读取);channel 生产者(channels/runtime/dispatch/processor.rs)通过工厂构建TurnModels并放进AgentTurnRequest
  4. 重复执行:session 轮次路径 + subagent runner;替换Agent.provider
  5. 更新测试:改为构建TurnModels/crateMockModel,替代手工实现的Provider
  6. 验证:两个 Cargo world 全绿;json_rpc_e2e;在 mock-backend 轮次上确认 streaming/cost/tool-timeline 对等(#4460 / 零美元轮次 / tool-timeline 三个风险点)。

8. 验证与对等性锁(Verification & parity locks)

迁移不能破坏任何已确立的行为契约,以下内容必须全部保持,而这些现在都已由 crate 装配完成——Motion A 只移动模型在何处构建,不改变路由方式

  • Provider-string 语法"openhuman"(managed backend,model = config.default_model)、"cloud"/缺失(primary_cloud;迁移后 legacy custom inference_url 在 primary 仍指向 OpenHuman 时优先)、"ollama:<model>[@<temp>]""lmstudio:<model>[@<temp>]""mlx:<model>[@<temp>]""local-openai:<model>[@<temp>]""<slug>:<model>[@<temp>]"(cloud_providers 按 slug 建 key,按 auth_style 构建 crate 原生 OpenAI Bearer 客户端或 Anthropic 变体)。@<temp>后缀为 per-workload 温度覆盖,上游发送的 model id 不含后缀。该语法当前完整实现于 src/openhuman/inference/provider/factory.rs。
  • inference.*RPC 行为
  • tier 别名集合chat-v1/burst-v1/reasoning-v1/agentic-v1/coding-v1/summarization-v1/vision-v1/hint:vision)。
  • fallback 顺序:单一同族备选;vision 仅 primary。
  • 每逻辑调用一次的 FIFO 用量推送:charged-USD-over-estimate 优先级、优雅降级。
  • FallbackSelected事件

在 docs/tinyagents-migration-plan-2026-07-22.md 中可看到这些锁的最终状态:agent loop 自 #4249/#4399 起已运行在 tinyagents 上,Phase 0 漂移行全部 CLOSED,Phase 1 基本关闭;crate 版本固定在 v2.1.0(Cargo.toml:107声明tinyagents = { version = "2.1", features = ["sqlite"] }Cargo.toml:677[patch.crates-io]指向vendor/tinyagents)。


9. 结语:一次"无上游改动"的迁移范本

这份设计文档最有价值的启示在于它的前提纠正:迁移类任务的第一步不是"动手实现",而是"验证现状与计划的差距"。调查证明 registry 迁移已在接缝层完成、crate 能力已够用,剩余工作被精确收敛为 host 侧的两个动作(Motion A 立即可做、Motion B 顺水推舟),且都不需要 crate 发版。今天回看当前仓库,Motion A 的TurnModels/TurnModelSource/工厂链路均已落地,而 docs/tinyagents-migration-plan-2026-07-22.md 与 docs/tinyagents-drift-ledger.md 继续承载着行级漂移台账与后续工作包(削平legacy_provider.rs门面、合并routing//tool_timeout//tool_status//model_council/到 crate 原语、对账工具模型等)。对想要深入理解 OpenHuman 模型层架构的读者,建议按"本文 → 迁移总计划 → 漂移台账 →src/openhuman/agent/tinyagents/src/openhuman/inference/provider/源码"的顺序阅读,即可完整还原一次大型模型层迁移的设计、执行与沉淀全过程。

【免费下载链接】openhumanOpenHuman is an open source personal AI for Mac, Windows and Linux — local-first memory, agent orchestration, and deep research.项目地址: https://gitcode.com/GitHub_Trending/op/openhuman

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

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

考试信息报名系统实战:SpringBoot+Vue+MySQL全链路开发与并发控制

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/10 7:38:43

如何理解 Kotlin inline 函数在 JVM 与 klib 后端内联语义的差异?

如何理解 Kotlin inline 函数在 JVM 与 klib 后端内联语义的差异&#xff1f; 【免费下载链接】kotlin The Kotlin Programming Language. 项目地址: https://gitcode.com/GitHub_Trending/ko/kotlin 当你维护一个包含 inline 函数的库&#xff0c;并且依赖库升级后该函…

作者头像 李华
网站建设 2026/9/10 7:37:08

Multica 自建部署怎么开启 Prometheus 指标并保护 /metrics 端点

Multica 自建部署怎么开启 Prometheus 指标并保护 /metrics 端点 【免费下载链接】multica Make humans and AI agents work as one team — open-source and self-hostable. 项目地址: https://gitcode.com/GitHub_Trending/mu/multica 在自建 Multica 时&#xff0c;默…

作者头像 李华
网站建设 2026/9/10 7:36:42

车载智能互联盒子怎么选?从原理到实操的避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/10 7:36:17

Humanizer技能详解:去除AI味,让文字更有温度

做内容这些年&#xff0c;我越来越觉得“humanizer”不是一个软件的名字&#xff0c;而是一套基本功。最近这个词又上了热搜&#xff0c;连带“humanizer skill”一起被大量讨论&#xff0c;很多人以为它是什么黑科技&#xff0c;其实拆开来看&#xff0c;就是把人机感过重的文…

作者头像 李华
网站建设 2026/9/10 7:34:37

Ruff 的版本号规则怎么理解:minor 版本引入哪些不兼容变更

Ruff 的版本号规则怎么理解&#xff1a;minor 版本引入哪些不兼容变更 【免费下载链接】ruff An extremely fast Python linter and code formatter, written in Rust. 项目地址: https://gitcode.com/GitHub_Trending/ru/ruff 如果你把项目的 Ruff 从 0.15.x 升到 0.16…

作者头像 李华