feat(telegram): el bot resuelve clientes por nombre y mide sus propios fallos

Dos de las cuatro mejoras de la auditoría del agente.

1. Las herramientas aceptan cliente_nombre además de cliente_id.

   De 27 parámetros que eran IDs, 10 eran de cliente. Para cada acción el
   modelo tenía que encadenar: listar_clientes → leer la respuesta →
   encontrar la fila → extraer el número → recién ahí llamar a la
   herramienta. Cuatro pasos, y basta que falle uno para que no se guarde
   nada — es la explicación más probable de la factura que nunca se creó,
   porque ese cliente podía no venir en la primera página del listado.

   Ahora el modelo pasa el nombre y el servidor lo resuelve, con búsqueda
   completa y no paginada. Si hay varios candidatos devuelve la lista para
   que el modelo pregunte cuál, en vez de elegir uno: cargarle una factura
   a la empresa equivocada es peor que preguntar. Una coincidencia exacta
   gana sobre las parciales, así "Metropolitana" no queda ambiguo solo
   porque existe "Metropolitana Norte".

2. Contadores de uso y fallo por herramienta, expuestos como
   estado_herramientas.

   Cada problema se venía diagnosticando de a un caso, reconstruyendo qué
   pasó después de que el usuario avisara. Ahora se puede preguntar
   directamente qué viene fallando y ver si es una herramienta puntual o el
   modelo eligiendo mal. En memoria a propósito: alcanza para responder eso
   y no agrega una tabla.

Quedan las otras dos: adelgazar el prompt de 5,4 KB moviendo los manuales
por dominio a las descripciones de las herramientas, y comparar modelos con
un mismo caso. La primera conviene hacerla con el sistema estable, porque
toca cómo decide el modelo en todos los flujos a la vez.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Lizandro Guarnizo
2026-08-15 21:24:06 -05:00
co-authored by Claude Sonnet 5
parent d0c9ed5656
commit e2440b04f1
3 changed files with 217 additions and 29 deletions
+32
View File
@@ -65,3 +65,35 @@ func TestErrorDeTool(t *testing.T) {
}
}
}
// El id explícito manda: si el modelo ya lo tiene, no hay que ir a la base.
// Es el único camino del resolvedor que se puede probar sin datos.
func TestResolverClienteIDPrefiereElIDExplicito(t *testing.T) {
id, err := ResolverClienteID(42, "cualquier nombre")
if err != nil || id != 42 {
t.Fatalf("= (%d, %v), esperaba (42, nil)", id, err)
}
}
// Sin id ni nombre no se puede adivinar: mejor un error claro que buscar en la
// base con la cadena vacía y traer un cliente al azar.
func TestResolverClienteIDSinDatos(t *testing.T) {
if _, err := ResolverClienteID(0, " "); err == nil {
t.Fatal("esperaba error cuando no hay ni id ni nombre")
}
}
// Las herramientas que piden cliente deben aceptar también el nombre: es lo
// que elimina el ciclo listar_clientes → leer → extraer id, que es donde se
// perdía la factura.
func TestHerramientasDeClienteAceptanNombre(t *testing.T) {
for _, tl := range agentTools() {
props := tl.Function.Parameters.Properties
if _, pide := props["cliente_id"]; !pide {
continue
}
if _, acepta := props["cliente_nombre"]; !acepta {
t.Errorf("%s pide cliente_id pero no acepta cliente_nombre: obliga al modelo a resolver el id por su cuenta", tl.Function.Name)
}
}
}