跳转至

flysafe 迁移指南:从 anthropic.New(BaseURL hack)minimax.New(ModeAnthropic)

适用版本:flyto-agent core PR1(anthropic 数据驱动能力)+ PR2(能力机制推广到 6 个 provider),2026-04-11 推送到主干之后. 面向读者:flysafe SDK 维护者. 文档状态:已验证(2026-04-11).代码路径分析 + curl 双路实测 + Go 端到端 integration test 三重通过,minimax.New(ModeAnthropic) 的构造链已补齐与 anthropic.New() 等同的 5 个 api.ClientOption(含 classifier / retry / overflow).置信度 100%.


1. TL;DR

  • flysafe 目前用 anthropic.New(Config{BaseURL: "https://api.minimaxi.com/anthropic", BearerAuth: true, …}) 把 MiniMax 的 Anthropic 兼容端点冒充成 Anthropic,这是历史 hack.
  • PR1+PR2 落地之后,这种用法会出现两个新坑:
  • thinking false-positive warning:WarningEvent{Code:"feature_unsupported"} 误报,模型其实支持;
  • caching 阈值被保守兜底:CachingMinTokens 回退到 4096(而正确值是 1024),short system prompt 的 caching silent 失效.
  • 推荐迁移:改用 minimax.New(minimax.Config{Mode: minimax.ModeAnthropic, …}),HTTP wire 格式完全等同,能力决策走 minimax 静态表,两个坑同时消除.
  • 次选:保留 anthropic hack,但在 engine 初始化时往 ModelRegistry 注入 "MiniMax-M2.7-highspeed"ModelInfo.PR1 的跨 provider 透明性保证 anthropic helper 会优先读 registry.
  • 不推荐:anthropic.Config.ModelOverrides,verify 后确认不 work(只影响 Models() 返回值,不改变 helper 决策路径).

2. 历史背景:flysafe 当前的 hack 用法

flysafe 是 flyto-agent core 的首个消费者,使用 MiniMax 的 Token Plan Key(MINIMAX_TOKEN_PLAN_KEY).这种 key 有两个特点:

  • 免费额度按 call 次数限额,不按 token 计费;
  • 必须通过 MiniMax 的 Anthropic 兼容端点调用:https://api.minimaxi.com/anthropic/v1/messages.

在 minimax provider 还没有 ModeAnthropic 实现,或者 flysafe 不知道其存在的时候,最自然的实现方式是复用 anthropic provider 的代码路径 + 改 BaseURL:

import "git.flytoex.net/yuanwei/git.flytoex.net/yuanwei/flyto-agent/pkg/providers/anthropic"

p := anthropic.New(anthropic.Config{
    APIKey:         os.Getenv("MINIMAX_TOKEN_PLAN_KEY"),
    BaseURL:        "https://api.minimaxi.com/anthropic",
    BearerAuth:     true,
    EnableCaching:  true,
    ThinkingBudget: 8000,
})
// 然后 Request.Model = "MiniMax-M2.7-highspeed"

为什么这个 hack 能工作

HTTP wire 层面,MiniMax 的 Anthropic 兼容端点真的是 Anthropic 协议:

  • POST /anthropic/v1/messages
  • Bearer auth(Authorization: Bearer $KEY),不是 Anthropic 官方的 x-api-key header
  • 请求体与响应体的 JSON schema 基本对齐 Anthropic
  • SSE 事件序列完整(message_startcontent_block_startcontent_block_delta → ... → message_stop)

minimax provider 代码中 provider.go:17-20 的 LEGACY 注释也明确记录了这条历史路径:

// 历史包袱(LEGACY): flysafe 早期通过 BaseURL="https://api.minimaxi.com/anthropic"
// 使用 MiniMax,走的是 Anthropic 兼容端点。
// 迁移路径:flysafe 改用 minimax.New(Config{APIKey: "..."}) 即可,
// 无需关心底层使用哪种格式。

在 PR1 之前,anthropic provider 的能力决策基本是 no-op(caching 全开,thinking 按 model ID 前缀判断),flysafe 的 hack 用法几乎零副作用.问题从 PR1 落地开始出现.


3. PR1+PR2 带来的问题

3.1 PR1+PR2 做了什么

RFC 全文参见 data-driven-capabilities-rfc.md.这里只讲对 flysafe 有影响的两点:

  1. 能力决策数据化.anthropic provider 的 SupportsThinking / CachingMinTokens 等能力不再硬编码,而是走 req.Capabilities(engine 从 ModelRegistry 注入).包内静态表(anthropicModels)降级为兜底.
  2. 双开关协议(want × can).用户 want 某能力(Config.ThinkingBudget > 0)但模型 can=false 时,provider 会 emit WarningEvent{Code:"feature_unsupported"},把 silent disable 转成 loud warning.

