Files
soft_usite/pkg/services/agent_message_test.go
Lizandro GuarnizoandClaude Sonnet 5 8eba3ab97f fix: preserva campos propietarios del proveedor en el loop de tool-calling
Gemini (modelos "thinking" como 2.5) exige que el tool_call se reenvíe con
su thought_signature intacto en la ronda siguiente, o rechaza con 400
"Function call is missing a thought_signature". Nuestro parseo a
agentMessage/agentToolCall no conocía ese campo y lo descartaba en el
unmarshal, así que cualquier tool call con Gemini fallaba apenas el modelo
pedía usar una herramienta (buscar_conocimiento incluida).

agentMessage ahora guarda los bytes JSON originales cuando se parsea de una
respuesta (UnmarshalJSON) y los reenvía tal cual al volver a serializar
(MarshalJSON) — preserva thought_signature u otro campo propietario que
llegue, sin que el código necesite conocer su nombre/forma exacta. Los
mensajes que armamos nosotros (user/tool/system) no llevan Raw y siguen
serializando normal. Afecta tanto a uMind como al agente de Telegram, que
comparten este mismo motor.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-12 22:39:09 -05:00

43 lines
1.6 KiB
Go

package services
import (
"encoding/json"
"strings"
"testing"
)
// TestAgentMessageRawRoundTrip confirma que un campo que el proveedor manda
// y que nuestra struct no conoce (ej. thought_signature de Gemini) sobrevive
// a un ciclo unmarshal->marshal en vez de perderse — es justo lo que hacía
// que Gemini rechazara la segunda ronda de una tool call.
func TestAgentMessageRawRoundTrip(t *testing.T) {
original := `{"role":"assistant","content":null,"tool_calls":[{"id":"call_1","type":"function","function":{"name":"buscar_conocimiento","arguments":"{}"},"thought_signature":"opaco-123"}]}`
var msg agentMessage
if err := json.Unmarshal([]byte(original), &msg); err != nil {
t.Fatalf("unmarshal: %v", err)
}
if len(msg.ToolCalls) != 1 || msg.ToolCalls[0].Function.Name != "buscar_conocimiento" {
t.Fatalf("no se poblaron los campos tipados normalmente: %+v", msg)
}
out, err := json.Marshal(msg)
if err != nil {
t.Fatalf("marshal: %v", err)
}
if !strings.Contains(string(out), `"thought_signature":"opaco-123"`) {
t.Errorf("se perdió el campo desconocido al reserializar: %s", out)
}
// Un mensaje armado por nosotros (no parseado) no debe llevar Raw ni
// romperse por eso — tiene que serializar normal.
propio := agentMessage{Role: "user", Content: "hola"}
outPropio, err := json.Marshal(propio)
if err != nil {
t.Fatalf("marshal de mensaje propio: %v", err)
}
if !strings.Contains(string(outPropio), `"role":"user"`) || !strings.Contains(string(outPropio), `"content":"hola"`) {
t.Errorf("mensaje propio no serializó bien: %s", outPropio)
}
}