Add support for multiple providers

This commit is contained in:
dwrz
2026-06-19 14:31:41 +00:00
parent c71300b1bf
commit f5e72b1c7a
12 changed files with 948 additions and 869 deletions

View File

@@ -20,8 +20,6 @@ import (
type Config struct {
// Key is the API key for authentication.
Key string `yaml:"key"`
// Model is the model identifier.
Model string `yaml:"model"`
// SystemMessage is prepended to all conversations.
SystemMessage string `yaml:"system_message"`
// Timeout is the maximum duration for a query (e.g., "5m").
@@ -33,9 +31,6 @@ type Config struct {
// Validate checks that required configuration values are present and valid.
func (cfg Config) Validate() error {
if cfg.Model == "" {
return fmt.Errorf("missing model")
}
if cfg.Timeout != "" {
if _, err := time.ParseDuration(cfg.Timeout); err != nil {
return fmt.Errorf("invalid timeout: %w", err)
@@ -51,7 +46,6 @@ func (cfg Config) Validate() error {
type Client struct {
client *openai.Client
log *slog.Logger
model string
registry *tool.Registry
systemMessage string
timeout time.Duration
@@ -71,7 +65,6 @@ func NewClient(
llm := &Client{
log: log,
model: cfg.Model,
systemMessage: cfg.SystemMessage,
registry: registry,
}
@@ -115,17 +108,11 @@ func (c *Client) ListModels(ctx context.Context) ([]openai.Model, error) {
return res.Models, nil
}
// DefaultModel returns the configured default model.
func (c *Client) DefaultModel() string {
return c.model
}
// ErrMaxIterations is returned when the agent loop exceeds the configured
// maximum number of iterations.
var ErrMaxIterations = fmt.Errorf("max iterations exceeded")
// Query sends messages to the LLM using the specified model.
// If model is empty, uses the default configured model.
// If systemMessage is non-empty, it overrides the configured system message.
// maxIterations caps the number of agent loop iterations; a value of 0
// means unlimited (no cap).
@@ -141,11 +128,6 @@ func (c *Client) Query(
ctx, cancel := context.WithTimeout(ctx, c.timeout)
defer cancel()
// Fallback to the default model.
if model == "" {
model = c.model
}
// Use per-request system message if provided, otherwise fall back to config.
effectiveMessage := c.systemMessage
if systemMessage != "" {
@@ -273,11 +255,6 @@ func (c *Client) QueryStream(
ctx, cancel := context.WithTimeout(ctx, c.timeout)
defer cancel()
// Fallback to the default model.
if model == "" {
model = c.model
}
// Use per-request system message if provided, otherwise fall back to config.
effectiveMessage := c.systemMessage
if systemMessage != "" {
@@ -412,11 +389,6 @@ func (c *Client) QueryStream(
result = fmt.Sprintf(
`{"ok": false, "error": %q}`, err,
)
} else {
c.log.Info(
"called tool",
slog.String("name", tc.Function.Name),
)
}
// Content cannot be empty.
if strings.TrimSpace(result) == "" {

View File

@@ -16,23 +16,13 @@ func TestConfigValidate(t *testing.T) {
wantErr: true,
},
{
name: "missing model",
cfg: Config{
URL: "http://localhost:8080",
},
wantErr: true,
},
{
name: "missing URL",
cfg: Config{
Model: "test-model",
},
name: "missing URL",
cfg: Config{},
wantErr: true,
},
{
name: "invalid timeout",
cfg: Config{
Model: "test-model",
URL: "http://localhost:8080",
Timeout: "not-a-duration",
},
@@ -41,15 +31,13 @@ func TestConfigValidate(t *testing.T) {
{
name: "valid minimal config",
cfg: Config{
Model: "test-model",
URL: "http://localhost:8080",
URL: "http://localhost:8080",
},
wantErr: false,
},
{
name: "valid config with timeout",
cfg: Config{
Model: "test-model",
URL: "http://localhost:8080",
Timeout: "15m",
},
@@ -58,7 +46,6 @@ func TestConfigValidate(t *testing.T) {
{
name: "valid config with complex timeout",
cfg: Config{
Model: "test-model",
URL: "http://localhost:8080",
Timeout: "1h30m45s",
},
@@ -68,7 +55,6 @@ func TestConfigValidate(t *testing.T) {
name: "valid full config",
cfg: Config{
Key: "sk-test-key",
Model: "test-model",
SystemMessage: "You are a helpful assistant.",
Timeout: "30m",
URL: "http://localhost:8080",
@@ -104,15 +90,13 @@ func TestNewClient(t *testing.T) {
{
name: "valid config without timeout",
cfg: Config{
Model: "test-model",
URL: "http://localhost:8080",
URL: "http://localhost:8080",
},
wantErr: false,
},
{
name: "valid config with timeout",
cfg: Config{
Model: "test-model",
URL: "http://localhost:8080",
Timeout: "10m",
},
@@ -122,7 +106,6 @@ func TestNewClient(t *testing.T) {
name: "valid config with all fields",
cfg: Config{
Key: "test-key",
Model: "test-model",
SystemMessage: "Test message",
Timeout: "5m",
URL: "http://localhost:8080",
@@ -147,22 +130,3 @@ func TestNewClient(t *testing.T) {
})
}
}
func TestClientDefaultModel(t *testing.T) {
cfg := Config{
Model: "my-custom-model",
URL: "http://localhost:8080",
}
client, err := NewClient(cfg, nil, nil)
if err != nil {
t.Fatalf("NewClient() error = %v", err)
}
if got := client.DefaultModel(); got != "my-custom-model" {
t.Errorf(
"DefaultModel() = %q, want %q",
got, "my-custom-model",
)
}
}