| 140 | } |
| 141 | |
| 142 | func (s *Spec) Validate() error { |
| 143 | if len(s.ConnectionString) == 0 { |
| 144 | return errors.New("`connection_string` is required") |
| 145 | } |
| 146 | if s.Lakebase != nil && len(s.Lakebase.Endpoint) == 0 { |
| 147 | return errors.New("`lakebase.endpoint` is required when `lakebase` is set") |
| 148 | } |
| 149 | if s.PgVectorConfig != nil { |
| 150 | if len(s.PgVectorConfig.Tables) == 0 { |
| 151 | return errors.New("`pgvector_config.tables` must contain at least 1 table") |
| 152 | } |
| 153 | seenSourceNames := make(map[string]struct{}, len(s.PgVectorConfig.Tables)) |
| 154 | seenTargetNames := make(map[string]struct{}, len(s.PgVectorConfig.Tables)) |
| 155 | for _, tbl := range s.PgVectorConfig.Tables { |
| 156 | if len(tbl.SourceTableName) == 0 { |
| 157 | return errors.New("`pgvector_config.tables.source_table_name` is required") |
| 158 | } |
| 159 | if len(tbl.TargetTableName) == 0 { |
| 160 | return errors.New("`pgvector_config.tables.target_table_name` is required") |
| 161 | } |
| 162 | if _, ok := seenSourceNames[tbl.SourceTableName]; ok { |
| 163 | return errors.New("`pgvector_config.tables` contains duplicate source table names: " + tbl.SourceTableName) |
| 164 | } |
| 165 | if _, ok := seenTargetNames[tbl.TargetTableName]; ok { |
| 166 | return errors.New("`pgvector_config.tables` contains duplicate target table names: " + tbl.TargetTableName) |
| 167 | } |
| 168 | seenSourceNames[tbl.SourceTableName] = struct{}{} |
| 169 | seenTargetNames[tbl.TargetTableName] = struct{}{} |
| 170 | if len(tbl.EmbedColumns) == 0 { |
| 171 | return errors.New("`pgvector_config.tables.embed_columns` must contain at least 1 column") |
| 172 | } |
| 173 | } |
| 174 | emb := s.PgVectorConfig.OpenAIEmbedding |
| 175 | if emb.Dimensions <= 0 || len(emb.APIKey) == 0 || len(emb.ModelName) == 0 { |
| 176 | return errors.New("`pgvector_config.openai_embedding` must have `dimensions`, `api_key`, and `model_name` set") |
| 177 | } |
| 178 | // Enforce model support and sync dimensions to the selected model |
| 179 | dims, err := embeddingDimensionsForModel(emb.ModelName) |
| 180 | if err != nil { |
| 181 | return err |
| 182 | } |
| 183 | s.PgVectorConfig.OpenAIEmbedding.Dimensions = dims |
| 184 | if s.PgVectorConfig.TextSplitter != nil { |
| 185 | ts := s.PgVectorConfig.TextSplitter |
| 186 | if ts.RecursiveText.ChunkSize <= 0 { |
| 187 | return errors.New("`pgvector_config.text_splitter.recursive_text.chunk_size` must be > 0") |
| 188 | } |
| 189 | if ts.RecursiveText.ChunkOverlap < 0 { |
| 190 | return errors.New("`pgvector_config.text_splitter.recursive_text.chunk_overlap` must be >= 0") |
| 191 | } |
| 192 | } |
| 193 | } |
| 194 | return nil |
| 195 | } |
| 196 | |
| 197 | func (Spec) JSONSchemaExtend(sc *jsonschema.Schema) { |
| 198 | sc.Properties.Value("batch_timeout").Default = "60s" |