package platformsettings import ( "context" "errors" "strings" "github.com/descrybe/descrybe-v2/apps/api/internal/processing" ) // DynamicPinecone resolves platform settings on each call so admin changes // apply without restarting the worker. Embeddings use admin AI role // "vectorization" (ResolveAIConfig) with OPENAI_EMBEDDING_* / OPENAI_* env fallback. type DynamicPinecone struct { Settings *Service } func (d *DynamicPinecone) Enabled() bool { if d == nil || d.Settings == nil { return false } cfg, err := d.Settings.ResolvePinecone(context.Background()) return err == nil && cfg.Configured() } func (d *DynamicPinecone) SuggestCategory(ctx context.Context, companyID, productText string, candidates []string) (string, error) { if d == nil || d.Settings == nil { return "", errors.New("pinecone not configured") } cfg, err := d.Settings.ResolvePinecone(ctx) if err != nil { return "", err } if !cfg.Configured() { return "", errors.New("pinecone not configured") } cat := processing.NewPineconeCategorizer(cfg.APIKey, cfg.Host, cfg.Namespace) if emb, eerr := d.Settings.ResolveEmbedder(ctx); eerr == nil && emb != nil { cat.Embedder = emb } return cat.SuggestCategory(ctx, companyID, productText, candidates) } // ResolveEmbedder builds an OpenAI-compatible Embedder from admin AI role // "vectorization" (DB), falling back to OPENAI_EMBEDDING_* then OPENAI_* env. // Returns (nil, nil) when unset so callers can keep Pinecone text-query mode. func (s *Service) ResolveEmbedder(ctx context.Context) (processing.Embedder, error) { if s == nil { return nil, nil } cfg, err := s.ResolveAIConfig(ctx, AIRoleVectorization) if err != nil { return nil, err } if strings.TrimSpace(cfg.APIKey) == "" { return nil, nil } model := strings.TrimSpace(cfg.Model) if model == "" { model = defaultEmbeddingModel } client := processing.NewOpenAIClient(cfg.APIKey, cfg.BaseURL, model, 0, 3) return client, nil }