Implementa sistema de skills para búsqueda inteligente
- Carga skills desde archivos .md en workspace/skills/ - Agrega skill vendedor_libreria con contexto completo - Agrega skill busqueda_productos con manejo de sinónimos - Agrega skill gestion_ventas para registrar ventas - Nueva tool listar_todo_el_stock para ver inventario completo - Mejora system prompt con reglas claras de uso de tools - Agrega mapeo de sinónimos (fibras→marcadores, bloc→papel) - Procesa múltiples productos en una sola consulta - Ofrece alternativas inteligentes cuando no hay stock
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user