Hoshimi

模型与账号行为

模型名如何路由到渠道,目录如何发现和缓存,能力和推理强度如何决定请求体,可用性探测,以及失败时如何换账号、重试、续期和记录额度。

本页讲网关在一个请求背后做的决定:把模型名交给哪个渠道,这个模型能做什么,reasoning_effort 最终变成了什么,失败时换不换账号、等不等、重不重试。日常使用不需要了解这些;当结果和预期不一样时,这里是答案。

模型名如何路由

请求里的 model 按下面的顺序解析:

  1. 写成 渠道/模型(如 kilo/some-model),且 渠道 是一个正在运行的渠道 id:直接交给它,斜杠后面的部分原样作为上游模型名。

  2. 只写模型 id:按内置的渠道顺序(cline、opencode、kilo、gemini、zcode、minimax、trae、qoder……)找第一个在目录中列出这个 id 的渠道。

  3. 都没找到,但只有一个渠道在运行:原样转发给它。

  4. 否则返回 404 model_not_found:

    Model '…' not found. Use `provider/model` or see /v1/models.

请求没有带 model 时使用 server.default_model;也没有设置时返回 model is required。

同一个 id 出现在多个渠道时,/v1/models 只列出第一个渠道的那一条。想指定渠道,就用 渠道/模型 的写法。

模型目录

每个渠道的模型列表来自上游自己的列表接口,能力信息来自 Hoshimi 内置的能力表和账号中的声明。

  • 缓存 5 分钟:同一渠道的列表在 5 分钟内不会重复拉取。
  • 防止异常缩水:新拉到的列表不到上一次的一半时,先保留旧列表一个周期,并在日志里警告 the catalogue came back at N of M models (likely a partial answer);再过一个周期仍然如此,才接受新列表。
  • 失败时保留旧的:拉取失败时保留上一次成功的目录,30 秒后再试。从未成功过的渠道使用它内置的后备列表;后备列表只是历史数据,不代表这些模型现在可用。
  • 跨重启保留:每次成功的目录保存在数据目录的 catalog-cache/ 中,启动时先恢复(只恢复 7 天以内、账号和选项完全相同的快照),然后立即在后台刷新。

网页界面的 模型 页和 Playground 通过 GET /api/v1/catalog/stream(SSE)加载目录:先发出已缓存的结果,再按渠道逐个发出新结果。拉取最多同时进行 8 个;一轮最多等 20 秒,没完成的渠道标记为超时,已经收到的模型保留。标准的 /v1/models 仍然是普通 JSON。

能力信息

管理接口 GET /api/v1/models 给出每个模型的能力:

字段含义
vision能否读图
reasoning是否推理(思考)
tools是否支持工具调用;null 表示不知道
context_window、max_output上下文长度和最大输出;没有资料时为 131072 和 32768
can_disable_thinking能否关闭思考
region_sensitive可用性是否取决于出口地区
free是否免费:true、false 或 null(不知道)
efforts、effort_default这个模型自己的推理强度档位和默认档位
probe最近一次可用性探测的结果
unavailable是否正被网关记为失效(见下文)

能力的来源,优先级从高到低:

  1. 渠道内置的能力表,以及账号或渠道选项中的声明(如 agg 的 model_specs,见 agg 聚合渠道);
  2. 上游列表本身带的元数据(部分渠道);
  3. models.dev,但只补充没有任何声明的模型的 vision 和 reasoning 两项。

models.dev 不提供上下文长度、输出上限和推理档位:同一个 id 在不同站点、不同套餐下的限制不同,这些必须描述你实际使用的那个端点。Hoshimi 内置了一份离线快照,联网后在后台拉取最新数据,只影响之后的查询,不会拖慢请求;拉取失败时保留已有信息。

free 是关于上游价格或访问方式的证据,不保证额度无限;没有证据时就是 null。本地模型不会自动标为免费。各站点的判断依据见 pi-free 迁移与差异。

推理强度

只有调用方明确给出档位时,网关才会改写请求:

  • Chat Completions 的 reasoning_effort,或 Responses 风格的 reasoning.effort;
  • 没有给出档位时请求体原样发送,调用方自己设置的 max_tokens 保持不变。Playground 的 自动(服务端默认) 不发送档位。
  • 原生 Anthropic Messages 请求(直接转发到支持 Messages 的上游时)保留调用方自己的 thinking 和输出设置,不经过这里的规则。

给出档位后,按模型分三种情况:

模型处理方式
有自己的档位菜单(efforts 不为空)选出档位,把这个模型声明的补丁合并进请求体(例如写入 reasoning_effort,或 thinking、output_config.effort);max_tokens 设为模型输出上限与调用方上限中较小的一个
推理模型但没有菜单用输出上限控制思考量:light 2,048、balanced 8,192、deep 用满模型的输出上限
不推理的模型不改写

档位名称的对应:

