mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-16 10:15:12 +00:00
fix: resolve embedding provider from DB registry + per-agent config (#134)
The embedding provider resolution only matched 3 hardcoded names (openai, openrouter, gemini), silently failing for DB-stored providers like "openai-embedding". This caused memory chunks to be stored without vectors even when a valid embedding provider was configured. Changes: - resolveEmbeddingProvider: fallback to provider registry for DB-stored provider names when hardcoded match fails - gateway startup: read per-agent memory config from DB (priority over config file defaults) for embedding provider resolution - memory IndexDocument: log embedding errors instead of swallowing them - memory admin ListChunks: return full chunk text instead of truncating to 200 chars, avoiding confusing partial content in the UI Co-authored-by: Luvu182 <208665161+Luvu182@users.noreply.github.com>
This commit is contained in:
1 parent
e9e0a4e813
commit
1b99406012
5 files changed
+53
-11
No files matched your search
+11
-2
@@ -338,9 +338,18 @@ func runGateway() {
|
||||
}
|
||||
|
||||
// Wire embedding provider to PGMemoryStore so IndexDocument generates vectors.
|
||||
// Per-agent DB config takes priority over config file defaults.
|
||||
if pgStores.Memory != nil {
|
||||
memCfg := cfg.Agents.Defaults.Memory
|
||||
if embProvider := resolveEmbeddingProvider(cfg, memCfg); embProvider != nil {
|
||||
if pgStores.Agents != nil {
|
||||
if defaultAgent, agErr := pgStores.Agents.GetByKey(context.Background(), "default"); agErr == nil {
|
||||
if agentMemCfg := defaultAgent.ParseMemoryConfig(); agentMemCfg != nil {
|
||||
memCfg = agentMemCfg
|
||||
slog.Debug("using per-agent memory config from DB", "agent", defaultAgent.AgentKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
if embProvider := resolveEmbeddingProvider(cfg, memCfg, providerRegistry); embProvider != nil {
|
||||
pgStores.Memory.SetEmbeddingProvider(embProvider)
|
||||
slog.Info("memory embeddings enabled", "provider", embProvider.Name(), "model", embProvider.Model())
|
||||
|
||||
@@ -495,7 +504,7 @@ func runGateway() {
|
||||
}
|
||||
if pgSkills, ok := pgStores.Skills.(*pg.PGSkillStore); ok {
|
||||
memCfg := cfg.Agents.Defaults.Memory
|
||||
if embProvider := resolveEmbeddingProvider(cfg, memCfg); embProvider != nil {
|
||||
if embProvider := resolveEmbeddingProvider(cfg, memCfg, providerRegistry); embProvider != nil {
|
||||
pgSkills.SetEmbeddingProvider(embProvider)
|
||||
skillSearchTool.SetEmbeddingSearcher(pgSkills, embProvider)
|
||||
slog.Info("skill embeddings enabled", "provider", embProvider.Name())
|
||||
|
||||
+32
-5
@@ -1,6 +1,7 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"strings"
|
||||
|
||||
"github.com/nextlevelbuilder/goclaw/internal/bus"
|
||||
@@ -13,23 +14,33 @@ import (
|
||||
)
|
||||
|
||||
// resolveEmbeddingProvider auto-selects an embedding provider based on config and available API keys.
|
||||
// Matching TS embedding provider auto-selection order.
|
||||
func resolveEmbeddingProvider(cfg *config.Config, memCfg *config.MemoryConfig) memory.EmbeddingProvider {
|
||||
// Resolution order:
|
||||
// 1. Explicit provider name from memCfg → hardcoded match → registry lookup (DB providers)
|
||||
// 2. Auto-detect: openai → openrouter → gemini (config-file keys)
|
||||
//
|
||||
// The optional providerReg allows resolving DB-stored provider names (e.g. "openai-embedding")
|
||||
// that don't match the hardcoded provider names.
|
||||
func resolveEmbeddingProvider(cfg *config.Config, memCfg *config.MemoryConfig, providerReg *providers.Registry) memory.EmbeddingProvider {
|
||||
// Explicit provider in config
|
||||
if memCfg != nil && memCfg.EmbeddingProvider != "" {
|
||||
return createEmbeddingProvider(memCfg.EmbeddingProvider, cfg, memCfg)
|
||||
if p := createEmbeddingProvider(memCfg.EmbeddingProvider, cfg, memCfg, providerReg); p != nil {
|
||||
return p
|
||||
}
|
||||
// Explicit name set but no match — don't auto-detect, log and return nil
|
||||
slog.Warn("embedding provider not found", "name", memCfg.EmbeddingProvider)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Auto-select: openai → openrouter → gemini
|
||||
for _, name := range []string{"openai", "openrouter", "gemini"} {
|
||||
if p := createEmbeddingProvider(name, cfg, memCfg); p != nil {
|
||||
if p := createEmbeddingProvider(name, cfg, memCfg, nil); p != nil {
|
||||
return p
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func createEmbeddingProvider(name string, cfg *config.Config, memCfg *config.MemoryConfig) memory.EmbeddingProvider {
|
||||
func createEmbeddingProvider(name string, cfg *config.Config, memCfg *config.MemoryConfig, providerReg *providers.Registry) memory.EmbeddingProvider {
|
||||
model := "text-embedding-3-small"
|
||||
apiBase := ""
|
||||
if memCfg != nil {
|
||||
@@ -71,6 +82,22 @@ func createEmbeddingProvider(name string, cfg *config.Config, memCfg *config.Mem
|
||||
return memory.NewOpenAIEmbeddingProvider("gemini", cfg.Providers.Gemini.APIKey, "https://generativelanguage.googleapis.com/v1beta/openai", geminiModel).
|
||||
WithDimensions(1536)
|
||||
}
|
||||
|
||||
// Fallback: resolve from provider registry (DB-stored providers like "openai-embedding").
|
||||
// Any OpenAI-compatible provider in the registry can serve embeddings.
|
||||
if providerReg != nil {
|
||||
if regProv, err := providerReg.Get(name); err == nil {
|
||||
if op, ok := regProv.(*providers.OpenAIProvider); ok {
|
||||
embBase := op.APIBase()
|
||||
if apiBase != "" {
|
||||
embBase = apiBase // memCfg override takes precedence
|
||||
}
|
||||
slog.Info("embedding provider resolved from registry", "name", name, "base", embBase, "model", model)
|
||||
return memory.NewOpenAIEmbeddingProvider(name, op.APIKey(), embBase, model)
|
||||
}
|
||||
slog.Warn("embedding provider in registry is not OpenAI-compatible", "name", name)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -458,7 +458,7 @@ func wireExtras(
|
||||
var delegateEmbProvider store.EmbeddingProvider
|
||||
if agentStore, ok := stores.Agents.(*pg.PGAgentStore); ok {
|
||||
memCfg := appCfg.Agents.Defaults.Memory
|
||||
if embProvider := resolveEmbeddingProvider(appCfg, memCfg); embProvider != nil {
|
||||
if embProvider := resolveEmbeddingProvider(appCfg, memCfg, providerReg); embProvider != nil {
|
||||
agentStore.SetEmbeddingProvider(embProvider)
|
||||
delegateEmbProvider = embProvider
|
||||
slog.Info("agent embeddings enabled")
|
||||
|
||||
@@ -127,7 +127,7 @@ func (s *PGMemoryStore) ListChunks(ctx context.Context, agentID, userID, path st
|
||||
var args []any
|
||||
if userID == "" {
|
||||
q = `SELECT c.id, c.start_line, c.end_line,
|
||||
LEFT(c.text, 200) AS text_preview,
|
||||
c.text AS text_preview,
|
||||
(c.embedding IS NOT NULL) AS has_embedding
|
||||
FROM memory_chunks c
|
||||
JOIN memory_documents d ON c.document_id = d.id
|
||||
@@ -136,7 +136,7 @@ func (s *PGMemoryStore) ListChunks(ctx context.Context, agentID, userID, path st
|
||||
args = []any{aid, path}
|
||||
} else {
|
||||
q = `SELECT c.id, c.start_line, c.end_line,
|
||||
LEFT(c.text, 200) AS text_preview,
|
||||
c.text AS text_preview,
|
||||
(c.embedding IS NOT NULL) AS has_embedding
|
||||
FROM memory_chunks c
|
||||
JOIN memory_documents d ON c.document_id = d.id
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
@@ -175,7 +176,12 @@ func (s *PGMemoryStore) IndexDocument(ctx context.Context, agentID, userID, path
|
||||
for i, c := range chunks {
|
||||
texts[i] = c.Text
|
||||
}
|
||||
embeddings, _ = s.provider.Embed(ctx, texts)
|
||||
var embErr error
|
||||
embeddings, embErr = s.provider.Embed(ctx, texts)
|
||||
if embErr != nil {
|
||||
slog.Warn("memory embedding failed, storing chunks without vectors",
|
||||
"path", path, "chunks", len(chunks), "error", embErr)
|
||||
}
|
||||
}
|
||||
|
||||
// Insert chunks
|
||||
|
||||
Reference in new issue
Block a user