Tema
RAGs
Un RAG es un conjunto de documentos indexados sobre el que se busca por significado. Estos endpoints te dejan consultarlo directamente desde tu backend, sin pasar por una inferencia ni por un agente.
Es la vía cuando quieres los fragmentos relevantes y el LLM lo pones tú, o cuando lo que necesitas es una búsqueda semántica dentro de tu producto.
Listar tus RAGs
bash
curl https://ur.tuempresa.com/api/rags \
-H "Authorization: Bearer $UR_TOKEN"json
[
{
"id": "6f1c...",
"name": "Documentación de ventas",
"identifier": "ventas-docs",
"status": "active",
"totalFiles": 128,
"processedFiles": 126,
"numberVectors": 4821,
"fileSource": {
"id": "9a22...",
"name": "Drive de ventas",
"identifier": "drive-ventas",
"provider": "google_drive"
}
}
]totalFiles frente a processedFiles es el indicador rápido de salud: si difieren mucho, hay ficheros sin indexar — mira status y los ficheros para ver por qué.
Buscar en un RAG
bash
curl -X POST https://ur.tuempresa.com/api/rags/ventas-docs/retrieve \
-H "Authorization: Bearer $UR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"semanticSearch": ["plazo de devolución"], "topK": 10}'json
{
"chunks": [
{
"text": "<untrusted_document source=\"documentos/devoluciones.pdf, lines 8-19\">\nEl plazo de devolución es de 30 días naturales...\n</untrusted_document>",
"score": 0.87
}
]
}Cada text llega envuelto en <untrusted_document source="…">…</untrusted_document>. El source es la URL del documento si la fuente es web, y la ruta del fichero en los demás casos. Si se lo pasas a tu propio modelo, déjalo tal cual: ese envoltorio es lo que le dice que el contenido son datos, no instrucciones. Si vas a enseñárselo a una persona, quítalo tú. Para que un documento no pueda cerrar el bloque antes de tiempo, una etiqueta <untrusted_document o </untrusted_document que venga dentro del propio texto llega como <untrusted_document, y en el source las comillas y los < > van como ", < y >.
El score no tiene una sola escala
Depende de cómo esté configurado el RAG, y lo dice scoreKind: cosine (similitud semántica, 0–1), rrf (búsqueda híbrida, ~0.016–0.03), bm25 (búsqueda por palabras: un RAG sin embeddings, searchMode: "bm25" o exactPhrase; un número sin tope, que solo sirve para comparar dentro de la misma respuesta) o rerank (con reranker). Si filtras por score en tu código, mira antes scoreKind.
Cuerpo
| Campo | Tipo | Por defecto | Descripción |
|---|---|---|---|
semanticSearch | string[] | — | De 1 a 10 queries. Obligatorio salvo con exactPhrase |
exactPhrase | string (≤ 500) | — | Busca los fragmentos que contienen esta frase palabra por palabra, en vez de los más relevantes. Ver Comprobar si una frase aparece |
topK | int 1–100 | 5 | Fragmentos a devolver por cada query |
useReranking | bool | false | Reordena, si el RAG tiene reranker |
candidateMultiplier | int 2–10 | 2 | Candidatos antes de reordenar |
similarityThreshold | number 0–1 | — | Descarta por debajo de este parecido (similitud coseno). No aplica en un RAG solo BM25 |
searchMode | auto · bm25 · semantic · hybrid | auto | Cómo buscar: bm25 es por palabras (sueltas, sin orden: códigos, referencias, nombres propios), semantic por significado y hybrid las dos. auto: cada RAG usa lo que tiene. El resto es una orden: si algún RAG no puede cumplirla (semantic/hybrid en un RAG sin embeddings, o bm25/hybrid mientras su índice BM25 se construye), 422 nombrando esos RAGs, sin buscar nada |
includeDuplicates | bool | false | Incluye fragmentos marcados como duplicados |
useAllFiles | bool | true | Informativo: el endpoint lo pone a false solo en cuanto mandas una selección, y a false sin selección no restringe nada |
selectedFiles | string[] | — | Rutas completas de fichero — ["documentos/informe.pdf"] |
selectedFolders | string[] | — | Carpetas sin barra final — ["documentos/informes"]; se expanden a sus ficheros indexados |
Aquí hace falta semanticSearch o exactPhrase
A diferencia de inferencias, donde solo hace falta si hay RAGs vinculados, aquí es el único sitio de donde sale la búsqueda: sin ninguno de los dos es 400.
Y ojo con selectedFiles: aquí es una lista plana de rutas (["a.pdf", "b.pdf"]), no la forma por RAG que usan las inferencias. Es el mismo nombre con distinta forma porque aquí el RAG ya viene en la URL. (En inferencias es además al revés en otra cosa: allí selectedFiles/selectedFolders solo surten efecto si mandas useAllFiles: false.)
No confundas tampoco estas rutas con las de los scopes: en selectedFolders la carpeta va sin barra final. Con ella, la expansión no encuentra ningún fichero y la búsqueda deja de estar acotada — se hace sobre todo el RAG, con 200 y sin ningún aviso.
Y providerFilter no existe en este endpoint: si lo mandas, se ignora sin error. El filtro por metadatos del proveedor solo está en inferencias.
Comprobar si una frase aparece
La búsqueda normal ordena por relevancia y devuelve los topK primeros, así que que algo no salga no demuestra que no esté: puede haber quedado por detrás de fragmentos parecidos. Ni siquiera searchMode: "bm25" busca la frase: puntúa sus palabras sueltas, sin orden. Para preguntar «¿aparece esto?», usa exactPhrase:
bash
curl -X POST https://ur.tuempresa.com/api/rags/guiones/retrieve \
-H "Authorization: Bearer $UR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"exactPhrase": "Wake up, Neo", "topK": 3}'json
{
"chunks": [{ "text": "<untrusted_document …>…\"Wake up, Neo...\"…</untrusted_document>", "score": 11.2, "scoreKind": "bm25" }],
"exactPhrase": { "exhaustive": true, "candidates": 87, "matches": 4, "unindexedFiles": 0 }
}- Devuelve los fragmentos que contienen esas palabras, seguidas y en ese orden, sin distinguir mayúsculas, acentos ni puntuación:
Wake up, NeoencuentraWAKE UP... NEO, pero nowake him up, Neo. Las palabras son enteras:contrano encuentracontrato. exactPhrase.exhaustive: truequiere decir que se miraron todos los fragmentos que podían contener la frase. Contrueychunksvacío, la frase no está en el RAG. Confalseno se puede afirmar: más de 500 candidatos, algún fichero que todavía no está en el índice (unindexedFiles: sin indexar, reindexándose o con error), una frase más larga que el solape entre fragmentos (200 caracteres por defecto) o un fallo que llega endegraded.matchescuenta todos los fragmentos que la contienen; se devuelven como muchotopK.- Sustituye a la búsqueda normal:
semanticSearch,searchMode,similarityThresholdy el reranking no se aplican. Los filtros (selectedFiles,selectedFolders) sí; los duplicados se miran siempre, porque un casi duplicado puede diferir justo en la frase. - El texto de un enlace cuenta como texto: «guía completa para saber más» se encuentra aunque «guía completa» sea un enlace.
- Una frase partida entre dos fragmentos no se encuentra: el solape del troceado lo evita mientras la frase sea más corta que él, y si no,
exhaustiveesfalse. - Una frase hecha solo de palabras vacías (the, de, la…) es
400: no hay palabra con la que buscar candidatos. Añade alguna que distinga. - Necesita el índice de palabras del RAG terminado: mientras se construye,
422.
topK cuenta por query, así que 10 queries con topK: 10 pueden dar hasta 100 fragmentos antes de deduplicar (por texto idéntico). Sea cual sea la combinación, la respuesta trae como mucho 60 fragmentos: el tope global de la instalación, configurable por quien la administra.
Igual que en inferencias, la búsqueda nunca lanza error: si algo se degrada, la respuesta incluye degraded.
Estado de un RAG
bash
curl https://ur.tuempresa.com/api/rags/ventas-docs \
-H "Authorization: Bearer $UR_TOKEN"json
{
"id": "6f1c...",
"name": "Documentación de ventas",
"identifier": "ventas-docs",
"status": "active",
"fileSourceScopeMode": "scoped",
"fileSourceScopes": [{ "type": "path", "value": "documentos/" }],
"totalFiles": 128,
"processedFiles": 126,
"numberVectors": 4821
}Listar los ficheros
bash
curl "https://ur.tuempresa.com/api/rags/ventas-docs/files?page=1&limit=50" \
-H "Authorization: Bearer $UR_TOKEN"page cambia la forma de la respuesta
Con page devuelve { data, total, page, limit }. Sin page devuelve el array completo, por compatibilidad con integraciones antiguas.
Usa siempre page en código nuevo: un RAG que crece puede devolver miles de filas de golpe.
limit va de 1 a 100, y por defecto es 20.
json
{
"data": [
{
"filePath": "documentos/informe.pdf",
"name": "informe.pdf",
"status": "indexed",
"indexedAt": "2026-03-01T10:00:00.000Z",
"size": 182344,
"mimeType": "application/pdf",
"externalId": "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs",
"metadata": { "tag": ["Confidencial"] },
"errorMessage": null,
"textQuality": null
}
],
"total": 128,
"page": 1,
"limit": 50
}status es pending, processing, indexed, error o deleted, y errorMessage dice por qué cuando es error. deleted es un fichero pendiente de purga: ya no forma parte del RAG (lo deja fuera un filtro o el scope, o desapareció de su fuente) y sus vectores se borran en el primer escaneo de su fuente pasadas 24 h (por defecto): en una fuente que se escanea cada semana, a la semana, y con la fuente o el RAG en pausa, no se borra. Mientras tanto no sale en ninguna búsqueda; si vuelve antes, se recupera sin volver a indexarlo. En el panel, la papelera de la fuente dice cuándo se borra cada uno y permite vaciarla en el acto. externalId es el id del fichero en el proveedor (en SharePoint y OneDrive, el id del ítem de Microsoft Graph; en fuentes web, la URL absoluta; en sistema de ficheros, null). No es un enlace: el de SharePoint lo construye la función get_link de la tool rag_retrieve, y abrirlo pide sesión en Microsoft 365 con acceso al fichero. metadata son los metadatos del fichero tal y como los guarda el escaneo: incluyen las etiquetas del proveedor, pero su contenido exacto no es un contrato estable.
textQuality es null cuando el texto salió limpio. Si no, cuenta lo que hay que saber de él:
| Campo | Qué quiere decir |
|---|---|
encoding, encodingSource | El fichero no era UTF-8 y se leyó con esta codificación: la que declara el propio fichero (declared), su marca de orden de bytes (bom) o, si no dice nada, windows-1252 (fallback). Es información, no un problema |
unreadableChars | Caracteres que no se pudieron leer (�). Las palabras que los llevan no se encuentran al buscar |
originBroken | Esos caracteres ya venían así en el fichero original: solo se arregla con una copia mejor del original |
mojibake | Trozos de texto mal codificado que quedan (ñ en vez de ñ) |
repaired | El texto venía doblemente codificado y se ha deshecho al leerlo |
Acotar qué se indexa
Un RAG puede estar en dos modos:
global— indexa todo lo que haya en su file source.scoped— indexa solo las rutas que le digas. En el panel es el interruptor Modo scope.
En modo scoped, estos dos endpoints gestionan esa lista desde tu código (útil si quien decide qué documentos entran es tu aplicación, no una persona en el panel):
bash
# Añadir
curl -X POST https://ur.tuempresa.com/api/rags/ventas-docs/scopes \
-H "Authorization: Bearer $UR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"type": "path", "value": "documentos/"}'
# Quitar
curl -X DELETE https://ur.tuempresa.com/api/rags/ventas-docs/scopes \
-H "Authorization: Bearer $UR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"type": "path", "value": "documentos/"}'Los dos piden que el dueño del token administre el RAG (que sea administrador, o que un grupo suyo se lo dé como admin): con uno que solo ve, responden 403 con manage_required.
Los dos devuelven {"ok": true}. El DELETE es idempotente: quitar un scope que no está también responde 200, no 404, así que no lo uses para comprobar si existía — relee la lista con GET /api/rags/{identifier}.
Un cambio de scope reescribe el índice, y no es inmediato
La lista no se aplica al guardarla: se aplica en el siguiente escaneo de la file source, que se adelanta al guardar. Ahí, lo que deje de estar dentro del scope pasa a pendiente de purga: deja de salir en las búsquedas en ese momento, y sus vectores se purgan en el primer escaneo de la fuente pasadas 24 h (por defecto). Si vuelve a entrar antes, se recupera sin reindexarlo.
Tenlo muy presente si sincronizas la lista desde código: un bucle que borre los scopes antiguos antes de añadir los nuevos pasa por un estado intermedio con consecuencias reales.
Un RAG scoped con la lista vacía indexa TODA la fuente
Quitar el último scope no deja el RAG sin nada: deja de estar acotado y, salvo que tenga un filtro por metadatos configurado en el panel, se traga la fuente entera en el siguiente escaneo. Para un Drive corporativo eso puede ser todo el disco de la empresa.
Si lo que quieres es dejarlo sin documentos, pásalo a modo global y desvincula la fuente, o deja un scope que no case con nada — pero no la lista vacía.
Formato del valor
| Qué | type | value |
|---|---|---|
| Carpeta | path | "documentos/" — incluye todo lo que cuelga de ella |
| Fichero | path | La ruta completa — "documentos/informe.pdf" |
Siempre type: "path", también en Google Drive y SharePoint
Ahí las rutas son las que ves en el explorador de la fuente (Carpeta/Subcarpeta/informe.pdf), desde su carpeta raíz, no los ids del proveedor.
El endpoint acepta también type: "externalId", pero hoy el escaneo no lo tiene en cuenta: un scope así no casa con ningún fichero, deja el RAG sin nada que indexar y elimina del índice lo ya indexado en ese mismo escaneo. No lo uses.
En Google Drive y SharePoint el scope sigue a la carpeta
Si renombras o mueves dentro de la fuente una carpeta que está en el scope, el RAG no la pierde: en el siguiente escaneo el value pasa a la ruta nueva y lo ya indexado se conserva sin volver a procesarse. Para eso UR guarda junto al scope el ID de la carpeta, que verás como externalId en GET /api/rags/{identifier} — no hace falta mandarlo, se rellena solo en el siguiente escaneo.
Solo lo puede rellenar mientras la ruta exista: un scope cuya carpeta ya se había renombrado antes de tener externalId no casa con nada y hay que volver a añadirlo con la ruta nueva. Y como el value cambia, para quitar un scope después de un renombrado usa la ruta nueva.
La barra final no cambia casi nada
"documentos/" y "documentos" acotan prácticamente lo mismo: la carpeta entera, con todo lo que haya debajo (sin barra, además, casa con un fichero que se llame exactamente así). Si un scope no surte efecto, el problema no es la barra — comprueba que la ruta es la que ves en la fuente.
Errores propios
| Código | Cuándo |
|---|---|
403 | Con "code":"manage_required": el dueño del token ve el RAG pero no lo administra. Pide que su grupo se lo dé como admin |
409 | Ese scope ya estaba en la lista |
422 | El RAG está en modo global: no tiene lista que gestionar |
Un 422 significa que primero hay que pasar el RAG a modo scoped en el panel.