feat(ai): add support for Ollama AI provider configuration

* Update README with Ollama integration details
* Add Ollama configuration to example YAML files
* Implement Ollama provider in AI factory
* Add tests for Ollama provider functionality
* Enhance config validation for Ollama settings
This commit is contained in:
Hein
2026-03-25 12:26:31 +02:00
parent ad05a9e228
commit c8ca272b03
12 changed files with 182 additions and 3 deletions

View File

@@ -62,6 +62,7 @@ type AIConfig struct {
Embeddings AIEmbeddingConfig `yaml:"embeddings"`
Metadata AIMetadataConfig `yaml:"metadata"`
LiteLLM LiteLLMConfig `yaml:"litellm"`
Ollama OllamaConfig `yaml:"ollama"`
OpenRouter OpenRouterAIConfig `yaml:"openrouter"`
}
@@ -84,6 +85,12 @@ type LiteLLMConfig struct {
MetadataModel string `yaml:"metadata_model"`
}
type OllamaConfig struct {
BaseURL string `yaml:"base_url"`
APIKey string `yaml:"api_key"`
RequestHeaders map[string]string `yaml:"request_headers"`
}
type OpenRouterAIConfig struct {
BaseURL string `yaml:"base_url"`
APIKey string `yaml:"api_key"`

View File

@@ -73,6 +73,10 @@ func defaultConfig() Config {
Model: "gpt-4o-mini",
Temperature: 0.1,
},
Ollama: OllamaConfig{
BaseURL: "http://localhost:11434/v1",
APIKey: "ollama",
},
},
Capture: CaptureConfig{
Source: DefaultSource,
@@ -97,6 +101,8 @@ func applyEnvOverrides(cfg *Config) {
overrideString(&cfg.Database.URL, "OB1_DATABASE_URL")
overrideString(&cfg.AI.LiteLLM.BaseURL, "OB1_LITELLM_BASE_URL")
overrideString(&cfg.AI.LiteLLM.APIKey, "OB1_LITELLM_API_KEY")
overrideString(&cfg.AI.Ollama.BaseURL, "OB1_OLLAMA_BASE_URL")
overrideString(&cfg.AI.Ollama.APIKey, "OB1_OLLAMA_API_KEY")
overrideString(&cfg.AI.OpenRouter.APIKey, "OB1_OPENROUTER_API_KEY")
if value, ok := os.LookupEnv("OB1_SERVER_PORT"); ok {

View File

@@ -69,3 +69,51 @@ logging:
t.Fatalf("server port = %d, want 9090", cfg.Server.Port)
}
}
func TestLoadAppliesOllamaEnvOverrides(t *testing.T) {
configPath := filepath.Join(t.TempDir(), "test.yaml")
if err := os.WriteFile(configPath, []byte(`
server:
port: 8080
mcp:
path: "/mcp"
auth:
keys:
- id: "test"
value: "secret"
database:
url: "postgres://from-file"
ai:
provider: "ollama"
embeddings:
model: "nomic-embed-text"
dimensions: 768
metadata:
model: "llama3.2"
ollama:
base_url: "http://localhost:11434/v1"
api_key: "ollama"
search:
default_limit: 10
max_limit: 50
logging:
level: "info"
`), 0o600); err != nil {
t.Fatalf("write config: %v", err)
}
t.Setenv("OB1_OLLAMA_BASE_URL", "https://ollama.example.com/v1")
t.Setenv("OB1_OLLAMA_API_KEY", "remote-key")
cfg, _, err := Load(configPath)
if err != nil {
t.Fatalf("Load() error = %v", err)
}
if cfg.AI.Ollama.BaseURL != "https://ollama.example.com/v1" {
t.Fatalf("ollama base url = %q, want env override", cfg.AI.Ollama.BaseURL)
}
if cfg.AI.Ollama.APIKey != "remote-key" {
t.Fatalf("ollama api key = %q, want env override", cfg.AI.Ollama.APIKey)
}
}

View File

@@ -28,7 +28,7 @@ func (c Config) Validate() error {
}
switch c.AI.Provider {
case "litellm", "openrouter":
case "litellm", "ollama", "openrouter":
default:
return fmt.Errorf("invalid config: unsupported ai.provider %q", c.AI.Provider)
}
@@ -45,6 +45,13 @@ func (c Config) Validate() error {
if strings.TrimSpace(c.AI.LiteLLM.APIKey) == "" {
return fmt.Errorf("invalid config: ai.litellm.api_key is required when ai.provider=litellm")
}
case "ollama":
if strings.TrimSpace(c.AI.Ollama.BaseURL) == "" {
return fmt.Errorf("invalid config: ai.ollama.base_url is required when ai.provider=ollama")
}
if strings.TrimSpace(c.AI.Ollama.APIKey) == "" {
return fmt.Errorf("invalid config: ai.ollama.api_key is required when ai.provider=ollama")
}
case "openrouter":
if strings.TrimSpace(c.AI.OpenRouter.BaseURL) == "" {
return fmt.Errorf("invalid config: ai.openrouter.base_url is required when ai.provider=openrouter")

View File

@@ -19,6 +19,10 @@ func validConfig() Config {
BaseURL: "http://localhost:4000/v1",
APIKey: "key",
},
Ollama: OllamaConfig{
BaseURL: "http://localhost:11434/v1",
APIKey: "ollama",
},
OpenRouter: OpenRouterAIConfig{
BaseURL: "https://openrouter.ai/api/v1",
APIKey: "key",
@@ -29,12 +33,17 @@ func validConfig() Config {
}
}
func TestValidateAcceptsLiteLLMAndOpenRouter(t *testing.T) {
func TestValidateAcceptsSupportedProviders(t *testing.T) {
cfg := validConfig()
if err := cfg.Validate(); err != nil {
t.Fatalf("Validate litellm error = %v", err)
}
cfg.AI.Provider = "ollama"
if err := cfg.Validate(); err != nil {
t.Fatalf("Validate ollama error = %v", err)
}
cfg.AI.Provider = "openrouter"
if err := cfg.Validate(); err != nil {
t.Fatalf("Validate openrouter error = %v", err)