Files
odidere/internal/llm/client.go
T

85 lines
2.2 KiB
Go
Raw Normal View History

2026-06-26 14:22:05 +00:00
package llm
import (
"fmt"
"log/slog"
"net/http"
"time"
)
2026-09-20 12:49:42 +00:00
const (
// chatCompletionsPath is the endpoint for chat completion requests.
chatCompletionsPath = "/chat/completions"
// chatTemplateEnableThinking is the chat-template argument that selects
// whether a reasoning model emits reasoning tokens.
chatTemplateEnableThinking = "enable_thinking"
2026-10-08 01:00:33 +00:00
// defaultTimeout limits requests when Config.Timeout is zero.
2026-09-20 12:49:42 +00:00
defaultTimeout = 5 * time.Minute
// sseDone is the sentinel that terminates a chat completion stream.
sseDone = "[DONE]"
)
// Config configures a Client.
2026-06-26 14:22:05 +00:00
type Config struct {
2026-09-20 12:49:42 +00:00
// Key is sent as a bearer token. An empty key disables authentication.
2026-06-26 14:22:05 +00:00
Key string `yaml:"key"`
2026-09-20 12:49:42 +00:00
// Provider is returned unchanged by Client.Provider. It is not sent to the
// inference endpoint.
Provider string `yaml:"provider"`
2026-10-08 01:00:33 +00:00
// Timeout limits each request, including reading a streaming response.
// It defaults to five minutes when zero.
Timeout time.Duration `yaml:"timeout"`
2026-09-20 12:49:42 +00:00
// URL is the endpoint root. Client appends /chat/completions or /models.
2026-06-26 14:22:05 +00:00
URL string `yaml:"url"`
}
2026-10-08 01:00:33 +00:00
// Validate reports whether Timeout is non-negative and URL is non-empty.
2026-06-26 14:22:05 +00:00
func (cfg Config) Validate() error {
2026-10-08 01:00:33 +00:00
if cfg.Timeout < 0 {
return fmt.Errorf("invalid timeout %v: must not be negative", cfg.Timeout)
2026-06-26 14:22:05 +00:00
}
if cfg.URL == "" {
return fmt.Errorf("missing URL")
}
return nil
}
2026-09-20 12:49:42 +00:00
// Client is an OpenAI-compatible inference client.
2026-06-26 14:22:05 +00:00
type Client struct {
2026-09-20 12:49:42 +00:00
baseURL string
httpc *http.Client
key string
log *slog.Logger
provider string
timeout time.Duration
2026-06-26 14:22:05 +00:00
}
2026-10-08 01:00:33 +00:00
// NewClient creates a client from cfg.
2026-09-20 12:49:42 +00:00
func NewClient(cfg Config, log *slog.Logger) (*Client, error) {
2026-06-26 14:22:05 +00:00
if err := cfg.Validate(); err != nil {
return nil, fmt.Errorf("invalid config: %w", err)
}
2026-09-20 12:49:42 +00:00
if log == nil {
return nil, fmt.Errorf("missing logger")
}
2026-06-26 14:22:05 +00:00
2026-10-08 01:00:33 +00:00
timeout := cfg.Timeout
if timeout == 0 {
timeout = defaultTimeout
2026-06-26 14:22:05 +00:00
}
2026-09-20 12:49:42 +00:00
return &Client{
baseURL: cfg.URL,
key: cfg.Key,
log: log,
provider: cfg.Provider,
httpc: &http.Client{},
timeout: timeout,
}, nil
}
2026-06-26 14:22:05 +00:00
2026-09-20 12:49:42 +00:00
// Provider returns the application-defined provider name supplied in Config.
func (c *Client) Provider() string {
return c.provider
2026-06-26 14:22:05 +00:00
}