核心 helper 的伪代码形态:

func (p *Provider) resolveThinkingSupport(req *flyto.Request) bool {
    if req.Capabilities != nil {
        return req.Capabilities.SupportsThinking
    }
    // 兜底路径:扫包级静态表
    return modelSupportsThinkingFallback(req.Model)
}

这里关键的一点是:anthropic provider 的兜底只扫 anthropicModels,不知道 MiniMax 模型的存在.flysafe 的 model ID "MiniMax-M2.7-highspeed"anthropicModels 里一定查不到.

3.2 坑 A:thinking false-positive warning

触发路径:

  1. flysafe 调用 p.Stream(ctx, req),req.Model = "MiniMax-M2.7-highspeed",req.Capabilities = nil(没经过 engine 注入).
  2. anthropic provider 的 detectFeatureWarnings(req) 里:
  3. req.NeedsThinking == true 或者 Config.ThinkingBudget > 0 → want=true
  4. resolveThinkingSupport(req)req.Capabilities 为 nil → 走兜底 modelSupportsThinkingFallback("MiniMax-M2.7-highspeed") → 扫 anthropicModels 静态表查不到 → false
  5. want=true, can=false → emit WarningEvent{Code:"feature_unsupported", Message:"... MiniMax-M2.7-highspeed 不支持 thinking ..."}.

实际真相:MiniMax-M2.7-highspeed 是支持 thinking 的.minimax provider 的静态表(minimaxModels)里 SupportsThinking=true.

行为后果: - thinking budget 仍然会注入到请求里(warning 只是提示,不阻断),所以运行结果零回归; - 但是 flysafe 的 observability / log 会产生误报 warning; - 用户看到 warning 会怀疑是不是模型配置错了,引入额外的心智负担.

3.3 坑 B:caching 阈值被保守兜底(silent 性能 degrade)

触发路径:

  1. req.Capabilities = nil.
  2. anthropic 的 resolveCachingMinTokens(req) 兜底扫 anthropicModels"MiniMax-M2.7-highspeed" → 查不到 → 返回保守兜底 4096.
  3. anthropic wire 层决定打 cache_control 标记的条件是 estimatedTokens >= minTokens.如果 flysafe 的 system prompt 估算 token 在 1024–4095 之间:
  4. 用 minimax 真实阈值 1024:应该打 cache mark
  5. 用 anthropic 兜底阈值 4096:不会打 cache mark
  6. MiniMax 端收到请求但没有 cache_control 标记 → 不命中 prompt cache → 按全量计费.

实际真相:minimax 静态表中 MiniMax-M2.7-highspeedCachingMinTokens = 1024.

行为后果: - 没有任何错误,没有任何 warning; - flysafe 原本期望的 caching 省钱悄无声息失效; - 只有对比账单或抓 wire 才能发现.这是比坑 A 更危险的一种回归.


4. 推荐方案:迁移到 minimax.New(ModeAnthropic)

4.1 Before / After 代码对比

Before(现状 hack):

import "git.flytoex.net/yuanwei/git.flytoex.net/yuanwei/flyto-agent/pkg/providers/anthropic"

p := anthropic.New(anthropic.Config{
    APIKey:         os.Getenv("MINIMAX_TOKEN_PLAN_KEY"),
    BaseURL:        "https://api.minimaxi.com/anthropic",
    BearerAuth:     true,
    EnableCaching:  true,
    ThinkingBudget: 8000,
})

After(推荐):

import "git.flytoex.net/yuanwei/git.flytoex.net/yuanwei/flyto-agent/pkg/providers/minimax"

p := minimax.New(minimax.Config{
    APIKey:         os.Getenv("MINIMAX_TOKEN_PLAN_KEY"),
    Region:         minimax.RegionChina,   // 默认即为 China,可省略
    Mode:           minimax.ModeAnthropic, // 关键:走 Anthropic 兼容端点
    ThinkingBudget: 8000,
})

几处关键变化:

  • 不再需要手写 BaseURL:minimax.New 内部根据 RegionMode 自动拼 https://api.minimaxi.com/anthropic(参见 core/pkg/providers/minimax/provider.go:111-130).
  • 不再需要 BearerAuth: true:minimax provider 在 ModeAnthropic 分支内部 append api.WithBearerAuth()(参见 core/pkg/providers/minimax/provider.go:129).
  • 不再需要 EnableCaching:minimax.Config 根本没有这个字段,minimax wire 层自动处理 caching,不需要消费者开关.
  • ThinkingBudget 字段名相同,语义一致.

