Spaces:
Running
Running
Add pgAdmin migration and Colab embedding loaders
Browse files
app/jobs/sync_place_embeddings.py
CHANGED
|
@@ -73,6 +73,10 @@ async def main() -> None:
|
|
| 73 |
counters.upserted,
|
| 74 |
counters.errors,
|
| 75 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 76 |
|
| 77 |
|
| 78 |
async def _flush_batch(
|
|
|
|
| 73 |
counters.upserted,
|
| 74 |
counters.errors,
|
| 75 |
)
|
| 76 |
+
if counters.errors:
|
| 77 |
+
raise RuntimeError(
|
| 78 |
+
f"Place embedding sync finished with {counters.errors} failed records"
|
| 79 |
+
)
|
| 80 |
|
| 81 |
|
| 82 |
async def _flush_batch(
|
app/jobs/sync_post_embeddings.py
CHANGED
|
@@ -73,6 +73,10 @@ async def main() -> None:
|
|
| 73 |
counters.upserted,
|
| 74 |
counters.errors,
|
| 75 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 76 |
|
| 77 |
|
| 78 |
async def _flush_batch(
|
|
|
|
| 73 |
counters.upserted,
|
| 74 |
counters.errors,
|
| 75 |
)
|
| 76 |
+
if counters.errors:
|
| 77 |
+
raise RuntimeError(
|
| 78 |
+
f"Post embedding sync finished with {counters.errors} failed records"
|
| 79 |
+
)
|
| 80 |
|
| 81 |
|
| 82 |
async def _flush_batch(
|
docs/fasttext_deployment.md
CHANGED
|
@@ -68,6 +68,10 @@ psql "host=<host> port=5432 dbname=nlp_vectors user=<admin> sslmode=require" `
|
|
| 68 |
La migracion trunca `place_embeddings` y `post_embeddings` porque son indices
|
| 69 |
derivados incompatibles. No toca la base transaccional de la API principal.
|
| 70 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
## 4. Repoblar PGVector
|
| 72 |
|
| 73 |
Con las credenciales writer en `.env`:
|
|
|
|
| 68 |
La migracion trunca `place_embeddings` y `post_embeddings` porque son indices
|
| 69 |
derivados incompatibles. No toca la base transaccional de la API principal.
|
| 70 |
|
| 71 |
+
Para hacerlo desde pgAdmin y ejecutar la carga pesada desde Google Colab consulta
|
| 72 |
+
`docs/pgadmin_colab_fasttext.md`. Incluye un SQL unico para Query Tool y dos scripts
|
| 73 |
+
independientes de carga.
|
| 74 |
+
|
| 75 |
## 4. Repoblar PGVector
|
| 76 |
|
| 77 |
Con las credenciales writer en `.env`:
|
docs/pgadmin_colab_fasttext.md
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Migracion FastText Con pgAdmin Y Google Colab
|
| 2 |
+
|
| 3 |
+
## 1. Antes De Modificar RDS
|
| 4 |
+
|
| 5 |
+
1. Publica esta version en la rama `hf-deploy` de GitHub.
|
| 6 |
+
2. Crea un snapshot de RDS.
|
| 7 |
+
3. Pausa el Space de Hugging Face y cualquier job de sincronizacion.
|
| 8 |
+
4. No ejecutes todavia los cargadores de Colab.
|
| 9 |
+
|
| 10 |
+
## 2. Migrar Desde pgAdmin
|
| 11 |
+
|
| 12 |
+
En pgAdmin selecciona la base `nlp_vectors`, abre **Tools > Query Tool**, carga el
|
| 13 |
+
archivo `sql/pgadmin_migrate_fasttext_300.sql` y ejecutalo completo.
|
| 14 |
+
|
| 15 |
+
El script hace en una sola transaccion:
|
| 16 |
+
|
| 17 |
+
- valida que ambas columnas sigan siendo `VECTOR(16)`;
|
| 18 |
+
- elimina las funciones e indices incompatibles;
|
| 19 |
+
- trunca solamente `place_embeddings` y `post_embeddings`;
|
| 20 |
+
- cambia ambas columnas a `VECTOR(300)`;
|
| 21 |
+
- reconstruye HNSW, funciones y permisos;
|
| 22 |
+
- confirma al final la dimension y que ambas tablas quedaron vacias.
|
| 23 |
+
|
| 24 |
+
No lo ejecutes una segunda vez: esta protegido y abortara si ya encuentra
|
| 25 |
+
`VECTOR(300)`.
|
| 26 |
+
|
| 27 |
+
## 3. Preparar Secrets En Colab
|
| 28 |
+
|
| 29 |
+
En el panel **Secrets** de Colab agrega y habilita **Notebook access** para:
|
| 30 |
+
|
| 31 |
+
| Nombre | Requerido | Uso |
|
| 32 |
+
|---|---:|---|
|
| 33 |
+
| `PGVECTOR_WRITER_PASSWORD` | Si | Password del rol `nlp_writer`. |
|
| 34 |
+
| `MAIN_API_INTERNAL_TOKEN` | Solo si aplica | Token para leer lugares/posts de la API principal. |
|
| 35 |
+
| `HF_TOKEN` | No | Puede ayudar con la descarga, pero el modelo es publico. |
|
| 36 |
+
|
| 37 |
+
Los scripts incluyen los valores no secretos actuales del host, base, usuario y URL
|
| 38 |
+
principal. Tambien puedes sobrescribirlos con Secrets llamados
|
| 39 |
+
`PGVECTOR_HOST`, `PGVECTOR_DATABASE`, `PGVECTOR_WRITER_USER` y
|
| 40 |
+
`MAIN_API_BASE_URL`.
|
| 41 |
+
|
| 42 |
+
## 4. Permitir Temporalmente La IP De Colab
|
| 43 |
+
|
| 44 |
+
RDS debe ser accesible desde el runtime. Obten la IP publica actual en una celda:
|
| 45 |
+
|
| 46 |
+
```python
|
| 47 |
+
!curl -s https://api.ipify.org
|
| 48 |
+
```
|
| 49 |
+
|
| 50 |
+
En el Security Group de RDS agrega temporalmente una regla de entrada TCP 5432 con
|
| 51 |
+
origen `<IP_OBTENIDA>/32`. No uses `0.0.0.0/0`. Elimina la regla cuando terminen
|
| 52 |
+
ambas cargas.
|
| 53 |
+
|
| 54 |
+
Si RDS es privado y no tiene una ruta publica, Colab no podra conectarse directamente;
|
| 55 |
+
en ese caso ejecuta los jobs desde una instancia dentro de la VPC.
|
| 56 |
+
|
| 57 |
+
## 5. Ejecutar Las Cargas
|
| 58 |
+
|
| 59 |
+
En un runtime de Colab con memoria suficiente:
|
| 60 |
+
|
| 61 |
+
```python
|
| 62 |
+
!git clone --branch hf-deploy https://github.com/AlleksDev/Frimeet-API-NLP.git
|
| 63 |
+
%cd Frimeet-API-NLP
|
| 64 |
+
```
|
| 65 |
+
|
| 66 |
+
Primero lugares:
|
| 67 |
+
|
| 68 |
+
```python
|
| 69 |
+
!python scripts/colab_initial_load_places.py
|
| 70 |
+
```
|
| 71 |
+
|
| 72 |
+
Despues publicaciones, reutilizando dependencias y el modelo ya descargado:
|
| 73 |
+
|
| 74 |
+
```python
|
| 75 |
+
!python scripts/colab_initial_load_posts.py --skip-install --skip-download
|
| 76 |
+
```
|
| 77 |
+
|
| 78 |
+
Para una prueba sin escrituras se pueden agregar `--dry-run --max-pages 1`. Para una
|
| 79 |
+
carga pequena real usa solamente `--max-pages 1`; despues puedes ejecutar otra vez
|
| 80 |
+
sin esa opcion y el job completara los registros faltantes. La ejecucion completa no
|
| 81 |
+
debe usar `--dry-run` ni `--max-pages`. El resultado correcto termina con `errors=0`
|
| 82 |
+
y el mensaje de carga terminada.
|
| 83 |
+
|
| 84 |
+
## 6. Verificar Desde pgAdmin
|
| 85 |
+
|
| 86 |
+
Despues de ambas cargas abre nuevamente Query Tool y ejecuta el contenido de
|
| 87 |
+
`sql/verify_fasttext_embeddings.sql`. Debe mostrar:
|
| 88 |
+
|
| 89 |
+
- filas mayores que cero;
|
| 90 |
+
- dimensiones minima y maxima iguales a `300`;
|
| 91 |
+
- modelo `facebook/fasttext-es-vectors`;
|
| 92 |
+
- version `common-crawl-300-v1`;
|
| 93 |
+
- normas cercanas a `1`.
|
| 94 |
+
|
| 95 |
+
Finalmente elimina la regla temporal del Security Group, actualiza las variables del
|
| 96 |
+
Space y despliega la nueva imagen.
|
scripts/colab_initial_load_places.py
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Carga inicial de embeddings FastText de lugares desde Google Colab.
|
| 2 |
+
|
| 3 |
+
Ejecutar desde la raiz de un clon de este repositorio:
|
| 4 |
+
|
| 5 |
+
!python scripts/colab_initial_load_places.py
|
| 6 |
+
|
| 7 |
+
El script lee credenciales desde los Secrets de Colab usando los mismos nombres
|
| 8 |
+
de las variables de entorno. Nunca imprime los valores secretos.
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
from __future__ import annotations
|
| 12 |
+
|
| 13 |
+
import argparse
|
| 14 |
+
import os
|
| 15 |
+
from pathlib import Path
|
| 16 |
+
import subprocess
|
| 17 |
+
import sys
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
REPO_ROOT = Path(__file__).resolve().parents[1]
|
| 21 |
+
DEFAULT_MODEL_PATH = "/content/fasttext-es/model.bin"
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def main() -> None:
|
| 25 |
+
args = _parse_args()
|
| 26 |
+
os.chdir(REPO_ROOT)
|
| 27 |
+
_configure_environment()
|
| 28 |
+
|
| 29 |
+
if not args.skip_install:
|
| 30 |
+
_run(
|
| 31 |
+
sys.executable,
|
| 32 |
+
"-m",
|
| 33 |
+
"pip",
|
| 34 |
+
"install",
|
| 35 |
+
"--quiet",
|
| 36 |
+
"-r",
|
| 37 |
+
str(REPO_ROOT / "requirements.txt"),
|
| 38 |
+
)
|
| 39 |
+
|
| 40 |
+
if not args.skip_download:
|
| 41 |
+
_run(
|
| 42 |
+
sys.executable,
|
| 43 |
+
"-m",
|
| 44 |
+
"app.shared.nlp.embeddings.download_fasttext_model",
|
| 45 |
+
"--repo-id",
|
| 46 |
+
os.environ["FASTTEXT_MODEL_REPO_ID"],
|
| 47 |
+
"--filename",
|
| 48 |
+
os.environ["FASTTEXT_MODEL_FILENAME"],
|
| 49 |
+
"--destination",
|
| 50 |
+
os.environ["FASTTEXT_MODEL_PATH"],
|
| 51 |
+
)
|
| 52 |
+
|
| 53 |
+
command = [
|
| 54 |
+
sys.executable,
|
| 55 |
+
"-m",
|
| 56 |
+
"app.jobs.initial_load_place_embeddings",
|
| 57 |
+
"--batch-size",
|
| 58 |
+
str(args.batch_size),
|
| 59 |
+
"--page-limit",
|
| 60 |
+
str(args.page_limit),
|
| 61 |
+
]
|
| 62 |
+
if args.max_pages is not None:
|
| 63 |
+
command.extend(["--max-pages", str(args.max_pages)])
|
| 64 |
+
if args.dry_run:
|
| 65 |
+
command.append("--dry-run")
|
| 66 |
+
|
| 67 |
+
_run(*command)
|
| 68 |
+
print("Carga de lugares terminada correctamente.")
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
def _configure_environment() -> None:
|
| 72 |
+
defaults = {
|
| 73 |
+
"ENV": "colab",
|
| 74 |
+
"MAIN_API_BASE_URL": "http://52.86.8.11",
|
| 75 |
+
"MAIN_API_PLACES_SEARCH_PATH": "/api/v1/places/search",
|
| 76 |
+
"MAIN_API_TIMEOUT_SECONDS": "60",
|
| 77 |
+
"MAIN_API_PLACES_PAGE_LIMIT": "50",
|
| 78 |
+
"MAIN_API_PLACES_PAGINATION_MODE": "cursor",
|
| 79 |
+
"VECTOR_STORE_PROVIDER": "aws_pgvector",
|
| 80 |
+
"PGVECTOR_HOST": "nlp-vector-db.c2jwncm87zsa.us-east-1.rds.amazonaws.com",
|
| 81 |
+
"PGVECTOR_PORT": "5432",
|
| 82 |
+
"PGVECTOR_DATABASE": "nlp_vectors",
|
| 83 |
+
"PGVECTOR_WRITER_USER": "nlp_writer",
|
| 84 |
+
"PGVECTOR_SSL_MODE": "require",
|
| 85 |
+
"EMBEDDING_PROVIDER": "fasttext",
|
| 86 |
+
"EMBEDDING_DIMENSION": "300",
|
| 87 |
+
"EMBEDDING_MODEL": "facebook/fasttext-es-vectors",
|
| 88 |
+
"EMBEDDING_VERSION": "common-crawl-300-v1",
|
| 89 |
+
"FASTTEXT_MODEL_PATH": DEFAULT_MODEL_PATH,
|
| 90 |
+
"FASTTEXT_MODEL_REPO_ID": "facebook/fasttext-es-vectors",
|
| 91 |
+
"FASTTEXT_MODEL_FILENAME": "model.bin",
|
| 92 |
+
"FASTTEXT_AUTO_DOWNLOAD": "false",
|
| 93 |
+
"LOG_LEVEL": "INFO",
|
| 94 |
+
}
|
| 95 |
+
for name, default in defaults.items():
|
| 96 |
+
os.environ[name] = _read_setting(name, default=default)
|
| 97 |
+
|
| 98 |
+
os.environ["PGVECTOR_WRITER_PASSWORD"] = _read_setting(
|
| 99 |
+
"PGVECTOR_WRITER_PASSWORD",
|
| 100 |
+
required=True,
|
| 101 |
+
)
|
| 102 |
+
|
| 103 |
+
for optional_name in (
|
| 104 |
+
"MAIN_API_INTERNAL_TOKEN",
|
| 105 |
+
"MAIN_API_AUTH_TOKEN",
|
| 106 |
+
"HF_TOKEN",
|
| 107 |
+
):
|
| 108 |
+
value = _read_setting(optional_name)
|
| 109 |
+
if value:
|
| 110 |
+
os.environ[optional_name] = value
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
def _read_setting(
|
| 114 |
+
name: str,
|
| 115 |
+
default: str | None = None,
|
| 116 |
+
required: bool = False,
|
| 117 |
+
) -> str:
|
| 118 |
+
value = os.getenv(name) or _read_colab_secret(name) or default
|
| 119 |
+
if required and not value:
|
| 120 |
+
raise RuntimeError(
|
| 121 |
+
f"Falta el Secret {name!r} en Colab. Activa tambien Notebook access."
|
| 122 |
+
)
|
| 123 |
+
return value or ""
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
def _read_colab_secret(name: str) -> str | None:
|
| 127 |
+
try:
|
| 128 |
+
from google.colab import userdata
|
| 129 |
+
|
| 130 |
+
value = userdata.get(name)
|
| 131 |
+
return str(value).strip() if value else None
|
| 132 |
+
except Exception:
|
| 133 |
+
return None
|
| 134 |
+
|
| 135 |
+
|
| 136 |
+
def _run(*command: str) -> None:
|
| 137 |
+
subprocess.run(list(command), cwd=REPO_ROOT, check=True)
|
| 138 |
+
|
| 139 |
+
|
| 140 |
+
def _parse_args() -> argparse.Namespace:
|
| 141 |
+
parser = argparse.ArgumentParser(description=__doc__)
|
| 142 |
+
parser.add_argument("--batch-size", type=int, default=25)
|
| 143 |
+
parser.add_argument("--page-limit", type=int, default=50)
|
| 144 |
+
parser.add_argument("--max-pages", type=int, default=None)
|
| 145 |
+
parser.add_argument("--dry-run", action="store_true")
|
| 146 |
+
parser.add_argument("--skip-install", action="store_true")
|
| 147 |
+
parser.add_argument("--skip-download", action="store_true")
|
| 148 |
+
return parser.parse_args()
|
| 149 |
+
|
| 150 |
+
|
| 151 |
+
if __name__ == "__main__":
|
| 152 |
+
main()
|
scripts/colab_initial_load_posts.py
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Carga inicial de embeddings FastText de publicaciones desde Google Colab.
|
| 2 |
+
|
| 3 |
+
Ejecutar desde la raiz de un clon de este repositorio:
|
| 4 |
+
|
| 5 |
+
!python scripts/colab_initial_load_posts.py
|
| 6 |
+
|
| 7 |
+
El script lee credenciales desde los Secrets de Colab usando los mismos nombres
|
| 8 |
+
de las variables de entorno. Nunca imprime los valores secretos.
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
from __future__ import annotations
|
| 12 |
+
|
| 13 |
+
import argparse
|
| 14 |
+
import os
|
| 15 |
+
from pathlib import Path
|
| 16 |
+
import subprocess
|
| 17 |
+
import sys
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
REPO_ROOT = Path(__file__).resolve().parents[1]
|
| 21 |
+
DEFAULT_MODEL_PATH = "/content/fasttext-es/model.bin"
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def main() -> None:
|
| 25 |
+
args = _parse_args()
|
| 26 |
+
os.chdir(REPO_ROOT)
|
| 27 |
+
_configure_environment()
|
| 28 |
+
|
| 29 |
+
if not args.skip_install:
|
| 30 |
+
_run(
|
| 31 |
+
sys.executable,
|
| 32 |
+
"-m",
|
| 33 |
+
"pip",
|
| 34 |
+
"install",
|
| 35 |
+
"--quiet",
|
| 36 |
+
"-r",
|
| 37 |
+
str(REPO_ROOT / "requirements.txt"),
|
| 38 |
+
)
|
| 39 |
+
|
| 40 |
+
if not args.skip_download:
|
| 41 |
+
_run(
|
| 42 |
+
sys.executable,
|
| 43 |
+
"-m",
|
| 44 |
+
"app.shared.nlp.embeddings.download_fasttext_model",
|
| 45 |
+
"--repo-id",
|
| 46 |
+
os.environ["FASTTEXT_MODEL_REPO_ID"],
|
| 47 |
+
"--filename",
|
| 48 |
+
os.environ["FASTTEXT_MODEL_FILENAME"],
|
| 49 |
+
"--destination",
|
| 50 |
+
os.environ["FASTTEXT_MODEL_PATH"],
|
| 51 |
+
)
|
| 52 |
+
|
| 53 |
+
command = [
|
| 54 |
+
sys.executable,
|
| 55 |
+
"-m",
|
| 56 |
+
"app.jobs.initial_load_post_embeddings",
|
| 57 |
+
"--batch-size",
|
| 58 |
+
str(args.batch_size),
|
| 59 |
+
"--page-limit",
|
| 60 |
+
str(args.page_limit),
|
| 61 |
+
]
|
| 62 |
+
if args.max_pages is not None:
|
| 63 |
+
command.extend(["--max-pages", str(args.max_pages)])
|
| 64 |
+
if args.dry_run:
|
| 65 |
+
command.append("--dry-run")
|
| 66 |
+
|
| 67 |
+
_run(*command)
|
| 68 |
+
print("Carga de publicaciones terminada correctamente.")
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
def _configure_environment() -> None:
|
| 72 |
+
defaults = {
|
| 73 |
+
"ENV": "colab",
|
| 74 |
+
"MAIN_API_BASE_URL": "http://52.86.8.11",
|
| 75 |
+
"MAIN_API_POSTS_SEARCH_PATH": "/api/v1/posts/search",
|
| 76 |
+
"MAIN_API_TIMEOUT_SECONDS": "60",
|
| 77 |
+
"MAIN_API_POSTS_PAGE_LIMIT": "50",
|
| 78 |
+
"MAIN_API_POSTS_PAGINATION_MODE": "cursor",
|
| 79 |
+
"VECTOR_STORE_PROVIDER": "aws_pgvector",
|
| 80 |
+
"PGVECTOR_HOST": "nlp-vector-db.c2jwncm87zsa.us-east-1.rds.amazonaws.com",
|
| 81 |
+
"PGVECTOR_PORT": "5432",
|
| 82 |
+
"PGVECTOR_DATABASE": "nlp_vectors",
|
| 83 |
+
"PGVECTOR_WRITER_USER": "nlp_writer",
|
| 84 |
+
"PGVECTOR_SSL_MODE": "require",
|
| 85 |
+
"EMBEDDING_PROVIDER": "fasttext",
|
| 86 |
+
"EMBEDDING_DIMENSION": "300",
|
| 87 |
+
"EMBEDDING_MODEL": "facebook/fasttext-es-vectors",
|
| 88 |
+
"EMBEDDING_VERSION": "common-crawl-300-v1",
|
| 89 |
+
"FASTTEXT_MODEL_PATH": DEFAULT_MODEL_PATH,
|
| 90 |
+
"FASTTEXT_MODEL_REPO_ID": "facebook/fasttext-es-vectors",
|
| 91 |
+
"FASTTEXT_MODEL_FILENAME": "model.bin",
|
| 92 |
+
"FASTTEXT_AUTO_DOWNLOAD": "false",
|
| 93 |
+
"LOG_LEVEL": "INFO",
|
| 94 |
+
}
|
| 95 |
+
for name, default in defaults.items():
|
| 96 |
+
os.environ[name] = _read_setting(name, default=default)
|
| 97 |
+
|
| 98 |
+
os.environ["PGVECTOR_WRITER_PASSWORD"] = _read_setting(
|
| 99 |
+
"PGVECTOR_WRITER_PASSWORD",
|
| 100 |
+
required=True,
|
| 101 |
+
)
|
| 102 |
+
|
| 103 |
+
for optional_name in (
|
| 104 |
+
"MAIN_API_INTERNAL_TOKEN",
|
| 105 |
+
"MAIN_API_AUTH_TOKEN",
|
| 106 |
+
"HF_TOKEN",
|
| 107 |
+
):
|
| 108 |
+
value = _read_setting(optional_name)
|
| 109 |
+
if value:
|
| 110 |
+
os.environ[optional_name] = value
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
def _read_setting(
|
| 114 |
+
name: str,
|
| 115 |
+
default: str | None = None,
|
| 116 |
+
required: bool = False,
|
| 117 |
+
) -> str:
|
| 118 |
+
value = os.getenv(name) or _read_colab_secret(name) or default
|
| 119 |
+
if required and not value:
|
| 120 |
+
raise RuntimeError(
|
| 121 |
+
f"Falta el Secret {name!r} en Colab. Activa tambien Notebook access."
|
| 122 |
+
)
|
| 123 |
+
return value or ""
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
def _read_colab_secret(name: str) -> str | None:
|
| 127 |
+
try:
|
| 128 |
+
from google.colab import userdata
|
| 129 |
+
|
| 130 |
+
value = userdata.get(name)
|
| 131 |
+
return str(value).strip() if value else None
|
| 132 |
+
except Exception:
|
| 133 |
+
return None
|
| 134 |
+
|
| 135 |
+
|
| 136 |
+
def _run(*command: str) -> None:
|
| 137 |
+
subprocess.run(list(command), cwd=REPO_ROOT, check=True)
|
| 138 |
+
|
| 139 |
+
|
| 140 |
+
def _parse_args() -> argparse.Namespace:
|
| 141 |
+
parser = argparse.ArgumentParser(description=__doc__)
|
| 142 |
+
parser.add_argument("--batch-size", type=int, default=25)
|
| 143 |
+
parser.add_argument("--page-limit", type=int, default=50)
|
| 144 |
+
parser.add_argument("--max-pages", type=int, default=None)
|
| 145 |
+
parser.add_argument("--dry-run", action="store_true")
|
| 146 |
+
parser.add_argument("--skip-install", action="store_true")
|
| 147 |
+
parser.add_argument("--skip-download", action="store_true")
|
| 148 |
+
return parser.parse_args()
|
| 149 |
+
|
| 150 |
+
|
| 151 |
+
if __name__ == "__main__":
|
| 152 |
+
main()
|
sql/pgadmin_migrate_fasttext_300.sql
ADDED
|
@@ -0,0 +1,322 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
-- Ejecutar completo una sola vez desde pgAdmin > Query Tool.
|
| 2 |
+
-- Requisitos:
|
| 3 |
+
-- 1. Tener un snapshot reciente de RDS.
|
| 4 |
+
-- 2. Detener temporalmente el Space y cualquier job de embeddings.
|
| 5 |
+
-- 3. Conectarse a la base nlp_vectors como propietario de las tablas o administrador.
|
| 6 |
+
--
|
| 7 |
+
-- Este script elimina solamente los indices derivados de busqueda. No toca la
|
| 8 |
+
-- base transaccional de la API principal. Si las columnas ya no son VECTOR(16),
|
| 9 |
+
-- aborta para evitar truncar accidentalmente una migracion ya terminada.
|
| 10 |
+
|
| 11 |
+
CREATE EXTENSION IF NOT EXISTS vector;
|
| 12 |
+
|
| 13 |
+
BEGIN;
|
| 14 |
+
|
| 15 |
+
LOCK TABLE public.place_embeddings IN ACCESS EXCLUSIVE MODE;
|
| 16 |
+
LOCK TABLE public.post_embeddings IN ACCESS EXCLUSIVE MODE;
|
| 17 |
+
|
| 18 |
+
DO $$
|
| 19 |
+
DECLARE
|
| 20 |
+
place_type TEXT;
|
| 21 |
+
post_type TEXT;
|
| 22 |
+
BEGIN
|
| 23 |
+
SELECT format_type(attribute.atttypid, attribute.atttypmod)
|
| 24 |
+
INTO place_type
|
| 25 |
+
FROM pg_attribute attribute
|
| 26 |
+
WHERE attribute.attrelid = 'public.place_embeddings'::regclass
|
| 27 |
+
AND attribute.attname = 'embedding'
|
| 28 |
+
AND NOT attribute.attisdropped;
|
| 29 |
+
|
| 30 |
+
SELECT format_type(attribute.atttypid, attribute.atttypmod)
|
| 31 |
+
INTO post_type
|
| 32 |
+
FROM pg_attribute attribute
|
| 33 |
+
WHERE attribute.attrelid = 'public.post_embeddings'::regclass
|
| 34 |
+
AND attribute.attname = 'embedding'
|
| 35 |
+
AND NOT attribute.attisdropped;
|
| 36 |
+
|
| 37 |
+
IF place_type <> 'vector(16)' OR post_type <> 'vector(16)' THEN
|
| 38 |
+
RAISE EXCEPTION
|
| 39 |
+
'Se esperaba VECTOR(16). Tipos encontrados: places=%, posts=%',
|
| 40 |
+
place_type,
|
| 41 |
+
post_type;
|
| 42 |
+
END IF;
|
| 43 |
+
END $$;
|
| 44 |
+
|
| 45 |
+
-- Las funciones antiguas se eliminan antes del cambio de dimension y se
|
| 46 |
+
-- reconstruyen dentro de la misma transaccion.
|
| 47 |
+
DROP FUNCTION IF EXISTS public.match_places(vector, integer, jsonb);
|
| 48 |
+
DROP FUNCTION IF EXISTS public.match_posts(vector, integer, jsonb);
|
| 49 |
+
DROP FUNCTION IF EXISTS public.upsert_place_embedding(
|
| 50 |
+
text, text, jsonb, vector, text, text, text, boolean
|
| 51 |
+
);
|
| 52 |
+
DROP FUNCTION IF EXISTS public.upsert_post_embedding(
|
| 53 |
+
text, text, jsonb, vector, text, text, text, boolean
|
| 54 |
+
);
|
| 55 |
+
|
| 56 |
+
DROP INDEX IF EXISTS public.place_embeddings_embedding_hnsw_idx;
|
| 57 |
+
DROP INDEX IF EXISTS public.post_embeddings_embedding_hnsw_idx;
|
| 58 |
+
|
| 59 |
+
-- No existe una conversion valida de los vectores mock de 16 dimensiones a
|
| 60 |
+
-- FastText. Ambas tablas son indices derivados y se reconstruiran desde cero.
|
| 61 |
+
TRUNCATE TABLE public.place_embeddings, public.post_embeddings;
|
| 62 |
+
|
| 63 |
+
ALTER TABLE public.place_embeddings
|
| 64 |
+
ALTER COLUMN embedding TYPE vector(300);
|
| 65 |
+
|
| 66 |
+
ALTER TABLE public.post_embeddings
|
| 67 |
+
ALTER COLUMN embedding TYPE vector(300);
|
| 68 |
+
|
| 69 |
+
CREATE INDEX place_embeddings_embedding_hnsw_idx
|
| 70 |
+
ON public.place_embeddings USING hnsw (embedding vector_cosine_ops);
|
| 71 |
+
|
| 72 |
+
CREATE INDEX post_embeddings_embedding_hnsw_idx
|
| 73 |
+
ON public.post_embeddings USING hnsw (embedding vector_cosine_ops);
|
| 74 |
+
|
| 75 |
+
CREATE INDEX IF NOT EXISTS place_embeddings_metadata_gin_idx
|
| 76 |
+
ON public.place_embeddings USING gin (metadata);
|
| 77 |
+
|
| 78 |
+
CREATE INDEX IF NOT EXISTS post_embeddings_metadata_gin_idx
|
| 79 |
+
ON public.post_embeddings USING gin (metadata);
|
| 80 |
+
|
| 81 |
+
CREATE OR REPLACE FUNCTION public.match_places(
|
| 82 |
+
query_embedding vector(300),
|
| 83 |
+
match_count integer,
|
| 84 |
+
filters jsonb DEFAULT '{}'::jsonb
|
| 85 |
+
)
|
| 86 |
+
RETURNS TABLE (
|
| 87 |
+
external_id text,
|
| 88 |
+
document text,
|
| 89 |
+
metadata jsonb,
|
| 90 |
+
score double precision
|
| 91 |
+
)
|
| 92 |
+
LANGUAGE sql
|
| 93 |
+
STABLE
|
| 94 |
+
SECURITY DEFINER
|
| 95 |
+
SET search_path = public
|
| 96 |
+
AS $$
|
| 97 |
+
SELECT
|
| 98 |
+
place.external_id,
|
| 99 |
+
place.document,
|
| 100 |
+
place.metadata,
|
| 101 |
+
1 - (place.embedding <=> query_embedding) AS score
|
| 102 |
+
FROM public.place_embeddings place
|
| 103 |
+
WHERE place.is_active = true
|
| 104 |
+
AND COALESCE((filters->>'is_active')::boolean, true) = true
|
| 105 |
+
AND ((filters ? 'city') IS FALSE OR lower(place.metadata->>'city') = lower(filters->>'city'))
|
| 106 |
+
AND ((filters ? 'state') IS FALSE OR lower(place.metadata->>'state') = lower(filters->>'state'))
|
| 107 |
+
AND ((filters ? 'category') IS FALSE OR lower(place.metadata->>'category') = lower(filters->>'category'))
|
| 108 |
+
AND ((filters ? 'price_range') IS FALSE OR place.metadata->>'price_range' = filters->>'price_range')
|
| 109 |
+
AND ((filters ? 'occasion') IS FALSE OR place.metadata->>'occasion' ILIKE ('%' || (filters->>'occasion') || '%'))
|
| 110 |
+
AND (
|
| 111 |
+
(filters ? 'place_ids') IS FALSE
|
| 112 |
+
OR place.external_id IN (
|
| 113 |
+
SELECT jsonb_array_elements_text(filters->'place_ids')
|
| 114 |
+
)
|
| 115 |
+
)
|
| 116 |
+
ORDER BY place.embedding <=> query_embedding
|
| 117 |
+
LIMIT match_count;
|
| 118 |
+
$$;
|
| 119 |
+
|
| 120 |
+
CREATE OR REPLACE FUNCTION public.match_posts(
|
| 121 |
+
query_embedding vector(300),
|
| 122 |
+
match_count integer,
|
| 123 |
+
filters jsonb DEFAULT '{}'::jsonb
|
| 124 |
+
)
|
| 125 |
+
RETURNS TABLE (
|
| 126 |
+
external_id text,
|
| 127 |
+
document text,
|
| 128 |
+
metadata jsonb,
|
| 129 |
+
score double precision
|
| 130 |
+
)
|
| 131 |
+
LANGUAGE sql
|
| 132 |
+
STABLE
|
| 133 |
+
SECURITY DEFINER
|
| 134 |
+
SET search_path = public
|
| 135 |
+
AS $$
|
| 136 |
+
SELECT
|
| 137 |
+
post.external_id,
|
| 138 |
+
post.document,
|
| 139 |
+
post.metadata,
|
| 140 |
+
1 - (post.embedding <=> query_embedding) AS score
|
| 141 |
+
FROM public.post_embeddings post
|
| 142 |
+
WHERE post.is_active = true
|
| 143 |
+
AND COALESCE((filters->>'is_active')::boolean, true) = true
|
| 144 |
+
AND ((filters ? 'city') IS FALSE OR lower(post.metadata->>'city') = lower(filters->>'city'))
|
| 145 |
+
ORDER BY post.embedding <=> query_embedding
|
| 146 |
+
LIMIT match_count;
|
| 147 |
+
$$;
|
| 148 |
+
|
| 149 |
+
CREATE OR REPLACE FUNCTION public.upsert_place_embedding(
|
| 150 |
+
p_external_id text,
|
| 151 |
+
p_document text,
|
| 152 |
+
p_metadata jsonb,
|
| 153 |
+
p_embedding vector(300),
|
| 154 |
+
p_content_hash text,
|
| 155 |
+
p_embedding_model text,
|
| 156 |
+
p_embedding_version text,
|
| 157 |
+
p_is_active boolean
|
| 158 |
+
)
|
| 159 |
+
RETURNS void
|
| 160 |
+
LANGUAGE sql
|
| 161 |
+
SECURITY DEFINER
|
| 162 |
+
SET search_path = public
|
| 163 |
+
AS $$
|
| 164 |
+
INSERT INTO public.place_embeddings (
|
| 165 |
+
external_id,
|
| 166 |
+
document,
|
| 167 |
+
metadata,
|
| 168 |
+
embedding,
|
| 169 |
+
content_hash,
|
| 170 |
+
embedding_model,
|
| 171 |
+
embedding_version,
|
| 172 |
+
is_active,
|
| 173 |
+
updated_at
|
| 174 |
+
)
|
| 175 |
+
VALUES (
|
| 176 |
+
p_external_id,
|
| 177 |
+
p_document,
|
| 178 |
+
p_metadata,
|
| 179 |
+
p_embedding,
|
| 180 |
+
p_content_hash,
|
| 181 |
+
p_embedding_model,
|
| 182 |
+
p_embedding_version,
|
| 183 |
+
p_is_active,
|
| 184 |
+
now()
|
| 185 |
+
)
|
| 186 |
+
ON CONFLICT (external_id) DO UPDATE SET
|
| 187 |
+
document = EXCLUDED.document,
|
| 188 |
+
metadata = EXCLUDED.metadata,
|
| 189 |
+
embedding = EXCLUDED.embedding,
|
| 190 |
+
content_hash = EXCLUDED.content_hash,
|
| 191 |
+
embedding_model = EXCLUDED.embedding_model,
|
| 192 |
+
embedding_version = EXCLUDED.embedding_version,
|
| 193 |
+
is_active = EXCLUDED.is_active,
|
| 194 |
+
updated_at = now();
|
| 195 |
+
$$;
|
| 196 |
+
|
| 197 |
+
CREATE OR REPLACE FUNCTION public.upsert_post_embedding(
|
| 198 |
+
p_external_id text,
|
| 199 |
+
p_document text,
|
| 200 |
+
p_metadata jsonb,
|
| 201 |
+
p_embedding vector(300),
|
| 202 |
+
p_content_hash text,
|
| 203 |
+
p_embedding_model text,
|
| 204 |
+
p_embedding_version text,
|
| 205 |
+
p_is_active boolean
|
| 206 |
+
)
|
| 207 |
+
RETURNS void
|
| 208 |
+
LANGUAGE sql
|
| 209 |
+
SECURITY DEFINER
|
| 210 |
+
SET search_path = public
|
| 211 |
+
AS $$
|
| 212 |
+
INSERT INTO public.post_embeddings (
|
| 213 |
+
external_id,
|
| 214 |
+
document,
|
| 215 |
+
metadata,
|
| 216 |
+
embedding,
|
| 217 |
+
content_hash,
|
| 218 |
+
embedding_model,
|
| 219 |
+
embedding_version,
|
| 220 |
+
is_active,
|
| 221 |
+
updated_at
|
| 222 |
+
)
|
| 223 |
+
VALUES (
|
| 224 |
+
p_external_id,
|
| 225 |
+
p_document,
|
| 226 |
+
p_metadata,
|
| 227 |
+
p_embedding,
|
| 228 |
+
p_content_hash,
|
| 229 |
+
p_embedding_model,
|
| 230 |
+
p_embedding_version,
|
| 231 |
+
p_is_active,
|
| 232 |
+
now()
|
| 233 |
+
)
|
| 234 |
+
ON CONFLICT (external_id) DO UPDATE SET
|
| 235 |
+
document = EXCLUDED.document,
|
| 236 |
+
metadata = EXCLUDED.metadata,
|
| 237 |
+
embedding = EXCLUDED.embedding,
|
| 238 |
+
content_hash = EXCLUDED.content_hash,
|
| 239 |
+
embedding_model = EXCLUDED.embedding_model,
|
| 240 |
+
embedding_version = EXCLUDED.embedding_version,
|
| 241 |
+
is_active = EXCLUDED.is_active,
|
| 242 |
+
updated_at = now();
|
| 243 |
+
$$;
|
| 244 |
+
|
| 245 |
+
CREATE OR REPLACE FUNCTION public.get_place_content_hashes(
|
| 246 |
+
p_external_ids text[]
|
| 247 |
+
)
|
| 248 |
+
RETURNS TABLE (
|
| 249 |
+
external_id text,
|
| 250 |
+
content_hash text
|
| 251 |
+
)
|
| 252 |
+
LANGUAGE sql
|
| 253 |
+
STABLE
|
| 254 |
+
SECURITY DEFINER
|
| 255 |
+
SET search_path = public
|
| 256 |
+
AS $$
|
| 257 |
+
SELECT place.external_id, place.content_hash
|
| 258 |
+
FROM public.place_embeddings place
|
| 259 |
+
WHERE place.external_id = ANY(p_external_ids);
|
| 260 |
+
$$;
|
| 261 |
+
|
| 262 |
+
CREATE OR REPLACE FUNCTION public.get_post_content_hashes(
|
| 263 |
+
p_external_ids text[]
|
| 264 |
+
)
|
| 265 |
+
RETURNS TABLE (
|
| 266 |
+
external_id text,
|
| 267 |
+
content_hash text
|
| 268 |
+
)
|
| 269 |
+
LANGUAGE sql
|
| 270 |
+
STABLE
|
| 271 |
+
SECURITY DEFINER
|
| 272 |
+
SET search_path = public
|
| 273 |
+
AS $$
|
| 274 |
+
SELECT post.external_id, post.content_hash
|
| 275 |
+
FROM public.post_embeddings post
|
| 276 |
+
WHERE post.external_id = ANY(p_external_ids);
|
| 277 |
+
$$;
|
| 278 |
+
|
| 279 |
+
-- Reaplica permisos solo cuando los roles ya existen.
|
| 280 |
+
DO $$
|
| 281 |
+
BEGIN
|
| 282 |
+
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'nlp_reader') THEN
|
| 283 |
+
EXECUTE 'GRANT USAGE ON SCHEMA public TO nlp_reader';
|
| 284 |
+
EXECUTE 'GRANT EXECUTE ON FUNCTION public.match_places(vector, integer, jsonb) TO nlp_reader';
|
| 285 |
+
EXECUTE 'GRANT EXECUTE ON FUNCTION public.match_posts(vector, integer, jsonb) TO nlp_reader';
|
| 286 |
+
END IF;
|
| 287 |
+
|
| 288 |
+
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'nlp_writer') THEN
|
| 289 |
+
EXECUTE 'GRANT USAGE ON SCHEMA public TO nlp_writer';
|
| 290 |
+
EXECUTE 'GRANT EXECUTE ON FUNCTION public.get_place_content_hashes(text[]) TO nlp_writer';
|
| 291 |
+
EXECUTE 'GRANT EXECUTE ON FUNCTION public.get_post_content_hashes(text[]) TO nlp_writer';
|
| 292 |
+
EXECUTE 'GRANT EXECUTE ON FUNCTION public.upsert_place_embedding(text, text, jsonb, vector, text, text, text, boolean) TO nlp_writer';
|
| 293 |
+
EXECUTE 'GRANT EXECUTE ON FUNCTION public.upsert_post_embedding(text, text, jsonb, vector, text, text, text, boolean) TO nlp_writer';
|
| 294 |
+
END IF;
|
| 295 |
+
END $$;
|
| 296 |
+
|
| 297 |
+
COMMIT;
|
| 298 |
+
|
| 299 |
+
-- Resultado esperado inmediatamente despues de migrar: dimension 300 y cero
|
| 300 |
+
-- filas. Las filas apareceran despues de ejecutar los scripts de Colab.
|
| 301 |
+
SELECT
|
| 302 |
+
table_name,
|
| 303 |
+
column_name,
|
| 304 |
+
udt_name,
|
| 305 |
+
format_type(attribute.atttypid, attribute.atttypmod) AS formatted_type
|
| 306 |
+
FROM information_schema.columns column_info
|
| 307 |
+
JOIN pg_attribute attribute
|
| 308 |
+
ON attribute.attrelid = (
|
| 309 |
+
quote_ident(column_info.table_schema) || '.' || quote_ident(column_info.table_name)
|
| 310 |
+
)::regclass
|
| 311 |
+
AND attribute.attname = column_info.column_name
|
| 312 |
+
WHERE column_info.table_schema = 'public'
|
| 313 |
+
AND column_info.table_name IN ('place_embeddings', 'post_embeddings')
|
| 314 |
+
AND column_info.column_name = 'embedding'
|
| 315 |
+
ORDER BY table_name;
|
| 316 |
+
|
| 317 |
+
SELECT 'place_embeddings' AS table_name, count(*) AS rows
|
| 318 |
+
FROM public.place_embeddings
|
| 319 |
+
UNION ALL
|
| 320 |
+
SELECT 'post_embeddings' AS table_name, count(*) AS rows
|
| 321 |
+
FROM public.post_embeddings;
|
| 322 |
+
|