remdb 0.2.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of remdb might be problematic. Click here for more details.

Files changed (187) hide show
  1. rem/__init__.py +2 -0
  2. rem/agentic/README.md +650 -0
  3. rem/agentic/__init__.py +39 -0
  4. rem/agentic/agents/README.md +155 -0
  5. rem/agentic/agents/__init__.py +8 -0
  6. rem/agentic/context.py +148 -0
  7. rem/agentic/context_builder.py +329 -0
  8. rem/agentic/mcp/__init__.py +0 -0
  9. rem/agentic/mcp/tool_wrapper.py +107 -0
  10. rem/agentic/otel/__init__.py +5 -0
  11. rem/agentic/otel/setup.py +151 -0
  12. rem/agentic/providers/phoenix.py +674 -0
  13. rem/agentic/providers/pydantic_ai.py +572 -0
  14. rem/agentic/query.py +117 -0
  15. rem/agentic/query_helper.py +89 -0
  16. rem/agentic/schema.py +396 -0
  17. rem/agentic/serialization.py +245 -0
  18. rem/agentic/tools/__init__.py +5 -0
  19. rem/agentic/tools/rem_tools.py +231 -0
  20. rem/api/README.md +420 -0
  21. rem/api/main.py +324 -0
  22. rem/api/mcp_router/prompts.py +182 -0
  23. rem/api/mcp_router/resources.py +536 -0
  24. rem/api/mcp_router/server.py +213 -0
  25. rem/api/mcp_router/tools.py +584 -0
  26. rem/api/routers/auth.py +229 -0
  27. rem/api/routers/chat/__init__.py +5 -0
  28. rem/api/routers/chat/completions.py +281 -0
  29. rem/api/routers/chat/json_utils.py +76 -0
  30. rem/api/routers/chat/models.py +124 -0
  31. rem/api/routers/chat/streaming.py +185 -0
  32. rem/auth/README.md +258 -0
  33. rem/auth/__init__.py +26 -0
  34. rem/auth/middleware.py +100 -0
  35. rem/auth/providers/__init__.py +13 -0
  36. rem/auth/providers/base.py +376 -0
  37. rem/auth/providers/google.py +163 -0
  38. rem/auth/providers/microsoft.py +237 -0
  39. rem/cli/README.md +455 -0
  40. rem/cli/__init__.py +8 -0
  41. rem/cli/commands/README.md +126 -0
  42. rem/cli/commands/__init__.py +3 -0
  43. rem/cli/commands/ask.py +565 -0
  44. rem/cli/commands/configure.py +423 -0
  45. rem/cli/commands/db.py +493 -0
  46. rem/cli/commands/dreaming.py +324 -0
  47. rem/cli/commands/experiments.py +1124 -0
  48. rem/cli/commands/mcp.py +66 -0
  49. rem/cli/commands/process.py +245 -0
  50. rem/cli/commands/schema.py +183 -0
  51. rem/cli/commands/serve.py +106 -0
  52. rem/cli/dreaming.py +363 -0
  53. rem/cli/main.py +88 -0
  54. rem/config.py +237 -0
  55. rem/mcp_server.py +41 -0
  56. rem/models/core/__init__.py +49 -0
  57. rem/models/core/core_model.py +64 -0
  58. rem/models/core/engram.py +333 -0
  59. rem/models/core/experiment.py +628 -0
  60. rem/models/core/inline_edge.py +132 -0
  61. rem/models/core/rem_query.py +243 -0
  62. rem/models/entities/__init__.py +43 -0
  63. rem/models/entities/file.py +57 -0
  64. rem/models/entities/image_resource.py +88 -0
  65. rem/models/entities/message.py +35 -0
  66. rem/models/entities/moment.py +123 -0
  67. rem/models/entities/ontology.py +191 -0
  68. rem/models/entities/ontology_config.py +131 -0
  69. rem/models/entities/resource.py +95 -0
  70. rem/models/entities/schema.py +87 -0
  71. rem/models/entities/user.py +85 -0
  72. rem/py.typed +0 -0
  73. rem/schemas/README.md +507 -0
  74. rem/schemas/__init__.py +6 -0
  75. rem/schemas/agents/README.md +92 -0
  76. rem/schemas/agents/core/moment-builder.yaml +178 -0
  77. rem/schemas/agents/core/rem-query-agent.yaml +226 -0
  78. rem/schemas/agents/core/resource-affinity-assessor.yaml +99 -0
  79. rem/schemas/agents/core/simple-assistant.yaml +19 -0
  80. rem/schemas/agents/core/user-profile-builder.yaml +163 -0
  81. rem/schemas/agents/examples/contract-analyzer.yaml +317 -0
  82. rem/schemas/agents/examples/contract-extractor.yaml +134 -0
  83. rem/schemas/agents/examples/cv-parser.yaml +263 -0
  84. rem/schemas/agents/examples/hello-world.yaml +37 -0
  85. rem/schemas/agents/examples/query.yaml +54 -0
  86. rem/schemas/agents/examples/simple.yaml +21 -0
  87. rem/schemas/agents/examples/test.yaml +29 -0
  88. rem/schemas/agents/rem.yaml +128 -0
  89. rem/schemas/evaluators/hello-world/default.yaml +77 -0
  90. rem/schemas/evaluators/rem/faithfulness.yaml +219 -0
  91. rem/schemas/evaluators/rem/lookup-correctness.yaml +182 -0
  92. rem/schemas/evaluators/rem/retrieval-precision.yaml +199 -0
  93. rem/schemas/evaluators/rem/retrieval-recall.yaml +211 -0
  94. rem/schemas/evaluators/rem/search-correctness.yaml +192 -0
  95. rem/services/__init__.py +16 -0
  96. rem/services/audio/INTEGRATION.md +308 -0
  97. rem/services/audio/README.md +376 -0
  98. rem/services/audio/__init__.py +15 -0
  99. rem/services/audio/chunker.py +354 -0
  100. rem/services/audio/transcriber.py +259 -0
  101. rem/services/content/README.md +1269 -0
  102. rem/services/content/__init__.py +5 -0
  103. rem/services/content/providers.py +806 -0
  104. rem/services/content/service.py +657 -0
  105. rem/services/dreaming/README.md +230 -0
  106. rem/services/dreaming/__init__.py +53 -0
  107. rem/services/dreaming/affinity_service.py +336 -0
  108. rem/services/dreaming/moment_service.py +264 -0
  109. rem/services/dreaming/ontology_service.py +54 -0
  110. rem/services/dreaming/user_model_service.py +297 -0
  111. rem/services/dreaming/utils.py +39 -0
  112. rem/services/embeddings/__init__.py +11 -0
  113. rem/services/embeddings/api.py +120 -0
  114. rem/services/embeddings/worker.py +421 -0
  115. rem/services/fs/README.md +662 -0
  116. rem/services/fs/__init__.py +62 -0
  117. rem/services/fs/examples.py +206 -0
  118. rem/services/fs/examples_paths.py +204 -0
  119. rem/services/fs/git_provider.py +935 -0
  120. rem/services/fs/local_provider.py +760 -0
  121. rem/services/fs/parsing-hooks-examples.md +172 -0
  122. rem/services/fs/paths.py +276 -0
  123. rem/services/fs/provider.py +460 -0
  124. rem/services/fs/s3_provider.py +1042 -0
  125. rem/services/fs/service.py +186 -0
  126. rem/services/git/README.md +1075 -0
  127. rem/services/git/__init__.py +17 -0
  128. rem/services/git/service.py +469 -0
  129. rem/services/phoenix/EXPERIMENT_DESIGN.md +1146 -0
  130. rem/services/phoenix/README.md +453 -0
  131. rem/services/phoenix/__init__.py +46 -0
  132. rem/services/phoenix/client.py +686 -0
  133. rem/services/phoenix/config.py +88 -0
  134. rem/services/phoenix/prompt_labels.py +477 -0
  135. rem/services/postgres/README.md +575 -0
  136. rem/services/postgres/__init__.py +23 -0
  137. rem/services/postgres/migration_service.py +427 -0
  138. rem/services/postgres/pydantic_to_sqlalchemy.py +232 -0
  139. rem/services/postgres/register_type.py +352 -0
  140. rem/services/postgres/repository.py +337 -0
  141. rem/services/postgres/schema_generator.py +379 -0
  142. rem/services/postgres/service.py +802 -0
  143. rem/services/postgres/sql_builder.py +354 -0
  144. rem/services/rem/README.md +304 -0
  145. rem/services/rem/__init__.py +23 -0
  146. rem/services/rem/exceptions.py +71 -0
  147. rem/services/rem/executor.py +293 -0
  148. rem/services/rem/parser.py +145 -0
  149. rem/services/rem/queries.py +196 -0
  150. rem/services/rem/query.py +371 -0
  151. rem/services/rem/service.py +527 -0
  152. rem/services/session/README.md +374 -0
  153. rem/services/session/__init__.py +6 -0
  154. rem/services/session/compression.py +360 -0
  155. rem/services/session/reload.py +77 -0
  156. rem/settings.py +1235 -0
  157. rem/sql/002_install_models.sql +1068 -0
  158. rem/sql/background_indexes.sql +42 -0
  159. rem/sql/install_models.sql +1038 -0
  160. rem/sql/migrations/001_install.sql +503 -0
  161. rem/sql/migrations/002_install_models.sql +1202 -0
  162. rem/utils/AGENTIC_CHUNKING.md +597 -0
  163. rem/utils/README.md +583 -0
  164. rem/utils/__init__.py +43 -0
  165. rem/utils/agentic_chunking.py +622 -0
  166. rem/utils/batch_ops.py +343 -0
  167. rem/utils/chunking.py +108 -0
  168. rem/utils/clip_embeddings.py +276 -0
  169. rem/utils/dict_utils.py +98 -0
  170. rem/utils/embeddings.py +423 -0
  171. rem/utils/examples/embeddings_example.py +305 -0
  172. rem/utils/examples/sql_types_example.py +202 -0
  173. rem/utils/markdown.py +16 -0
  174. rem/utils/model_helpers.py +236 -0
  175. rem/utils/schema_loader.py +229 -0
  176. rem/utils/sql_types.py +348 -0
  177. rem/utils/user_id.py +81 -0
  178. rem/utils/vision.py +330 -0
  179. rem/workers/README.md +506 -0
  180. rem/workers/__init__.py +5 -0
  181. rem/workers/dreaming.py +502 -0
  182. rem/workers/engram_processor.py +312 -0
  183. rem/workers/sqs_file_processor.py +193 -0
  184. remdb-0.2.6.dist-info/METADATA +1191 -0
  185. remdb-0.2.6.dist-info/RECORD +187 -0
  186. remdb-0.2.6.dist-info/WHEEL +4 -0
  187. remdb-0.2.6.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,379 @@
