Nik Afiq 520f5d1ffb
Some checks failed
CI / build-ai-gateway (push) Has been cancelled
CI / build-ha-gateway (push) Has been cancelled
CI / build-discord-bot (push) Has been cancelled
CI / test (push) Has been cancelled
feat: add ai-gateway microservice with gRPC API for AI logic
- Implemented new gRPC service `AIService` in `proto/ai/v1/ai.proto` for handling natural language queries.
- Generated Go code for the gRPC service and messages in `gen/ai/v1/`.
- Created `services/ai-gateway/` directory structure with necessary files for the service.
- Added configuration loading and structured logging.
- Implemented domain logic for intent parsing and interaction with Home Assistant.
- Established outbound adapters for Ollama and Home Assistant with mTLS support.
- Updated `go.work` to include the new service and maintain existing dependencies.
- Modified `discord-bot` to use the new `ai-gateway` for AI interactions.
- Added deployment manifest for Kubernetes and CI/CD configuration for building and deploying the service.
2026-04-21 21:52:28 +09:00

90 lines
2.4 KiB
Go

package config
import (
"errors"
"fmt"
"os"
"path/filepath"
"time"
)
// Config holds runtime configuration for the AI gRPC gateway.
type Config struct {
GRPCPort string
OllamaURL string
OllamaModel string
OllamaTimeout time.Duration
HAGatewayAddr string
HAGatewayServerName string
TLSDir string
OTELEndpoint string
LogLevel string
LogFormat string
LightCacheTTL time.Duration
}
// Load reads configuration from environment variables and applies defaults.
func Load() (*Config, error) {
ollamaTimeout, err := parseDurationEnv("OLLAMA_TIMEOUT", 30*time.Second)
if err != nil {
return nil, err
}
cacheTTL, err := parseDurationEnv("LIGHT_CACHE_TTL", 60*time.Second)
if err != nil {
return nil, err
}
cfg := &Config{
GRPCPort: getenvDefault("GRPC_PORT", "50052"),
OllamaURL: getenvDefault("OLLAMA_URL", "http://192.168.7.96:11434"),
OllamaModel: getenvDefault("OLLAMA_MODEL", "llama3"),
OllamaTimeout: ollamaTimeout,
HAGatewayAddr: getenvDefault("HA_GATEWAY_ADDR", "ha-gateway.home-services.svc.cluster.local:50051"),
HAGatewayServerName: getenvDefault("HA_GATEWAY_SERVER_NAME", "ha-gateway.home-services.svc.cluster.local"),
TLSDir: os.Getenv("TLS_DIR"),
OTELEndpoint: os.Getenv("OTEL_ENDPOINT"),
LogLevel: getenvDefault("LOG_LEVEL", "info"),
LogFormat: getenvDefault("LOG_FORMAT", "json"),
LightCacheTTL: cacheTTL,
}
if cfg.TLSDir != "" {
if err := validateTLSDir(cfg.TLSDir); err != nil {
return nil, err
}
}
return cfg, nil
}
func getenvDefault(key, fallback string) string {
if v := os.Getenv(key); v != "" {
return v
}
return fallback
}
func parseDurationEnv(key string, fallback time.Duration) (time.Duration, error) {
if v := os.Getenv(key); v != "" {
d, err := time.ParseDuration(v)
if err != nil {
return 0, fmt.Errorf("parse %s: %w", key, err)
}
return d, nil
}
return fallback, nil
}
func validateTLSDir(dir string) error {
required := []string{"tls.crt", "tls.key", "ca.crt"}
for _, name := range required {
path := filepath.Join(dir, name)
info, err := os.Stat(path)
if err != nil {
return fmt.Errorf("tls dir validation failed for %s: %w", path, err)
}
if info.IsDir() {
return fmt.Errorf("tls dir validation failed for %s: %w", path, errors.New("expected file"))
}
}
return nil
}