diff --git a/pymesbot/backend/main.py b/pymesbot/backend/main.py index 1c89979..6adacf4 100644 --- a/pymesbot/backend/main.py +++ b/pymesbot/backend/main.py @@ -35,57 +35,72 @@ import httpx ANTHROPIC_API_KEY = "6fef8efda3d24eb9ad3d718daf1ae9a1.RcFc7QPe5uZLr2mS" ANTHROPIC_BASE_URL = "https://api.z.ai/api/anthropic" -# System prompt for the AI sales assistant -SYSTEM_PROMPT = """Sos el asistente de ventas de Demo Librería, una librería escolar en Argentina. +# Skills directory +SKILLS_DIR = Path(__file__).parent.parent / "picoclaw" / "workspace" / "skills" -Tu trabajo es ayudar al vendedor a atender clientes de forma rápida y con información exacta del inventario. + +def load_skills() -> str: + """Carga todos los skills disponibles y los combina en un string""" + skills_content = [] + + if SKILLS_DIR.exists(): + for skill_file in sorted(SKILLS_DIR.glob("*.md")): + try: + with open(skill_file, "r", encoding="utf-8") as f: + content = f.read() + skills_content.append( + f"\n{'=' * 60}\nSKILL: {skill_file.stem}\n{'=' * 60}\n{content}" + ) + except Exception as e: + logger.warning(f"Error cargando skill {skill_file}: {e}") + + return "\n\n".join(skills_content) if skills_content else "" + + +# Base system prompt +BASE_SYSTEM_PROMPT = """Sos el asistente de ventas de Demo Librería, una librería escolar en Argentina. + +## REGLA CRÍTICA - SIEMPRE USAR HERRAMIENTAS +**NUNCA respondas de memoria.** Antes de dar cualquier información sobre productos, precios o stock, DEBÉS usar una herramienta. ## HERRAMIENTAS DISPONIBLES -Tenés acceso a estas herramientas. SIEMPRE usarlas antes de responder sobre stock o precios: -1. `buscar_productos` - Busca productos en el inventario por nombre o descripción - Input: query (string) - nombre o descripción del producto +1. `buscar_productos` - Busca productos específicos por nombre + Úsala cuando el usuario mencione un producto específico: "cuadernos", "lápices", "biromes", etc. -2. `confirmar_venta` - Registra una venta y descuenta el stock - Input: producto_nombre (string), cantidad (number) +2. `listar_todo_el_stock` - Muestra TODO el inventario completo + Úsala cuando el usuario quiera ver todo: "mostrame todo", "qué tienen", "stock" + +3. `confirmar_venta` - Registra una venta + Úsala solo cuando el usuario confirme que se vendió algo -## REGLAS OBLIGATORIAS (nunca violarlas) +## INSTRUCCIONES IMPORTANTES -1. **NUNCA des precios aproximados.** Siempre usá `buscar_productos` para obtener el precio exacto. - Si la herramienta no devuelve el producto, decí que no tenés ese dato, no inventes. +1. **NUNCA inventar precios.** Siempre usar herramientas primero. +2. **MÚLTIPLES PRODUCTOS**: Cuando pidan varios productos ("bloc, lápices y fibras"), buscar CADA UNO por separado. +3. Si no hay stock, ofrecer alternativas. +4. Ser conciso (máximo 3-4 líneas). +5. Usar español argentino: "vos", "tenés", "querés". +6. Preguntar siempre al final: "¿Se concretó la venta?" -2. **Si no hay stock de un producto**, ofrecé SIEMPRE la alternativa más cercana que sí haya stock. - Ej: "No tenemos Bic azul, pero sí tenemos Faber roja a $750." +## MAPEO DE SINÓNIMOS +- "fibras" → buscar "marcadores" o "colores" +- "bloc" → buscar "papel" +- "lapices de colores" → buscar "caja colores" +- "biromes" → buscar "birome" -3. **Al final de cada consulta exitosa**, preguntá: "¿Se concretó la venta?" - Si dicen sí, preguntá cuántas unidades y usá `confirmar_venta`. +## EJEMPLOS -4. **Respondé SIEMPRE en español argentino coloquial pero profesional.** - Usá "vos", "tenés", "querés". No uses "usted" ni español neutro. +Usuario: "bloc de hojas, lápices de 12 y fibras" +Acción: +1. buscar_productos("bloc") +2. buscar_productos("caja colores") +3. buscar_productos("fibras") +Respuesta: Resultados de los 3 productos""" -5. **Sé conciso.** Una respuesta de chat, no un ensayo. Máximo 3-4 líneas por respuesta. - -6. **Cuando el usuario pida múltiples productos** (ej: "10 cuadernos y 5 lápices"), - usá `buscar_productos` para CADA producto por separado y respondé con la info de todos. - -## EJEMPLOS DE RESPUESTAS CORRECTAS - -Vendedor: "tenés birome bic azul?" -Bot: [usa buscar_productos con "birome bic azul"] -Bot: "Sí, tenemos Bic Cristal azul a $850. Quedan 23. ¿Se vendió?" - -Vendedor: "no hay regla 30cm" -Bot: [usa buscar_productos con "regla 30cm"] -Bot: "No tenemos regla de 30cm por ahora 😕 Pero sí hay de 20cm (Maped, $650, stock 8). ¿Te sirve esa?" - -Vendedor: "10 cuadernos y 5 lápices" -Bot: [usa buscar_productos con "cuaderno"] -Bot: [usa buscar_productos con "lápiz"] -Bot: "Encontré: -• Cuaderno Rivadavia 48 Hojas - $2500 (stock: 20) -• Lápiz Faber Castell 2B - $450 (stock: 100) - -¿Se concretó la venta? ¿Cuántas unidades de cada uno?""" +# Combinar base prompt con skills +SKILLS_CONTENT = load_skills() +SYSTEM_PROMPT = f"{BASE_SYSTEM_PROMPT}\n\n{SKILLS_CONTENT}" def db_buscar_productos(query: str) -> list: @@ -128,6 +143,40 @@ def db_buscar_productos(query: str) -> list: ] +def db_listar_todo_el_stock() -> dict: + """Lista todo el inventario organizado por categorías""" + conn = sqlite3.connect(DB_PATH) + conn.row_factory = sqlite3.Row + cursor = conn.cursor() + + cursor.execute(""" + SELECT DISTINCT nombre, marca, categoria, precio, stock + FROM productos + WHERE activo = 1 AND stock > 0 + ORDER BY categoria, nombre + """) + + productos = cursor.fetchall() + conn.close() + + # Organizar por categorías + categorias = {} + for p in productos: + cat = p["categoria"] + if cat not in categorias: + categorias[cat] = [] + categorias[cat].append( + { + "nombre": p["nombre"], + "marca": p["marca"] or "", + "precio": p["precio"], + "stock": p["stock"], + } + ) + + return {"categorias": categorias, "total_productos": len(productos)} + + def db_confirmar_venta(producto_nombre: str, cantidad: int) -> dict: """Registra una venta en la base de datos""" conn = sqlite3.connect(DB_PATH) @@ -202,27 +251,35 @@ async def chat_with_ai(message: str, session_id: str = "pymesbot") -> Optional[s tools = [ { "name": "buscar_productos", - "description": "Busca productos en el inventario por nombre o descripción. Usar SIEMPRE antes de dar precios.", + "description": "Busca productos específicos en el inventario por nombre o descripción. Usar SIEMPRE antes de dar información sobre productos.", "input_schema": { "type": "object", "properties": { "query": { "type": "string", - "description": "Nombre o descripción del producto a buscar", + "description": "Nombre o descripción del producto a buscar (ej: 'cuaderno', 'birome', 'lápices')", } }, "required": ["query"], }, }, + { + "name": "listar_todo_el_stock", + "description": "Muestra TODO el inventario completo de la librería organizado por categorías. Usar cuando el usuario quiera ver todo el stock disponible.", + "input_schema": { + "type": "object", + "properties": {}, + }, + }, { "name": "confirmar_venta", - "description": "Registra una venta y descuenta el stock del inventario.", + "description": "Registra una venta y descuenta el stock del inventario. Usar SOLO cuando el usuario confirme explícitamente que se vendió algo.", "input_schema": { "type": "object", "properties": { "producto_nombre": { "type": "string", - "description": "Nombre del producto vendido", + "description": "Nombre exacto o similar del producto vendido", }, "cantidad": { "type": "integer", @@ -297,6 +354,15 @@ async def chat_with_ai(message: str, session_id: str = "pymesbot") -> Optional[s "content": json.dumps({"productos": productos}), } ) + elif tool_name == "listar_todo_el_stock": + inventario = db_listar_todo_el_stock() + tool_results.append( + { + "type": "tool_result", + "tool_use_id": tool_call.get("id"), + "content": json.dumps(inventario), + } + ) elif tool_name == "confirmar_venta": producto_nombre = tool_input.get("producto_nombre", "") cantidad = tool_input.get("cantidad", 1)