Mantener al día lo que el agente sabe
Guía para empujar documentos a una colección de conocimiento desde un sistema propio, con una
credencial de organización. Sirve para un flujo de n8n, para una GitHub Action, o para cualquier
cosa que sepa hacer un PUT.
Qué resuelve esto
Un agente contesta con lo que tiene en sus colecciones de conocimiento. Hasta ahora esas colecciones se llenaban de tres maneras y ninguna se sostenía sola: subiendo archivos a mano por la pantalla —y acordándose de volver a subirlos cada vez que cambian—, conectando Notion, o escribiendo un script propio con el usuario y la contraseña de una persona en variables de entorno.
El resultado es siempre el mismo: la documentación se sube una vez y después envejece. Y un agente que cita documentación vencida es peor que uno que no cita nada, porque suena igual de seguro.
Con esto, el sistema que ya tiene los archivos los empuja cuando cambian. Nadie tiene que acordarse de nada.
1. La llamada
Code
Code
| Campo | Qué es |
|---|---|
collectionId | La colección donde vive el documento. Tiene que ser de la organización de la credencial. |
filename | La ruta relativa del archivo, no el nombre a secas. Es la identidad del documento. |
content | El contenido, en markdown. |
La respuesta:
Code
status: "processing" y no "ready" a propósito: el documento se trocea y se vectoriza después, en
segundo plano. Decir "listo" en la respuesta sería prometer que el agente ya lo sabe cuando todavía
no lo leyó. En condiciones normales tarda segundos.
2. Es PUT porque reemplazar es lo normal
Empujar dos veces el mismo filename deja un documento, no dos. El segundo pisa al primero y
sus trozos se rehacen con el contenido nuevo.
Esa es la parte importante de todo esto. Si cada llamada creara un documento nuevo, un flujo que corre en cada push dejaría veinte copias del mismo archivo, y el agente citaría la que le tocara — con una de ellas vencida y contestada con la misma seguridad que la buena.
Por eso el filename es la ruta relativa y no el nombre. Cinco README.md en cinco carpetas
son cinco documentos distintos; mandar sólo README.md los haría pisarse entre ellos. Mandá
docs/README.md, api/README.md, etc.
Cambiar el nombre de un archivo crea un documento nuevo y deja el viejo. Para nosotros el nombre es la identidad, así que un renombre no lo podemos distinguir de un archivo nuevo. Si renombrás mucho, conviene borrar por la pantalla lo que quedó colgando.
Lo que gestiona una fuente conectada no se toca. Si la colección tiene Notion conectado y una
página se llama igual que tu archivo, tu PUT crea el suyo y deja la página de Notion en paz. Cada
uno tiene su dueño.
3. El ritmo: 200 pedidos por minuto
Es el techo por dirección IP, compartido con el resto de la API externa (el de la credencial es de
3.000). El pedido 201 dentro del mismo minuto recibe 429.
No tenés que adivinar cuánto esperar. Cada respuesta trae en qué anda tu balde, y el 429 trae
además cuántos segundos faltan:
| Encabezado | Qué dice |
|---|---|
RateLimit-Limit | El techo que se está contando. |
RateLimit-Remaining | Cuántos pedidos te quedan en esta ventana. |
RateLimit-Reset | En cuántos segundos se libera un cupo. |
Retry-After | Sólo en el 429: cuántos segundos esperar antes de reintentar. |
La regla práctica: empujá sólo lo que cambió. Además de esquivar el límite, es lo barato — un documento que se vuelve a empujar se re-vectoriza aunque no haya cambiado ni una palabra.
Para el relleno inicial de un repositorio grande, hay que espaciar. Quinientos archivos a 200 por minuto son unos 3 minutos, y se corre una sola vez.
4. Una GitHub Action que empuja lo que cambió
Code
Dos cosas de esa receta que valen para cualquier implementación:
- Armá el JSON con una herramienta, no concatenando. El contenido de un
.mdtrae comillas, saltos de línea y backslashes. El primer archivo con una comilla rompe un JSON armado a mano. - Mirá el código HTTP, no el código de salida de
curl.curlsale 0 con un403o un429: el pedido salió bien, el que dijo que no fue el servidor. Sin el-w '%{http_code}'el flujo queda verde sin haber subido nada.
5. Un flujo de n8n
La misma idea con nodos, para el caso de que los archivos no vivan en un repositorio.
- Disparador — lo que corresponda: un cron diario, un webhook, un cambio en un Drive.
- Un nodo por archivo — si vienen varios,
Split In Batchescon tamaño 1 y una espera de medio segundo entre lotes. En paralelo funciona igual de bien del lado nuestro, pero se choca con el límite de 200 por minuto mucho más rápido. - HTTP Request:
- Método:
PUT - URL:
https://<host>/v1/external/knowledge/documents - Autenticación: credencial de tipo Header Auth, nombre
Authorization, valorBearer lel_.... Como credencial de n8n, no escrita adentro del nodo — adentro del nodo queda en claro en cada exportación del flujo. - Cuerpo: JSON, con
collectionId,filenameycontent.
- Método:
- Rama de error — que un
4xxavise a alguien. Un flujo que falla en silencio deja la documentación vencida sin que nadie se entere, que es exactamente el problema que vinimos a resolver.
6. Qué puede salir mal
| Código | Qué pasó | Qué hacer |
|---|---|---|
400 | Falta un campo, o el contenido está vacío. | El cuerpo trae errors, con el campo que falló y la forma de lo que mandaste ("string(13)"), nunca su valor. message sigue diciendo "Validation failed": el detalle está al lado. |
401 | La credencial no existe, se revocó, o no viaja en Authorization: Bearer. | Si venía funcionando, es que se revocó o se rotó. La respuesta trae WWW-Authenticate: Bearer. |
403 | A la credencial le falta knowledge:write. | El mensaje nombra el permiso. Se agrega desde la pantalla de desarrolladores. |
404 | La colección no existe o no es de tu organización. | Las dos cosas se responden igual a propósito: distinguirlas diría si el id de otro existe. |
413 | El documento pasa los 15 MB. | Partilo. Un documento de ese tamaño además contesta mal: el buscador trae trozos, y mientras más grande el archivo, menos preciso el trozo. |
429 | Pasaron un techo por minuto: 200 pedidos por dirección IP o 3.000 por credencial. | Esperá lo que diga Retry-After y reintentá. Ver la sección 3. |
500 | No pudimos encolar la indexación. | El documento queda marcado como fallido, no a medio hacer. Reintentá; si sigue, escribinos. |
Sobre el 500: si la indexación no se pudo encolar, el documento queda en estado failed y se ve
así en la pantalla. Es a propósito. Dejarlo en processing sería peor: se vería igual que uno recién
subido y nadie lo volvería a mirar nunca.
7. Si el agente no cita tus documentos
Empujar el documento no alcanza para que el agente lo use. Tres cosas tienen que darse, y las primeras dos son de una sola vez:
La colección tiene que estar asignada al agente. Un documento en una colección que ningún agente
tiene asignada está indexado y no lo alcanza nadie. Se hace desde la pantalla, o con
PUT /v1/external/agents/{agentId}/knowledge/{collectionId} — ver la sección 8.
La búsqueda de documentos ya viene incluida, no hay que prenderla: un agente con conocimiento asignado puede leerlo. Antes eran dos permisos separados y la gente hacía uno y daba por hecho el otro.
Y el agente tiene que querer buscar. Es lo que sorprende: el modelo decide si consultar la documentación, y si la pregunta le queda lejos de lo que él cree que hace, contesta de su perfil sin buscar. Nos pasó probando esto — un agente de ventas al que le preguntamos por una garantía de equipos contestó "nosotros no vendemos equipos" sin abrir un solo documento, y tenía razón.
Si te pasa: revisá que lo que subiste sea del dominio del agente, y que sus instrucciones le digan que consulte la documentación antes de responder. Para comprobar rápido si buscó o no, preguntale algo cuyo dato sólo pueda salir de tu documento.
8. Armar la colección desde cero, y borrar
Hasta acá todo asume que la colección ya existe y que alguien se la asignó al agente. Esos dos pasos también se hacen por API, así que un flujo puede aprovisionar desde cero sin que nadie entre al panel.
Crear la colección
Code
Code
El slug lo elegís vos, y es la llave. Llamar dos veces con el mismo slug devuelve la misma
colección, no una copia: 201 la primera vez, 200 las siguientes. Eso es lo que hace que un
flujo se pueda reintentar sin dejar guias-precios y guias-precios-2, las dos medio llenas, con el
agente leyendo una mientras el flujo empuja documentos a la otra.
Por eso el slug no se deriva del nombre: si lo hiciéramos, corregir una tilde en el nombre crearía una colección nueva y vacía.
Asignársela al agente
Code
Sin cuerpo. Contesta 204. Asignar la misma dos veces no la duplica ni falla.
Va de a una colección, y no "acá está la lista completa", a propósito: con la lista, para agregar una tendrías que leer las que hay, sumar la tuya y mandar todo — y entre el leer y el mandar, lo que alguien haya asignado desde la pantalla desaparece sin que nadie se entere.
Para quitarla, el mismo camino con DELETE. La colección no se borra: deja de estar asignada a ese
agente. Quitar algo que no estaba tampoco es un error.
Ver lo que hay
Code
Las dos piden knowledge:read, que es un permiso distinto del de escribir. Contestan así:
Code
hasMore es lo único que dice cuándo parar; el offset siguiente es offset + data.length. El
limit va hasta 500, y pedir más da 400 en vez de recortarse en silencio — recortar convertiría
"pediste mal" en un número plausible.
Borrar un documento
Code
Contesta 204. Borra sus trozos y lo saca de circulación: el agente deja de poder citarlo. Borrarlo
dos veces da 404 la segunda, que es información honesta —ya no está— y no rompe un reintento.
9. Qué entra es responsabilidad tuya
Un campo que no está en la tabla se descarta en silencio. No da error. Si mandás
organizationId no pasa nada: la organización sale de la credencial y de ningún otro lado. Vale la
pena saberlo porque un campo mal escrito —fileName en vez de filename— se va a descartar igual, y
el error que vas a ver es el del campo que falta.
La API guarda lo que le mandes, tal cual. No inspeccionamos el contenido, no lo filtramos y no tachamos nada.
Eso tiene una consecuencia concreta que conviene tener presente: lo que empujes se trocea, se indexa y el agente lo puede citar textualmente a quien esté conversando con él. La regla, en una línea:
No empujes nada que no quieras que el agente diga en voz alta.
Si tus archivos tienen datos personales —RUT, correos, teléfonos, montos de un cliente—, la decisión
de ponerlos ahí es tuya y el filtro va de tu lado, antes del PUT. Nuestro script interno de
ingesta, por ejemplo, tacha los RUT y excluye carpetas por una lista antes de subir nada; eso es una
decisión de ese script, no algo que la API haga por vos.
Lo que sí hacemos de nuestro lado: el contenido queda dentro de tu organización y ninguna otra lo alcanza. La organización sale de la credencial y de ningún otro lado — nunca la leemos del cuerpo ni de la URL.
10. La credencial
Se emite desde Desarrolladores en el panel. Los permisos de conocimiento son dos:
knowledge:write— empujar, borrar, crear colecciones y asignarlas. Es la fuente de las respuestas del agente.knowledge:read— sólo ver qué colecciones y documentos hay.
Dale el más chico que sirva: un flujo que sincroniza documentación necesita el primero; uno que audita, el segundo.
- Se muestra una sola vez. De nuestro lado guardamos sólo un hash. Si se pierde, se emite otra.
knowledge:writees el permiso más peligroso que hay: quien lo tiene cambia la fuente de las respuestas del agente. Una credencial para sincronizar documentación no necesita poder conversar, y una que conversa no necesita poder reescribir lo que el agente sabe.- Guardala como secreto, no adentro del código ni de un nodo exportable.
- Se puede revocar en cualquier momento, y revocar es definitivo.