Lelemon API
  • Guías
  • Referencia de la API
Crear un agenteConversar con un agenteConocimiento del agenteAcciones HTTPEscalamiento a un humanoMandar un mensaje a una conversaciónQue el agente escriba primeroAgendar en tu propia pantallaInventariar tus agentes para una auditoriaCatalogos y respuestas rapidas de un embudoDisponibilidad de una persona del equipoMetricas de las conversaciones
powered by Zudoku
Guías

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
PUT https://<host>/v1/external/knowledge/documents Authorization: Bearer lel_<prefijo>_<secreto> Content-Type: application/json
Code
{ "collectionId": "3f2b1c4d-...", "filename": "docs/precios.md", "content": "# Precios\n\nEl plan chico sale 19.990 + IVA.\n" }
CampoQué es
collectionIdLa colección donde vive el documento. Tiene que ser de la organización de la credencial.
filenameLa ruta relativa del archivo, no el nombre a secas. Es la identidad del documento.
contentEl contenido, en markdown.

La respuesta:

Code
{ "id": "9a8b7c6d-...", "collectionId": "3f2b1c4d-...", "filename": "docs/precios.md", "status": "processing" }

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:

EncabezadoQué dice
RateLimit-LimitEl techo que se está contando.
RateLimit-RemainingCuántos pedidos te quedan en esta ventana.
RateLimit-ResetEn cuántos segundos se libera un cupo.
Retry-AfterSó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
name: sincronizar conocimiento on: push: branches: [main] paths: ['docs/**/*.md'] jobs: empujar: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: # Hacen falta dos commits para poder preguntar qué cambió. fetch-depth: 2 - name: empujar los .md que cambiaron env: LELEMON_KEY: ${{ secrets.LELEMON_API_KEY }} COLECCION: ${{ vars.LELEMON_COLLECTION_ID }} HOST: ${{ vars.LELEMON_HOST }} run: | set -euo pipefail # Sólo lo agregado o modificado. Para los borrados hay # `DELETE /v1/external/knowledge/documents/{id}` — ver la sección 8. git diff --name-only --diff-filter=AM HEAD~1 HEAD -- 'docs/**/*.md' > cambiados.txt while IFS= read -r archivo; do [ -f "$archivo" ] || continue # `--arg` y no interpolación: el contenido tiene comillas y saltos # de línea, y armar el JSON a mano lo rompe en el primer archivo # que traiga un `"`. jq -n \ --arg c "$COLECCION" \ --arg f "$archivo" \ --rawfile t "$archivo" \ '{collectionId:$c, filename:$f, content:$t}' > cuerpo.json codigo=$(curl -sS -o respuesta.json -w '%{http_code}' \ -X PUT "$HOST/v1/external/knowledge/documents" \ -H "Authorization: Bearer $LELEMON_KEY" \ -H 'Content-Type: application/json' \ --data @cuerpo.json) if [ "$codigo" != "200" ]; then echo "::error::$archivo devolvio $codigo" cat respuesta.json exit 1 fi echo "ok $archivo" # Medio segundo entre archivos: el techo son 200 por minuto. sleep 0.5 done < cambiados.txt

Dos cosas de esa receta que valen para cualquier implementación:

  • Armá el JSON con una herramienta, no concatenando. El contenido de un .md trae 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. curl sale 0 con un 403 o un 429: 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.

  1. Disparador — lo que corresponda: un cron diario, un webhook, un cambio en un Drive.
  2. Un nodo por archivo — si vienen varios, Split In Batches con 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.
  3. HTTP Request:
    • Método: PUT
    • URL: https://<host>/v1/external/knowledge/documents
    • Autenticación: credencial de tipo Header Auth, nombre Authorization, valor Bearer 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, filename y content.
  4. Rama de error — que un 4xx avise 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ódigoQué pasóQué hacer
400Falta 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.
401La 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.
403A la credencial le falta knowledge:write.El mensaje nombra el permiso. Se agrega desde la pantalla de desarrolladores.
404La 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.
413El 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.
429Pasaron 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.
500No 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
POST https://<host>/v1/external/knowledge/collections Authorization: Bearer lel_<prefijo>_<secreto>
Code
{ "slug": "guias-precios", "name": "Guías de precios" }

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
PUT https://<host>/v1/external/agents/{agentId}/knowledge/{collectionId}

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
GET /v1/external/knowledge/collections?limit=50&offset=0 GET /v1/external/knowledge/collections/{collectionId}/documents?limit=50&offset=0

Las dos piden knowledge:read, que es un permiso distinto del de escribir. Contestan así:

Code
{ "data": [ ... ], "limit": 50, "offset": 0, "hasMore": true }

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
DELETE /v1/external/knowledge/documents/{documentId}

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:write es 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.
Conversar con un agenteAcciones HTTP
On this page
  • Qué resuelve esto
  • 1. La llamada
  • 2. Es PUT porque reemplazar es lo normal
  • 3. El ritmo: 200 pedidos por minuto
  • 4. Una GitHub Action que empuja lo que cambió
  • 5. Un flujo de n8n
  • 6. Qué puede salir mal
  • 7. Si el agente no cita tus documentos
  • 8. Armar la colección desde cero, y borrar
    • Crear la colección
    • Asignársela al agente
    • Ver lo que hay
    • Borrar un documento
  • 9. Qué entra es responsabilidad tuya
  • 10. La credencial
JSON
JSON
YAML
JSON
JSON