diff --git a/internal/database/migrations/005_make_dimensions_nullable.sql b/internal/database/migrations/005_make_dimensions_nullable.sql new file mode 100644 index 0000000..4af65aa --- /dev/null +++ b/internal/database/migrations/005_make_dimensions_nullable.sql @@ -0,0 +1,9 @@ +-- Make dimensions nullable in instances table for consistency with definitions +-- This allows migration from older databases that may not have dimensions set + +ALTER TABLE instances ALTER COLUMN "dimensions" DROP NOT NULL; + +---- create above / drop below ---- + +-- Rollback: Make dimensions NOT NULL again +ALTER TABLE instances ALTER COLUMN "dimensions" SET NOT NULL; diff --git a/internal/database/models.go b/internal/database/models.go index c6d4356..516f462 100644 --- a/internal/database/models.go +++ b/internal/database/models.go @@ -26,8 +26,8 @@ type Definition struct { Description pgtype.Text `db:"description" json:"description"` APIStandard string `db:"api_standard" json:"api_standard"` Model string `db:"model" json:"model"` - Dimensions int32 `db:"dimensions" json:"dimensions"` - ContextLimit int32 `db:"context_limit" json:"context_limit"` + Dimensions pgtype.Int4 `db:"dimensions" json:"dimensions"` + ContextLimit pgtype.Int4 `db:"context_limit" json:"context_limit"` IsPublic bool `db:"is_public" json:"is_public"` CreatedAt pgtype.Timestamp `db:"created_at" json:"created_at"` UpdatedAt pgtype.Timestamp `db:"updated_at" json:"updated_at"` @@ -62,10 +62,10 @@ type Instance struct { Description pgtype.Text `db:"description" json:"description"` APIStandard string `db:"api_standard" json:"api_standard"` Model string `db:"model" json:"model"` - Dimensions int32 `db:"dimensions" json:"dimensions"` + Dimensions pgtype.Int4 `db:"dimensions" json:"dimensions"` CreatedAt pgtype.Timestamp `db:"created_at" json:"created_at"` UpdatedAt pgtype.Timestamp `db:"updated_at" json:"updated_at"` - ContextLimit int32 `db:"context_limit" json:"context_limit"` + ContextLimit pgtype.Int4 `db:"context_limit" json:"context_limit"` DefinitionID pgtype.Int4 `db:"definition_id" json:"definition_id"` APIKeyEncrypted []byte `db:"api_key_encrypted" json:"api_key_encrypted"` } diff --git a/internal/database/queries.sql.go b/internal/database/queries.sql.go index 2d24022..ed2f700 100644 --- a/internal/database/queries.sql.go +++ b/internal/database/queries.sql.go @@ -1749,8 +1749,8 @@ type RetrieveInstanceRow struct { HasAPIKey bool `db:"has_api_key" json:"has_api_key"` APIStandard string `db:"api_standard" json:"api_standard"` Model string `db:"model" json:"model"` - Dimensions int32 `db:"dimensions" json:"dimensions"` - ContextLimit int32 `db:"context_limit" json:"context_limit"` + Dimensions pgtype.Int4 `db:"dimensions" json:"dimensions"` + ContextLimit pgtype.Int4 `db:"context_limit" json:"context_limit"` CreatedAt pgtype.Timestamp `db:"created_at" json:"created_at"` UpdatedAt pgtype.Timestamp `db:"updated_at" json:"updated_at"` } @@ -1805,8 +1805,8 @@ type RetrieveInstanceByIDRow struct { Description pgtype.Text `db:"description" json:"description"` APIStandard string `db:"api_standard" json:"api_standard"` Model string `db:"model" json:"model"` - Dimensions int32 `db:"dimensions" json:"dimensions"` - ContextLimit int32 `db:"context_limit" json:"context_limit"` + Dimensions pgtype.Int4 `db:"dimensions" json:"dimensions"` + ContextLimit pgtype.Int4 `db:"context_limit" json:"context_limit"` CreatedAt pgtype.Timestamp `db:"created_at" json:"created_at"` UpdatedAt pgtype.Timestamp `db:"updated_at" json:"updated_at"` } @@ -1866,8 +1866,8 @@ type RetrieveInstanceByProjectRow struct { Description pgtype.Text `db:"description" json:"description"` APIStandard string `db:"api_standard" json:"api_standard"` Model string `db:"model" json:"model"` - Dimensions int32 `db:"dimensions" json:"dimensions"` - ContextLimit int32 `db:"context_limit" json:"context_limit"` + Dimensions pgtype.Int4 `db:"dimensions" json:"dimensions"` + ContextLimit pgtype.Int4 `db:"context_limit" json:"context_limit"` CreatedAt pgtype.Timestamp `db:"created_at" json:"created_at"` UpdatedAt pgtype.Timestamp `db:"updated_at" json:"updated_at"` } @@ -1932,8 +1932,8 @@ type RetrieveInstanceByProjectForUserRow struct { Description pgtype.Text `db:"description" json:"description"` APIStandard string `db:"api_standard" json:"api_standard"` Model string `db:"model" json:"model"` - Dimensions int32 `db:"dimensions" json:"dimensions"` - ContextLimit int32 `db:"context_limit" json:"context_limit"` + Dimensions pgtype.Int4 `db:"dimensions" json:"dimensions"` + ContextLimit pgtype.Int4 `db:"context_limit" json:"context_limit"` CreatedAt pgtype.Timestamp `db:"created_at" json:"created_at"` UpdatedAt pgtype.Timestamp `db:"updated_at" json:"updated_at"` AccessRole pgtype.Text `db:"access_role" json:"access_role"` @@ -1989,8 +1989,8 @@ type RetrieveInstanceByProjectIDRow struct { Description pgtype.Text `db:"description" json:"description"` APIStandard string `db:"api_standard" json:"api_standard"` Model string `db:"model" json:"model"` - Dimensions int32 `db:"dimensions" json:"dimensions"` - ContextLimit int32 `db:"context_limit" json:"context_limit"` + Dimensions pgtype.Int4 `db:"dimensions" json:"dimensions"` + ContextLimit pgtype.Int4 `db:"context_limit" json:"context_limit"` CreatedAt pgtype.Timestamp `db:"created_at" json:"created_at"` UpdatedAt pgtype.Timestamp `db:"updated_at" json:"updated_at"` } @@ -2126,8 +2126,8 @@ type RetrieveSharedInstanceRow struct { Description pgtype.Text `db:"description" json:"description"` APIStandard string `db:"api_standard" json:"api_standard"` Model string `db:"model" json:"model"` - Dimensions int32 `db:"dimensions" json:"dimensions"` - ContextLimit int32 `db:"context_limit" json:"context_limit"` + Dimensions pgtype.Int4 `db:"dimensions" json:"dimensions"` + ContextLimit pgtype.Int4 `db:"context_limit" json:"context_limit"` CreatedAt pgtype.Timestamp `db:"created_at" json:"created_at"` UpdatedAt pgtype.Timestamp `db:"updated_at" json:"updated_at"` } @@ -2316,8 +2316,8 @@ type UpsertDefinitionParams struct { Description pgtype.Text `db:"description" json:"description"` APIStandard string `db:"api_standard" json:"api_standard"` Model string `db:"model" json:"model"` - Dimensions int32 `db:"dimensions" json:"dimensions"` - ContextLimit int32 `db:"context_limit" json:"context_limit"` + Dimensions pgtype.Int4 `db:"dimensions" json:"dimensions"` + ContextLimit pgtype.Int4 `db:"context_limit" json:"context_limit"` IsPublic bool `db:"is_public" json:"is_public"` } @@ -2442,8 +2442,8 @@ type UpsertInstanceParams struct { APIKeyEncrypted []byte `db:"api_key_encrypted" json:"api_key_encrypted"` APIStandard string `db:"api_standard" json:"api_standard"` Model string `db:"model" json:"model"` - Dimensions int32 `db:"dimensions" json:"dimensions"` - ContextLimit int32 `db:"context_limit" json:"context_limit"` + Dimensions pgtype.Int4 `db:"dimensions" json:"dimensions"` + ContextLimit pgtype.Int4 `db:"context_limit" json:"context_limit"` } type UpsertInstanceRow struct { diff --git a/internal/handlers/admin.go b/internal/handlers/admin.go index 7b23672..dfc322b 100644 --- a/internal/handlers/admin.go +++ b/internal/handlers/admin.go @@ -88,7 +88,12 @@ func sanityCheckFunc(ctx context.Context, input *models.SanityCheckRequest) (*mo // Create a map with the single LLM service instance llmDimensions := make(map[int32]int32) - llmDimensions[instance.InstanceID] = instance.Dimensions + if instance.Dimensions.Valid { + llmDimensions[instance.InstanceID] = instance.Dimensions.Int32 + } else { + issues = append(issues, fmt.Sprintf("Project %s: LLM service instance does not have dimensions configured", projectName)) + continue + } // Get all embeddings for this project embeddings, err := queries.GetEmbeddingsByProject(ctx, database.GetEmbeddingsByProjectParams{ diff --git a/internal/handlers/embeddings.go b/internal/handlers/embeddings.go index 802d05e..240e32a 100644 --- a/internal/handlers/embeddings.go +++ b/internal/handlers/embeddings.go @@ -95,7 +95,10 @@ func postProjEmbeddingsFunc(ctx context.Context, input *models.PostProjEmbedding } // Validate embedding dimensions - if err := ValidateEmbeddingDimensions(embedding, instance.Dimensions); err != nil { + if !instance.Dimensions.Valid { + return nil, huma.Error500InternalServerError("LLM service instance does not have dimensions configured") + } + if err := ValidateEmbeddingDimensions(embedding, instance.Dimensions.Int32); err != nil { return nil, huma.Error400BadRequest(fmt.Sprintf("Dimension validation failed for input %s: %v", embedding.TextID, err)) } diff --git a/internal/handlers/llm_services.go b/internal/handlers/llm_services.go index cc41c7d..9ae88a6 100644 --- a/internal/handlers/llm_services.go +++ b/internal/handlers/llm_services.go @@ -74,8 +74,10 @@ func putDefinitionFunc(ctx context.Context, input *models.PutDefinitionRequest) Description: pgtype.Text{String: input.Body.Description, Valid: true}, APIStandard: input.Body.APIStandard, Model: input.Body.Model, - Dimensions: int32(input.Body.Dimensions), - ContextLimit: int32(input.Body.ContextLimit), + // Note: Using != 0 to determine if value was provided (due to omitempty in JSON). + // This means 0 cannot be explicitly stored, which is acceptable for these fields. + Dimensions: pgtype.Int4{Int32: int32(input.Body.Dimensions), Valid: input.Body.Dimensions != 0}, + ContextLimit: pgtype.Int4{Int32: int32(input.Body.ContextLimit), Valid: input.Body.ContextLimit != 0}, IsPublic: input.Body.IsPublic, }) if err != nil { @@ -177,8 +179,8 @@ func getDefinitionFunc(ctx context.Context, input *models.GetDefinitionRequest) Description: def.Description.String, APIStandard: def.APIStandard, Model: def.Model, - Dimensions: def.Dimensions, - ContextLimit: def.ContextLimit, + Dimensions: def.Dimensions.Int32, + ContextLimit: def.ContextLimit.Int32, IsPublic: def.IsPublic, } response := &models.GetDefinitionResponse{} @@ -466,7 +468,8 @@ func putInstanceFunc(ctx context.Context, input *models.PutInstanceRequest) (*mo APIKeyEncrypted: APIKeyEncrypted, APIStandard: input.Body.APIStandard, Model: input.Body.Model, - Dimensions: int32(input.Body.Dimensions), + Dimensions: pgtype.Int4{Int32: int32(input.Body.Dimensions), Valid: input.Body.Dimensions != 0}, + ContextLimit: pgtype.Int4{Int32: int32(input.Body.ContextLimit), Valid: input.Body.ContextLimit != 0}, }) if err != nil { return huma.Error500InternalServerError(fmt.Sprintf("unable to upload llm service instance: %v", err)) @@ -591,11 +594,11 @@ func postInstanceFromDefinitionFunc(ctx context.Context, input *models.PostInsta if input.Body.Model == "" { input.Body.Model = definition.Model } - if input.Body.Dimensions == 0 { - input.Body.Dimensions = definition.Dimensions + if input.Body.Dimensions == 0 && definition.Dimensions.Valid { + input.Body.Dimensions = definition.Dimensions.Int32 } - if input.Body.ContextLimit == 0 { - input.Body.ContextLimit = definition.ContextLimit + if input.Body.ContextLimit == 0 && definition.ContextLimit.Valid { + input.Body.ContextLimit = definition.ContextLimit.Int32 } // 1. Upsert LLM service instance @@ -608,8 +611,8 @@ func postInstanceFromDefinitionFunc(ctx context.Context, input *models.PostInsta APIKeyEncrypted: APIKeyEncrypted, APIStandard: input.Body.APIStandard, Model: input.Body.Model, - Dimensions: int32(input.Body.Dimensions), - ContextLimit: int32(input.Body.ContextLimit), + Dimensions: pgtype.Int4{Int32: int32(input.Body.Dimensions), Valid: input.Body.Dimensions != 0}, + ContextLimit: pgtype.Int4{Int32: int32(input.Body.ContextLimit), Valid: input.Body.ContextLimit != 0}, }) if err != nil { return fmt.Errorf("unable to upload llm service instance: %v", err) @@ -715,8 +718,8 @@ func getInstanceFunc(ctx context.Context, input *models.GetInstanceRequest) (*mo // APIKey: "", // Never return API key APIStandard: llm.APIStandard, Model: llm.Model, - Dimensions: llm.Dimensions, - ContextLimit: llm.ContextLimit, + Dimensions: llm.Dimensions.Int32, + ContextLimit: llm.ContextLimit.Int32, } response := &models.GetInstanceResponse{} response.Body = ls diff --git a/internal/handlers/similars.go b/internal/handlers/similars.go index 3ecb783..90163e6 100644 --- a/internal/handlers/similars.go +++ b/internal/handlers/similars.go @@ -166,8 +166,11 @@ func postSimilarFunc(ctx context.Context, input *models.PostSimilarRequest) (*mo } // Validate that the vector dimensions match the LLM service instance dimensions - if len(input.Body.Vector) != int(instance.Dimensions) { - return nil, huma.Error400BadRequest(fmt.Sprintf("vector dimension mismatch: expected %d dimensions, got %d", instance.Dimensions, len(input.Body.Vector))) + if !instance.Dimensions.Valid { + return nil, huma.Error500InternalServerError("LLM service instance does not have dimensions configured") + } + if len(input.Body.Vector) != int(instance.Dimensions.Int32) { + return nil, huma.Error400BadRequest(fmt.Sprintf("vector dimension mismatch: expected %d dimensions, got %d", instance.Dimensions.Int32, len(input.Body.Vector))) } // Convert the vector to pgvector HalfVector format (half-precision float16)