83 lines
1.9 KiB
Go
83 lines
1.9 KiB
Go
|
|
package llm
|
||
|
|
|
||
|
|
import (
|
||
|
|
"fmt"
|
||
|
|
"log/slog"
|
||
|
|
"net/http"
|
||
|
|
"time"
|
||
|
|
)
|
||
|
|
|
||
|
|
// Config holds the configuration for an LLM client.
|
||
|
|
type Config struct {
|
||
|
|
// Key is the API key for authentication.
|
||
|
|
Key string `yaml:"key"`
|
||
|
|
// SystemMessage is prepended to all conversations.
|
||
|
|
SystemMessage string `yaml:"system_message"`
|
||
|
|
// Timeout is the maximum duration for a query (e.g., "5m").
|
||
|
|
// Defaults to 5 minutes if empty.
|
||
|
|
Timeout string `yaml:"timeout"`
|
||
|
|
// URL is the base URL of the OpenAI-compatible API endpoint.
|
||
|
|
URL string `yaml:"url"`
|
||
|
|
}
|
||
|
|
|
||
|
|
// Validate checks that required configuration values are present and valid.
|
||
|
|
func (cfg Config) Validate() error {
|
||
|
|
if cfg.Timeout != "" {
|
||
|
|
if _, err := time.ParseDuration(cfg.Timeout); err != nil {
|
||
|
|
return fmt.Errorf("invalid timeout: %w", err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if cfg.URL == "" {
|
||
|
|
return fmt.Errorf("missing URL")
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// Client wraps an OpenAI-compatible client with tool execution support.
|
||
|
|
type Client struct {
|
||
|
|
baseURL string
|
||
|
|
httpc *http.Client
|
||
|
|
key string
|
||
|
|
log *slog.Logger
|
||
|
|
registry *Registry
|
||
|
|
systemMessage string
|
||
|
|
timeout time.Duration
|
||
|
|
tools []APITool
|
||
|
|
}
|
||
|
|
|
||
|
|
// NewClient creates a new LLM client with the provided configuration.
|
||
|
|
// The registry is optional; if nil, tool calling is disabled.
|
||
|
|
func NewClient(
|
||
|
|
cfg Config,
|
||
|
|
registry *Registry,
|
||
|
|
log *slog.Logger,
|
||
|
|
) (*Client, error) {
|
||
|
|
if err := cfg.Validate(); err != nil {
|
||
|
|
return nil, fmt.Errorf("invalid config: %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
timeout := 5 * time.Minute
|
||
|
|
if cfg.Timeout != "" {
|
||
|
|
d, err := time.ParseDuration(cfg.Timeout)
|
||
|
|
if err != nil {
|
||
|
|
return nil, fmt.Errorf("parse timeout: %v", err)
|
||
|
|
}
|
||
|
|
timeout = d
|
||
|
|
}
|
||
|
|
|
||
|
|
llm := &Client{
|
||
|
|
baseURL: cfg.URL,
|
||
|
|
key: cfg.Key,
|
||
|
|
log: log,
|
||
|
|
systemMessage: cfg.SystemMessage,
|
||
|
|
registry: registry,
|
||
|
|
httpc: &http.Client{},
|
||
|
|
timeout: timeout,
|
||
|
|
}
|
||
|
|
|
||
|
|
// Convert tools from registry.
|
||
|
|
llm.tools = registry.ToAPI()
|
||
|
|
|
||
|
|
return llm, nil
|
||
|
|
}
|