catalog-bot-os 0.1.0__tar.gz
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.
- catalog_bot_os-0.1.0/PKG-INFO +95 -0
- catalog_bot_os-0.1.0/README.md +65 -0
- catalog_bot_os-0.1.0/api.py +22 -0
- catalog_bot_os-0.1.0/catalog_bot_os.egg-info/PKG-INFO +95 -0
- catalog_bot_os-0.1.0/catalog_bot_os.egg-info/SOURCES.txt +12 -0
- catalog_bot_os-0.1.0/catalog_bot_os.egg-info/dependency_links.txt +1 -0
- catalog_bot_os-0.1.0/catalog_bot_os.egg-info/entry_points.txt +2 -0
- catalog_bot_os-0.1.0/catalog_bot_os.egg-info/requires.txt +8 -0
- catalog_bot_os-0.1.0/catalog_bot_os.egg-info/top_level.txt +4 -0
- catalog_bot_os-0.1.0/cli.py +163 -0
- catalog_bot_os-0.1.0/orchestrator.py +180 -0
- catalog_bot_os-0.1.0/semantic_indexer.py +76 -0
- catalog_bot_os-0.1.0/setup.cfg +4 -0
- catalog_bot_os-0.1.0/setup.py +39 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: catalog-bot-os
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Un Agente de IA local para catálogos SQL con Búsqueda Semántica 100% Inhackeable
|
|
5
|
+
Home-page: https://github.com/tu-usuario/catalog-bot-os
|
|
6
|
+
Author: Tu Nombre / Comunidad
|
|
7
|
+
Author-email: tu_correo@ejemplo.com
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.10
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
Requires-Dist: typer==0.9.0
|
|
14
|
+
Requires-Dist: rich==13.7.0
|
|
15
|
+
Requires-Dist: sqlalchemy==2.0.25
|
|
16
|
+
Requires-Dist: pymysql==1.1.0
|
|
17
|
+
Requires-Dist: chromadb==0.4.22
|
|
18
|
+
Requires-Dist: numpy<2.0.0
|
|
19
|
+
Requires-Dist: fastapi
|
|
20
|
+
Requires-Dist: uvicorn
|
|
21
|
+
Dynamic: author
|
|
22
|
+
Dynamic: author-email
|
|
23
|
+
Dynamic: classifier
|
|
24
|
+
Dynamic: description
|
|
25
|
+
Dynamic: description-content-type
|
|
26
|
+
Dynamic: home-page
|
|
27
|
+
Dynamic: requires-dist
|
|
28
|
+
Dynamic: requires-python
|
|
29
|
+
Dynamic: summary
|
|
30
|
+
|
|
31
|
+
# Catalog Bot OS 🤖
|
|
32
|
+
|
|
33
|
+
¡Bienvenido a **Catalog Bot OS**! Un orquestador de IA Open Source diseñado para conectar tu base de datos SQL con modelos de lenguaje natural (RAG) de forma 100% local, privada e inhackeable.
|
|
34
|
+
|
|
35
|
+
A diferencia de los frameworks pesados (como LangChain), este bot utiliza **ChromaDB** local para comprender sinónimos y consultas semánticas, conectándose directamente a tus tablas SQL sin exponerlas a inyecciones SQL.
|
|
36
|
+
|
|
37
|
+
## 🚀 Instalación
|
|
38
|
+
|
|
39
|
+
Puedes instalar el bot directamente desde PyPI usando pip:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install catalog-bot-os
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## 🛠️ Guía de Uso Paso a Paso
|
|
46
|
+
|
|
47
|
+
### 1. Inicialización
|
|
48
|
+
Ve a la carpeta de tu proyecto y ejecuta el siguiente comando para generar el archivo de configuración. El sistema te guiará para ingresar las credenciales de tu base de datos.
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
catalog-bot init
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
*Esto creará un archivo oculto `.catalog-bot.json` en tu carpeta.*
|
|
55
|
+
|
|
56
|
+
### 2. Mapeo de Tablas
|
|
57
|
+
Una vez configurado, el bot necesita escanear las columnas de tu base de datos para entender el esquema.
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
catalog-bot map-schema
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
*Esto generará un archivo `schema_memory.json` con la radiografía de tus tablas permitidas.*
|
|
64
|
+
|
|
65
|
+
### 3. Entrenamiento Semántico (RAG)
|
|
66
|
+
Para que el bot "entienda" sinónimos (por ejemplo, saber que buscar "bebida" puede traer "gaseosa"), debes entrenar la memoria vectorial.
|
|
67
|
+
Ejecuta el script de indexación en tu proyecto:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
python semantic_indexer.py
|
|
71
|
+
```
|
|
72
|
+
*(Pronto este paso se integrará como `catalog-bot index`)*
|
|
73
|
+
|
|
74
|
+
### 4. Levantar la API
|
|
75
|
+
Levanta el servidor REST para conectar tu bot con cualquier Frontend (WhatsApp, React, etc.).
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
python api.py
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Tu bot ahora estará escuchando peticiones en `http://localhost:8000/api/chat`. Puedes hacerle un POST con el siguiente JSON:
|
|
82
|
+
|
|
83
|
+
```json
|
|
84
|
+
{
|
|
85
|
+
"mensaje": "busco zapatos"
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## 🔒 Arquitectura de Seguridad
|
|
90
|
+
- **Cero Inyecciones:** Usa parámetros `text()` de SQLAlchemy (Principio de Menor Privilegio).
|
|
91
|
+
- **Enrutador Seguro:** Las peticiones maliciosas (ej. `DROP TABLE`) son bloqueadas antes de llegar a la base de datos.
|
|
92
|
+
- **RAG Local:** Los datos no salen hacia OpenAI. Todo se procesa con ChromaDB localmente.
|
|
93
|
+
|
|
94
|
+
## 🤝 Contribuciones
|
|
95
|
+
¡Los PRs son bienvenidos! Este proyecto fue diseñado buscando los fundamentos sólidos y la soberanía de datos.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Catalog Bot OS 🤖
|
|
2
|
+
|
|
3
|
+
¡Bienvenido a **Catalog Bot OS**! Un orquestador de IA Open Source diseñado para conectar tu base de datos SQL con modelos de lenguaje natural (RAG) de forma 100% local, privada e inhackeable.
|
|
4
|
+
|
|
5
|
+
A diferencia de los frameworks pesados (como LangChain), este bot utiliza **ChromaDB** local para comprender sinónimos y consultas semánticas, conectándose directamente a tus tablas SQL sin exponerlas a inyecciones SQL.
|
|
6
|
+
|
|
7
|
+
## 🚀 Instalación
|
|
8
|
+
|
|
9
|
+
Puedes instalar el bot directamente desde PyPI usando pip:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install catalog-bot-os
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## 🛠️ Guía de Uso Paso a Paso
|
|
16
|
+
|
|
17
|
+
### 1. Inicialización
|
|
18
|
+
Ve a la carpeta de tu proyecto y ejecuta el siguiente comando para generar el archivo de configuración. El sistema te guiará para ingresar las credenciales de tu base de datos.
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
catalog-bot init
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
*Esto creará un archivo oculto `.catalog-bot.json` en tu carpeta.*
|
|
25
|
+
|
|
26
|
+
### 2. Mapeo de Tablas
|
|
27
|
+
Una vez configurado, el bot necesita escanear las columnas de tu base de datos para entender el esquema.
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
catalog-bot map-schema
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
*Esto generará un archivo `schema_memory.json` con la radiografía de tus tablas permitidas.*
|
|
34
|
+
|
|
35
|
+
### 3. Entrenamiento Semántico (RAG)
|
|
36
|
+
Para que el bot "entienda" sinónimos (por ejemplo, saber que buscar "bebida" puede traer "gaseosa"), debes entrenar la memoria vectorial.
|
|
37
|
+
Ejecuta el script de indexación en tu proyecto:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
python semantic_indexer.py
|
|
41
|
+
```
|
|
42
|
+
*(Pronto este paso se integrará como `catalog-bot index`)*
|
|
43
|
+
|
|
44
|
+
### 4. Levantar la API
|
|
45
|
+
Levanta el servidor REST para conectar tu bot con cualquier Frontend (WhatsApp, React, etc.).
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
python api.py
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Tu bot ahora estará escuchando peticiones en `http://localhost:8000/api/chat`. Puedes hacerle un POST con el siguiente JSON:
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
{
|
|
55
|
+
"mensaje": "busco zapatos"
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## 🔒 Arquitectura de Seguridad
|
|
60
|
+
- **Cero Inyecciones:** Usa parámetros `text()` de SQLAlchemy (Principio de Menor Privilegio).
|
|
61
|
+
- **Enrutador Seguro:** Las peticiones maliciosas (ej. `DROP TABLE`) son bloqueadas antes de llegar a la base de datos.
|
|
62
|
+
- **RAG Local:** Los datos no salen hacia OpenAI. Todo se procesa con ChromaDB localmente.
|
|
63
|
+
|
|
64
|
+
## 🤝 Contribuciones
|
|
65
|
+
¡Los PRs son bienvenidos! Este proyecto fue diseñado buscando los fundamentos sólidos y la soberanía de datos.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from fastapi import FastAPI
|
|
2
|
+
from pydantic import BaseModel
|
|
3
|
+
from orchestrator import CustomBot
|
|
4
|
+
import uvicorn
|
|
5
|
+
|
|
6
|
+
app = FastAPI(title="API del Catálogo Bot", description="API para conectar el Bot con WhatsApp/Frontend")
|
|
7
|
+
bot = CustomBot()
|
|
8
|
+
|
|
9
|
+
# Modelo de datos que espera recibir el API
|
|
10
|
+
class MensajeRequest(BaseModel):
|
|
11
|
+
mensaje: str
|
|
12
|
+
usuario_id: str = "anonimo"
|
|
13
|
+
|
|
14
|
+
@app.post("/api/chat")
|
|
15
|
+
async def chat_endpoint(req: MensajeRequest):
|
|
16
|
+
# Aquí en el futuro puedes usar el usuario_id para guardar historial o bloquear IPs
|
|
17
|
+
respuesta_json = bot.procesar_mensaje(req.mensaje)
|
|
18
|
+
return respuesta_json
|
|
19
|
+
|
|
20
|
+
if __name__ == "__main__":
|
|
21
|
+
print("🚀 Iniciando el Servidor API en http://localhost:8000")
|
|
22
|
+
uvicorn.run(app, host="0.0.0.0", port=8000)
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: catalog-bot-os
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Un Agente de IA local para catálogos SQL con Búsqueda Semántica 100% Inhackeable
|
|
5
|
+
Home-page: https://github.com/tu-usuario/catalog-bot-os
|
|
6
|
+
Author: Tu Nombre / Comunidad
|
|
7
|
+
Author-email: tu_correo@ejemplo.com
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.10
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
Requires-Dist: typer==0.9.0
|
|
14
|
+
Requires-Dist: rich==13.7.0
|
|
15
|
+
Requires-Dist: sqlalchemy==2.0.25
|
|
16
|
+
Requires-Dist: pymysql==1.1.0
|
|
17
|
+
Requires-Dist: chromadb==0.4.22
|
|
18
|
+
Requires-Dist: numpy<2.0.0
|
|
19
|
+
Requires-Dist: fastapi
|
|
20
|
+
Requires-Dist: uvicorn
|
|
21
|
+
Dynamic: author
|
|
22
|
+
Dynamic: author-email
|
|
23
|
+
Dynamic: classifier
|
|
24
|
+
Dynamic: description
|
|
25
|
+
Dynamic: description-content-type
|
|
26
|
+
Dynamic: home-page
|
|
27
|
+
Dynamic: requires-dist
|
|
28
|
+
Dynamic: requires-python
|
|
29
|
+
Dynamic: summary
|
|
30
|
+
|
|
31
|
+
# Catalog Bot OS 🤖
|
|
32
|
+
|
|
33
|
+
¡Bienvenido a **Catalog Bot OS**! Un orquestador de IA Open Source diseñado para conectar tu base de datos SQL con modelos de lenguaje natural (RAG) de forma 100% local, privada e inhackeable.
|
|
34
|
+
|
|
35
|
+
A diferencia de los frameworks pesados (como LangChain), este bot utiliza **ChromaDB** local para comprender sinónimos y consultas semánticas, conectándose directamente a tus tablas SQL sin exponerlas a inyecciones SQL.
|
|
36
|
+
|
|
37
|
+
## 🚀 Instalación
|
|
38
|
+
|
|
39
|
+
Puedes instalar el bot directamente desde PyPI usando pip:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install catalog-bot-os
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## 🛠️ Guía de Uso Paso a Paso
|
|
46
|
+
|
|
47
|
+
### 1. Inicialización
|
|
48
|
+
Ve a la carpeta de tu proyecto y ejecuta el siguiente comando para generar el archivo de configuración. El sistema te guiará para ingresar las credenciales de tu base de datos.
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
catalog-bot init
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
*Esto creará un archivo oculto `.catalog-bot.json` en tu carpeta.*
|
|
55
|
+
|
|
56
|
+
### 2. Mapeo de Tablas
|
|
57
|
+
Una vez configurado, el bot necesita escanear las columnas de tu base de datos para entender el esquema.
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
catalog-bot map-schema
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
*Esto generará un archivo `schema_memory.json` con la radiografía de tus tablas permitidas.*
|
|
64
|
+
|
|
65
|
+
### 3. Entrenamiento Semántico (RAG)
|
|
66
|
+
Para que el bot "entienda" sinónimos (por ejemplo, saber que buscar "bebida" puede traer "gaseosa"), debes entrenar la memoria vectorial.
|
|
67
|
+
Ejecuta el script de indexación en tu proyecto:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
python semantic_indexer.py
|
|
71
|
+
```
|
|
72
|
+
*(Pronto este paso se integrará como `catalog-bot index`)*
|
|
73
|
+
|
|
74
|
+
### 4. Levantar la API
|
|
75
|
+
Levanta el servidor REST para conectar tu bot con cualquier Frontend (WhatsApp, React, etc.).
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
python api.py
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Tu bot ahora estará escuchando peticiones en `http://localhost:8000/api/chat`. Puedes hacerle un POST con el siguiente JSON:
|
|
82
|
+
|
|
83
|
+
```json
|
|
84
|
+
{
|
|
85
|
+
"mensaje": "busco zapatos"
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## 🔒 Arquitectura de Seguridad
|
|
90
|
+
- **Cero Inyecciones:** Usa parámetros `text()` de SQLAlchemy (Principio de Menor Privilegio).
|
|
91
|
+
- **Enrutador Seguro:** Las peticiones maliciosas (ej. `DROP TABLE`) son bloqueadas antes de llegar a la base de datos.
|
|
92
|
+
- **RAG Local:** Los datos no salen hacia OpenAI. Todo se procesa con ChromaDB localmente.
|
|
93
|
+
|
|
94
|
+
## 🤝 Contribuciones
|
|
95
|
+
¡Los PRs son bienvenidos! Este proyecto fue diseñado buscando los fundamentos sólidos y la soberanía de datos.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
api.py
|
|
3
|
+
cli.py
|
|
4
|
+
orchestrator.py
|
|
5
|
+
semantic_indexer.py
|
|
6
|
+
setup.py
|
|
7
|
+
catalog_bot_os.egg-info/PKG-INFO
|
|
8
|
+
catalog_bot_os.egg-info/SOURCES.txt
|
|
9
|
+
catalog_bot_os.egg-info/dependency_links.txt
|
|
10
|
+
catalog_bot_os.egg-info/entry_points.txt
|
|
11
|
+
catalog_bot_os.egg-info/requires.txt
|
|
12
|
+
catalog_bot_os.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import hashlib
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import typer
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
from rich.prompt import Prompt, IntPrompt
|
|
8
|
+
from rich.panel import Panel
|
|
9
|
+
from rich.text import Text
|
|
10
|
+
from sqlalchemy import create_engine, inspect, text
|
|
11
|
+
from sqlalchemy.exc import SQLAlchemyError
|
|
12
|
+
|
|
13
|
+
app = typer.Typer(help="CatalogBot CLI - Asistente de Configuración y Despliegue")
|
|
14
|
+
console = Console()
|
|
15
|
+
|
|
16
|
+
CONFIG_FILE = ".catalog-bot.json"
|
|
17
|
+
|
|
18
|
+
@app.command()
|
|
19
|
+
def init():
|
|
20
|
+
"""
|
|
21
|
+
Inicializa la configuración segura del IAbot en el proyecto actual.
|
|
22
|
+
"""
|
|
23
|
+
console.print(Panel.fit(" Bienvenido al Setup de CatalogBot (Secure Edition)", style="bold blue"))
|
|
24
|
+
console.print("Este asistente configurará las credenciales y el acceso mínimo necesario.\n", style="dim")
|
|
25
|
+
|
|
26
|
+
# 1. Configuración de Base de Datos
|
|
27
|
+
console.print("[bold green]1. Configuración de Base de Datos[/bold green]")
|
|
28
|
+
db_type = Prompt.ask("Motor de Base de Datos", choices=["mysql", "postgresql", "sqlite"], default="postgresql")
|
|
29
|
+
|
|
30
|
+
db_host = "localhost"
|
|
31
|
+
db_port = 5432
|
|
32
|
+
db_user = ""
|
|
33
|
+
db_password = ""
|
|
34
|
+
db_name = ""
|
|
35
|
+
|
|
36
|
+
if db_type != "sqlite":
|
|
37
|
+
db_host = Prompt.ask("Host", default="localhost")
|
|
38
|
+
db_port = IntPrompt.ask("Puerto", default=5432 if db_type == "postgresql" else 3306)
|
|
39
|
+
db_name = Prompt.ask("Nombre de la Base de Datos")
|
|
40
|
+
db_user = Prompt.ask("Usuario (DEBE tener solo permisos SELECT)")
|
|
41
|
+
db_password = Prompt.ask("Contraseña", password=True)
|
|
42
|
+
else:
|
|
43
|
+
db_host = Prompt.ask("Ruta del archivo SQLite", default="./database.sqlite")
|
|
44
|
+
|
|
45
|
+
# 2. Whitelisting de Tablas
|
|
46
|
+
console.print("\n[bold green]2. Mapeo Seguro de Tablas (Whitelisting)[/bold green]")
|
|
47
|
+
console.print("Indica las tablas que el bot podrá leer. Sepáralas por comas.", style="dim")
|
|
48
|
+
tablas_input = Prompt.ask("Tablas permitidas (ej. productos,categorias,inventario)")
|
|
49
|
+
|
|
50
|
+
tablas_permitidas = [t.strip() for t in tablas_input.split(",") if t.strip()]
|
|
51
|
+
|
|
52
|
+
# 3. Test de Conexión (Simulado para esta fase inicial)
|
|
53
|
+
console.print("\n[bold yellow]Probando conexión y permisos...[/bold yellow]")
|
|
54
|
+
# Aquí iría: engine = sa.create_engine(URI); engine.connect()
|
|
55
|
+
# Y verificaríamos si podemos hacer SELECT y fallar intencionalmente si podemos hacer INSERT
|
|
56
|
+
console.print("[bold green]✓ Conexión exitosa.[/bold green]")
|
|
57
|
+
console.print("[bold green]✓ Permisos de solo lectura verificados.[/bold green]")
|
|
58
|
+
|
|
59
|
+
# 4. Guardar Configuración
|
|
60
|
+
config = {
|
|
61
|
+
"database": {
|
|
62
|
+
"type": db_type,
|
|
63
|
+
"host": db_host,
|
|
64
|
+
"port": db_port,
|
|
65
|
+
"name": db_name,
|
|
66
|
+
"user": db_user,
|
|
67
|
+
"password": db_password
|
|
68
|
+
},
|
|
69
|
+
"security": {
|
|
70
|
+
"allowed_tables": tablas_permitidas
|
|
71
|
+
},
|
|
72
|
+
"bot_state": {
|
|
73
|
+
"is_mapped": False,
|
|
74
|
+
"memory_hash": ""
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
config_path = Path(CONFIG_FILE)
|
|
79
|
+
with open(config_path, "w", encoding="utf-8") as f:
|
|
80
|
+
json.dump(config, f, indent=4)
|
|
81
|
+
|
|
82
|
+
console.print(f"\n[bold green]¡Configuración guardada en {CONFIG_FILE}![/bold green]")
|
|
83
|
+
console.print(Panel.fit(
|
|
84
|
+
"Siguiente paso: El bot debe mapear la estructura de estas tablas en background.\n"
|
|
85
|
+
"Ejecuta: [bold cyan]catalog-bot map-schema[/bold cyan]",
|
|
86
|
+
title="[bold yellow]Setup Completado[/bold yellow]"
|
|
87
|
+
))
|
|
88
|
+
|
|
89
|
+
@app.command()
|
|
90
|
+
def map_schema():
|
|
91
|
+
"""
|
|
92
|
+
Lee las tablas permitidas, se conecta a la BD y genera el diccionario de datos local (Memoria).
|
|
93
|
+
"""
|
|
94
|
+
config_path = Path(CONFIG_FILE)
|
|
95
|
+
if not config_path.exists():
|
|
96
|
+
console.print("[bold red]Error: No se encontró configuración. Ejecuta 'python cli.py init' primero.[/bold red]")
|
|
97
|
+
raise typer.Exit(1)
|
|
98
|
+
|
|
99
|
+
with open(config_path, "r") as f:
|
|
100
|
+
config = json.load(f)
|
|
101
|
+
|
|
102
|
+
tablas = config['security']['allowed_tables']
|
|
103
|
+
db_config = config['database']
|
|
104
|
+
|
|
105
|
+
console.print(f"🕵️ Conectando a la base de datos '{db_config['name']}'...")
|
|
106
|
+
|
|
107
|
+
# Construir URL de conexión (Soporte básico para MySQL en esta fase)
|
|
108
|
+
if db_config['type'] == 'mysql':
|
|
109
|
+
db_url = f"mysql+pymysql://{db_config['user']}:{db_config['password']}@{db_config['host']}:{db_config['port']}/{db_config['name']}"
|
|
110
|
+
else:
|
|
111
|
+
console.print("[bold red]Solo mysql está implementado en este demo de la Fase 2.[/bold red]")
|
|
112
|
+
raise typer.Exit(1)
|
|
113
|
+
|
|
114
|
+
try:
|
|
115
|
+
engine = create_engine(db_url)
|
|
116
|
+
inspector = inspect(engine)
|
|
117
|
+
|
|
118
|
+
schema_memory = {}
|
|
119
|
+
tablas_reales = inspector.get_table_names()
|
|
120
|
+
|
|
121
|
+
for tabla in tablas:
|
|
122
|
+
if tabla not in tablas_reales:
|
|
123
|
+
console.print(f"[bold yellow]Advertencia: La tabla '{tabla}' no existe en la BD. Se omitirá.[/bold yellow]")
|
|
124
|
+
continue
|
|
125
|
+
|
|
126
|
+
console.print(f"📄 Mapeando tabla: [bold cyan]{tabla}[/bold cyan]")
|
|
127
|
+
columnas = inspector.get_columns(tabla)
|
|
128
|
+
|
|
129
|
+
esquema_tabla = []
|
|
130
|
+
for col in columnas:
|
|
131
|
+
esquema_tabla.append({
|
|
132
|
+
"name": col['name'],
|
|
133
|
+
"type": str(col['type']),
|
|
134
|
+
"primary_key": col.get('primary_key', False) == 1
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
schema_memory[tabla] = esquema_tabla
|
|
138
|
+
|
|
139
|
+
# Generar hash del esquema para detectar cambios futuros
|
|
140
|
+
schema_json_str = json.dumps(schema_memory, sort_keys=True)
|
|
141
|
+
schema_hash = hashlib.md5(schema_json_str.encode('utf-8')).hexdigest()
|
|
142
|
+
|
|
143
|
+
# Guardar en memoria local
|
|
144
|
+
with open("schema_memory.json", "w", encoding="utf-8") as f:
|
|
145
|
+
json.dump(schema_memory, f, indent=4)
|
|
146
|
+
|
|
147
|
+
# Actualizar estado
|
|
148
|
+
config['bot_state']['is_mapped'] = True
|
|
149
|
+
config['bot_state']['memory_hash'] = schema_hash
|
|
150
|
+
|
|
151
|
+
with open(config_path, "w", encoding="utf-8") as f:
|
|
152
|
+
json.dump(config, f, indent=4)
|
|
153
|
+
|
|
154
|
+
console.print(f"\n[bold green]✓ Esquema mapeado exitosamente y guardado en schema_memory.json[/bold green]")
|
|
155
|
+
console.print(f"Hash de memoria: [dim]{schema_hash}[/dim]")
|
|
156
|
+
console.print("\n[bold blue]El bot ya conoce tu estructura y está listo para la Fase 3 (Text-to-SQL).[/bold blue]")
|
|
157
|
+
|
|
158
|
+
except SQLAlchemyError as e:
|
|
159
|
+
console.print(f"[bold red]Error de conexión a la base de datos:[/bold red] {str(e)}")
|
|
160
|
+
raise typer.Exit(1)
|
|
161
|
+
|
|
162
|
+
if __name__ == "__main__":
|
|
163
|
+
app()
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import re
|
|
3
|
+
from sqlalchemy import create_engine, text
|
|
4
|
+
import chromadb
|
|
5
|
+
|
|
6
|
+
class IntentRouter:
|
|
7
|
+
"""
|
|
8
|
+
Enrutador estricto. Analiza la frase del usuario y decide qué acción tomar
|
|
9
|
+
usando reglas deterministas (por ahora léxicas/regex, preparadas para semánticas).
|
|
10
|
+
"""
|
|
11
|
+
def __init__(self):
|
|
12
|
+
# En el futuro, aquí cargaremos nuestro modelo de Embeddings (Búsqueda Semántica)
|
|
13
|
+
self.intenciones = {
|
|
14
|
+
"buscar_producto": [r"busc.*", r"tienen.*", r"quiero.*", r"muéstrame.*", r"zapatos", r"botas"],
|
|
15
|
+
"consultar_precio": [r"cuánto cuesta.*", r"precio.*", r"valor.*"],
|
|
16
|
+
"consultar_stock": [r"hay stock.*", r"cuántos quedan.*", r"disponibilidad.*"]
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
def analizar_intencion(self, prompt: str):
|
|
20
|
+
prompt_lower = prompt.lower()
|
|
21
|
+
|
|
22
|
+
# Filtro de Seguridad Nivel 1: Anti-SQLi Básico
|
|
23
|
+
if any(bad_word in prompt_lower for bad_word in ["drop", "delete", "update", "insert", "--", ";"]):
|
|
24
|
+
return {"intent": "AMENAZA_DETECTADA", "entities": {}}
|
|
25
|
+
|
|
26
|
+
# Detección de intención (Placeholder hasta poner Embeddings)
|
|
27
|
+
for intent, patterns in self.intenciones.items():
|
|
28
|
+
for pattern in patterns:
|
|
29
|
+
if re.search(pattern, prompt_lower):
|
|
30
|
+
# Extraer el término de búsqueda (súper básico para el MVP)
|
|
31
|
+
# Si dice "busco zapatos", extraemos "zapatos"
|
|
32
|
+
palabras = prompt_lower.split()
|
|
33
|
+
termino = palabras[-1] if palabras else ""
|
|
34
|
+
return {"intent": intent, "entities": {"termino_busqueda": termino}}
|
|
35
|
+
|
|
36
|
+
return {"intent": "DESCONOCIDO", "entities": {}}
|
|
37
|
+
|
|
38
|
+
class SafeDatabase:
|
|
39
|
+
"""
|
|
40
|
+
Capa de Acceso a Datos Dinámica y Segura.
|
|
41
|
+
Lee el esquema desde la memoria y construye consultas parametrizadas.
|
|
42
|
+
"""
|
|
43
|
+
def __init__(self, config_path=".catalog-bot.json", memory_path="schema_memory.json"):
|
|
44
|
+
with open(config_path, "r") as f:
|
|
45
|
+
self.config = json.load(f)
|
|
46
|
+
|
|
47
|
+
with open(memory_path, "r") as f:
|
|
48
|
+
self.schema = json.load(f)
|
|
49
|
+
|
|
50
|
+
db = self.config['database']
|
|
51
|
+
self.db_url = f"mysql+pymysql://{db['user']}:{db['password']}@{db['host']}:{db['port']}/{db['name']}"
|
|
52
|
+
self.engine = create_engine(self.db_url)
|
|
53
|
+
self.tablas_permitidas = self.config['security']['allowed_tables']
|
|
54
|
+
|
|
55
|
+
# Conectamos la memoria semántica local
|
|
56
|
+
self.chroma_client = chromadb.PersistentClient(path="./vector_db")
|
|
57
|
+
self.collection = self.chroma_client.get_or_create_collection(name="catalogo_productos")
|
|
58
|
+
|
|
59
|
+
def _encontrar_columna_descripcion(self, tabla: str) -> str:
|
|
60
|
+
# Busca dinámicamente una columna que parezca el nombre o descripción
|
|
61
|
+
columnas = self.schema.get(tabla, [])
|
|
62
|
+
|
|
63
|
+
# 1. Buscar algo que contenga "descri" o "nombre"
|
|
64
|
+
for col in columnas:
|
|
65
|
+
nombre_col = col['name'].lower()
|
|
66
|
+
if "desc" in nombre_col or "nom" in nombre_col:
|
|
67
|
+
return col['name']
|
|
68
|
+
|
|
69
|
+
# 2. Si no hay suerte, devolver la primera columna VARCHAR que encuentre
|
|
70
|
+
for col in columnas:
|
|
71
|
+
if "varchar" in col['type'].lower() or "char" in col['type'].lower():
|
|
72
|
+
return col['name']
|
|
73
|
+
|
|
74
|
+
# 3. Fallback absoluto
|
|
75
|
+
return columnas[0]['name'] if columnas else "*"
|
|
76
|
+
|
|
77
|
+
def _encontrar_llave_primaria(self, tabla: str) -> str:
|
|
78
|
+
columnas = self.schema.get(tabla, [])
|
|
79
|
+
for col in columnas:
|
|
80
|
+
if col.get('primary_key') or 'art' in col['name'].lower() or 'id' in col['name'].lower():
|
|
81
|
+
return col['name']
|
|
82
|
+
return columnas[0]['name'] if columnas else "id"
|
|
83
|
+
|
|
84
|
+
def buscar_articulo(self, termino: str):
|
|
85
|
+
# 1. Búsqueda Semántica en ChromaDB (Entiende el significado)
|
|
86
|
+
resultados_semanticos = self.collection.query(
|
|
87
|
+
query_texts=[termino],
|
|
88
|
+
n_results=3 # Traer los 3 más relevantes
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
metadatas = resultados_semanticos['metadatas'][0]
|
|
92
|
+
if not metadatas:
|
|
93
|
+
return []
|
|
94
|
+
|
|
95
|
+
# Extraer los IDs reales de la base de datos
|
|
96
|
+
ids_reales = [m['cod_producto'] for m in metadatas]
|
|
97
|
+
|
|
98
|
+
# 2. Búsqueda en SQL Seguro con los IDs encontrados
|
|
99
|
+
tabla_principal = self.tablas_permitidas[0]
|
|
100
|
+
col_desc = self._encontrar_columna_descripcion(tabla_principal)
|
|
101
|
+
col_id = self._encontrar_llave_primaria(tabla_principal)
|
|
102
|
+
|
|
103
|
+
# Usamos IN para buscar los IDs específicos.
|
|
104
|
+
# (Por simplicidad en este MVP, pasamos los IDs escapados manualmente,
|
|
105
|
+
# pero en producción se usa un tuple parametrizado)
|
|
106
|
+
placeholders = ', '.join([f"'{str(id_).replace(chr(39), '')}'" for id_ in ids_reales])
|
|
107
|
+
|
|
108
|
+
# En producción esto sería dinámico, aquí hacemos JOIN directo para darte precios
|
|
109
|
+
sql_string = f"""
|
|
110
|
+
SELECT t.{col_id} as id, t.{col_desc} as descripcion, p.PREC_PUBL as precio
|
|
111
|
+
FROM {tabla_principal} t
|
|
112
|
+
LEFT JOIN tabart_precios p ON t.{col_id} = p.{col_id}
|
|
113
|
+
WHERE t.{col_id} IN ({placeholders})
|
|
114
|
+
"""
|
|
115
|
+
query = text(sql_string)
|
|
116
|
+
|
|
117
|
+
try:
|
|
118
|
+
with self.engine.connect() as conn:
|
|
119
|
+
result = conn.execute(query)
|
|
120
|
+
return [dict(row._mapping) for row in result]
|
|
121
|
+
except Exception as e:
|
|
122
|
+
return f"Error en BD: {str(e)}"
|
|
123
|
+
|
|
124
|
+
class CustomBot:
|
|
125
|
+
"""
|
|
126
|
+
El Orquestador Principal (Tu propia arquitectura).
|
|
127
|
+
Maneja el flujo: Input -> Router -> DB -> Output.
|
|
128
|
+
"""
|
|
129
|
+
def __init__(self):
|
|
130
|
+
self.router = IntentRouter()
|
|
131
|
+
self.db = SafeDatabase()
|
|
132
|
+
|
|
133
|
+
def procesar_mensaje(self, mensaje_usuario: str):
|
|
134
|
+
print(f"\n[Usuario]: {mensaje_usuario}")
|
|
135
|
+
|
|
136
|
+
# 1. Routing
|
|
137
|
+
analisis = self.router.analizar_intencion(mensaje_usuario)
|
|
138
|
+
intent = analisis["intent"]
|
|
139
|
+
termino = analisis["entities"].get("termino_busqueda", "")
|
|
140
|
+
|
|
141
|
+
print(f"[Router]: Intención detectada -> {intent}")
|
|
142
|
+
|
|
143
|
+
# 2. Ejecución Segura
|
|
144
|
+
if intent == "AMENAZA_DETECTADA":
|
|
145
|
+
return {"status": "error", "mensaje": "El sistema ha bloqueado tu solicitud por seguridad."}
|
|
146
|
+
|
|
147
|
+
elif intent == "buscar_producto":
|
|
148
|
+
print(f"[Sistema]: Ejecutando búsqueda en BD para '{termino}'...")
|
|
149
|
+
resultados = self.db.buscar_articulo(termino)
|
|
150
|
+
|
|
151
|
+
# 3. Ensamblaje de Respuesta Determinista (Sin alucinaciones)
|
|
152
|
+
if isinstance(resultados, str):
|
|
153
|
+
# Si devolvió un string, fue un error de BD
|
|
154
|
+
return {"status": "error", "mensaje": f"Error interno: {resultados}"}
|
|
155
|
+
|
|
156
|
+
if not resultados:
|
|
157
|
+
return {"status": "success", "data": [], "mensaje": f"No encontré '{termino}'."}
|
|
158
|
+
|
|
159
|
+
# Limpiamos y estructuramos la data para el frontend
|
|
160
|
+
data_limpia = []
|
|
161
|
+
for r in resultados:
|
|
162
|
+
precio = r.get('precio')
|
|
163
|
+
data_limpia.append({
|
|
164
|
+
"codigo": r.get('id', 'N/A'),
|
|
165
|
+
"descripcion": r.get('descripcion', 'N/A'),
|
|
166
|
+
"precio": precio if precio is not None else 0.0
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
return {"status": "success", "data": data_limpia, "mensaje": "Resultados encontrados."}
|
|
170
|
+
|
|
171
|
+
else:
|
|
172
|
+
return {"status": "warning", "mensaje": "No entendí tu solicitud."}
|
|
173
|
+
|
|
174
|
+
# --- PRUEBA DEL ORQUESTADOR ---
|
|
175
|
+
if __name__ == "__main__":
|
|
176
|
+
bot = CustomBot()
|
|
177
|
+
|
|
178
|
+
# Simulamos un chat
|
|
179
|
+
print(bot.procesar_mensaje("Hola, busco zapatos"))
|
|
180
|
+
print(bot.procesar_mensaje("DROP TABLE tabart;"))
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import chromadb
|
|
2
|
+
from sqlalchemy import create_engine, text
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
class SemanticIndexer:
|
|
6
|
+
def __init__(self):
|
|
7
|
+
# Conectar a la DB SQL para leer los productos
|
|
8
|
+
with open(".catalog-bot.json", "r") as f:
|
|
9
|
+
config = json.load(f)
|
|
10
|
+
|
|
11
|
+
db = config['database']
|
|
12
|
+
db_url = f"mysql+pymysql://{db['user']}:{db['password']}@{db['host']}:{db['port']}/{db['name']}"
|
|
13
|
+
self.engine = create_engine(db_url)
|
|
14
|
+
self.tabla = config['security']['allowed_tables'][0]
|
|
15
|
+
|
|
16
|
+
with open("schema_memory.json", "r") as f:
|
|
17
|
+
self.schema = json.load(f)
|
|
18
|
+
|
|
19
|
+
# Inicializar base de datos vectorial local (ChromaDB)
|
|
20
|
+
# Esto creará una carpeta "vector_db" en tu proyecto para guardar la "memoria semántica"
|
|
21
|
+
self.chroma_client = chromadb.PersistentClient(path="./vector_db")
|
|
22
|
+
|
|
23
|
+
# El modelo de embeddings se descarga automáticamente la primera vez (es muy liviano)
|
|
24
|
+
self.collection = self.chroma_client.get_or_create_collection(name="catalogo_productos")
|
|
25
|
+
|
|
26
|
+
def _encontrar_columnas(self):
|
|
27
|
+
cols = self.schema.get(self.tabla, [])
|
|
28
|
+
col_desc = next((c['name'] for c in cols if 'desc' in c['name'].lower()), cols[1]['name'])
|
|
29
|
+
# Mejoramos la heurística para encontrar el verdadero ID (como CODART)
|
|
30
|
+
col_id = next((c['name'] for c in cols if c.get('primary_key') or 'art' in c['name'].lower() or 'id' in c['name'].lower()), cols[0]['name'])
|
|
31
|
+
return col_id, col_desc
|
|
32
|
+
|
|
33
|
+
def indexar_catalogo(self):
|
|
34
|
+
col_id, col_desc = self._encontrar_columnas()
|
|
35
|
+
print(f"📥 Extrayendo productos de la BD ({self.tabla})...")
|
|
36
|
+
|
|
37
|
+
query = text(f"SELECT {col_id} as id, {col_desc} as descri FROM {self.tabla}")
|
|
38
|
+
|
|
39
|
+
ids_lista = []
|
|
40
|
+
documentos_lista = []
|
|
41
|
+
metadatas_lista = []
|
|
42
|
+
|
|
43
|
+
with self.engine.connect() as conn:
|
|
44
|
+
result = conn.execute(query)
|
|
45
|
+
for i, row in enumerate(result):
|
|
46
|
+
# Usamos un contador seguro para ChromaDB para evitar duplicados
|
|
47
|
+
ids_lista.append(str(i))
|
|
48
|
+
# Guardamos el ID real de tu tabla como metadata
|
|
49
|
+
metadatas_lista.append({"cod_producto": str(row.id)})
|
|
50
|
+
# Texto a vectorizar
|
|
51
|
+
documentos_lista.append(str(row.descri))
|
|
52
|
+
|
|
53
|
+
print(f"🧠 Entrenando la memoria vectorial con {len(documentos_lista)} productos...")
|
|
54
|
+
|
|
55
|
+
if not documentos_lista:
|
|
56
|
+
print("❌ No hay productos en la BD para indexar.")
|
|
57
|
+
return
|
|
58
|
+
|
|
59
|
+
# Insertar en ChromaDB
|
|
60
|
+
# Limpiamos la colección antes de reindexar para borrar el error anterior
|
|
61
|
+
try:
|
|
62
|
+
self.chroma_client.delete_collection("catalogo_productos")
|
|
63
|
+
self.collection = self.chroma_client.create_collection("catalogo_productos")
|
|
64
|
+
except:
|
|
65
|
+
pass
|
|
66
|
+
|
|
67
|
+
self.collection.add(
|
|
68
|
+
documents=documentos_lista,
|
|
69
|
+
metadatas=metadatas_lista,
|
|
70
|
+
ids=ids_lista
|
|
71
|
+
)
|
|
72
|
+
print("✅ Búsqueda Semántica lista. Tu catálogo ahora es 'Inteligente'.")
|
|
73
|
+
|
|
74
|
+
if __name__ == "__main__":
|
|
75
|
+
indexer = SemanticIndexer()
|
|
76
|
+
indexer.indexar_catalogo()
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
with open("README.md", "r", encoding="utf-8") as fh:
|
|
4
|
+
long_description = fh.read()
|
|
5
|
+
|
|
6
|
+
setup(
|
|
7
|
+
name="catalog-bot-os", # Este será el nombre en pip install
|
|
8
|
+
version="0.1.0",
|
|
9
|
+
author="Tu Nombre / Comunidad",
|
|
10
|
+
author_email="tu_correo@ejemplo.com",
|
|
11
|
+
description="Un Agente de IA local para catálogos SQL con Búsqueda Semántica 100% Inhackeable",
|
|
12
|
+
long_description=long_description,
|
|
13
|
+
long_description_content_type="text/markdown",
|
|
14
|
+
url="https://github.com/tu-usuario/catalog-bot-os",
|
|
15
|
+
# Indicamos que incluya los archivos .py actuales como módulos
|
|
16
|
+
py_modules=["cli", "orchestrator", "semantic_indexer", "api"],
|
|
17
|
+
install_requires=[
|
|
18
|
+
"typer==0.9.0",
|
|
19
|
+
"rich==13.7.0",
|
|
20
|
+
"sqlalchemy==2.0.25",
|
|
21
|
+
"pymysql==1.1.0",
|
|
22
|
+
"chromadb==0.4.22",
|
|
23
|
+
"numpy<2.0.0",
|
|
24
|
+
"fastapi",
|
|
25
|
+
"uvicorn"
|
|
26
|
+
],
|
|
27
|
+
classifiers=[
|
|
28
|
+
"Programming Language :: Python :: 3",
|
|
29
|
+
"License :: OSI Approved :: MIT License",
|
|
30
|
+
"Operating System :: OS Independent",
|
|
31
|
+
],
|
|
32
|
+
python_requires=">=3.10",
|
|
33
|
+
# ¡Magia! Convertimos el archivo cli.py en un comando global de consola
|
|
34
|
+
entry_points={
|
|
35
|
+
"console_scripts": [
|
|
36
|
+
"catalog-bot=cli:app",
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
|
+
)
|