openai_model_mapping.go 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. package service
  2. import "strings"
  3. // resolveOpenAIForwardModel 解析 OpenAI 兼容转发使用的模型。
  4. // messagesDispatchMappedModel 是调用方已为 /v1/messages 解析的显式调度结果;
  5. // 普通 OpenAI 请求必须传空,避免将分组配置作为通用模型兜底。
  6. func resolveOpenAIForwardModel(account *Account, requestedModel, messagesDispatchMappedModel string) string {
  7. messagesDispatchMappedModel = strings.TrimSpace(messagesDispatchMappedModel)
  8. if account == nil {
  9. if messagesDispatchMappedModel != "" {
  10. return messagesDispatchMappedModel
  11. }
  12. return requestedModel
  13. }
  14. mappedModel, matched := account.ResolveMappedModel(requestedModel)
  15. if !matched && messagesDispatchMappedModel != "" {
  16. return messagesDispatchMappedModel
  17. }
  18. return mappedModel
  19. }
  20. // openAIOAuthForeignModelPrefixes 列出明确属于其他厂商家族的模型名前缀。
  21. // Codex 上游不可能服务这些模型:转发阶段 normalizeOpenAIModelForUpstream
  22. // 对未知模型原样透传,上游必然返回不可重试的 400。
  23. //
  24. // 采用保守黑名单而非 Codex 模型白名单:未知/自定义别名保持「允许」,
  25. // 以兼容渠道级模型映射等「账号选定之后才改写模型名」的部署方式
  26. // (调度过滤看到的是改写前的原始模型名)。前缀分类的先例见
  27. // ResolveThinkingProtocol(thinking_protocol.go)。
  28. var openAIOAuthForeignModelPrefixes = []string{
  29. "deepseek-",
  30. "glm-",
  31. "kimi-",
  32. "moonshot-",
  33. "qwen-",
  34. "qwen2-",
  35. "qwen3-",
  36. "qwen4-",
  37. "qwq-",
  38. "minimax-",
  39. "gemini-",
  40. "gemma-",
  41. "grok-",
  42. "doubao-",
  43. "hunyuan-",
  44. "llama-",
  45. "llama2-",
  46. "llama3-",
  47. "meta-llama",
  48. "mistral-",
  49. "mixtral-",
  50. "baichuan-",
  51. "ernie-",
  52. "step-",
  53. "seed-",
  54. "yi-",
  55. }
  56. // isOpenAIOAuthServableModel 判断「空 model_mapping 的 OpenAI OAuth 账号」能否
  57. // 服务请求模型。空映射默认仍是「允许」,仅排除明确属于其他厂商家族的模型
  58. // (deepseek-*/glm-*、以及 Kimi Code 官方 bare ID k3 / k3-256k 等)——这类
  59. // 请求原样透传必然被 Codex 上游以不可重试的 400 拒绝,且不触发 failover,
  60. // 应在调度阶段就跳过该账号,把请求让给显式声明支持该模型的账号(#3662)。
  61. // bare k3 仅精确匹配(取 last segment 后),不使用宽泛 k3- 前缀,以免误伤
  62. // 自定义别名;显式 model_mapping 命中路径不经过本函数,语义不变。
  63. func isOpenAIOAuthServableModel(requestedModel string) bool {
  64. model := strings.ToLower(lastOpenAIModelSegment(requestedModel))
  65. if model == "" {
  66. return true // 空模型交由上层必填校验处理
  67. }
  68. // Kimi Code 官方 bare model ID:无厂商前缀,prefix 黑名单挡不住。
  69. if model == "k3" || model == "k3-256k" {
  70. return false
  71. }
  72. for _, prefix := range openAIOAuthForeignModelPrefixes {
  73. if strings.HasPrefix(model, prefix) {
  74. return false
  75. }
  76. }
  77. return true
  78. }
  79. // resolveOpenAICompactForwardModel determines the compact-only upstream model
  80. // for /responses/compact requests. It never affects normal /responses traffic.
  81. // When no compact-specific mapping matches, the input model is returned as-is.
  82. func resolveOpenAICompactForwardModel(account *Account, model string) string {
  83. trimmedModel := strings.TrimSpace(model)
  84. if trimmedModel == "" || account == nil {
  85. return trimmedModel
  86. }
  87. mappedModel, matched := account.ResolveCompactMappedModel(trimmedModel)
  88. if !matched {
  89. return trimmedModel
  90. }
  91. if trimmedMapped := strings.TrimSpace(mappedModel); trimmedMapped != "" {
  92. return trimmedMapped
  93. }
  94. return trimmedModel
  95. }