thinking_protocol_test.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. package service
  2. import "testing"
  3. func TestResolveThinkingProtocol(t *testing.T) {
  4. tests := []struct {
  5. name string
  6. modelID string
  7. want ThinkingProtocol
  8. }{
  9. // Anthropic 官方
  10. {"claude-sonnet-4-5", "claude-sonnet-4-5", ThinkingProtocolAnthropicStrict},
  11. {"claude-opus-4-5", "claude-opus-4-5-20251101", ThinkingProtocolAnthropicStrict},
  12. {"claude-haiku full id", "claude-haiku-4-5-20251001", ThinkingProtocolAnthropicStrict},
  13. {"opus short", "opus-4-5", ThinkingProtocolAnthropicStrict},
  14. {"sonnet short", "sonnet-4-5", ThinkingProtocolAnthropicStrict},
  15. {"haiku short", "haiku-4-5", ThinkingProtocolAnthropicStrict},
  16. {"upper case Claude", "Claude-Sonnet-4-5", ThinkingProtocolAnthropicStrict},
  17. // 第三方兼容上游
  18. {"deepseek-v4-pro", "deepseek-v4-pro", ThinkingProtocolPassbackRequired},
  19. {"deepseek-r2-thinking", "deepseek-r2-thinking", ThinkingProtocolPassbackRequired},
  20. {"kimi-coding", "kimi-coding-v2", ThinkingProtocolPassbackRequired},
  21. {"kimi-k2-thinking", "kimi-k2-thinking", ThinkingProtocolPassbackRequired},
  22. {"kimi-k3 platform", "kimi-k3", ThinkingProtocolPassbackRequired},
  23. {"kimi code bare k3", "k3", ThinkingProtocolPassbackRequired},
  24. {"kimi code bare k3-256k", "k3-256k", ThinkingProtocolPassbackRequired},
  25. {"moonshot-v1", "moonshot-v1-32k", ThinkingProtocolPassbackRequired},
  26. {"glm-5.1", "glm-5.1", ThinkingProtocolPassbackRequired},
  27. {"qwen-2 thinking variant", "qwen-2-72b-thinking", ThinkingProtocolPassbackRequired},
  28. {"qwen3 thinking (real Alibaba naming)", "qwen3-235b-a22b-thinking-2507", ThinkingProtocolPassbackRequired},
  29. {"qwen3-next thinking", "qwen3-next-80b-a3b-thinking", ThinkingProtocolPassbackRequired},
  30. {"upper case Deepseek", "DeepSeek-V4-Pro", ThinkingProtocolPassbackRequired},
  31. // MiniMax M 系列(Anthropic 兼容端点要求 thinking round-trip)
  32. {"MiniMax-M2 (case-sensitive original)", "MiniMax-M2", ThinkingProtocolPassbackRequired},
  33. {"MiniMax-M2.1", "MiniMax-M2.1", ThinkingProtocolPassbackRequired},
  34. {"MiniMax-M2.5", "MiniMax-M2.5", ThinkingProtocolPassbackRequired},
  35. {"MiniMax-M2.7", "MiniMax-M2.7", ThinkingProtocolPassbackRequired},
  36. {"MiniMax-M2.7-highspeed", "MiniMax-M2.7-highspeed", ThinkingProtocolPassbackRequired},
  37. {"minimax-m2 lowercase", "minimax-m2", ThinkingProtocolPassbackRequired},
  38. // 未知 / 保守
  39. {"empty", "", ThinkingProtocolUnknown},
  40. {"gpt-5", "gpt-5.1", ThinkingProtocolUnknown},
  41. {"gemini", "gemini-3-pro-preview", ThinkingProtocolUnknown},
  42. {"qwen3 non-thinking", "qwen3-32b", ThinkingProtocolUnknown},
  43. {"qwen2 non-thinking", "qwen-2-72b", ThinkingProtocolUnknown},
  44. {"random vendor", "yi-large", ThinkingProtocolUnknown},
  45. // 相似但未知的 k3 型号:不得因含 k3 被宽泛匹配为 passback-required
  46. {"k3-like unknown", "foo-k3-bar", ThinkingProtocolUnknown},
  47. // MiniMax 非 M 系列(如 abab、speech 等其他产品线)—— unknown
  48. {"minimax abab non-M", "abab6.5-chat", ThinkingProtocolUnknown},
  49. // Doubao 走 OpenAI 协议,不属于本网关 Anthropic 路径——归 unknown
  50. {"doubao goes via openai", "doubao-1-5-thinking-vision-pro-250428", ThinkingProtocolUnknown},
  51. // Hunyuan T1 未暴露 Anthropic 端点——归 unknown
  52. {"hunyuan t1 no anthropic endpoint", "hunyuan-t1", ThinkingProtocolUnknown},
  53. {"hy-t1 short alias", "hy-t1", ThinkingProtocolUnknown},
  54. // claude-something 但不是 anthropic 官方命名风格——也归 strict(前缀匹配优先)
  55. {"weird claude prefix", "claude-experimental-fork", ThinkingProtocolAnthropicStrict},
  56. }
  57. for _, tt := range tests {
  58. t.Run(tt.name, func(t *testing.T) {
  59. got := ResolveThinkingProtocol(tt.modelID)
  60. if got != tt.want {
  61. t.Errorf("ResolveThinkingProtocol(%q) = %v, want %v", tt.modelID, got, tt.want)
  62. }
  63. })
  64. }
  65. }
  66. func TestShouldPreFilterThinkingBlocks(t *testing.T) {
  67. tests := []struct {
  68. modelID string
  69. want bool
  70. }{
  71. {"claude-sonnet-4-5", true},
  72. {"deepseek-v4-pro", false},
  73. {"kimi-coding", false},
  74. {"glm-5.1", false},
  75. {"gpt-5.1", false},
  76. {"", false},
  77. }
  78. for _, tt := range tests {
  79. t.Run(tt.modelID, func(t *testing.T) {
  80. if got := ShouldPreFilterThinkingBlocks(tt.modelID); got != tt.want {
  81. t.Errorf("ShouldPreFilterThinkingBlocks(%q) = %v, want %v", tt.modelID, got, tt.want)
  82. }
  83. })
  84. }
  85. }
  86. func TestShouldRectifyThinkingSignatureError(t *testing.T) {
  87. if !ShouldRectifyThinkingSignatureError("claude-sonnet-4-5") {
  88. t.Error("anthropic-strict should rectify signature error")
  89. }
  90. if ShouldRectifyThinkingSignatureError("deepseek-v4-pro") {
  91. t.Error("passback-required must NOT rectify (would break protocol contract)")
  92. }
  93. if ShouldRectifyThinkingSignatureError("gpt-5.1") {
  94. t.Error("unknown should NOT rectify (conservative default)")
  95. }
  96. if ShouldRectifyThinkingSignatureError("") {
  97. t.Error("empty model id should NOT rectify")
  98. }
  99. }
  100. // ShouldApplyRetryFilters 与 ShouldPreFilterThinkingBlocks 必须语义一致:
  101. // 仅 anthropic-strict 走变形,避免预过滤跳过但 retry 路径反而剥离的语义裂缝。
  102. func TestShouldApplyRetryFiltersMirrorsPreFilter(t *testing.T) {
  103. models := []string{
  104. "claude-sonnet-4-5", "claude-opus-4-5-20251101", "haiku-4-5",
  105. "deepseek-v4-pro", "kimi-coding", "glm-5.1",
  106. "qwen3-235b-a22b-thinking-2507", "qwen3-32b",
  107. "gpt-5.1", "gemini-3-pro-preview", "yi-large", "",
  108. }
  109. for _, m := range models {
  110. t.Run(m, func(t *testing.T) {
  111. if got := ShouldApplyRetryFilters(m); got != ShouldPreFilterThinkingBlocks(m) {
  112. t.Errorf("ShouldApplyRetryFilters(%q)=%v but ShouldPreFilterThinkingBlocks=%v — must match",
  113. m, got, ShouldPreFilterThinkingBlocks(m))
  114. }
  115. })
  116. }
  117. }