refactor(providers): adopt functional options and preserve timeout migration
This commit is contained in:
parent
9e0408d7cd
commit
9d3f0afcc6
6 changed files with 195 additions and 111 deletions
|
|
@ -374,6 +374,7 @@ type ProviderConfig struct {
|
||||||
APIKey string `json:"api_key" env:"PICOCLAW_PROVIDERS_{{.Name}}_API_KEY"`
|
APIKey string `json:"api_key" env:"PICOCLAW_PROVIDERS_{{.Name}}_API_KEY"`
|
||||||
APIBase string `json:"api_base" env:"PICOCLAW_PROVIDERS_{{.Name}}_API_BASE"`
|
APIBase string `json:"api_base" env:"PICOCLAW_PROVIDERS_{{.Name}}_API_BASE"`
|
||||||
Proxy string `json:"proxy,omitempty" env:"PICOCLAW_PROVIDERS_{{.Name}}_PROXY"`
|
Proxy string `json:"proxy,omitempty" env:"PICOCLAW_PROVIDERS_{{.Name}}_PROXY"`
|
||||||
|
RequestTimeout int `json:"request_timeout,omitempty" env:"PICOCLAW_PROVIDERS_{{.Name}}_REQUEST_TIMEOUT"`
|
||||||
AuthMethod string `json:"auth_method,omitempty" env:"PICOCLAW_PROVIDERS_{{.Name}}_AUTH_METHOD"`
|
AuthMethod string `json:"auth_method,omitempty" env:"PICOCLAW_PROVIDERS_{{.Name}}_AUTH_METHOD"`
|
||||||
ConnectMode string `json:"connect_mode,omitempty" env:"PICOCLAW_PROVIDERS_{{.Name}}_CONNECT_MODE"` // only for Github Copilot, `stdio` or `grpc`
|
ConnectMode string `json:"connect_mode,omitempty" env:"PICOCLAW_PROVIDERS_{{.Name}}_CONNECT_MODE"` // only for Github Copilot, `stdio` or `grpc`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,7 @@ func ConvertProvidersToModelList(cfg *Config) []ModelConfig {
|
||||||
APIKey: p.OpenAI.APIKey,
|
APIKey: p.OpenAI.APIKey,
|
||||||
APIBase: p.OpenAI.APIBase,
|
APIBase: p.OpenAI.APIBase,
|
||||||
Proxy: p.OpenAI.Proxy,
|
Proxy: p.OpenAI.Proxy,
|
||||||
|
RequestTimeout: p.OpenAI.RequestTimeout,
|
||||||
AuthMethod: p.OpenAI.AuthMethod,
|
AuthMethod: p.OpenAI.AuthMethod,
|
||||||
}, true
|
}, true
|
||||||
},
|
},
|
||||||
|
|
@ -82,6 +83,7 @@ func ConvertProvidersToModelList(cfg *Config) []ModelConfig {
|
||||||
APIKey: p.Anthropic.APIKey,
|
APIKey: p.Anthropic.APIKey,
|
||||||
APIBase: p.Anthropic.APIBase,
|
APIBase: p.Anthropic.APIBase,
|
||||||
Proxy: p.Anthropic.Proxy,
|
Proxy: p.Anthropic.Proxy,
|
||||||
|
RequestTimeout: p.Anthropic.RequestTimeout,
|
||||||
AuthMethod: p.Anthropic.AuthMethod,
|
AuthMethod: p.Anthropic.AuthMethod,
|
||||||
}, true
|
}, true
|
||||||
},
|
},
|
||||||
|
|
@ -99,6 +101,7 @@ func ConvertProvidersToModelList(cfg *Config) []ModelConfig {
|
||||||
APIKey: p.OpenRouter.APIKey,
|
APIKey: p.OpenRouter.APIKey,
|
||||||
APIBase: p.OpenRouter.APIBase,
|
APIBase: p.OpenRouter.APIBase,
|
||||||
Proxy: p.OpenRouter.Proxy,
|
Proxy: p.OpenRouter.Proxy,
|
||||||
|
RequestTimeout: p.OpenRouter.RequestTimeout,
|
||||||
}, true
|
}, true
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -115,6 +118,7 @@ func ConvertProvidersToModelList(cfg *Config) []ModelConfig {
|
||||||
APIKey: p.Groq.APIKey,
|
APIKey: p.Groq.APIKey,
|
||||||
APIBase: p.Groq.APIBase,
|
APIBase: p.Groq.APIBase,
|
||||||
Proxy: p.Groq.Proxy,
|
Proxy: p.Groq.Proxy,
|
||||||
|
RequestTimeout: p.Groq.RequestTimeout,
|
||||||
}, true
|
}, true
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -131,6 +135,7 @@ func ConvertProvidersToModelList(cfg *Config) []ModelConfig {
|
||||||
APIKey: p.Zhipu.APIKey,
|
APIKey: p.Zhipu.APIKey,
|
||||||
APIBase: p.Zhipu.APIBase,
|
APIBase: p.Zhipu.APIBase,
|
||||||
Proxy: p.Zhipu.Proxy,
|
Proxy: p.Zhipu.Proxy,
|
||||||
|
RequestTimeout: p.Zhipu.RequestTimeout,
|
||||||
}, true
|
}, true
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -147,6 +152,7 @@ func ConvertProvidersToModelList(cfg *Config) []ModelConfig {
|
||||||
APIKey: p.VLLM.APIKey,
|
APIKey: p.VLLM.APIKey,
|
||||||
APIBase: p.VLLM.APIBase,
|
APIBase: p.VLLM.APIBase,
|
||||||
Proxy: p.VLLM.Proxy,
|
Proxy: p.VLLM.Proxy,
|
||||||
|
RequestTimeout: p.VLLM.RequestTimeout,
|
||||||
}, true
|
}, true
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -163,6 +169,7 @@ func ConvertProvidersToModelList(cfg *Config) []ModelConfig {
|
||||||
APIKey: p.Gemini.APIKey,
|
APIKey: p.Gemini.APIKey,
|
||||||
APIBase: p.Gemini.APIBase,
|
APIBase: p.Gemini.APIBase,
|
||||||
Proxy: p.Gemini.Proxy,
|
Proxy: p.Gemini.Proxy,
|
||||||
|
RequestTimeout: p.Gemini.RequestTimeout,
|
||||||
}, true
|
}, true
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -179,6 +186,7 @@ func ConvertProvidersToModelList(cfg *Config) []ModelConfig {
|
||||||
APIKey: p.Nvidia.APIKey,
|
APIKey: p.Nvidia.APIKey,
|
||||||
APIBase: p.Nvidia.APIBase,
|
APIBase: p.Nvidia.APIBase,
|
||||||
Proxy: p.Nvidia.Proxy,
|
Proxy: p.Nvidia.Proxy,
|
||||||
|
RequestTimeout: p.Nvidia.RequestTimeout,
|
||||||
}, true
|
}, true
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -195,6 +203,7 @@ func ConvertProvidersToModelList(cfg *Config) []ModelConfig {
|
||||||
APIKey: p.Ollama.APIKey,
|
APIKey: p.Ollama.APIKey,
|
||||||
APIBase: p.Ollama.APIBase,
|
APIBase: p.Ollama.APIBase,
|
||||||
Proxy: p.Ollama.Proxy,
|
Proxy: p.Ollama.Proxy,
|
||||||
|
RequestTimeout: p.Ollama.RequestTimeout,
|
||||||
}, true
|
}, true
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -211,6 +220,7 @@ func ConvertProvidersToModelList(cfg *Config) []ModelConfig {
|
||||||
APIKey: p.Moonshot.APIKey,
|
APIKey: p.Moonshot.APIKey,
|
||||||
APIBase: p.Moonshot.APIBase,
|
APIBase: p.Moonshot.APIBase,
|
||||||
Proxy: p.Moonshot.Proxy,
|
Proxy: p.Moonshot.Proxy,
|
||||||
|
RequestTimeout: p.Moonshot.RequestTimeout,
|
||||||
}, true
|
}, true
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -227,6 +237,7 @@ func ConvertProvidersToModelList(cfg *Config) []ModelConfig {
|
||||||
APIKey: p.ShengSuanYun.APIKey,
|
APIKey: p.ShengSuanYun.APIKey,
|
||||||
APIBase: p.ShengSuanYun.APIBase,
|
APIBase: p.ShengSuanYun.APIBase,
|
||||||
Proxy: p.ShengSuanYun.Proxy,
|
Proxy: p.ShengSuanYun.Proxy,
|
||||||
|
RequestTimeout: p.ShengSuanYun.RequestTimeout,
|
||||||
}, true
|
}, true
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -243,6 +254,7 @@ func ConvertProvidersToModelList(cfg *Config) []ModelConfig {
|
||||||
APIKey: p.DeepSeek.APIKey,
|
APIKey: p.DeepSeek.APIKey,
|
||||||
APIBase: p.DeepSeek.APIBase,
|
APIBase: p.DeepSeek.APIBase,
|
||||||
Proxy: p.DeepSeek.Proxy,
|
Proxy: p.DeepSeek.Proxy,
|
||||||
|
RequestTimeout: p.DeepSeek.RequestTimeout,
|
||||||
}, true
|
}, true
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -259,6 +271,7 @@ func ConvertProvidersToModelList(cfg *Config) []ModelConfig {
|
||||||
APIKey: p.Cerebras.APIKey,
|
APIKey: p.Cerebras.APIKey,
|
||||||
APIBase: p.Cerebras.APIBase,
|
APIBase: p.Cerebras.APIBase,
|
||||||
Proxy: p.Cerebras.Proxy,
|
Proxy: p.Cerebras.Proxy,
|
||||||
|
RequestTimeout: p.Cerebras.RequestTimeout,
|
||||||
}, true
|
}, true
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -275,6 +288,7 @@ func ConvertProvidersToModelList(cfg *Config) []ModelConfig {
|
||||||
APIKey: p.VolcEngine.APIKey,
|
APIKey: p.VolcEngine.APIKey,
|
||||||
APIBase: p.VolcEngine.APIBase,
|
APIBase: p.VolcEngine.APIBase,
|
||||||
Proxy: p.VolcEngine.Proxy,
|
Proxy: p.VolcEngine.Proxy,
|
||||||
|
RequestTimeout: p.VolcEngine.RequestTimeout,
|
||||||
}, true
|
}, true
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -321,6 +335,7 @@ func ConvertProvidersToModelList(cfg *Config) []ModelConfig {
|
||||||
APIKey: p.Qwen.APIKey,
|
APIKey: p.Qwen.APIKey,
|
||||||
APIBase: p.Qwen.APIBase,
|
APIBase: p.Qwen.APIBase,
|
||||||
Proxy: p.Qwen.Proxy,
|
Proxy: p.Qwen.Proxy,
|
||||||
|
RequestTimeout: p.Qwen.RequestTimeout,
|
||||||
}, true
|
}, true
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -337,6 +352,7 @@ func ConvertProvidersToModelList(cfg *Config) []ModelConfig {
|
||||||
APIKey: p.Mistral.APIKey,
|
APIKey: p.Mistral.APIKey,
|
||||||
APIBase: p.Mistral.APIBase,
|
APIBase: p.Mistral.APIBase,
|
||||||
Proxy: p.Mistral.Proxy,
|
Proxy: p.Mistral.Proxy,
|
||||||
|
RequestTimeout: p.Mistral.RequestTimeout,
|
||||||
}, true
|
}, true
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -166,6 +166,27 @@ func TestConvertProvidersToModelList_Proxy(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestConvertProvidersToModelList_RequestTimeout(t *testing.T) {
|
||||||
|
cfg := &Config{
|
||||||
|
Providers: ProvidersConfig{
|
||||||
|
Ollama: ProviderConfig{
|
||||||
|
APIKey: "ollama-key",
|
||||||
|
RequestTimeout: 300,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
result := ConvertProvidersToModelList(cfg)
|
||||||
|
|
||||||
|
if len(result) != 1 {
|
||||||
|
t.Fatalf("len(result) = %d, want 1", len(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
if result[0].RequestTimeout != 300 {
|
||||||
|
t.Errorf("RequestTimeout = %d, want %d", result[0].RequestTimeout, 300)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestConvertProvidersToModelList_AuthMethod(t *testing.T) {
|
func TestConvertProvidersToModelList_AuthMethod(t *testing.T) {
|
||||||
cfg := &Config{
|
cfg := &Config{
|
||||||
Providers: ProvidersConfig{
|
Providers: ProvidersConfig{
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ package providers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/sipeed/picoclaw/pkg/providers/openai_compat"
|
"github.com/sipeed/picoclaw/pkg/providers/openai_compat"
|
||||||
)
|
)
|
||||||
|
|
@ -31,12 +32,12 @@ func NewHTTPProviderWithMaxTokensFieldAndRequestTimeout(
|
||||||
requestTimeoutSeconds int,
|
requestTimeoutSeconds int,
|
||||||
) *HTTPProvider {
|
) *HTTPProvider {
|
||||||
return &HTTPProvider{
|
return &HTTPProvider{
|
||||||
delegate: openai_compat.NewProviderWithMaxTokensFieldAndTimeout(
|
delegate: openai_compat.NewProvider(
|
||||||
apiKey,
|
apiKey,
|
||||||
apiBase,
|
apiBase,
|
||||||
proxy,
|
proxy,
|
||||||
maxTokensField,
|
openai_compat.WithMaxTokensField(maxTokensField),
|
||||||
requestTimeoutSeconds,
|
openai_compat.WithRequestTimeout(time.Duration(requestTimeoutSeconds)*time.Second),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,27 +34,27 @@ type Provider struct {
|
||||||
httpClient *http.Client
|
httpClient *http.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Option func(*Provider)
|
||||||
|
|
||||||
const defaultRequestTimeout = 120 * time.Second
|
const defaultRequestTimeout = 120 * time.Second
|
||||||
|
|
||||||
func NewProvider(apiKey, apiBase, proxy string) *Provider {
|
func WithMaxTokensField(maxTokensField string) Option {
|
||||||
return NewProviderWithMaxTokensField(apiKey, apiBase, proxy, "")
|
return func(p *Provider) {
|
||||||
|
p.maxTokensField = maxTokensField
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewProviderWithMaxTokensField(apiKey, apiBase, proxy, maxTokensField string) *Provider {
|
func WithRequestTimeout(timeout time.Duration) Option {
|
||||||
return NewProviderWithMaxTokensFieldAndTimeout(apiKey, apiBase, proxy, maxTokensField, 0)
|
return func(p *Provider) {
|
||||||
}
|
if timeout > 0 {
|
||||||
|
p.httpClient.Timeout = timeout
|
||||||
func NewProviderWithMaxTokensFieldAndTimeout(
|
}
|
||||||
apiKey, apiBase, proxy, maxTokensField string,
|
}
|
||||||
requestTimeoutSeconds int,
|
|
||||||
) *Provider {
|
|
||||||
timeout := defaultRequestTimeout
|
|
||||||
if requestTimeoutSeconds > 0 {
|
|
||||||
timeout = time.Duration(requestTimeoutSeconds) * time.Second
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewProvider(apiKey, apiBase, proxy string, opts ...Option) *Provider {
|
||||||
client := &http.Client{
|
client := &http.Client{
|
||||||
Timeout: timeout,
|
Timeout: defaultRequestTimeout,
|
||||||
}
|
}
|
||||||
|
|
||||||
if proxy != "" {
|
if proxy != "" {
|
||||||
|
|
@ -68,12 +68,36 @@ func NewProviderWithMaxTokensFieldAndTimeout(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Provider{
|
p := &Provider{
|
||||||
apiKey: apiKey,
|
apiKey: apiKey,
|
||||||
apiBase: strings.TrimRight(apiBase, "/"),
|
apiBase: strings.TrimRight(apiBase, "/"),
|
||||||
maxTokensField: maxTokensField,
|
|
||||||
httpClient: client,
|
httpClient: client,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, opt := range opts {
|
||||||
|
if opt != nil {
|
||||||
|
opt(p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewProviderWithMaxTokensField(apiKey, apiBase, proxy, maxTokensField string) *Provider {
|
||||||
|
return NewProvider(apiKey, apiBase, proxy, WithMaxTokensField(maxTokensField))
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewProviderWithMaxTokensFieldAndTimeout(
|
||||||
|
apiKey, apiBase, proxy, maxTokensField string,
|
||||||
|
requestTimeoutSeconds int,
|
||||||
|
) *Provider {
|
||||||
|
return NewProvider(
|
||||||
|
apiKey,
|
||||||
|
apiBase,
|
||||||
|
proxy,
|
||||||
|
WithMaxTokensField(maxTokensField),
|
||||||
|
WithRequestTimeout(time.Duration(requestTimeoutSeconds)*time.Second),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) Chat(
|
func (p *Provider) Chat(
|
||||||
|
|
|
||||||
|
|
@ -340,3 +340,24 @@ func TestProvider_RequestTimeoutOverride(t *testing.T) {
|
||||||
t.Fatalf("http timeout = %v, want %v", p.httpClient.Timeout, 300*time.Second)
|
t.Fatalf("http timeout = %v, want %v", p.httpClient.Timeout, 300*time.Second)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestProvider_FunctionalOptionMaxTokensField(t *testing.T) {
|
||||||
|
p := NewProvider("key", "https://example.com/v1", "", WithMaxTokensField("max_completion_tokens"))
|
||||||
|
if p.maxTokensField != "max_completion_tokens" {
|
||||||
|
t.Fatalf("maxTokensField = %q, want %q", p.maxTokensField, "max_completion_tokens")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProvider_FunctionalOptionRequestTimeout(t *testing.T) {
|
||||||
|
p := NewProvider("key", "https://example.com/v1", "", WithRequestTimeout(45*time.Second))
|
||||||
|
if p.httpClient.Timeout != 45*time.Second {
|
||||||
|
t.Fatalf("http timeout = %v, want %v", p.httpClient.Timeout, 45*time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProvider_FunctionalOptionRequestTimeoutNonPositive(t *testing.T) {
|
||||||
|
p := NewProvider("key", "https://example.com/v1", "", WithRequestTimeout(-1*time.Second))
|
||||||
|
if p.httpClient.Timeout != defaultRequestTimeout {
|
||||||
|
t.Fatalf("http timeout = %v, want %v", p.httpClient.Timeout, defaultRequestTimeout)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue