fix(telegram): si una herramienta falla, el bot lo dice aunque el modelo no
Confirmado que la factura nunca se creó: la lista de /app/facturas no filtra nada que pudiera esconderla. La herramienta falló y el modelo informó "FACTURA GUARDADA EXITOSAMENTE" igual. El commit anterior le prohíbe eso por prompt, pero un prompt es una sugerencia. Esto es la garantía: el código junta los errores que devolvieron las herramientas durante la conversación y los agrega a la respuesta. Si algo no se completó, se ve, diga lo que diga el modelo. No reemplaza al prompt — el modelo sigue debiendo explicar el fallo con sus palabras. Es la red por debajo, para que un "guardado exitosamente" sobre algo que no se guardó no pueda pasar desapercibido otra vez. Sigue sin saberse por qué falló aquella vez, porque hasta hoy el resultado de las herramientas no se registraba. Con el log y este aviso, el próximo intento lo va a decir en el momento. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
560dc261e9
commit
d0c9ed5656
@@ -1859,6 +1859,9 @@ Comandos: /reset · /instancias · /ayuda`, nil
|
||||
|
||||
// Loop de function calling (máximo 6 rondas)
|
||||
var finalResponse string
|
||||
// Herramientas que devolvieron error en esta conversación, para avisarlo al
|
||||
// final aunque el modelo diga otra cosa.
|
||||
var fallidas []string
|
||||
for round := 0; round < 6; round++ {
|
||||
aiMsg, _, err := callAI(ai, messages, tools)
|
||||
if err != nil {
|
||||
@@ -1892,6 +1895,9 @@ Comandos: /reset · /instancias · /ayuda`, nil
|
||||
// guardó algo aunque la herramienta haya devuelto un error, así que
|
||||
// el log es la única fuente confiable de qué pasó de verdad.
|
||||
log.Printf("[AGENT] Resultado de %s: %s", tc.Function.Name, recortar(toolResult, 500))
|
||||
if msg := errorDeTool(toolResult); msg != "" {
|
||||
fallidas = append(fallidas, fmt.Sprintf("%s: %s", tc.Function.Name, msg))
|
||||
}
|
||||
|
||||
// Agregar resultado al contexto (solo en memoria, no en BD)
|
||||
messages = append(messages, agentMessage{
|
||||
@@ -1906,9 +1912,32 @@ Comandos: /reset · /instancias · /ayuda`, nil
|
||||
if finalResponse == "" {
|
||||
finalResponse = "El agente completó las acciones solicitadas."
|
||||
}
|
||||
|
||||
// Aviso determinista, escrito por el código y no por el modelo.
|
||||
//
|
||||
// El modelo puede informar "guardado exitosamente" aunque la herramienta
|
||||
// haya devuelto un error — ya pasó con una factura que nunca se creó. El
|
||||
// prompt se lo prohíbe, pero un prompt es una sugerencia: esto es la
|
||||
// garantía. Si algo falló, se ve, diga lo que diga el modelo.
|
||||
if len(fallidas) > 0 {
|
||||
finalResponse += "\n\n⚠️ No se completó: " + strings.Join(fallidas, " · ")
|
||||
}
|
||||
return finalResponse, nil
|
||||
}
|
||||
|
||||
// errorDeTool devuelve el mensaje si el resultado de una herramienta fue un
|
||||
// error, o "" si salió bien. executeAgentTool serializa los fallos como
|
||||
// {"error": "..."}, así que alcanza con mirar ese campo.
|
||||
func errorDeTool(resultado string) string {
|
||||
var r struct {
|
||||
Error string `json:"error"`
|
||||
}
|
||||
if err := json.Unmarshal([]byte(resultado), &r); err != nil {
|
||||
return ""
|
||||
}
|
||||
return r.Error
|
||||
}
|
||||
|
||||
// recortar limita lo que se escribe al log: un resultado de tool puede traer
|
||||
// listados enteros y no vale la pena volcarlos completos.
|
||||
func recortar(s string, n int) string {
|
||||
|
||||
@@ -43,3 +43,25 @@ func TestExisteHerramientaParaFacturaSinAdjunto(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// El modelo puede informar éxito aunque la herramienta haya fallado — pasó con
|
||||
// una factura que nunca se creó. El aviso lo escribe el código a partir del
|
||||
// resultado real, así que no depende de que el modelo se porte bien.
|
||||
func TestErrorDeTool(t *testing.T) {
|
||||
casos := []struct {
|
||||
nombre string
|
||||
resultado string
|
||||
esperado string
|
||||
}{
|
||||
{"error de la herramienta", `{"error": "cliente_id requerido"}`, "cliente_id requerido"},
|
||||
{"resultado exitoso", `{"ok": true, "factura_id": 12}`, ""},
|
||||
{"lista de datos", `{"items": [], "total": 0}`, ""},
|
||||
{"resultado no JSON", `algo suelto`, ""},
|
||||
{"error vacío no cuenta", `{"error": ""}`, ""},
|
||||
}
|
||||
for _, cas := range casos {
|
||||
if got := errorDeTool(cas.resultado); got != cas.esperado {
|
||||
t.Errorf("%s: errorDeTool(%s) = %q, esperaba %q", cas.nombre, cas.resultado, got, cas.esperado)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user