Volver al Blog

Mi agente seguía perdiendo hechos que ya había guardado

2026-06-184 min read

AIdaemon se ejecuta completamente en mi propia máquina, con modelos de código abierto como Gemma 4, sin llamar a ninguna API en la nube. Guarda hechos sobre mí mientras hablo con él, y luego los busca cuando se los pregunto. Guardarlos ya funcionaba. Encontrarlos era lo que me fallaba. Le preguntaba sobre algo que sabía que había almacenado y no obtenía nada útil, o dos hechos que estaban en el tema correcto pero se perdían el punto.

La búsqueda se basa en el significado. Cada hecho se convierte en un vector cuando se guarda, mi pregunta se convierte en un vector cuando pregunto, y clasifica los hechos por la cercanía de ambos. El problema es que cerca no es lo mismo que correcto. Una pregunta saca todo lo que comparte su tema, por lo que unos hechos extensos sobre lo mismo desplazan al corto que realmente contiene la respuesta. En una consulta que seguía fallando, comprobé dónde estaba la respuesta. Alrededor del puesto 30. La búsqueda leyó los primeros y se detuvo mucho antes de llegar allí.

Por qué la esencia no es suficiente

Esa primera búsqueda utiliza un bi-encoder, un modelo pequeño llamado all-MiniLM-L6-v2. Codifica cada hecho por sí solo cuando lo guardas, y tu consulta por sí sola cuando buscas, y luego compara los dos. Nunca los ve uno al lado del otro. Esa es toda la razón por la que es rápido. Incrustas todo una vez y una búsqueda es solo matemática barata sobre el almacén, sin llamada al modelo cuando preguntas.

También es por eso que se equivoca en el orden. Un bi-encoder capta el tema general y falla en la coincidencia precisa. Preguntas sobre una cosa y saca cualquier cosa en el mismo vecindario mientras que el hecho que querías se hunde. Es lo suficientemente barato como para ejecutarlo en cada hecho que tienes, que es exactamente por lo que recurres a él cuando todo tiene que vivir en tu propio hardware. Simplemente no es lo suficientemente cuidadoso como para clasificarlos bien. La mayoría de las veces es suficiente. Cuando necesito una respuesta específica, se desmorona.

Dejando que un segundo modelo lea realmente

La solución es un reranker. Es un cross-encoder, por lo que lee mi pregunta y un hecho candidato juntos, en el mismo pase, y califica qué tan bien responde ese hecho a la pregunta. Donde el bi-encoder comparó dos resúmenes construidos por separado, este lee el par. Mejor pregunta, mejor respuesta. La desventaja es la velocidad. Es una ejecución de modelo por hecho, por lo que no puedes apuntarlo a todo un almacén de memoria y esperar.

La forma de evitarlo es vieja y aburrida, y funciona. Recuperar primero, clasificar después. Deja que el modelo barato agarre un montón amplio de hechos posiblemente relevantes, y luego gasta el caro solo en ese montón corto.

En AIdaemon, el bi-encoder extrae los 50 candidatos principales con un umbral laxo de 0.22. Lo mantengo más laxo que el 0.30 que uso para la memoria que se inserta directamente en los prompts, porque los sinónimos obtienen una puntuación baja y un umbral estricto descartaría el hecho correcto antes de que el reranker lo viera. La primera etapa no tiene que ser correcta. Solo tiene que colocar la respuesta en algún lugar dentro de los 50. Luego, el cross-encoder relee los 50 contra la pregunta y los reordena. El hecho que estaba en el puesto 30 finalmente se lee en sus propios términos, salta y regresa. No cambié nada sobre cómo se almacenan los hechos, solo cómo se ordenan al salir.

// etapa 1: el bi-encoder lanza una red amplia (coseno sobre todos los hechos activos)
let mut pool = cosine_rank(&query_vec, &facts, MIN_SCORE); // 0.22
pool.truncate(CANDIDATE_POOL); // 50

// etapa 2: el cross-encoder relee cada par (consulta, hecho) y reordena
let docs: Vec<String> = pool.iter().map(|c| c.text()).collect();
let ranked = reranker.rerank(&query, docs, false, None)?;

El reranker se ejecuta en la misma máquina que todo lo demás, a través del crate fastembed como un modelo ONNX, sin una API de rerank a la que llamar. Estoy usando Jina Reranker v2 Base Multilingual, y elegí multilingüe a propósito, ya que mis notas a AIdaemon cambian entre inglés y español, y un reranker solo en inglés fallaría precisamente en los hechos que más me importan que se obtengan correctamente.

Mantenerlo barato

Un segundo modelo es una segunda cosa que puede fallar, por lo que se mantiene bajo control. Solo se carga la primera vez que una búsqueda lo necesita, porque la descarga no es pequeña. Si falla al cargarse, la búsqueda vuelve al orden de coseno simple que usaba antes. Nada falla, simplemente se vuelve menos preciso. Y solo se ejecuta cuando le pido explícitamente al agente que busque algo. La memoria que se integra en cada prompt por sí sola todavía toma el camino barato. Ejecutar 50 hechos a través de un cross-encoder es razonable una vez, cuando hice una pregunta. En cada mensaje sería un desperdicio.

Recuperar y luego clasificar no es nuevo. Los motores de búsqueda se han apoyado en esto durante años. Simplemente resulta que también se adapta bien a la memoria de los agentes. El primer modelo agarra un montón amplio, el segundo lee ese montón correctamente, y esa segunda lectura cuesta casi nada, cincuenta cadenas cortas a través de un modelo. Es la razón por la que AIdaemon ahora me devuelve el hecho que guardó en lugar de quedarse vacío cuando pregunto.

Mantente Actualizado

Recibe las últimas publicaciones e ideas directamente en tu bandeja de entrada.

Unsubscribe anytime. No spam, ever.