feat: 更新主流大模型供应商目录
- 按云服务、Coding Plan、API 网关和本地模型分组 - 新增兼容模型发现与供应商协议校验
This commit is contained in:
1 parent
01e4435f29
commit
d8b124fc7c
15 files changed
+838
-237
No files matched your search
@@ -1,7 +1,7 @@
|
||||
// anthropic.go - Anthropic Messages 协议 LLM 客户端实现 // Logan.Li
|
||||
// 支持官方 Claude(api.anthropic.com)及智谱 GLM 编码套餐(open.bigmodel.cn/api/anthropic)等
|
||||
// 所有 Anthropic /v1/messages 兼容端点。与 openai.go 实现同一 Client 接口,由 client.go 按 Protocol 路由。
|
||||
// 鉴权统一用 Authorization: Bearer(智谱编码套餐实测通过;官方 Claude 较新版本亦支持)+ anthropic-version。
|
||||
// 同时发送 x-api-key 与 Bearer,兼容 Anthropic 官方和国内编码套餐网关。 // Logan.Li
|
||||
package llm
|
||||
|
||||
import (
|
||||
@@ -167,7 +167,10 @@ func buildTools(tools []Tool) []anthropicTool {
|
||||
// isAnthropicThinkingModel 判断是否为支持 thinking 的 Claude 模型 // Logan.Li
|
||||
func isAnthropicThinkingModel(model string) bool {
|
||||
m := strings.ToLower(model)
|
||||
return strings.Contains(m, "claude-3-7") || strings.Contains(m, "claude-3.7")
|
||||
return strings.Contains(m, "claude-3-7") || strings.Contains(m, "claude-3.7") ||
|
||||
strings.Contains(m, "claude-sonnet-4") || strings.Contains(m, "claude-opus-4") ||
|
||||
strings.Contains(m, "claude-sonnet-5") || strings.Contains(m, "claude-opus-5") ||
|
||||
strings.Contains(m, "claude-fable-5")
|
||||
}
|
||||
|
||||
// buildRequest 构造 Anthropic 请求体(Complete 与 Stream 共用) // Logan.Li
|
||||
@@ -209,6 +212,7 @@ func (c *AnthropicClient) newHTTPRequest(ctx context.Context, body []byte) (*htt
|
||||
}
|
||||
httpReq.Header.Set("Content-Type", "application/json")
|
||||
httpReq.Header.Set("Authorization", "Bearer "+c.cfg.APIKey)
|
||||
httpReq.Header.Set("x-api-key", c.cfg.APIKey)
|
||||
httpReq.Header.Set("anthropic-version", "2023-06-01")
|
||||
return httpReq, nil
|
||||
}
|
||||
@@ -356,8 +360,8 @@ func (c *AnthropicClient) Stream(ctx context.Context, req *CompletionRequest, on
|
||||
}
|
||||
}
|
||||
return &CompletionResponse{
|
||||
Model: c.cfg.Model,
|
||||
Model: c.cfg.Model,
|
||||
Message: Message{Role: "assistant", Content: contentBuilder.String()},
|
||||
Usage: usage,
|
||||
Usage: usage,
|
||||
}, nil
|
||||
}
|
||||
@@ -49,10 +49,14 @@ func NewOpenAIClient(cfg Config) Client {
|
||||
// Name 返回客户端名称 // Logan.Li
|
||||
func (c *OpenAIClient) Name() string { return "openai-compatible(" + c.cfg.Model + ")" }
|
||||
|
||||
// isOpenAIReasoningModel 判断是否为需要 reasoning_effort 的 OpenAI o-series 模型 // Logan.Li
|
||||
// isOpenAIReasoningModel 判断是否为使用新推理参数的 OpenAI 模型。 // Logan.Li
|
||||
func isOpenAIReasoningModel(model string) bool {
|
||||
m := strings.ToLower(model)
|
||||
return strings.Contains(m, "o1") || strings.Contains(m, "o3")
|
||||
if index := strings.LastIndex(m, "/"); index >= 0 {
|
||||
m = m[index+1:]
|
||||
}
|
||||
return strings.HasPrefix(m, "o1") || strings.HasPrefix(m, "o3") ||
|
||||
strings.HasPrefix(m, "o4") || strings.HasPrefix(m, "gpt-5")
|
||||
}
|
||||
|
||||
// isGenericReasoningModel 判断是否为通用推理开关模型(DeepSeek R1 等) // Logan.Li
|
||||
@@ -61,33 +65,35 @@ func isGenericReasoningModel(model string) bool {
|
||||
return strings.Contains(m, "reasoning") || strings.Contains(m, "r1") || strings.Contains(m, "deepseek-reasoner")
|
||||
}
|
||||
|
||||
// applyReasoningParams 根据模型类型应用 reasoning 参数 // Logan.Li
|
||||
// applyReasoningParams 根据模型类型应用 token 与 reasoning 参数。 // Logan.Li
|
||||
func applyReasoningParams(payload *openaiRequest, enabled bool) {
|
||||
if !enabled {
|
||||
return
|
||||
}
|
||||
if isOpenAIReasoningModel(payload.Model) {
|
||||
// OpenAI o-series 使用 reasoning_effort,temperature 必须固定为 1(不传) // Logan.Li
|
||||
payload.ReasoningEffort = "medium"
|
||||
// 新版 OpenAI 推理模型使用 max_completion_tokens 且不接收 temperature。 // Logan.Li
|
||||
payload.MaxCompletionTokens = payload.MaxTokens
|
||||
payload.MaxTokens = 0
|
||||
payload.Temperature = 0
|
||||
if enabled {
|
||||
payload.ReasoningEffort = "medium"
|
||||
}
|
||||
return
|
||||
}
|
||||
if isGenericReasoningModel(payload.Model) {
|
||||
if enabled && isGenericReasoningModel(payload.Model) {
|
||||
payload.Reasoning = true
|
||||
}
|
||||
}
|
||||
|
||||
// openaiRequest OpenAI chat/completions 请求体 // Logan.Li
|
||||
type openaiRequest struct {
|
||||
Model string `json:"model"`
|
||||
Messages []Message `json:"messages"`
|
||||
Tools []Tool `json:"tools,omitempty"`
|
||||
ToolChoice string `json:"tool_choice,omitempty"` // "auto" 让模型自主决定
|
||||
Temperature float64 `json:"temperature,omitempty"`
|
||||
MaxTokens int `json:"max_tokens,omitempty"`
|
||||
Stream bool `json:"stream,omitempty"` // 流式补全(Stream 方法置 true)
|
||||
ReasoningEffort string `json:"reasoning_effort,omitempty"` // OpenAI o-series 推理强度 // Logan.Li
|
||||
Reasoning bool `json:"reasoning,omitempty"` // DeepSeek 等部分平台推理开关 // Logan.Li
|
||||
Model string `json:"model"`
|
||||
Messages []Message `json:"messages"`
|
||||
Tools []Tool `json:"tools,omitempty"`
|
||||
ToolChoice string `json:"tool_choice,omitempty"` // "auto" 让模型自主决定
|
||||
Temperature float64 `json:"temperature,omitempty"`
|
||||
MaxTokens int `json:"max_tokens,omitempty"`
|
||||
MaxCompletionTokens int `json:"max_completion_tokens,omitempty"` // 新版 OpenAI 推理模型的输出上限 // Logan.Li
|
||||
Stream bool `json:"stream,omitempty"` // 流式补全(Stream 方法置 true)
|
||||
ReasoningEffort string `json:"reasoning_effort,omitempty"` // OpenAI o-series 推理强度 // Logan.Li
|
||||
Reasoning bool `json:"reasoning,omitempty"` // DeepSeek 等部分平台推理开关 // Logan.Li
|
||||
}
|
||||
|
||||
// openaiResponse OpenAI chat/completions 响应体 // Logan.Li
|
||||
@@ -245,9 +251,9 @@ func (c *OpenAIClient) Stream(ctx context.Context, req *CompletionRequest, onDel
|
||||
}
|
||||
}
|
||||
return &CompletionResponse{
|
||||
Model: c.cfg.Model,
|
||||
Model: c.cfg.Model,
|
||||
Message: Message{Role: "assistant", Content: contentBuilder.String()},
|
||||
Usage: usage,
|
||||
Usage: usage,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package llm
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestApplyReasoningParamsForModernOpenAIModels(t *testing.T) {
|
||||
payload := openaiRequest{
|
||||
Model: "openai/gpt-5.4",
|
||||
Temperature: 0.7,
|
||||
MaxTokens: 8192,
|
||||
}
|
||||
applyReasoningParams(&payload, true)
|
||||
|
||||
if payload.Temperature != 0 || payload.MaxTokens != 0 {
|
||||
t.Fatalf("unsupported parameters were not cleared: %+v", payload)
|
||||
}
|
||||
if payload.MaxCompletionTokens != 8192 || payload.ReasoningEffort != "medium" {
|
||||
t.Fatalf("modern OpenAI parameters were not applied: %+v", payload)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyReasoningParamsWithoutReasoningEffort(t *testing.T) {
|
||||
payload := openaiRequest{Model: "gpt-5.4-mini", Temperature: 0.7, MaxTokens: 4096}
|
||||
applyReasoningParams(&payload, false)
|
||||
|
||||
if payload.MaxCompletionTokens != 4096 || payload.ReasoningEffort != "" {
|
||||
t.Fatalf("model compatibility parameters are incorrect: %+v", payload)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnthropicThinkingModels(t *testing.T) {
|
||||
for _, model := range []string{"claude-3-7-sonnet", "claude-sonnet-4-6", "claude-opus-4-8", "claude-sonnet-5", "claude-fable-5"} {
|
||||
if !isAnthropicThinkingModel(model) {
|
||||
t.Fatalf("%q should support thinking parameters", model)
|
||||
}
|
||||
}
|
||||
if isAnthropicThinkingModel("claude-haiku-4-5") {
|
||||
t.Fatal("Claude Haiku should not receive thinking parameters")
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user