4.2 HTTP wire 层等价性

以下两条路径在 HTTP 线上完全等同:

维度 anthropic.New + BaseURL hack minimax.New(ModeAnthropic)
URL https://api.minimaxi.com/anthropic/v1/messages https://api.minimaxi.com/anthropic/v1/messages
Auth header Authorization: Bearer $KEY Authorization: Bearer $KEY
请求体 JSON schema Anthropic messages API Anthropic messages API
SSE event 序列 Anthropic 协议 Anthropic 协议

4.3 为什么迁移成本低

  • 调用侧 API 变化仅是 import 路径 + 字段名删减,没有语义变化.
  • Go 类型检查会直接提示 EnableCaching 字段不存在,迁移漏改点有编译器兜底.
  • Request / Response 类型(flyto.Request / flyto.Event)保持不变.
  • 能力决策走 minimax 静态表(minimaxModels):
  • MiniMax-M2.7-highspeed: SupportsThinking=true, CachingMinTokens=1024 → 两个坑同时消失.
  • 还可以顺带拿到 minimax provider 的 detectFeatureWarnings 语义(dispatcher 层统一,两条 stream path 共用,见 core/pkg/providers/minimax/provider.go:169-171).

5. 次选方案:保留 hack + 手动往 registry 注入 ModelInfo

如果 flysafe 因为某些原因(比如代码库里还有别的地方依赖 anthropic.Provider 的具体类型)短期不方便切 provider 实例类型,有一个零 provider 切换的补救方案.

5.1 原理:PR1 的跨 provider 透明性

PR1 设计时就明确约定:registry 注入的 req.Capabilities 优先于任何 provider 包内静态表.anthropic provider 的 helper 结构是:

if req.Capabilities != nil {
    return req.Capabilities.SupportsThinking  // 直接返回 registry 数据
}
// 否则才扫 anthropicModels 兜底

这意味着:只要 registry 里有 "MiniMax-M2.7-highspeed" 的条目,哪怕 provider 是 anthropic 代码路径,能力决策也会走 registry 数据,两个坑同样消失.

5.2 示例代码

import (
    "git.flytoex.net/yuanwei/git.flytoex.net/yuanwei/flyto-agent/pkg/config"
    "git.flytoex.net/yuanwei/git.flytoex.net/yuanwei/flyto-agent/pkg/flyto"
)

// 在 engine 初始化处(flysafe 自己的 main/bootstrap)
registry := config.NewModelRegistry()
registry.Register("MiniMax-M2.7-highspeed", &flyto.ModelInfo{
    ID:               "MiniMax-M2.7-highspeed",
    SupportsThinking: true,
    SupportsCaching:  true,
    CachingMinTokens: 1024,
    ContextWindow:    205_000,
    // 其他字段按需补齐
})

// 然后把 registry 挂到 engine 上,engine 会在每次 Stream 前
// 把 ModelInfo 填到 req.Capabilities。

5.3 次选方案的代价

  • 代价是 flysafe 需要自己维护一份 MiniMax 的 ModelInfo,和 minimax provider 的静态表人肉保持同步;
  • 未来 minimax provider 升级(比如增加新模型,调整 CachingMinTokens)时,flysafe 这边拿不到自动更新;
  • 但是紧急修复两个坑时这是最小侵入的选项.

6. 不推荐方案:anthropic.Config.ModelOverrides

看起来 anthropic provider 的 Config 里有个 ModelOverrides []flyto.ModelInfo 字段,直觉上应该能解决问题:

// 直觉上以为这样就能救
p := anthropic.New(anthropic.Config{
    // ... 老的 hack 字段
    ModelOverrides: []flyto.ModelInfo{{
        ID:               "MiniMax-M2.7-highspeed",
        SupportsThinking: true,
        CachingMinTokens: 1024,
    }},
})

但这个方案不 work.verify 过代码后确认:

  • ModelOverrides 字段的作用范围只影响 Provider.Models() 方法的返回值(给 CLI / probe 工具列模型用);
  • resolveThinkingSupport / resolveCachingMinTokens 等 helper 只扫 anthropicModels 包级变量,不读 Config.ModelOverrides.

所以注入 ModelOverrides 之后: - p.Models() 里会出现 MiniMax-M2.7-highspeed(没用); - 但 warning / caching 决策路径完全不变,两个坑都原样存在.