1
+ """
2
+ Schema generation utility from Pydantic models.
3
+
4
+ Scans a directory of Pydantic models and generates complete database schemas including:
5
+ - Primary tables
6
+ - Embeddings tables
7
+ - KV_STORE triggers
8
+ - Indexes (foreground and background)
9
+ - Migrations
10
+
11
+ Usage:
12
+ from rem.services.postgres.schema_generator import SchemaGenerator
13
+
14
+ generator = SchemaGenerator()
15
+ schema = generator.generate_from_directory("src/rem/models/entities")
16
+
17
+ # Write to file
18
+ with open("src/rem/sql/schema.sql", "w") as f:
19
+ f.write(schema)
20
+ """
21
+
22
+ import importlib.util
23
+ import inspect
24
+ from pathlib import Path
25
+ from typing import Type
26
+
27
+ from loguru import logger
28
+ from pydantic import BaseModel
29
+
30
+ from ...settings import settings
31
+ from .register_type import register_type
32
+
33
+
34
+ class SchemaGenerator:
35
+ """
36
+ Generate database schema from Pydantic models in a directory.
37
+
38
+ Discovers all Pydantic models in Python files and generates:
39
+ - CREATE TABLE statements
40
+ - Embeddings tables
41
+ - KV_STORE triggers
42
+ - Indexes
43
+ """
44
+
45
+ def __init__(self, output_dir: Path | None = None):
46
+ """
47
+ Initialize schema generator.
48
+
49
+ Args:
50
+ output_dir: Optional directory for output files (defaults to settings.sql_dir)
51
+ """
52
+ self.output_dir = output_dir or Path(settings.sql_dir)
53
+ self.schemas: dict[str, dict] = {}
54
+
55
+ def discover_models(self, directory: str | Path) -> dict[str, Type[BaseModel]]:
56
+ """
57
+ Discover all Pydantic models in a directory.
58
+
59
+ Args:
60
+ directory: Path to directory containing Python files with models
61
+
62
+ Returns:
63
+ Dict mapping model name to model class
64
+ """
65
+ import sys
66
+ import importlib
67
+
68
+ directory = Path(directory).resolve()
69
+ models = {}
70
+
71
+ logger.info(f"Discovering models in {directory}")
72
+
73
+ # Add src directory to Python path to handle relative imports
74
+ src_dir = directory
75
+ while src_dir.name != "src" and src_dir.parent != src_dir:
76
+ src_dir = src_dir.parent
77
+
78
+ if src_dir.name == "src" and str(src_dir) not in sys.path:
79
+ sys.path.insert(0, str(src_dir))
80
+ logger.debug(f"Added {src_dir} to sys.path for relative imports")
81
+
82
+ # Convert directory path to module path
83
+ # e.g., /path/to/src/rem/models/entities -> rem.models.entities
84
+ try:
85
+ rel_path = directory.relative_to(src_dir)
86
+ module_path = str(rel_path).replace("/", ".")
87
+
88
+ # Import the package to get all submodules
89
+ package = importlib.import_module(module_path)
90
+
91
+ # Find all Python files in the directory
92
+ for py_file in directory.rglob("*.py"):
93
+ if py_file.name.startswith("_"):
94
+ continue
95
+
96
+ try:
97
+ # Build module name from file path
98
+ rel_file = py_file.relative_to(src_dir)
99
+ module_name = str(rel_file.with_suffix("")).replace("/", ".")
100
+
101
+ # Import the module
102
+ module = importlib.import_module(module_name)
103
+
104
+ # Find Pydantic models
105
+ for name, obj in inspect.getmembers(module):
106
+ if (
107
+ inspect.isclass(obj)
108
+ and issubclass(obj, BaseModel)
109
+ and obj is not BaseModel
110
+ and not name.startswith("_")
111
+ # Only include models defined in this module
112
+ and obj.__module__ == module_name
113
+ ):
114
+ models[name] = obj
115
+ logger.debug(f"Found model: {name} in {module_name}")
116
+
117
+ except Exception as e:
118
+ logger.warning(f"Failed to load {py_file}: {e}")
119
+
120
+ except Exception as e:
121
+ logger.error(f"Failed to discover models in {directory}: {e}")
122
+
123
+ logger.info(f"Discovered {len(models)} models")
124
+ return models
125
+
126
+ def infer_table_name(self, model: Type[BaseModel]) -> str:
127
+ """
128
+ Infer table name from model class name.
129
+
130
+ Converts CamelCase to snake_case and pluralizes.
131
+
132
+ Examples:
133
+ Resource -> resources
134
+ UserProfile -> user_profiles
135
+ Message -> messages
136
+
137
+ Args:
138
+ model: Pydantic model class
139
+
140
+ Returns:
141
+ Table name
142
+ """
143
+ import re
144
+
145
+ name = model.__name__
146
+
147
+ # Convert CamelCase to snake_case
148
+ name = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", name)
149
+ name = re.sub("([a-z0-9])([A-Z])", r"\1_\2", name).lower()
150
+
151
+ # Simple pluralization (add 's' if doesn't end in 's')
152
+ if not name.endswith("s"):
153
+ if name.endswith("y"):
154
+ name = name[:-1] + "ies" # category -> categories
155
+ else:
156
+ name = name + "s" # resource -> resources
157
+
158
+ return name
159
+
160
+ def infer_entity_key_field(self, model: Type[BaseModel]) -> str:
161
+ """
162
+ Infer which field to use as entity_key in KV_STORE.
163
+
164
+ Priority:
165
+ 1. Field with json_schema_extra={\"entity_key\": True}
166
+ 2. Field named \"name\"
167
+ 3. Field named \"key\"
168
+ 4. Field named \"label\"
169
+ 5. First string field
170
+
171
+ Args:
172
+ model: Pydantic model class
173
+
174
+ Returns:
175
+ Field name to use as entity_key
176
+ """
177
+ # Check for explicit entity_key marker
178
+ for field_name, field_info in model.model_fields.items():
179
+ json_extra = getattr(field_info, "json_schema_extra", None)
180
+ if json_extra and isinstance(json_extra, dict):
181
+ if json_extra.get("entity_key"):
182
+ return field_name
183
+
184
+ # Check for key fields in priority order: id -> uri -> key -> name
185
+ # (matching sql_builder.get_entity_key convention)
186
+ for candidate in ["id", "uri", "key", "name"]:
187
+ if candidate in model.model_fields:
188
+ return candidate
189
+
190
+ # Should never reach here for CoreModel subclasses (they all have id)
191
+ logger.error(f"No suitable entity_key field found for {model.__name__}, using 'id'")
192
+ return "id"
193
+
194
+ async def generate_schema_for_model(
195
+ self,
196
+ model: Type[BaseModel],
197
+ table_name: str | None = None,
198
+ entity_key_field: str | None = None,
199
+ ) -> dict:
200
+ """
201
+ Generate schema for a single model.
202
+
203
+ Args:
204
+ model: Pydantic model class
205
+ table_name: Optional table name (inferred if not provided)
206
+ entity_key_field: Optional entity key field (inferred if not provided)
207
+
208
+ Returns:
209
+ Dict with SQL statements and metadata
210
+ """
211
+ if table_name is None:
212
+ table_name = self.infer_table_name(model)
213
+
214
+ if entity_key_field is None:
215
+ entity_key_field = self.infer_entity_key_field(model)
216
+
217
+ logger.info(f"Generating schema for {model.__name__} -> {table_name}")
218
+
219
+ schema = await register_type(
220
+ model=model,
221
+ table_name=table_name,
222
+ entity_key_field=entity_key_field,
223
+ tenant_scoped=True,
224
+ create_embeddings=True,
225
+ create_kv_trigger=True,
226
+ )
227
+
228
+ self.schemas[table_name] = schema
229
+ return schema
230
+
231
+ async def generate_from_directory(
232
+ self, directory: str | Path, output_file: str | None = None
233
+ ) -> str:
234
+ """
235
+ Generate complete schema from all models in a directory.
236
+
237
+ Args:
238
+ directory: Path to directory with Pydantic models
239
+ output_file: Optional output file path (relative to output_dir)
240
+
241
+ Returns:
242
+ Complete SQL schema as string
243
+ """
244
+ # Discover models
245
+ models = self.discover_models(directory)
246
+
247
+ # Generate schemas for each model
248
+ for model_name, model in models.items():
249
+ await self.generate_schema_for_model(model)
250
+
251
+ # Combine into single SQL file
252
+ sql_parts = [
253
+ "-- REM Model Schema (install_models.sql)",
254
+ "-- Generated from Pydantic models",
255
+ f"-- Source directory: {directory}",
256
+ "-- Generated at: " + __import__("datetime").datetime.now().isoformat(),
257
+ "--",
258
+ "-- DO NOT EDIT MANUALLY - Regenerate with: rem db schema generate",
259
+ "--",
260
+ "-- This script creates:",
261
+ "-- 1. Primary entity tables",
262
+ "-- 2. Embeddings tables (embeddings_<table>)",
263
+ "-- 3. KV_STORE triggers for cache maintenance",
264
+ "-- 4. Indexes (foreground only, background indexes separate)",
265
+ "",
266
+ "-- ============================================================================",
267
+ "-- PREREQUISITES CHECK",
268
+ "-- ============================================================================",
269
+ "",
270
+ "DO $$",
271
+ "BEGIN",
272
+ " -- Check that install.sql has been run",
273
+ " IF NOT EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'kv_store') THEN",
274
+ " RAISE EXCEPTION 'KV_STORE table not found. Run migrations/001_install.sql first.';",
275
+ " END IF;",
276
+ "",
277
+ " IF NOT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'vector') THEN",
278
+ " RAISE EXCEPTION 'pgvector extension not found. Run migrations/001_install.sql first.';",
279
+ " END IF;",
280
+ "",
281
+ " RAISE NOTICE 'Prerequisites check passed';",
282
+ "END $$;",
283
+ "",
284
+ ]
285
+
286
+ # Add each table schema
287
+ for table_name, schema in self.schemas.items():
288
+ sql_parts.append("-- " + "=" * 70)
289
+ sql_parts.append(f"-- {table_name.upper()} (Model: {schema['model']})")
290
+ sql_parts.append("-- " + "=" * 70)
291
+ sql_parts.append("")
292
+
293
+ # Primary table
294
+ if "table" in schema["sql"]:
295
+ sql_parts.append(schema["sql"]["table"])
296
+ sql_parts.append("")
297
+
298
+ # Embeddings table
299
+ if "embeddings" in schema["sql"] and schema["sql"]["embeddings"]:
300
+ sql_parts.append(f"-- Embeddings for {table_name}")
301
+ sql_parts.append(schema["sql"]["embeddings"])
302
+ sql_parts.append("")
303
+
304
+ # KV_STORE trigger
305
+ if "kv_trigger" in schema["sql"]:
306
+ sql_parts.append(f"-- KV_STORE trigger for {table_name}")
307
+ sql_parts.append(schema["sql"]["kv_trigger"])
308
+ sql_parts.append("")
309
+
310
+ # Add migration record
311
+ sql_parts.append("-- ============================================================================")
312
+ sql_parts.append("-- RECORD MIGRATION")
313
+ sql_parts.append("-- ============================================================================")
314
+ sql_parts.append("")
315
+ sql_parts.append("INSERT INTO rem_migrations (name, type, version)")
316
+ sql_parts.append("VALUES ('install_models.sql', 'models', '1.0.0')")
317
+ sql_parts.append("ON CONFLICT (name) DO UPDATE")
318
+ sql_parts.append("SET applied_at = CURRENT_TIMESTAMP,")
319
+ sql_parts.append(" applied_by = CURRENT_USER;")
320
+ sql_parts.append("")
321
+
322
+ # Completion message
323
+ sql_parts.append("DO $$")
324
+ sql_parts.append("BEGIN")
325
+ sql_parts.append(" RAISE NOTICE '============================================================';")
326
+ sql_parts.append(f" RAISE NOTICE 'REM Model Schema Applied: {len(self.schemas)} tables';")
327
+ sql_parts.append(" RAISE NOTICE '============================================================';")
328
+ for table_name in sorted(self.schemas.keys()):
329
+ embeddable = len(self.schemas[table_name].get("embeddable_fields", []))
330
+ embed_info = f" ({embeddable} embeddable fields)" if embeddable else ""
331
+ sql_parts.append(f" RAISE NOTICE ' ✓ {table_name}{embed_info}';")
332
+ sql_parts.append(" RAISE NOTICE '';")
333
+ sql_parts.append(" RAISE NOTICE 'Next: Run background indexes if needed';")
334
+ sql_parts.append(" RAISE NOTICE ' rem db migrate --background-indexes';")
335
+ sql_parts.append(" RAISE NOTICE '============================================================';")
336
+ sql_parts.append("END $$;")
337
+
338
+ complete_sql = "\n".join(sql_parts)
339
+
340
+ # Write to file if specified
341
+ if output_file:
342
+ output_path = self.output_dir / output_file
343
+ output_path.parent.mkdir(parents=True, exist_ok=True)
344
+ output_path.write_text(complete_sql)
345
+ logger.info(f"Schema written to {output_path}")
346
+
347
+ return complete_sql
348
+
349
+ def generate_background_indexes(self) -> str:
350
+ """
351
+ Generate SQL for background index creation.
352
+
353
+ These indexes are created CONCURRENTLY to avoid blocking writes.
354
+ Should be run after initial data load.
355
+
356
+ Returns:
357
+ SQL for background index creation
358
+ """
359
+ sql_parts = [
360
+ "-- Background index creation",
361
+ "-- Run AFTER initial data load to avoid blocking writes",
362
+ "",
363
+ ]
364
+
365
+ for table_name, schema in self.schemas.items():
366
+ if not schema.get("embeddable_fields"):
367
+ continue
368
+
369
+ embeddings_table = f"embeddings_{table_name}"
370
+
371
+ sql_parts.append(f"-- HNSW vector index for {embeddings_table}")
372
+ sql_parts.append(
373
+ f"CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_{embeddings_table}_vector_hnsw"
374
+ )
375
+ sql_parts.append(f"ON {embeddings_table}")
376
+ sql_parts.append("USING hnsw (embedding vector_cosine_ops);")
377
+ sql_parts.append("")
378
+
379
+ return "\n".join(sql_parts)