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.
@@ -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,2 @@
1
+ [console_scripts]
2
+ catalog-bot = cli:app
@@ -0,0 +1,8 @@
1
+ typer==0.9.0
2
+ rich==13.7.0
3
+ sqlalchemy==2.0.25
4
+ pymysql==1.1.0
5
+ chromadb==0.4.22
6
+ numpy<2.0.0
7
+ fastapi
8
+ uvicorn
@@ -0,0 +1,4 @@
1
+ api
2
+ cli
3
+ orchestrator
4
+ semantic_indexer
@@ -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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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
+ )