ADR 0004:LLM 用 Gemini 3.8 Flash,CLIProxy 主通道 + Vercel AI Gateway 兜底
This content is not available in your language yet.
- 状态:已采纳(2026-09-25);调用位置由 ADR 0005 修订:通道、切换、熔断规则原样搬到 outbrief-daemon 生成简报,配置从服务端环境变量改为
~/.outbrief/daemon.json的llm;/v1/llm/*与流式问答切换规则(第 6 条)随服务端问答一起取消 - 决策者:zhibo li
- 取代:ADR 0002 第 1 条中的“CLIProxy + gemini-3.6-flash-high”
2026-09 更新:这份 ADR 随简报生成一起从 outbrief-server 搬到了 outbrief-daemon。通道、超时、推理参数、切换条件和熔断参数不变,只是配置从服务端
LLM_PRIMARY_*/LLM_FALLBACK_*环境变量改成每台电脑的~/.outbrief/daemon.json里的llm.primary/llm.fallback(每个通道直接写具体的model,没有逻辑模型这一层;没配的通道不启用,没有任何通道时简报记为failed)。下文的/v1/llm/*、流式问答等仍在服务端的部分以服务端仓库为准。
简报、问答、下一步提示词三类调用都在服务端(ADR 0002)。只接一个 CLIProxy 时,CLIProxy 或 SSH 隧道一挂,来电就只剩原文,问答直接不可用。需要一个备用通道,并明确什么时候切、切到哪、怎么记录。
- 一个逻辑模型
gemini-3.8-flash(协议LlmModel),每个通道映射到自己的模型名。- 主通道 CLIProxy:
LLM_PRIMARY_BASE_URL(默认http://127.0.0.1:18317/v1),模型gemini-3.8-flash-high(low)。 - 兜底通道 Vercel AI Gateway:
LLM_FALLBACK_BASE_URL(默认https://ai-gateway.vercel.sh/v1),模型google/gemini-3.8-flash。 - 两个通道都走 Vercel AI SDK v7 的
createOpenAICompatible。没有 API key 的通道不启用;两个都没有时/v1/llm/*返回 503,简报记为failed,服务照常启动。
- 主通道 CLIProxy:
- 推理参数:主通道发
reasoning_effort: "none",兜底通道不发任何推理参数。 Vercel 上显式传 effort 反而会打开 thinking(2026-08-07 实测),不传就是不思考。 - 切换条件(按调用逐个判断,先主后备): 主通道抛错、返回非 2xx、超时、结构化输出不符合 schema、
finishReason === "content-filter",或者主通道熔断器处于打开状态。调用方主动取消(客户端断开、服务关闭)不切换,也不计入熔断。 - 超时:主通道 30 s,兜底 45 s(
LLM_*_TIMEOUT_MS)。没用 youtube-dubbing-proxy 的 10 s:简报是一次性输出几千字的结构化 JSON,Vercel 实测单次 11.3 s,10 s 会把正常请求判成超时。流式调用中这个值是相邻两个分块之间允许的最长间隔。 - 熔断器每个通道一个,参数与 youtube-dubbing-proxy
openAiWrapperConfigTemplate相同: 基于次数的滑动窗口 5 次,5 次全部失败(100 %)即打开;打开 30 s 后半开,放行 2 次试探调用,两次都成功才关闭,任一失败重新打开 30 s。主通道打开期间请求直接走兜底,不再碰主通道。 - 流式(
/v1/llm/qa)只能在第一个文本 token 之前切换。 实现上先从主通道的流里拉出第一个非空文本块再提交响应;在这之前失败就换兜底,两个都失败返回 502。第一个 token 之后出错只结束当前流,不拼接另一个通道的回答,由客户端提示并重试。 - 每次逻辑调用输出一行 JSON 日志(
msg: "llm_call"):任务、最终通道、耗时、是否切换、切换原因(error | timeout | content_filter | schema | circuit_open)。简报把产出通道写进briefs.llm_channel,llm_calls/rewritten/supplemented记录是否触发了覆盖率重写与补充说明。
依据(实测,2026-09-25,同一份简报请求)
Section titled “依据(实测,2026-09-25,同一份简报请求)”- CLIProxy
gemini-3.8-flash-high(low)+reasoning_effort: none:3.3 s。 - CLIProxy
gemini-3.8-flash-high:3.5 s。 - Vercel AI Gateway
google/gemini-3.8-flash(不传推理参数):11.3 s。 - 以上全部
finish_reason = stop,JSON 均通过 schema 校验。 pnpm brief:eval跑 5 份真实 Codex 汇报(主通道):critical 事实覆盖率全部 100 %,口播字数占原文 6 %~11 %,每份一次调用约 5.5~6.6 s;同一份汇报主 / 兜底通道各跑一次都通过。- Gemini 的结构化输出会把可空字符串字段写坏:
recommendedOptionId应为"a"时经常返回"aToString"、"a-保留 v1 三个月过渡",reason被省略。因此发给模型的 schema 里这两个字段是普通字符串(空串表示没有推荐),服务端再把不指向任何选项的推荐置为 null。
备选与放弃原因
Section titled “备选与放弃原因”- 只用 CLIProxy:依赖本机隧道和第三方反代,单点故障时整通电话降级成念原文。
- 只用 Vercel AI Gateway:比 CLIProxy 慢 3 倍多,且按量计费;只适合做兜底。
- 流式中途切换通道续写:两个模型的前后半句拼在一起,语义和口吻都会断裂,宁可让用户重问。
- 新增环境变量
LLM_PRIMARY_*、LLM_FALLBACK_*(见.env.example),key 只放服务端.env。 - 熔断状态在进程内,多实例部署时各自统计。
- 主通道长时间不可用时,简报延迟从约 6 s 上升到约 18 s(兜底通道)。