如果后续有人想改 anthropic provider 让 ModelOverrides 真的参与能力决策,那是另一个 RFC 的事.目前不要依赖这条路径.


7. 验证状态

flyto-agent core 维护者做过的三重验证:

7.1 代码路径分析

对比 anthropic.New(BaseURL hack)minimax.New(ModeAnthropic) 的初始化路径,确认 HTTP wire 层完全等同:

  • 都实例化 internal/transport 下的 api.Client(package 名为 api)
  • 都使用 api.WithBearerAuth()
  • baseURL 都是 https://api.minimaxi.com/anthropic
  • message path 都是 /v1/messages
  • 2026-04-11 起,两者的 api.ClientOption 链完全对等:WithMessagePath / WithAPIVersion("2023-06-01") / WithClassifier(AnthropicClassifier) / WithRetryPolicy(NewAnthropicRetryPolicy) / WithOverflowHandler(DefaultOverflowHandler) / WithBearerAuth.见 core/pkg/providers/minimax/provider.go:122-149.

7.2 curl 双路实测(2026-04-11)

从 flyto-agent core 的维护者环境直接用 curl 打 MiniMax 的 Anthropic 兼容端点,复现命令:

非流式 + 不带 anthropic-version header:

curl -sS -D /tmp/h.txt -o /tmp/b.json \
  -X POST "https://api.minimaxi.com/anthropic/v1/messages" \
  -H "Authorization: Bearer $MINIMAX_TOKEN_PLAN_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "MiniMax-M2.7-highspeed",
    "max_tokens": 64,
    "messages": [{"role":"user","content":"Say hello."}]
  }'
# 预期:HTTP/1.1 200 OK,body 为 Anthropic Message 格式

流式 SSE + 不带 anthropic-version header:

curl -sS -N \
  -X POST "https://api.minimaxi.com/anthropic/v1/messages" \
  -H "Authorization: Bearer $MINIMAX_TOKEN_PLAN_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "MiniMax-M2.7-highspeed",
    "max_tokens": 64,
    "stream": true,
    "messages": [{"role":"user","content":"Say hello."}]
  }'
# 预期:HTTP/1.1 200 OK,完整 Anthropic 协议 SSE 事件序列
# (message_start / content_block_start / content_block_delta ... / message_stop)

两路均 HTTP 200 通过.

7.3 Go 端到端 integration test(2026-04-11 补做)

