-
Notifications
You must be signed in to change notification settings - Fork 40
feat: make embedding dimensions configurable via EMBEDDINGS_DIMENSIONS #222
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -44,8 +44,10 @@ func (db *PostgreSQL) getExecutor(tx pgx.Tx) Executor { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return db.pool | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // NewPostgreSQL creates a new instance of the PostgreSQL database | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func NewPostgreSQL(ctx context.Context, connectionURI string, authz auth.Authorizer) (*PostgreSQL, error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // NewPostgreSQL creates a new instance of the PostgreSQL database. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // embeddingDimensions configures the pgvector column size for semantic search. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Pass 0 to skip dimension reconciliation (e.g., when embeddings are disabled). | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func NewPostgreSQL(ctx context.Context, connectionURI string, authz auth.Authorizer, embeddingDimensions int) (*PostgreSQL, error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Parse connection config for pool settings | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| config, err := pgxpool.ParseConfig(connectionURI) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -81,12 +83,131 @@ func NewPostgreSQL(ctx context.Context, connectionURI string, authz auth.Authori | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return nil, fmt.Errorf("failed to run database migrations: %w", err) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // After migrations, ensure the semantic_embedding vector columns exist with | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // the configured dimension. The initial schema deliberately omits these | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // columns so the dimension is never hardcoded in SQL. This function creates | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // them on first run and reconciles the dimension if it changes later. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if embeddingDimensions > 0 { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if err := ensureVectorDimensions(ctx, conn.Conn(), embeddingDimensions); err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return nil, fmt.Errorf("failed to reconcile vector dimensions: %w", err) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+86
to
+94
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return &PostgreSQL{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pool: pool, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| authz: authz, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, nil | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // ensureVectorDimensions creates or reconciles the semantic_embedding vector | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // columns to match the configured EMBEDDINGS_DIMENSIONS. The initial SQL | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // migration deliberately omits these columns so the dimension is never | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // hardcoded in SQL. This function: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // - Creates the columns + HNSW indexes on first startup (fresh install) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // - Alters the columns if the configured dimension changes (provider switch) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // - No-ops if the columns already exist with the correct dimension | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func ensureVectorDimensions(ctx context.Context, conn *pgx.Conn, dimensions int) error { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Check whether the column exists and, if so, its current dimension. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // pgvector stores dimension+4 in atttypmod (the 4 accounts for the typmod header). | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var currentDim int | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| columnExists := true | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| err := conn.QueryRow(ctx, ` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| SELECT a.atttypmod - 4 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| FROM pg_attribute a | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| JOIN pg_class c ON a.attrelid = c.oid | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| JOIN pg_namespace n ON c.relnamespace = n.oid | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| WHERE n.nspname = 'public' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| AND c.relname = 'servers' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| AND a.attname = 'semantic_embedding' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| AND a.atttypmod > 0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| `).Scan(¤tDim) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Column doesn't exist yet (fresh install or pre-embeddings schema). | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| columnExists = false | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if columnExists && currentDim == dimensions { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return nil // Already correct, nothing to do. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+122
to
+130
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| AND a.atttypmod > 0 | |
| `).Scan(¤tDim) | |
| if err != nil { | |
| // Column doesn't exist yet (fresh install or pre-embeddings schema). | |
| columnExists = false | |
| } | |
| if columnExists && currentDim == dimensions { | |
| return nil // Already correct, nothing to do. | |
| `).Scan(¤tDim) | |
| if err != nil { | |
| if errors.Is(err, pgx.ErrNoRows) { | |
| // Column doesn't exist yet (fresh install or pre-embeddings schema). | |
| columnExists = false | |
| } else { | |
| return fmt.Errorf("failed to query semantic_embedding column: %w", err) | |
| } | |
| } | |
| if columnExists { | |
| // Column exists but has no valid typmod / dimension; this is an unexpected state | |
| // that we can't safely reconcile automatically. | |
| if currentDim <= 0 { | |
| return fmt.Errorf("semantic_embedding column exists but has no valid dimension typmod") | |
| } | |
| if currentDim == dimensions { | |
| return nil // Already correct, nothing to do. | |
| } |
Copilot
AI
Mar 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Existence/dimension detection is only done against public.servers.semantic_embedding, but the result is applied to both servers and agents. If the two tables ever get out of sync (e.g., one has the column and the other doesn’t, or dimensions differ), this can either no-op incorrectly or attempt an ALTER on a missing column. Consider checking existence + current dimension per table (or at least verifying both tables match before returning early).
Copilot
AI
Mar 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
UPDATE %s SET semantic_embedding = NULL will rewrite every row, even when semantic_embedding is already NULL, causing unnecessary bloat and potentially long startup times on large tables. Restrict this to rows that actually have embeddings (e.g., WHERE semantic_embedding IS NOT NULL) or use an ALTER COLUMN ... TYPE ... USING NULL approach to avoid a full-table update step.
| if _, err := tx.Exec(ctx, fmt.Sprintf("UPDATE %s SET semantic_embedding = NULL", table)); err != nil { | |
| if _, err := tx.Exec(ctx, fmt.Sprintf("UPDATE %s SET semantic_embedding = NULL WHERE semantic_embedding IS NOT NULL", table)); err != nil { |
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -84,7 +84,7 @@ func App(_ context.Context, opts ...types.AppOptions) error { | |||||||||||||
| return fmt.Errorf("failed to create database via factory: %w", err) | ||||||||||||||
| } | ||||||||||||||
| } else { | ||||||||||||||
| baseDB, err := internaldb.NewPostgreSQL(ctx, cfg.DatabaseURL, authz) | ||||||||||||||
| baseDB, err := internaldb.NewPostgreSQL(ctx, cfg.DatabaseURL, authz, cfg.Embeddings.Dimensions) | ||||||||||||||
|
||||||||||||||
| baseDB, err := internaldb.NewPostgreSQL(ctx, cfg.DatabaseURL, authz, cfg.Embeddings.Dimensions) | |
| embeddingDimensions := 0 | |
| if cfg.Embeddings.Enabled { | |
| embeddingDimensions = cfg.Embeddings.Dimensions | |
| } | |
| baseDB, err := internaldb.NewPostgreSQL(ctx, cfg.DatabaseURL, authz, embeddingDimensions) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This CLI path passes
embeddingDimensions=0, which skips creating thesemantic_embeddingvector columns. If--generate-embeddingsis enabled, the importer later callsUpsertServerEmbedding/SetServerEmbeddingwhich updatessemantic_embeddingand will fail on a fresh schema (the vector column is no longer created in the initial migration). Consider passingcfg.Embeddings.Dimensionswhen embeddings generation is requested (or otherwise ensuringensureVectorDimensionsruns before storing embeddings).