Добавил логи
This commit is contained in:
@@ -31,7 +31,7 @@ func TestIntegration_OpenAICompat(t *testing.T) {
|
||||
APIKey: key,
|
||||
Model: model,
|
||||
Timeout: 90 * time.Second,
|
||||
})
|
||||
}, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("New: %v", err)
|
||||
}
|
||||
|
||||
+8
-4
@@ -14,6 +14,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -74,13 +75,16 @@ type Config struct {
|
||||
// ErrUnknownType — запрошенный [llm].type не поддерживается.
|
||||
var ErrUnknownType = errors.New("llm: unknown provider type")
|
||||
|
||||
// New собирает провайдер по дискриминатору cfg.Type.
|
||||
func New(cfg Config) (Provider, error) {
|
||||
// New собирает провайдер по дискриминатору cfg.Type. logger nil → slog.Default().
|
||||
func New(cfg Config, logger *slog.Logger) (Provider, error) {
|
||||
if logger == nil {
|
||||
logger = slog.Default()
|
||||
}
|
||||
switch cfg.Type {
|
||||
case "openai-compat":
|
||||
return newOpenAICompat(cfg)
|
||||
return newOpenAICompat(cfg, logger)
|
||||
case "":
|
||||
return nil, fmt.Errorf("%w: %q (укажите [llm].type)", ErrUnknownType, cfg.Type)
|
||||
return nil, fmt.Errorf("%w: %q (set [llm].type)", ErrUnknownType, cfg.Type)
|
||||
default:
|
||||
return nil, fmt.Errorf("%w: %q", ErrUnknownType, cfg.Type)
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ func newTestProvider(t *testing.T, baseURL, apiKey string) *openAICompat {
|
||||
BaseURL: baseURL,
|
||||
APIKey: apiKey,
|
||||
Model: "test-model",
|
||||
})
|
||||
}, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("newOpenAICompat: %v", err)
|
||||
}
|
||||
@@ -215,22 +215,22 @@ func TestComplete_EmptyMessages(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestNew_UnknownType(t *testing.T) {
|
||||
if _, err := New(Config{Type: "anthropic", Model: "x", BaseURL: "http://x"}); err == nil {
|
||||
if _, err := New(Config{Type: "anthropic", Model: "x", BaseURL: "http://x"}, nil); err == nil {
|
||||
t.Fatal("want error for unknown type")
|
||||
}
|
||||
if _, err := New(Config{Type: ""}); err == nil {
|
||||
if _, err := New(Config{Type: ""}, nil); err == nil {
|
||||
t.Fatal("want error for empty type")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNew_OpenAICompatValidation(t *testing.T) {
|
||||
if _, err := New(Config{Type: "openai-compat", Model: "x"}); err == nil {
|
||||
if _, err := New(Config{Type: "openai-compat", Model: "x"}, nil); err == nil {
|
||||
t.Fatal("want error for empty base_url")
|
||||
}
|
||||
if _, err := New(Config{Type: "openai-compat", BaseURL: "http://x"}); err == nil {
|
||||
if _, err := New(Config{Type: "openai-compat", BaseURL: "http://x"}, nil); err == nil {
|
||||
t.Fatal("want error for empty model")
|
||||
}
|
||||
if _, err := New(Config{Type: "openai-compat", BaseURL: "http://x", Model: "m"}); err != nil {
|
||||
if _, err := New(Config{Type: "openai-compat", BaseURL: "http://x", Model: "m"}, nil); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
+22
-1
@@ -6,6 +6,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
@@ -26,10 +27,11 @@ type openAICompat struct {
|
||||
apiKey string
|
||||
model string
|
||||
retryWait time.Duration // базовая пауза между ретраями (0 в тестах)
|
||||
log *slog.Logger
|
||||
}
|
||||
|
||||
// newOpenAICompat собирает клиент из конфига.
|
||||
func newOpenAICompat(cfg Config) (*openAICompat, error) {
|
||||
func newOpenAICompat(cfg Config, logger *slog.Logger) (*openAICompat, error) {
|
||||
if cfg.BaseURL == "" {
|
||||
return nil, fmt.Errorf("llm: empty base_url")
|
||||
}
|
||||
@@ -51,12 +53,16 @@ func newOpenAICompat(cfg Config) (*openAICompat, error) {
|
||||
transport = &http.Transport{Proxy: http.ProxyURL(proxyURL)}
|
||||
}
|
||||
|
||||
if logger == nil {
|
||||
logger = slog.Default()
|
||||
}
|
||||
return &openAICompat{
|
||||
endpoint: strings.TrimRight(cfg.BaseURL, "/") + "/chat/completions",
|
||||
hc: &http.Client{Timeout: timeout, Transport: transport},
|
||||
apiKey: cfg.APIKey,
|
||||
model: cfg.Model,
|
||||
retryWait: baseRetryWait,
|
||||
log: logger,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -119,15 +125,30 @@ func (c *openAICompat) Complete(ctx context.Context, req Request) (Response, err
|
||||
}
|
||||
}
|
||||
|
||||
c.log.Debug("llm: request",
|
||||
"endpoint", c.endpoint, "model", c.model,
|
||||
"attempt", attempt, "max_attempts", maxAttempts)
|
||||
start := time.Now()
|
||||
resp, retryable, err := c.do(ctx, body)
|
||||
if err == nil {
|
||||
c.log.Debug("llm: response ok",
|
||||
"model", resp.Model, "attempt", attempt,
|
||||
"duration", time.Since(start),
|
||||
"total_tokens", resp.Usage.TotalTokens, "cost", resp.Usage.Cost)
|
||||
return resp, nil
|
||||
}
|
||||
lastErr = err
|
||||
if !retryable {
|
||||
c.log.Error("llm: request failed (non-retryable)",
|
||||
"model", c.model, "attempt", attempt, "duration", time.Since(start), "err", err)
|
||||
return Response{}, err
|
||||
}
|
||||
c.log.Warn("llm: request failed, will retry",
|
||||
"model", c.model, "attempt", attempt, "max_attempts", maxAttempts,
|
||||
"duration", time.Since(start), "err", err)
|
||||
}
|
||||
c.log.Error("llm: all attempts exhausted",
|
||||
"model", c.model, "max_attempts", maxAttempts, "err", lastErr)
|
||||
return Response{}, fmt.Errorf("llm: exhausted %d attempts: %w", maxAttempts, lastErr)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user