新增文件:core/pkg/providers/minimax/provider_integration_test.go(//go:build integration tag 隔离,默认 go test ./... 不触发).

测试核心路径: 1. 从 core/.env 加载 MINIMAX_TOKEN_PLAN_KEY 2. 构造 minimax.New(Config{APIKey: key, Region: RegionChina, Mode: ModeAnthropic}) 3. 发一个最小 prompt("Say ONE word: OK")走完整 provider.Stream(ctx, req) 链路 4. 断言 1: 收到 *flyto.UsageEvent(provider 层的流终止信号;DoneEvent 由 engine 层追加,不在 provider channel 中) 5. 断言 2: 拼接 TextEvent + TextDeltaEvent 的内容包含 "OK" 6. 断言 3: 没有任何 WarningEvent{Code:"feature_unsupported"}(minimax provider 走自己的 minimaxModels 静态表,M2.7-highspeed.SupportsThinking=true 被正确识别)

运行命令:

go test -tags integration -run TestIntegration_ModeAnthropic_TokenPlan \
    ./pkg/providers/minimax/ -v

实测结果(2026-04-11,补齐 robustness options 之前 + 之后各跑一次):

=== RUN   TestIntegration_ModeAnthropic_TokenPlan
    provider_integration_test.go:120: ✓ 端到端通过: model=MiniMax-M2.7-highspeed
        mode=Anthropic text="OKOK"
        events=map[text:1 text_delta:1 thinking:1 thinking_delta:5 usage:1]
--- PASS: TestIntegration_ModeAnthropic_TokenPlan (4.07s)

两次跑都通过.text="OKOK" 是因为 TextDeltaEvent 增量和 TextEvent 完整块都包含 "OK",拼接后是 "OKOK".收到的 thinking_delta 事件证明 M2.7-highspeed 的 thinking 协议完整工作.

7.4 置信度与建议

  • 代码路径等价 + curl 双路 + Go 端到端 integration test,置信度 100%.
  • 建议 flysafe 的切换步骤:
  • 在 staging / dev 环境切到 minimax.New(ModeAnthropic);
  • 跑 flysafe 的主回归集,对比 Before/After 的 log,token 消耗,warning 事件;
  • 特别关注:之前的 WarningEvent{Code:"feature_unsupported"} 应该消失;caching 命中率应该提升(阈值从 4096 降到 1024,更多短 system prompt 能打上 cache,账单应该下降);
  • 无异常后切全量生产.
  • 回滚方案:一行 git revert 即可.minimax.New(ModeAnthropic)anthropic.New(BaseURL hack) 在 HTTP wire 层完全等同,server side 看不出差别,回滚零风险.

8. ModeAnthropic 构造链的演化(2026-04-11 补齐 robustness options)

8.1 此前缺失的 4 个 option(历史)

在 2026-04-11 之前,minimax.New(ModeAnthropic) 在构造 api.Client 时相比 anthropic.New() 少注入了 4 个 option.这些都不影响成功路径,但在错误 / 重试 / 异常处理上会有可观察差异:

缺失的 option anthropic.New 行为 旧 minimax.New(ModeAnthropic) 行为 影响
WithAPIVersion("2023-06-01") 注入 anthropic-version header 不发 anthropic-version header 无影响.minimax 端不要求这个 header(curl 实测确认).
WithClassifier(AnthropicClassifier) 错误消息按 Anthropic 错误码分类 DefaultClassifier,错误消息通用化 弱影响.出错时错误消息可读性稍差,但错误码和 retryable 判定不受影响.
WithRetryPolicy(NewAnthropicRetryPolicy) 针对 429 / 529 有专用重试 backoff 使用默认重试策略 中等影响.生产环境遇到 MiniMax 端限流 / 过载时,重试行为没有针对性优化.
WithOverflowHandler(DefaultOverflowHandler) 自动修正 max_tokens 超过模型上限的情况 不做修正,错误直接抛回调用方 极低影响.只有当 flysafe 显式传超限 max_tokens 才触发.

8.2 2026-04-11 补齐

core/pkg/providers/minimax/provider.go:122-149 已更新 ModeAnthropic 分支,完整对齐 anthropic/provider.go:269-283 的构造链:

case ModeAnthropic:
    var opts []api.ClientOption
    opts = append(opts,
        api.WithMessagePath("/v1/messages"),
        api.WithAPIVersion("2023-06-01"),
        api.WithClassifier(&api.AnthropicClassifier{Hinter: &api.DefaultHinter{}}),
        api.WithRetryPolicy(retry.NewAnthropicRetryPolicy(retry.AnthropicRetryOpts{})),
        api.WithOverflowHandler(retry.DefaultOverflowHandler()),
    )
    if cfg.HTTPClient != nil {
        opts = append(opts, api.WithHTTPClient(cfg.HTTPClient))
    }
    opts = append(opts, api.WithBearerAuth())
    p.anthroClient = api.NewClient(cfg.APIKey, baseURL+"/anthropic", opts...)

补齐后的端到端验证:integration test 双重跑通过(补齐前 + 补齐后各一次),成功路径不受影响;错误 / 重试 / 溢出路径的 robustness 现在与 anthropic.New() 完全对等.

8.3 如果之后发现新缺陷

如果 flysafe 使用中发现 minimax.New(ModeAnthropic)anthropic.New() 还有行为差异(除了 endpoint 和 Bearer auth 这种故意的差异),不要在 flysafe 侧 workaround,请回 flyto-agent core 提 issue / PR.理由:

  • 这些属于 provider 级别的 protocol 细节,是 core 的责任边界;
  • flysafe 侧的 workaround 会在未来 core 修复时形成双重重试之类的隐患.

9. 支持 / 提问渠道

TODO:待定.目前可以在 flyto-agent core 仓库提 issue,或在团队 IM 找 core 维护者.


附录:相关文件 / 行号参考

  • minimax provider 主文件:core/pkg/providers/minimax/provider.go
  • ModeAnthropic 常量定义:provider.go:45-48
  • LEGACY flysafe 注释:provider.go:17-20
  • New(cfg Config) 构造:provider.go:103-146
  • ModeAnthropic 分支构造 anthroClient:provider.go:121-130
  • Stream dispatcher(统一 warning 检测):provider.go:158-180
  • 数据驱动能力 RFC:docs/architecture/data-driven-capabilities-rfc.md
  • 数据驱动能力迁移记录:docs/architecture/data-driven-capabilities-migration.md
  • minimax provider 文档:docs/providers/minimax.md(§11 同步了消费者注意事项)