fix(umind): el agente se quedaba sin rondas buscando y nunca llegaba a responder
Los eventos de producción mostraban "se agotaron las rondas" tres veces seguidas en un agente que respondía bien en el chat de prueba. La diferencia no era el widget: era la conversación. Un modelo que encadena búsquedas —busca una cosa, con eso busca otra— se comía las tres rondas pidiendo herramientas y el bucle terminaba sin que hubiera redactado nada, aunque para entonces ya tuviera toda la información junta. En el chat de prueba las consultas eran más cortas y no encadenaban, así que ahí nunca se veía. Ahora son cuatro rondas y, sobre todo, la última se llama SIN herramientas: sin nada que pedir, al modelo no le queda otra que responder con lo que juntó. Que es exactamente lo que se quiere en el último turno. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
97e66b82e3
commit
cdfbff605e
@@ -290,8 +290,20 @@ func ProcessWidgetMessage(agente *models.UmindAgente, sessionID, userText string
|
|||||||
// ronda, "se agotaron las rondas" no dice dónde mirar.
|
// ronda, "se agotaron las rondas" no dice dónde mirar.
|
||||||
var herramientasPedidas []string
|
var herramientasPedidas []string
|
||||||
|
|
||||||
for round := 0; round < 3; round++ {
|
// La última vuelta se llama SIN herramientas. Un modelo que encadena
|
||||||
aiMsg, tokens, err := callAI(&ai, messages, tools)
|
// búsquedas —busca una cosa, después otra— se comía las rondas pidiendo
|
||||||
|
// tools y nunca llegaba a redactar: el visitante recibía el mensaje
|
||||||
|
// genérico aunque el agente tuviera toda la información junta. Sin
|
||||||
|
// herramientas disponibles no le queda otra que responder con lo que ya
|
||||||
|
// juntó, que es exactamente lo que se quiere en el último turno.
|
||||||
|
const maxRondas = 4
|
||||||
|
|
||||||
|
for round := 0; round < maxRondas; round++ {
|
||||||
|
disponibles := tools
|
||||||
|
if round == maxRondas-1 {
|
||||||
|
disponibles = nil
|
||||||
|
}
|
||||||
|
aiMsg, tokens, err := callAI(&ai, messages, disponibles)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[UMIND] Error llamando AI (agente %d) round %d: %v", agente.ID, round, err)
|
log.Printf("[UMIND] Error llamando AI (agente %d) round %d: %v", agente.ID, round, err)
|
||||||
models.RegistrarEventoUmind(agente.ID, "error", "ai", fmt.Sprintf("Error contactando el AI (ronda %d)", round), err.Error())
|
models.RegistrarEventoUmind(agente.ID, "error", "ai", fmt.Sprintf("Error contactando el AI (ronda %d)", round), err.Error())
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
// El bucle de herramientas tiene que garantizar que la última vuelta sea sin
|
||||||
|
// herramientas. Si no, un modelo que encadena búsquedas se come todas las
|
||||||
|
// rondas pidiendo tools y nunca redacta: el visitante recibe el mensaje
|
||||||
|
// genérico aunque el agente ya tenga toda la información junta.
|
||||||
|
//
|
||||||
|
// Es exactamente lo que pasaba en producción: tres eventos seguidos de
|
||||||
|
// "se agotaron las rondas" en un agente que respondía bien en el chat de
|
||||||
|
// prueba, donde la conversación era más corta y no encadenaba búsquedas.
|
||||||
|
func TestLaUltimaRondaVaSinHerramientas(t *testing.T) {
|
||||||
|
const maxRondas = 4
|
||||||
|
|
||||||
|
hubo := 0
|
||||||
|
ultimaConHerramientas := false
|
||||||
|
|
||||||
|
for round := 0; round < maxRondas; round++ {
|
||||||
|
conHerramientas := round != maxRondas-1
|
||||||
|
hubo++
|
||||||
|
if round == maxRondas-1 {
|
||||||
|
ultimaConHerramientas = conHerramientas
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if hubo != maxRondas {
|
||||||
|
t.Errorf("se corrieron %d rondas, esperaba %d", hubo, maxRondas)
|
||||||
|
}
|
||||||
|
if ultimaConHerramientas {
|
||||||
|
t.Error("la última ronda ofreció herramientas: el modelo puede volver a pedir una y quedarse sin responder")
|
||||||
|
}
|
||||||
|
if maxRondas < 3 {
|
||||||
|
t.Errorf("con %d rondas no alcanza para buscar y después redactar", maxRondas)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Con la última ronda sin herramientas, un modelo que pide búsquedas en todas
|
||||||
|
// las vueltas igual termina respondiendo.
|
||||||
|
func TestUnModeloQuePideBusquedasSiempreIgualResponde(t *testing.T) {
|
||||||
|
const maxRondas = 4
|
||||||
|
respondio := false
|
||||||
|
|
||||||
|
for round := 0; round < maxRondas; round++ {
|
||||||
|
hayHerramientas := round != maxRondas-1
|
||||||
|
// El modelo simulado pide herramienta siempre que pueda.
|
||||||
|
pideHerramienta := hayHerramientas
|
||||||
|
if !pideHerramienta {
|
||||||
|
respondio = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !respondio {
|
||||||
|
t.Error("el bucle terminó sin respuesta: es el fallback genérico que ve el visitante")
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user