调用方发送无菜单模型(阶梯)有菜单的模型
minimal、lowlight菜单中有同名档位就用它;否则 minimal 换成 low
mediumbalanced菜单中有就用它
highdeep菜单中有就用它
xhigh、maxdeep菜单中有同名档位就用它;否则 xhigh 换成 max
light、balanced、deep原样分别换成 low、medium、high
  • 菜单模型经过上面的对应后仍然找不到档位时,使用它的默认档位;阶梯模型收到不认识的档位时使用 balanced。
  • 菜单模型请求中原有的 reasoning_effort、reasoning 字段,如果不在补丁里,会被移除;max_completion_tokens 统一换成 max_tokens。
  • 不能关闭思考的模型(can_disable_thinking 为 false),阶梯上限加倍,因为思考和回答共用同一个上限。
  • 阶梯的实际上限还会被模型的输出上限和调用方自己更小的 max_tokens 进一步压低,但最低 512;所以明确选择档位时,低于 512 的调用方上限会被提高到 512。菜单模型最低为 1。
  • 阶梯模型的 reasoning_effort 和 reasoning 字段会被移除,因为上游并不执行它们,只有 max_tokens 起作用。

各站点的菜单写法不同:例如 Kilo 用 reasoning 对象,MiniMax 和 ZCode 用 output_config.effort,CodeArts 用 thinking.type,Raccoon 用 extra_body.thinking,Gemini Code Assist 的 gemini-3.8-flash 提供 low、medium、high、tiered(由模型自己决定)。不要假设所有模型都有同样的档位,用 GET /api/v1/models 查看每个 id 的 efforts 和每一档对应的上限。

可用性探测

有些渠道的模型列表只是一串 id,说不出这个出口实际能用哪些模型。探测用最小的请求(发送 ping,最多 64 个 token)逐个问一遍。

在 模型 页点 探测,或调用管理接口:

# 探测默认的渠道
curl -X POST http://127.0.0.1:10600/api/v1/probe -H "Authorization: Bearer <工具访问密钥>"

# 只探测指定的模型
curl -X POST http://127.0.0.1:10600/api/v1/probe \
  -H "Authorization: Bearer <工具访问密钥>" -H "Content-Type: application/json" \
  -d '{"models": ["kilo/<model-id>", "cline/<model-id>"]}'

# 读取结果
curl http://127.0.0.1:10600/api/v1/probe -H "Authorization: Bearer <工具访问密钥>"
  • 不指定模型时探测 opencode、nvidia 和 agg 这几个列表无法说明可用性的渠道。Kilo 的免费列表已经由上游筛选过,不在默认范围内。
  • 探测在后台进行,同时最多 2 个,每个最多等 45 秒。已有一轮在进行时,再次发起不会叠加,返回 started: false。
  • 无法解析的模型名放在返回的 unresolved 中。
  • 结果保存在 hoshimi.db 中,重启后仍在,并附在 模型 页和 GET /api/v1/models 的每一行上。
结果界面显示含义
available可用模型产生了内容或 token
region-blocked地区受限这个出口所在的地区被拒绝
unavailable不可用针对这个模型的拒绝
throttled限流额度不足,没能问到模型
unknown未知凭据、网络或服务器出错,无法判断模型本身

只有 unavailable 是关于模型本身的结论;地区、额度和未知的结果都不说明模型不存在。Playground 为图片寻找描述模型时,会跳过 unavailable 和 region-blocked 的模型。

记住失效的模型

正常请求中,网关也会记住上游说“没有这个模型”的情况:

  • HTTP 404 或 410,或 400、405、406、415、418、422、451 的响应正文明确说模型不存在(如 model not found、does not exist、不存在、已下线),算作一次“这个模型失效”。
  • 正文同时提到额度、限流、余额、未登录、凭据等账号问题时不算;401、402、429 和 5xx 无论正文说什么都不算。
  • 渠道中被问到的账号都这样回答,才记一次。

记下之后:

  • 从第一次记录起 10 分钟内,对这个模型的请求直接返回 503 model_unavailable(… is temporarily unavailable: the station stopped answering for it.),不再访问上游;
  • 10 分钟内累计 3 次,变为本次运行期间一直失效;
  • 模型重新正常回答时清除记录;
  • POST /api/v1/models/unavailable/reset 或重启服务器清除所有记录。

这份记录只在内存中,与保存在数据库里的探测结果不同。被记为失效的模型在 模型 页和 GET /api/v1/models 中标为 unavailable,但不一定从列表中消失。

账号轮换与重试

一个渠道有多个账号时,请求从第一个可用的账号开始;跳过需要重新登录和正在暂停轮换的账号。失败后按失败的类型决定下一步:

