Добавил логи

This commit is contained in:
2026-06-14 19:37:09 +03:00
parent d4bf8a8cad
commit 81ed58ecff
28 changed files with 379 additions and 121 deletions
+1 -1
View File
@@ -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
View File
@@ -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)
}
+6 -6
View File
@@ -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
View File
@@ -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)
}