失败类型典型的上游回答网关的做法
凭据被拒401,或正文是凭据问题的 403先尝试刷新这个账号的令牌,刷新成功就用它重试;否则换下一个账号
权限不足正文不是凭据问题的 403不换账号,直接返回
额度或限流402、429,或正文提到额度、限流、余额不足把这个账号暂停轮换,然后换下一个账号
策略拦截CodeBuddy 业务码 11140,或“安全审核”等暂停这个账号 30 分钟,不刷新令牌,换下一个账号
模型饱和CodeBuddy 业务码 14003(只提供 SWITCH_MODEL),或“模型繁忙”等不换账号,直接把上游的说明返回给调用方:所有账号都会遇到同样的情况
模型不存在404、410 等(见上一节)继续问其他账号;全部如此时记为失效
可恢复的错误408、409、425、500、502、503、504、507、Cloudflare 521–527、Anthropic 529,以及不在任何表中的状态码换下一个账号
请求本身的问题400、405、406、415、418、422、451不换账号,直接返回:换哪个账号都一样

规则:

  • 一个请求最多尝试 server.max_account_retries(默认 3)个不同的账号。
  • 额度暂停的时长:优先用响应头 Retry-After,其次用正文中写明的重置时间(如 将在 2026-09-11 18:08:17 UTC+8 重置、reset at … UTC+8,按其中的时区换算),都没有时暂停 1 小时。已有的暂停不会被缩短。
  • 账号都问过了,而最后一次失败是可恢复的错误(不是凭据、额度、策略或模型不存在)时,等一会儿再从头试一轮,最多两次。等待时间优先用 Retry-After 或正文中的重置时间,最长 8 秒;Retry-After: 0 表示立即重试。没有时间时随机等待(上限从 0.25 秒起翻倍,最长 8 秒)。
  • 已经向调用方输出了内容的请求,绝不会自动重放,以免同一段回答出现两次。
  • 渠道里没有任何可以尝试的账号(都需要登录或都在暂停中)时,返回 502:Provider '…' exhausted all accounts.

暂停轮换的账号和原因显示在 渠道 页(暂停中,如 被限流、被策略阻止)和 GET /api/v1/credits 的 cooled 中。暂停记录只在内存中,重启后清空。

凭据续期

带有刷新令牌的账号由续期定时器维护(server.auto_refresh):

  • 启动 30 秒后第一轮,之后每 30 分钟一轮;
  • 每轮续期 1 小时内到期的账号;没有记录过期时间的账号每轮都续期;
  • 续期成功后,新的令牌只写回 config.json 中对应的那一个账号;
  • 上游拒绝刷新令牌本身(如 invalid_grant,或没有说明原因的 401)时,账号被标为 需要登录,不再参与轮换,直到重新登录;其他失败下一轮再试。单纯的 403 不会让账号作废。

请求中遇到凭据被拒时也会立即续期一次。续期状态和手动续期:

curl http://127.0.0.1:10600/api/v1/refresh -H "Authorization: Bearer <工具访问密钥>"

curl -X POST http://127.0.0.1:10600/api/v1/refresh \
  -H "Authorization: Bearer <工具访问密钥>" -H "Content-Type: application/json" \
  -d '{"provider": "cline", "force": true}'

force 表示不管是否快到期都续期;标为需要登录的账号即使 force 也会跳过。网页上对应 渠道 页的 立即续期 和 续期所有账户。

余额、签到与被动额度

余额和签到

GET /api/v1/credits 读取各账号的余额、暂停轮换的账号和签到状态;POST /api/v1/credits/claim(可带 {"provider": "…"})立即签到;GET /api/v1/credits/checkins 列出签到历史。没有余额或签到接口的渠道会被跳过。

开启 server.auto_checkin 时,启动 30 秒后进行第一轮签到,之后每 24 小时一轮。签到按北京时间(UTC+8)的日历日计算;当天已有账号签到成功(或站点回答今天已签过)后,同一天不再重复。签到逐个账号依次进行,不并发,以免触发站点的风控。每次结果(定时的和手动的)都记入 365 天的历史。

被动额度

上游的每个回答都可能带着额度信息。网关在推理请求的响应头里读取剩余量和上限,不额外发请求。按下面的顺序,取第一对能解析的:

剩余量上限
x-ratelimit-remaining-requestsx-ratelimit-limit-requests
x-ratelimit-remainingx-ratelimit-limit
ratelimit-remaining-requestsratelimit-limit-requests
ratelimit-remainingratelimit-limit
x-ratelimit-remaining-requests-dayx-ratelimit-limit-requests-day
x-ratelimit-remaining-dayx-ratelimit-limit-day
  • 数值解析是严格的:10 requests 不会被读成 10。剩余量和上限两个头都在、却没有一对能解析时,记一次“表头无法解析”。
  • 百分比限制在 0–100;读数超过 5 分钟视为过时。
  • 每个渠道另有四个计数:响应总数、401/403、429、5xx。
  • 渠道 页显示读数:10% 及以下为红色,25% 及以下为琥珀色,过时的读数标为 (旧)。
  • 读数按渠道(不是按账号)记录,只在内存中,记录失败也不会影响推理。管理接口只返回表头名称,不返回原始值。

本页目录