diff --git a/docs/API_RAG.md b/docs/API_RAG.md index d6ec99e..385abd9 100644 --- a/docs/API_RAG.md +++ b/docs/API_RAG.md @@ -2,188 +2,109 @@ **Proyecto:** Workspace de tools IA para empresas **Modulo:** RAG -**Ultima actualizacion:** 2026-04-05 -**Ultima modificacion por:** Agente tools IA para potenciar servicios empresariales -**Estado:** Operativa en VPS2 +**Ultima actualizacion:** 2026-09-08 +**Ultima modificacion por:** Agente RAG 2 +**Estado:** Operativa en produccion; endpoints de descubrimiento pendientes de despliegue ---- +Esta guia explica como descubrir y consumir la API. El contrato tecnico completo y canonico se publica en OpenAPI 3.1.1; este documento prioriza el camino rapido, las decisiones de uso y los ejemplos habituales. -## Proposito +## Acceso rapido -Dejar una referencia rapida y practica de la API del servicio `RAG` para poder conectarlo facilmente desde modelos, agentes, flujos de `n8n` y otras aplicaciones. - ---- - -## Base URL actual - -Produccion actual en `VPS2`: +Base URL de produccion: ```text https://rag.por-correo.com ``` ---- +Cuando se despliegue esta mejora: -## Endpoints disponibles +| Recurso | URL | Uso | +|---|---|---| +| Ayuda resumida | `GET /help` | Descubrimiento rapido para personas y agentes | +| Contrato OpenAPI | `GET /openapi.json` | Esquemas, parametros, respuestas y errores | +| Playground | `GET /playground` | Pruebas manuales desde navegador | -### 1. `GET /health` +La API no requiere autenticacion actualmente. No debe asumirse que esta situacion es definitiva: la autenticacion y la proteccion de operaciones sensibles forman parte del pendiente 5. -Sirve para comprobar si el servicio esta levantado y si la conexion con `Qdrant` esta operativa. +## Elegir la operacion correcta -Ejemplo: +| Necesidad | Endpoint recomendado | +|---|---| +| Comprobar servicio y capacidades | `GET /health` | +| Ver scopes ingeridos | `GET /sources` | +| Recuperar contexto para otro agente | `POST /retrieve` | +| Obtener una respuesta final con citas | `POST /answer` | +| Comparar una respuesta sin RAG | `POST /answer/direct` | +| Conversar con bootstrap e historial | `POST /chat` | +| Ingerir una ruta accesible por el servidor | `POST /ingest` | +| Subir un archivo o carpeta ZIP | `POST /ingest/upload` | +| Eliminar una fuente antes de reingestar | `POST /cleanup` | +| Consultar o actualizar evaluaciones | `/logs/*` | -```bash -curl -sS "https://rag.por-correo.com/health" -``` +## Catalogo completo -Respuesta esperada aproximada: +La API queda formada por 16 operaciones tras incorporar el descubrimiento: + +| Metodo | Ruta | Descripcion | +|---|---|---| +| `GET` | `/help` | Catalogo resumido derivado de OpenAPI | +| `GET` | `/openapi.json` | Contrato OpenAPI 3.1.1 | +| `GET` | `/playground` | Interfaz web de prueba | +| `GET` | `/health` | Estado, proveedores, parsers y chunking | +| `GET` | `/sources` | Scopes disponibles en Qdrant | +| `GET` | `/models/answer` | Modelo por defecto y modelos seleccionables | +| `POST` | `/ingest` | Ingesta desde una ruta del servidor | +| `POST` | `/ingest/upload` | Upload `multipart/form-data` | +| `POST` | `/cleanup` | Borrado controlado por scope | +| `POST` | `/retrieve` | Recuperacion de contexto | +| `POST` | `/answer` | Respuesta apoyada en retrieval | +| `POST` | `/answer/direct` | Respuesta del modelo sin retrieval | +| `POST` | `/chat` | Conversacion con contexto opcional | +| `GET` | `/logs/recent` | Logs recientes de evaluacion | +| `POST` | `/logs/manual` | Registro manual de una evaluacion | +| `PATCH` | `/logs/{id}` | Seguimiento y resolucion de un log | + +## Contratos comunes + +### Scope + +Permite limitar una consulta o una operacion a fuentes concretas: ```json { - "ok": true, - "service": "rag", - "environment": "production", - "embeddings": { - "provider": "openrouter", - "model": "qwen/qwen3-embedding-8b" - }, - "answer": { - "provider": "openrouter", - "model": "openai/gpt-4.1-mini" - }, - "vectorStore": { - "ok": true, - "kind": "qdrant" - } + "sourceId": "customer-a:manual:v1", + "sourceRef": "customer-a-errors.md", + "tags": ["customer-a", "support"] } ``` ---- +Todos los campos son opcionales en consultas. Para `POST /cleanup` es obligatorio enviar al menos `sourceId` o `sourceRef`; las etiquetas solas no autorizan un borrado. -### 2. `POST /ingest` +### Modos e intents -Sirve para ingerir una fuente en el RAG. +| Campo | Valores | Default | +|---|---|---| +| `mode` en retrieval y answer | `documental`, `codigo`, `auto` | `auto` | +| `mode` en chat | `documental`, `codigo`, `auto` | `documental` | +| `intent` | `specific`, `bootstrap` | `specific` | +| `mode` en ingesta | `mechanical`, `interactive` | `mechanical` | -Puede usarse sobre: -- un archivo -- una carpeta +### Errores -Payload base: +Los errores JSON siguen esta forma: ```json { - "sourceType": "folder", - "sourceRef": "/ruta/a/la/carpeta", - "mode": "mechanical", - "tags": ["workspace", "global-docs"] + "ok": false, + "error": "Description of the error" } ``` -Campos: +Actualmente la mayoria de fallos de validacion o de proveedores se devuelven como `500`. `POST /cleanup` y `POST /ingest/upload` tambien pueden devolver `400`, y `PATCH /logs/{id}` devuelve `404` si no encuentra el log. Mejorar la validacion queda fuera de este bloque para no cambiar silenciosamente el comportamiento existente. -- `sourceId`: opcional, permite aislar la fuente en un scope propio -- `sourceType`: `file` o `folder` -- `sourceRef`: ruta de la fuente -- `readPath`: uso interno cuando se sube un archivo y el backend lo procesa desde una ruta temporal -- `mode`: `mechanical` o `interactive` -- `tags`: etiquetas opcionales para clasificar la fuente +## Recuperar contexto -Ejemplo documental: - -```bash -curl -sS -X POST "https://rag.por-correo.com/ingest" \ - -H "Content-Type: application/json" \ - -d '{ - "sourceType": "folder", - "sourceRef": "/home/pancho/Documentos/Empresa/Desarrollo/IA/docs", - "mode": "mechanical", - "tags": ["workspace", "global-docs"] - }' -``` - -Ejemplo codigo: - -```bash -curl -sS -X POST "https://rag.por-correo.com/ingest" \ - -H "Content-Type: application/json" \ - -d '{ - "sourceType": "folder", - "sourceRef": "/home/pancho/Documentos/Empresa/Desarrollo/IA/RAG/src", - "mode": "mechanical", - "tags": ["rag", "module-code"] - }' -``` - -Respuesta esperada aproximada: - -```json -{ - "accepted": true, - "filesDiscovered": 10, - "documentsProcessed": 10, - "chunksStored": 45, - "collectionName": "rag_chunks" -} -``` - -Ejemplo de aislamiento en un scope propio: - -```bash -curl -sS -X POST "https://rag.por-correo.com/ingest" \ - -H "Content-Type: application/json" \ - -d '{ - "sourceId": "src:default:manual:pdf-tecnico-cliente-a", - "sourceType": "file", - "sourceRef": "/ruta/a/documento.pdf", - "mode": "mechanical", - "tags": ["cliente-a", "pdf-tecnico"] - }' -``` - ---- - -### 3. `POST /retrieve` - -Sirve para recuperar contexto del RAG sin pedir una respuesta final al modelo. - -Es ideal para: -- agentes IA -- nodos tool de n8n -- flujos que quieran contexto rico y trazable - -Payload base: - -```json -{ - "mode": "documental", - "intent": "specific", - "model": "openai/gpt-4.1-mini", - "query": "que tenemos pendiente por hacer en este workspace", - "scope": { - "sourceRef": "/home/pancho/Documentos/Empresa/Desarrollo/IA/docs" - } -} -``` - -Campos: - -- `mode`: - - `documental` - - `codigo` - - `auto` -- `intent`: - - `specific` - - `bootstrap` -- `query`: consulta del usuario o del flujo -- `scope` opcional: - - `sourceId` - - `sourceRef` - - `tags` - -Nota util: -- si una ingesta se hizo con `sourceId` propio, despues puedes consultar solo ese material usando `scope.sourceId` - -#### Ejemplo `retrieve` documental +`POST /retrieve` es la operacion recomendada cuando otro agente o flujo generara la respuesta final. ```bash curl -sS -X POST "https://rag.por-correo.com/retrieve" \ @@ -191,329 +112,81 @@ curl -sS -X POST "https://rag.por-correo.com/retrieve" \ -d '{ "mode": "documental", "intent": "specific", - "query": "que tenemos pendiente por hacer en este workspace", + "query": "What does error FAT07 mean?", "scope": { - "sourceRef": "/home/pancho/Documentos/Empresa/Desarrollo/IA/docs" - } + "sourceId": "customer-a:manual:v1" + }, + "useModelInRetrieve": false }' ``` -#### Ejemplo `retrieve` bootstrap +Campos principales: -```bash -curl -sS -X POST "https://rag.por-correo.com/retrieve" \ - -H "Content-Type: application/json" \ - -d '{ - "mode": "documental", - "intent": "bootstrap", - "query": "dame un mapa inicial del workspace y sus lineas de trabajo principales", - "scope": { - "sourceRef": "/home/pancho/Documentos/Empresa/Desarrollo/IA/docs" - } - }' -``` +| Campo | Requerido | Funcion | +|---|---|---| +| `query` | Si | Consulta de recuperacion | +| `mode` | No | Tipo de conocimiento | +| `intent` | No | Consulta concreta o mapa bootstrap | +| `scope` | No | Limita las fuentes | +| `useModelInRetrieve` | No | Añade `modelSummary` generado por un modelo | +| `model` | No | Override usado solo para `modelSummary` | +| `bootstrapMeta` | No | Trazabilidad del bootstrap en logs | -#### Ejemplo `retrieve` codigo - -```bash -curl -sS -X POST "https://rag.por-correo.com/retrieve" \ - -H "Content-Type: application/json" \ - -d '{ - "mode": "codigo", - "intent": "specific", - "query": "como se construye source_id en el rag", - "scope": { - "sourceRef": "/home/pancho/Documentos/Empresa/Desarrollo/IA/RAG/src" - } - }' -``` - -Respuesta resumida esperada: +Respuesta resumida: ```json { "mode": "documental", "intent": "specific", - "summary": "...", - "topics": ["PENDIENTES_GENERALES.md"], + "summary": "Retrieved 5 relevant chunks.", + "topics": ["support-errors.md"], "criticalPoints": [], "items": [ { - "chunkId": "chk:...", - "documentId": "doc:...", - "sourceId": "src:...", - "title": "PENDIENTES_GENERALES.md", - "sectionTitle": "Resumen rapido", + "chunkId": "chk:example", + "documentId": "doc:example", + "sourceId": "customer-a:manual:v1", + "title": "support-errors.md", "content": "...", "score": 0.87 } ], - "followUpRefs": ["doc:..."], + "followUpRefs": ["doc:example"], "scope": { - "sourceRef": "/home/pancho/Documentos/Empresa/Desarrollo/IA/docs" + "sourceId": "customer-a:manual:v1" } } ``` ---- +## Obtener una respuesta -### 4. `POST /answer` - -Sirve para pedir al RAG una respuesta final apoyada en el contexto recuperado. - -Es util para: -- agentes conversacionales -- respuestas listas para usuario -- pruebas rapidas desde n8n o aplicaciones - -Tambien permite indicar el modelo de respuesta explicitamente mediante el campo opcional `model`. - -Payload base: +`POST /answer` ejecuta retrieval y genera una respuesta final con citas. ```json { "mode": "documental", "intent": "specific", - "model": "openai/gpt-4.1-mini", - "query": "que tenemos pendiente por hacer en este workspace", + "query": "Explain the documented resolution for FAT07", "scope": { - "sourceRef": "/home/pancho/Documentos/Empresa/Desarrollo/IA/docs" - } -} -``` - -#### Ejemplo `answer` documental - -```bash -curl -sS -X POST "https://rag.por-correo.com/answer" \ - -H "Content-Type: application/json" \ - -d '{ - "mode": "documental", - "intent": "specific", - "query": "que tenemos pendiente por hacer en este workspace", - "scope": { - "sourceRef": "/home/pancho/Documentos/Empresa/Desarrollo/IA/docs" - } - }' -``` - -#### Ejemplo `answer` codigo - -```bash -curl -sS -X POST "https://rag.por-correo.com/answer" \ - -H "Content-Type: application/json" \ - -d '{ - "mode": "codigo", - "intent": "specific", - "query": "como se construye source_id en el rag", - "scope": { - "sourceRef": "/home/pancho/Documentos/Empresa/Desarrollo/IA/RAG/src" - } - }' -``` - -Respuesta resumida esperada: - -```json -{ - "mode": "codigo", - "intent": "specific", - "model": "openai/gpt-4.1-mini", - "answer": "...", - "summary": "...", - "topics": ["ids.ts"], - "criticalPoints": [], - "citations": [ - { - "chunkId": "chk:...", - "documentId": "doc:...", - "title": "ids.ts", - "sectionTitle": "export function buildSourceId(...)", - "startLine": 20, - "endLine": 25 - } - ], - "scope": { - "sourceRef": "/home/pancho/Documentos/Empresa/Desarrollo/IA/RAG/src" - } -} -``` - -### 5. `POST /answer/direct` - -Sirve para pedir una respuesta directa al modelo sin usar contexto del RAG. - -Es util para comparar: -- respuesta sin RAG -- respuesta con RAG - -Payload base: - -```json -{ - "query": "que es este servicio", + "sourceId": "customer-a:manual:v1" + }, "model": "openai/gpt-4.1-mini" } ``` ---- +Tambien acepta `preloadedContext` y `bootstrapMeta`. La respuesta incluye `model`, `answer`, resumen del retrieval, temas, puntos criticos, citas y scope. -### 6. `POST /chat` - -Sirve para conversar con un modelo usando: - -- contexto bootstrap precargado -- historial reciente de mensajes -- y opcionalmente una consulta adicional al RAG durante la propia conversacion - -Payload base: +`POST /answer/direct` no consulta el vector store: ```json { - "message": "y ahora cuales son los siguientes pasos mas naturales?", - "history": [ - { - "role": "user", - "content": "dame un mapa inicial del workspace" - } - ], - "mode": "documental", + "query": "Explain what a RAG service is", "model": "openai/gpt-4.1-mini", - "preloadedContext": "", - "allowAdditionalRetrieve": true, - "scope": { - "sourceRef": "/home/pancho/Documentos/Empresa/Desarrollo/IA/docs" - } + "preloadedContext": "Optional context supplied by the caller" } ``` ---- - -### 7. `POST /ingest/upload` - -Sirve para subir un archivo directamente al backend desde una interfaz web o cliente compatible con `multipart/form-data`. - -Es util para: -- PDFs o documentos sueltos -- pruebas rapidas desde el playground -- crear un scope aislado para una carga concreta - -Campos esperados del formulario: - -- `file` -- `sourceId` opcional -- `mode` -- `tags` - -Si se usa `sourceId`, el archivo subido no se mezcla con otros scopes salvo que elijas reutilizar ese mismo identificador. - ---- - -### 8. `POST /cleanup` - -Sirve para eliminar vectores (chunks) ya ingeridos en el RAG segun un scope determinado, permitiendo limpiar contexto viejo antes de una reingesta. - -Payload base (requiere `sourceId` o `sourceRef` por seguridad): - -```json -{ - "scope": { - "sourceId": "src:default:folder:src_a9df8105" - } -} -``` - -Respuesta esperada: - -```json -{ - "ok": true, - "deleted": 124 -} -``` - ---- - -### 9. `GET /logs/recent` - -Devuelve los logs recientes de evaluacion guardados por el sistema. - -Sirve para revisar: -- consultas con contexto insuficiente -- respuestas problemáticas -- logs manuales marcados por el usuario - -Ejemplo: - -```bash -curl -sS "https://rag.por-correo.com/logs/recent" -``` - -Tambien admite actualizacion posterior del estado de revision mediante `PATCH /logs/:id`. - ---- - -### 10. `POST /logs/manual` - -Permite registrar manualmente una consulta o respuesta que quieras revisar despues. - -Es util cuando: -- la respuesta no te convence -- detectas una carencia del RAG -- quieres dejar una nota humana asociada a una consulta - -Payload base: - -```json -{ - "operation": "answer", - "reason": "manual_review_requested", - "query": "prueba de log manual", - "mode": "documental", - "intent": "specific", - "model": "openai/gpt-4.1-mini", - "note": "la respuesta parece demasiado generica", - "scope": { - "sourceRef": "/home/pancho/Documentos/Empresa/Desarrollo/IA/docs" - } -} -``` - ---- - -### 11. `PATCH /logs/:id` - -Permite actualizar el seguimiento de un log ya creado. - -Ejemplo: - -```bash -curl -sS -X PATCH "https://rag.por-correo.com/logs/" \ - -H "Content-Type: application/json" \ - -d '{ - "reviewStatus": "resolved", - "severity": "high", - "reviewedBy": "Paco POR-CORREO", - "resolutionNote": "Se ajusto el bootstrap para scopes aislados.", - "fixReference": "6557aea" - }' -``` - -Respuesta esperada resumida: - -```json -{ - "model": "openai/gpt-4.1-mini", - "answer": "...", - "usedBootstrapContext": true, - "usedAdditionalRetrieve": true, - "retrieved": { - "summary": "..." - } -} -``` - -Respuesta esperada: +Su respuesta real solo contiene: ```json { @@ -522,82 +195,165 @@ Respuesta esperada: } ``` ---- +## Chat con bootstrap -## Recomendacion practica para n8n - -Si quieres probarlo rapido desde `n8n`: - -1. Usa un nodo `HTTP Request` -2. Metodo `POST` -3. URL: - -```text -https://rag.por-correo.com/retrieve -``` - -o - -```text -https://rag.por-correo.com/answer -``` - -4. Body en JSON -5. Empieza usando `answer` si quieres una respuesta directa -6. Usa `retrieve` si quieres dar el contexto al agente y que el modelo final responda por su cuenta - -Payload recomendado para primera prueba en n8n: +`POST /chat` reutiliza contexto precargado, historial reciente y, opcionalmente, una nueva consulta al RAG: ```json { + "message": "What should I check next?", + "history": [ + { + "role": "user", + "content": "Give me an initial map of this project" + }, + { + "role": "assistant", + "content": "The project contains..." + } + ], "mode": "documental", - "intent": "specific", - "query": "que tenemos pendiente por hacer en este workspace", + "preloadedContext": "Previously loaded bootstrap", + "allowAdditionalRetrieve": true, "scope": { - "sourceRef": "/home/pancho/Documentos/Empresa/Desarrollo/IA/docs" + "sourceId": "project-docs" } } ``` ---- - -## Nota importante - -El `scope` es clave para no mezclar fuentes distintas. - -Ejemplos de `scope` utiles: - -- documentacion global del workspace: +Respuesta: ```json { - "sourceRef": "/home/pancho/Documentos/Empresa/Desarrollo/IA/docs" + "model": "openai/gpt-4.1-mini", + "answer": "...", + "usedBootstrapContext": true, + "usedAdditionalRetrieve": true, + "retrieved": { + "summary": "...", + "items": [] + } } ``` -- documentacion del modulo RAG: +## Ingesta + +### Ruta del servidor + +`POST /ingest` requiere `sourceType` y `sourceRef`: ```json { - "sourceRef": "/home/pancho/Documentos/Empresa/Desarrollo/IA/RAG/docs" + "sourceId": "project-docs", + "sourceType": "folder", + "sourceRef": "/data/project-docs", + "mode": "mechanical", + "tags": ["project", "documentation"] } ``` -- codigo del modulo RAG: +`readPath` es un campo interno para separar la ruta fisica de lectura de la referencia logica. Los consumidores normales no deben enviarlo. + +### Upload + +`POST /ingest/upload` usa `multipart/form-data`: + +| Campo | Requerido | Formato | +|---|---|---| +| `file` | Si | Archivo binario o ZIP | +| `sourceId` | No | Identificador logico | +| `mode` | No | `mechanical` o `interactive` | +| `tags` | No | Lista separada por comas | +| `isZipFolder` | No | Texto `true` o `false` | + +```bash +curl -sS -X POST "https://rag.por-correo.com/ingest/upload" \ + -F "file=@./support-errors.md" \ + -F "sourceId=customer-a:manual:v1" \ + -F "mode=mechanical" \ + -F "tags=customer-a,support" +``` + +Ambas variantes responden `202` con el origen aceptado, archivos descubiertos, documentos procesados, chunks almacenados y coleccion. El upload añade `uploadedResource`. + +## Limpieza controlada + +`POST /cleanup` elimina los chunks que coinciden con un scope: ```json { - "sourceRef": "/home/pancho/Documentos/Empresa/Desarrollo/IA/RAG/src" + "scope": { + "sourceId": "customer-a:manual:v1" + } } ``` ---- +Respuesta: -## Siguiente evolucion natural +```json +{ + "ok": true, + "deleted": 36 +} +``` -Mas adelante esta API podra exponerse tambien mediante un `MCP`, pero a dia de hoy ya puede consumirse directamente por HTTP desde: +Esta operacion es destructiva. Verifica primero el scope con `GET /sources`. -- modelos -- agentes -- flujos de `n8n` -- aplicaciones propias +## Evaluacion + +`GET /logs/recent?limit=20` devuelve los logs recientes. Si no se indica `limit`, usa 20. + +`POST /logs/manual` registra una observacion humana: + +```json +{ + "operation": "retrieve", + "reason": "manual_review_requested", + "query": "What does error FAT07 mean?", + "mode": "documental", + "intent": "specific", + "scope": { + "sourceId": "customer-a:manual:v1" + }, + "note": "The first result is too generic" +} +``` + +`PATCH /logs/{id}` actualiza su seguimiento: + +```json +{ + "reviewStatus": "resolved", + "severity": "high", + "reviewedBy": "maintainer", + "resolutionNote": "Retrieval ranking adjusted and validated.", + "fixReference": "commit-or-ticket-reference" +} +``` + +Valores admitidos: + +| Campo | Valores | +|---|---| +| `reviewStatus` | `pending`, `in_progress`, `resolved`, `ignored` | +| `severity` | `low`, `medium`, `high` | + +## Integracion recomendada con n8n + +Para una `HTTP Request Tool` conectada a un AI Agent: + +1. Usa `POST /retrieve`. +2. Fija `mode`, `intent` y `scope` en el nodo. +3. Permite que el agente complete solo `query`. +4. Mantiene `useModelInRetrieve=false` para evitar procesar la consulta con dos modelos. +5. Entrega `items[].content` y sus metadatos al agente final. + +Usa `POST /answer` solamente cuando el propio RAG deba generar la respuesta final. + +## Fuente de verdad y mantenimiento + +- `GET /openapi.json` es el contrato publico para integraciones y herramientas. +- `GET /help` es el indice rapido derivado del mismo contrato. +- Este documento explica flujos y decisiones de uso. +- `src/app.ts` contiene la implementacion efectiva. +- Cualquier cambio de endpoint debe actualizar OpenAPI y esta guia en el mismo bloque de trabajo. diff --git a/docs/BITACORA_DISENO_RAG.md b/docs/BITACORA_DISENO_RAG.md index 0f2835d..3499072 100644 --- a/docs/BITACORA_DISENO_RAG.md +++ b/docs/BITACORA_DISENO_RAG.md @@ -492,7 +492,9 @@ El soporte inicial se entiende para PDFs con texto extraible. PDFs escaneados o --- -## Pendientes inmediatos del diseño +## Decisiones de diseño que estaban pendientes + +**Estado actual:** Resueltas e implementadas. Esta lista se conserva como registro historico del camino de diseño, no como backlog vigente. 1. Definir como se construiran exactamente `source_id`, `document_id` y `chunk_id`. 2. Definir la estrategia concreta del chunking inicial. @@ -501,6 +503,8 @@ El soporte inicial se entiende para PDFs con texto extraible. PDFs escaneados o 5. Definir como se accedera a los servicios minimos del RAG desde la API. 6. Definir si la ingesta asistida por modelo estara presente desde la v1 o quedara para la siguiente fase. +Los pendientes actuales se mantienen en [`PENDIENTES_RAG.md`](./PENDIENTES_RAG.md). + --- ## Rol de esta bitacora dentro del modulo diff --git a/docs/HISTORIAL_SESIONES.md b/docs/HISTORIAL_SESIONES.md index c68d7bd..74d0eed 100644 --- a/docs/HISTORIAL_SESIONES.md +++ b/docs/HISTORIAL_SESIONES.md @@ -2,7 +2,7 @@ **Proyecto:** Workspace de tools IA para empresas **Modulo:** RAG -**Ultima actualizacion:** 2026-04-06 +**Ultima actualizacion:** 2026-09-08 **Ultima modificacion por:** Agente RAG 2 **Estado:** Activo @@ -64,3 +64,24 @@ Dar continuidad al RAG en `RAG/` a partir del estado actual documentado. - Soporte explicito para flujos de `bootstrap` y `precarga` dirigida antes de revisar codigo. - Ajuste del paquete portable del agente para usar placeholder `__IA_WORKSPACE_ROOT__` y poder reinstalarlo correctamente en otros equipos sin depender de rutas locales de este PC. - Creacion de `RAG/agente_gstreamer/` como carpeta autocontenida para llevar el agente a otro PC con configuracion, scripts e instrucciones en un solo paquete. + +--- + +### 2026-09-08 - Agente RAG 2 + +**Modelo:** gpt-5.6-sol +**Session ID OpenCode:** `ses_29bdbd003ffeLrLjUlFgnp08Y7` +**Directorio:** `/home/pancho/Documentos/Empresa/Desarrollo/IA/RAG` + +**Trabajo realizado:** +- Auditoria de las 14 operaciones HTTP existentes y de sus contratos reales. +- Implementacion de `GET /help` y `GET /openapi.json`, dejando 16 operaciones documentadas. +- Creacion de un contrato OpenAPI 3.1.1 con esquemas, parametros, respuestas, errores y ejemplos. +- Reescritura de `docs/API_RAG.md` como guia operativa enlazada al contrato OpenAPI. +- Correccion del estado obsoleto de playground, logs, cleanup, stack y documentos de diseño. +- Correccion de la metodologia: local se limita a compilacion y comprobaciones estaticas; los flujos HTTP se validan tras publicar en produccion. +- Validacion satisfactoria con `npm run check`, `npm run build`, comprobacion de referencias OpenAPI y contraste automatico entre rutas implementadas y documentadas. + +**Estado final:** +- Pendiente 1 implementado en local. +- Pendiente commit, push, despliegue y validacion en `https://rag.por-correo.com` antes de marcarlo completado. diff --git a/docs/LOGS_EVALUACION.md b/docs/LOGS_EVALUACION.md index 5aed4e3..e95dca7 100644 --- a/docs/LOGS_EVALUACION.md +++ b/docs/LOGS_EVALUACION.md @@ -2,9 +2,9 @@ **Proyecto:** Workspace de tools IA para empresas **Modulo:** RAG -**Ultima actualizacion:** 2026-04-06 -**Ultima modificacion por:** Agente tools IA para potenciar servicios empresariales -**Estado:** Implementado en codigo, pendiente de redeploy +**Ultima actualizacion:** 2026-09-08 +**Ultima modificacion por:** Agente RAG 2 +**Estado:** Implementado y operativo en produccion --- diff --git a/docs/METODOLOGIA_ITERACION_Y_REDEPLOY.md b/docs/METODOLOGIA_ITERACION_Y_REDEPLOY.md index cc27d46..82fcf35 100644 --- a/docs/METODOLOGIA_ITERACION_Y_REDEPLOY.md +++ b/docs/METODOLOGIA_ITERACION_Y_REDEPLOY.md @@ -2,8 +2,8 @@ **Proyecto:** Workspace de tools IA para empresas **Modulo:** RAG -**Ultima actualizacion:** 2026-04-06 -**Ultima modificacion por:** Agente tools IA para potenciar servicios empresariales +**Ultima actualizacion:** 2026-09-08 +**Ultima modificacion por:** Agente RAG 2 **Estado:** Activa --- @@ -13,7 +13,7 @@ Dejar explicado de forma explicita el flujo de trabajo que se esta siguiendo para: - implementar mejoras del RAG -- validarlas localmente +- comprobar localmente que compilan y mantienen consistencia estatica - subir solo los cambios adecuados al repo propio - avisar al usuario cuando ya solo queda hacer `Deploy` en EasyPanel @@ -26,7 +26,7 @@ La idea es que futuros agentes o nuevas sesiones no tengan que redescubrir este El flujo correcto que ya esta funcionando bien es este: 1. implementar cambios en local -2. validar localmente antes de tocar produccion +2. validar compilacion y consistencia estatica 3. documentar lo relevante 4. hacer commit y push solo de lo que debe versionarse 5. avisar al usuario de que ya puede hacer `Deploy` en EasyPanel @@ -51,9 +51,9 @@ Esto incluye: --- -### 2. Validacion local obligatoria +### 2. Comprobacion local obligatoria -Antes de pedir redeploy, se valida en local. +Antes de pedir redeploy, se comprueba en local que el proyecto compila y que los artefactos estaticos son coherentes. Minimo esperado: @@ -62,16 +62,9 @@ cd /home/pancho/Documentos/Empresa/Desarrollo/IA/RAG npm run build ``` -Y si aplica, pruebas HTTP locales contra: +Tambien se pueden ejecutar comprobaciones estaticas que no levanten el servicio, como validar la estructura de un contrato OpenAPI o contrastar rutas documentadas. -- `/health` -- `/ingest` -- `/retrieve` -- `/answer` -- `/chat` -- endpoints de logs - -No se debe pedir deploy al usuario sin haber comprobado antes que la mejora compila y que el flujo principal funciona en local. +No se levantan flujos ni servicios locales para validacion funcional. Las pruebas HTTP se realizan despues de publicar, contra el entorno real de produccion. --- @@ -130,7 +123,7 @@ Regla importante: Cuando los cambios ya estan: - implementados -- validados en local +- compilados y comprobados estaticamente - documentados - subidos al repo @@ -182,11 +175,13 @@ Probar segun aplique: Un agente no deberia decir "haz deploy" si no se cumplen estas condiciones: - compila en local -- el flujo nuevo ha sido probado localmente +- las comprobaciones estaticas aplicables son correctas - la documentacion minima esta actualizada - el commit y push ya estan hechos - no se han subido secretos por error +La validacion funcional del flujo se completa despues del deploy y exclusivamente en produccion. + --- ## Resultado esperado de esta metodologia diff --git a/docs/PENDIENTES_RAG.md b/docs/PENDIENTES_RAG.md new file mode 100644 index 0000000..ec150dd --- /dev/null +++ b/docs/PENDIENTES_RAG.md @@ -0,0 +1,92 @@ +# Pendientes priorizados del RAG + +**Ultima actualizacion:** 2026-09-08 +**Responsable de la priorizacion:** Usuario +**Estado:** Activo + +Este documento es la fuente canonica del orden de trabajo pendiente del modulo RAG. La numeracion ya refleja la prioridad final indicada por el usuario. + +## 1. Documentacion y descubrimiento de la API + +**Estado:** Implementado; pendiente de publicacion y validacion en produccion. + +- Implementar `/help` o una especificacion OpenAPI consultable. +- Actualizar documentos desfasados que todavia muestran como pendientes funciones ya desplegadas, como playground, logs y cleanup. +- Documentar los contratos, parametros, respuestas y errores reales de todos los endpoints. +- Mantener la documentacion alineada con produccion para que agentes e integradores no tengan que reconstruir el comportamiento desde el codigo. + +## 2. Ciclo de vida del conocimiento + +- Crear un catalogo de fuentes y versiones ingeridas. +- Saber que documento esta vigente, obsoleto, reemplazado o pendiente de reingesta. +- Evitar duplicados y permitir actualizaciones incrementales controladas. +- Registrar fecha de indexacion, modelo de embeddings, proveedor y version del contenido. +- Facilitar el reemplazo o rollback de una fuente sin depender de operaciones manuales dificiles de auditar. + +## 3. OCR integrado en la ingesta + +- Detectar PDFs con capturas, imagenes o una capa textual insuficiente. +- Ejecutar OCR automaticamente o bloquear la ingesta para revision cuando no pueda garantizarse la cobertura. +- Generar un documento intermedio auditable cuando sea necesario. +- Verificar el resultado antes de sustituir una fuente vigente. +- Evitar que se repita el problema detectado con el PDF de FacturaTech. + +## 4. Mejora del retrieval + +- Anadir busqueda hibrida semantica y textual para codigos exactos como `FAT07`, `504` o `SQLSTATE[23505]`. +- Mejorar el ranking usando coincidencias de codigo, regla, modulo y mensaje, no solo proximidad semantica. +- Crear un conjunto estable de consultas de evaluacion basado en casos reales. +- Medir precision, resultados irrelevantes y consultas ambiguas. +- Refinar retrieval y chunking con evidencia de uso, no mediante cambios generales sin medicion. + +## 5. Seguridad de la API + +- Anadir autenticacion para consumidores autorizados. +- Proteger especialmente uploads, cleanup y operaciones sobre logs. +- Aplicar rate limiting y limites de tamano o tipo de archivo. +- Revisar CORS, exposicion publica, registros sensibles y permisos por operacion. +- Sustituir la configuracion sin credenciales de n8n cuando exista el mecanismo de autenticacion definitivo. + +## 6. Pruebas automatizadas + +- Incorporar un framework y scripts de pruebas; actualmente el proyecto no tiene suite automatizada. +- Cubrir ingesta, parsing, chunking, scopes, cleanup y retrieval. +- Probar contratos y respuestas de error de la API. +- Anadir pruebas de regresion con consultas reales como las validadas para FacturaTech. +- Separar pruebas unitarias, de integracion y verificaciones opcionales contra servicios remotos. + +## 7. Operacion y mantenimiento + +- Resolver la colision de `sourceRef` al ingerir carpetas homonimas. +- Separar claramente configuracion versionable y secretos locales. +- Verificar y cerrar documentalmente la migracion del repositorio raiz de RAG y su despliegue en EasyPanel. +- Revisar copias temporales o backups asociados a la migracion antes de eliminarlos. +- Mantener un procedimiento fiable de despliegue, verificacion y rollback. + +## 8. Sistema de evaluacion + +- Revisar periodicamente los logs de evaluacion almacenados en Qdrant. +- Convertir incidencias reales en casos de prueba permanentes. +- Anadir metricas de recuperacion, calidad y contexto insuficiente. +- Mantener trazabilidad entre una incidencia, el cambio aplicado y la validacion posterior. +- Diferenciar claramente observacion manual, alerta automatica y regresion confirmada. + +## 9. Capa MCP + +- Exponer capacidades del RAG como tools MCP reutilizables. +- Definir inicialmente retrieval y consulta de fuentes; limitar operaciones destructivas. +- Mantener HTTP como API base y MCP como adaptador, evitando duplicar la logica del servicio. +- Definir autenticacion, contratos y permisos antes de exponer operaciones adicionales. + +## 10. Modelo de `answer` + +- Evaluar y sustituir `openai/gpt-4.1-mini` por el modelo definitivo de respuesta. +- Comparar calidad, coste, latencia y dependencia del proveedor. +- Mantener `answer` construido sobre `retrieve` y evitar un segundo camino de recuperacion. +- Considerar esta tarea despues de las anteriores; integraciones como WhatsApp ya pueden usar `/retrieve` y dejar la respuesta final a su propio agente. + +## Regla de mantenimiento + +- No cambiar este orden sin confirmacion del usuario. +- Al completar un punto, registrar evidencia y marcar su estado sin renumerar silenciosamente los demas. +- Las tareas detalladas pueden vivir en documentos independientes, pero este archivo conserva la prioridad global del modulo. diff --git a/docs/PLAYGROUND.md b/docs/PLAYGROUND.md index b9aaf9c..6cb5a47 100644 --- a/docs/PLAYGROUND.md +++ b/docs/PLAYGROUND.md @@ -2,9 +2,9 @@ **Proyecto:** Workspace de tools IA para empresas **Modulo:** RAG -**Ultima actualizacion:** 2026-04-06 -**Ultima modificacion por:** Agente tools IA para potenciar servicios empresariales -**Estado:** Implementado en codigo, pendiente de redeploy +**Ultima actualizacion:** 2026-09-08 +**Ultima modificacion por:** Agente RAG 2 +**Estado:** Implementado y operativo en produccion --- diff --git a/docs/SISTEMA_RAG_BASE.md b/docs/SISTEMA_RAG_BASE.md index 8824747..8df82b7 100644 --- a/docs/SISTEMA_RAG_BASE.md +++ b/docs/SISTEMA_RAG_BASE.md @@ -96,16 +96,16 @@ Estado confirmado: - dominio activo: `https://rag.por-correo.com` - conexion operativa con `Qdrant` - soporte funcional para `documental` y `codigo` -- endpoints operativos: `GET /health`, `POST /ingest`, `POST /retrieve`, `POST /answer` +- API HTTP operativa con ingesta, upload, cleanup, retrieval, answer, chat, catalogo de fuentes/modelos y logs de evaluacion +- playground web operativo en `/playground` +- contrato OpenAPI y ayuda de descubrimiento implementados, pendientes de despliegue Pruebas funcionales ya superadas: - consulta documental sobre pendientes del workspace - consulta conceptual sobre caracteristicas del RAG - consulta tecnica en modo codigo sobre la construccion de `source_id` -Pendientes de evolucion de esta base: -- sustituir el modelo actual de `answer` por una alternativa alineada con la decision de no depender de OpenAI en esa capa -- seguir refinando retrieval y answer segun resultados de uso real +Los pendientes vigentes, su prioridad y su estado se mantienen en [`PENDIENTES_RAG.md`](./PENDIENTES_RAG.md). --- diff --git a/docs/STACK_TECNICO_V1.md b/docs/STACK_TECNICO_V1.md index 9c4463c..5993abc 100644 --- a/docs/STACK_TECNICO_V1.md +++ b/docs/STACK_TECNICO_V1.md @@ -160,10 +160,24 @@ Extensiones de codigo ya soportadas: - encaja bien con una futura capa MCP Endpoints funcionales actuales: +- `GET /help` +- `GET /openapi.json` +- `GET /playground` - `GET /health` +- `GET /sources` +- `GET /models/answer` +- `GET /logs/recent` - `POST /ingest` +- `POST /ingest/upload` +- `POST /cleanup` - `POST /retrieve` - `POST /answer` +- `POST /answer/direct` +- `POST /chat` +- `POST /logs/manual` +- `PATCH /logs/:id` + +`/help` y `/openapi.json` estan implementados en codigo y quedaran disponibles en produccion tras el siguiente despliegue. --- @@ -216,10 +230,9 @@ La v1 se construira con una idea clara: --- -## Pendiente inmediato +## Pendientes vigentes -- Revisar y sustituir mas adelante el modelo actual de `answer` por una opcion alineada con la estrategia de no depender de OpenAI para esa capa. -- Documentar la metodologia reutilizable para desplegar futuros servicios correctamente en EasyPanel. +El orden de trabajo actual se mantiene exclusivamente en [`PENDIENTES_RAG.md`](./PENDIENTES_RAG.md) para evitar listas duplicadas y contradictorias. --- @@ -231,7 +244,7 @@ Estado confirmado: - servicio activo en `https://rag.por-correo.com` - `Qdrant` conectado correctamente por red interna - variables de entorno cargadas en produccion -- `health`, `retrieve` y `answer` probados con exito +- endpoints principales, playground, cleanup y logs de evaluacion probados con exito Observacion abierta: - aparece un warning de deprecacion relacionado con `punycode`, pero no esta bloqueando el funcionamiento del servicio diff --git a/docs/TASK_LIMPIEZA.md b/docs/TASK_LIMPIEZA.md index c3e4a18..7cb6e46 100644 --- a/docs/TASK_LIMPIEZA.md +++ b/docs/TASK_LIMPIEZA.md @@ -2,8 +2,8 @@ **Proyecto:** Workspace de tools IA para empresas **Modulo:** RAG -**Ultima actualizacion:** 2026-04-06 -**Estado:** En implementacion +**Ultima actualizacion:** 2026-09-08 +**Estado:** Completada y validada en produccion --- @@ -29,3 +29,10 @@ Permitir borrar contenido ya ingerido del RAG de forma controlada antes de reing - Borrar el codigo de `RAG/src` previamente ingerido. - Reingestar `RAG/src`. - Confirmar que se mantiene la integridad sin duplicar fragmentos. + +## Resultado + +- `POST /cleanup` esta implementado y exige `sourceId` o `sourceRef` para evitar borrados masivos accidentales. +- La pestaña `Limpieza` esta disponible en el playground. +- El flujo fue validado inicialmente limpiando y reingiriendo `RAG/src`. +- Posteriormente se volvio a validar en produccion al sustituir una ingesta incompleta de FacturaTech por 36 chunks revisados. diff --git a/src/api/openapi.ts b/src/api/openapi.ts new file mode 100644 index 0000000..272b9d8 --- /dev/null +++ b/src/api/openapi.ts @@ -0,0 +1,663 @@ +const ref = (name: string) => ({ $ref: `#/components/schemas/${name}` }); + +const jsonContent = (schema: Record, example?: unknown) => ({ + "application/json": { + schema, + ...(example === undefined ? {} : { example }) + } +}); + +const jsonResponse = (description: string, schema: Record, example?: unknown) => ({ + description, + content: jsonContent(schema, example) +}); + +const serverError = jsonResponse("Internal or upstream service error.", ref("Error")); + +export const openApiDocument = { + openapi: "3.1.1", + info: { + title: "RAG Service API", + version: "0.1.0", + description: "HTTP API for ingesting, retrieving, answering from, and evaluating scoped RAG knowledge." + }, + jsonSchemaDialect: "https://json-schema.org/draft/2020-12/schema", + servers: [ + { url: "https://rag.por-correo.com", description: "Production" }, + { url: "http://localhost:3000", description: "Local development" } + ], + tags: [ + { name: "Discovery", description: "API contract and browser playground." }, + { name: "Status", description: "Service capabilities and available resources." }, + { name: "Ingestion", description: "Knowledge ingestion and cleanup." }, + { name: "Retrieval", description: "Context retrieval and model-backed answers." }, + { name: "Evaluation", description: "Evaluation log capture and review." } + ], + paths: { + "/help": { + get: { + tags: ["Discovery"], + summary: "Discover the API", + description: "Returns a concise endpoint catalog generated from this OpenAPI document.", + responses: { + "200": jsonResponse("API discovery catalog.", ref("HelpResponse")) + } + } + }, + "/openapi.json": { + get: { + tags: ["Discovery"], + summary: "Get the OpenAPI contract", + responses: { + "200": jsonResponse("OpenAPI 3.1.1 document.", { type: "object" }) + } + } + }, + "/playground": { + get: { + tags: ["Discovery"], + summary: "Open the browser playground", + responses: { + "200": { + description: "Playground HTML page.", + content: { "text/html": { schema: { type: "string" } } } + } + } + } + }, + "/health": { + get: { + tags: ["Status"], + summary: "Check service health and capabilities", + responses: { + "200": jsonResponse("Current service status.", ref("HealthResponse")) + } + } + }, + "/sources": { + get: { + tags: ["Status"], + summary: "List available knowledge scopes", + responses: { + "200": jsonResponse("Scopes currently stored in the vector database.", { + type: "array", + items: ref("AvailableScope") + }), + "500": serverError + } + } + }, + "/models/answer": { + get: { + tags: ["Status"], + summary: "List answer models", + description: "Returns the configured default and up to 80 selectable answer models.", + responses: { + "200": jsonResponse("Available answer models.", ref("AnswerModelsResponse")), + "500": serverError + } + } + }, + "/ingest": { + post: { + tags: ["Ingestion"], + summary: "Ingest a server-accessible file or folder", + requestBody: { + required: true, + content: jsonContent(ref("IngestRequest"), { + sourceType: "folder", + sourceRef: "/data/project-docs", + mode: "mechanical", + tags: ["project-docs"] + }) + }, + responses: { + "202": jsonResponse("Ingestion completed and accepted.", ref("IngestResponse")), + "500": serverError + } + } + }, + "/ingest/upload": { + post: { + tags: ["Ingestion"], + summary: "Upload and ingest a file or ZIP folder", + requestBody: { + required: true, + content: { + "multipart/form-data": { + schema: ref("UploadIngestRequest") + } + } + }, + responses: { + "202": jsonResponse("Upload ingested successfully.", ref("UploadIngestResponse")), + "400": jsonResponse("The file field is missing.", ref("Error"), { ok: false, error: "Missing file upload" }), + "500": serverError + } + } + }, + "/cleanup": { + post: { + tags: ["Ingestion"], + summary: "Delete chunks matching a source scope", + description: "Requires sourceId or sourceRef to prevent accidental mass deletion. Tags alone are not accepted.", + requestBody: { + required: true, + content: jsonContent(ref("CleanupRequest"), { + scope: { sourceId: "customer-a:manual:v1" } + }) + }, + responses: { + "200": jsonResponse("Matching chunks deleted.", ref("CleanupResponse")), + "400": jsonResponse("Scope is missing or unsafe for cleanup.", ref("Error")), + "500": serverError + } + } + }, + "/retrieve": { + post: { + tags: ["Retrieval"], + summary: "Retrieve scoped context", + description: "Returns retrieved chunks. Set useModelInRetrieve to true to also generate modelSummary.", + requestBody: { + required: true, + content: jsonContent(ref("RetrieveRequest"), { + mode: "documental", + intent: "specific", + query: "How is the service deployed?", + scope: { sourceId: "project-docs" }, + useModelInRetrieve: false + }) + }, + responses: { + "200": jsonResponse("Retrieved context.", ref("RetrieveResponse")), + "500": serverError + } + } + }, + "/answer": { + post: { + tags: ["Retrieval"], + summary: "Answer using retrieved RAG context", + requestBody: { + required: true, + content: jsonContent(ref("AnswerRequest")) + }, + responses: { + "200": jsonResponse("Model answer with retrieval citations.", ref("AnswerResponse")), + "500": serverError + } + } + }, + "/answer/direct": { + post: { + tags: ["Retrieval"], + summary: "Answer without retrieving RAG context", + description: "Primarily used to compare direct model output with RAG-backed output.", + requestBody: { + required: true, + content: jsonContent(ref("DirectAnswerRequest")) + }, + responses: { + "200": jsonResponse("Direct model answer.", ref("DirectAnswerResponse")), + "500": serverError + } + } + }, + "/chat": { + post: { + tags: ["Retrieval"], + summary: "Chat with optional bootstrap and additional retrieval", + requestBody: { + required: true, + content: jsonContent(ref("ChatRequest")) + }, + responses: { + "200": jsonResponse("Chat response and retrieval usage metadata.", ref("ChatResponse")), + "500": serverError + } + } + }, + "/logs/recent": { + get: { + tags: ["Evaluation"], + summary: "List recent evaluation logs", + parameters: [{ + name: "limit", + in: "query", + required: false, + description: "Maximum number of logs. Defaults to 20.", + schema: { type: "integer", default: 20 } + }], + responses: { + "200": jsonResponse("Recent evaluation logs.", { type: "array", items: ref("EvaluationLog") }), + "500": serverError + } + } + }, + "/logs/manual": { + post: { + tags: ["Evaluation"], + summary: "Create a manual evaluation log", + requestBody: { + required: true, + content: jsonContent(ref("ManualLogRequest")) + }, + responses: { + "201": jsonResponse("Evaluation log created.", ref("EvaluationLog")), + "500": serverError + } + } + }, + "/logs/{id}": { + patch: { + tags: ["Evaluation"], + summary: "Update evaluation log review status", + parameters: [{ + name: "id", + in: "path", + required: true, + schema: { type: "string" } + }], + requestBody: { + required: true, + content: jsonContent(ref("UpdateLogRequest")) + }, + responses: { + "200": jsonResponse("Updated evaluation log.", ref("EvaluationLog")), + "404": jsonResponse("Evaluation log not found.", ref("Error"), { ok: false, error: "Log not found" }), + "500": serverError + } + } + } + }, + components: { + schemas: { + Error: { + type: "object", + required: ["ok", "error"], + properties: { + ok: { type: "boolean", const: false }, + error: { type: "string" } + } + }, + Scope: { + type: "object", + properties: { + sourceId: { type: "string" }, + sourceRef: { type: "string" }, + tags: { type: "array", items: { type: "string" } } + } + }, + AvailableScope: { + type: "object", + required: ["sourceId", "sourceRef", "chunkModes", "tags"], + properties: { + sourceId: { type: "string" }, + sourceRef: { type: "string" }, + chunkModes: { type: "array", items: { $ref: "#/components/schemas/ChunkMode" } }, + tags: { type: "array", items: { type: "string" } } + } + }, + ChunkMode: { type: "string", enum: ["documental", "codigo", "auto"] }, + RetrieveIntent: { type: "string", enum: ["bootstrap", "specific"] }, + IngestRequest: { + type: "object", + required: ["sourceType", "sourceRef"], + properties: { + sourceId: { type: "string" }, + sourceType: { type: "string", enum: ["file", "folder"] }, + sourceRef: { type: "string" }, + readPath: { type: "string", description: "Internal filesystem path used when the logical sourceRef differs from the read location." }, + mode: { type: "string", enum: ["mechanical", "interactive"], default: "mechanical" }, + tags: { type: "array", items: { type: "string" } } + } + }, + IngestResponse: { + type: "object", + required: ["accepted", "source", "filesDiscovered", "documentsProcessed", "chunksStored", "collectionName"], + properties: { + accepted: { type: "boolean", const: true }, + source: ref("IngestRequest"), + filesDiscovered: { type: "integer" }, + documentsProcessed: { type: "integer" }, + chunksStored: { type: "integer" }, + collectionName: { type: "string" } + } + }, + UploadIngestRequest: { + type: "object", + required: ["file"], + properties: { + file: { type: "string", format: "binary" }, + sourceId: { type: "string" }, + mode: { type: "string", enum: ["mechanical", "interactive"], default: "mechanical" }, + tags: { type: "string", description: "Comma-separated tags." }, + isZipFolder: { type: "string", enum: ["true", "false"], default: "false" } + } + }, + UploadIngestResponse: { + allOf: [ + ref("IngestResponse"), + { + type: "object", + required: ["uploadedResource"], + properties: { uploadedResource: { type: "string" } } + } + ] + }, + CleanupRequest: { + type: "object", + required: ["scope"], + properties: { scope: ref("Scope") } + }, + CleanupResponse: { + type: "object", + required: ["ok", "deleted"], + properties: { + ok: { type: "boolean", const: true }, + deleted: { type: "integer" } + } + }, + BootstrapMeta: { + type: "object", + properties: { + query: { type: "string" }, + mode: ref("ChunkMode"), + scope: ref("Scope"), + model: { type: "string" }, + usedModelSummary: { type: "boolean" } + } + }, + RetrieveRequest: { + type: "object", + required: ["query"], + properties: { + mode: { ...ref("ChunkMode"), default: "auto" }, + intent: { ...ref("RetrieveIntent"), default: "specific" }, + query: { type: "string" }, + scope: ref("Scope"), + model: { type: "string", description: "Model override used only when useModelInRetrieve is true." }, + useModelInRetrieve: { type: "boolean", default: false }, + bootstrapMeta: ref("BootstrapMeta") + } + }, + RetrievedItem: { + type: "object", + required: ["chunkId", "documentId", "sourceId", "title", "content", "score"], + properties: { + chunkId: { type: "string" }, + documentId: { type: "string" }, + sourceId: { type: "string" }, + title: { type: "string" }, + sectionTitle: { type: "string" }, + content: { type: "string" }, + score: { type: "number" }, + chunkMode: ref("ChunkMode"), + startLine: { type: "integer" }, + endLine: { type: "integer" } + } + }, + RetrieveResponse: { + type: "object", + required: ["mode", "intent", "summary", "topics", "criticalPoints", "items", "followUpRefs"], + properties: { + mode: ref("ChunkMode"), + intent: ref("RetrieveIntent"), + model: { type: "string" }, + summary: { type: "string" }, + modelSummary: { type: "string" }, + topics: { type: "array", items: { type: "string" } }, + criticalPoints: { type: "array", items: { type: "string" } }, + items: { type: "array", items: ref("RetrievedItem") }, + followUpRefs: { type: "array", items: { type: "string" } }, + scope: ref("Scope") + } + }, + AnswerRequest: { + type: "object", + required: ["query"], + properties: { + mode: { ...ref("ChunkMode"), default: "auto" }, + intent: { ...ref("RetrieveIntent"), default: "specific" }, + query: { type: "string" }, + scope: ref("Scope"), + model: { type: "string" }, + preloadedContext: { type: "string" }, + bootstrapMeta: ref("BootstrapMeta") + } + }, + Citation: { + type: "object", + required: ["chunkId", "documentId", "title"], + properties: { + chunkId: { type: "string" }, + documentId: { type: "string" }, + title: { type: "string" }, + sectionTitle: { type: "string" }, + startLine: { type: "integer" }, + endLine: { type: "integer" } + } + }, + AnswerResponse: { + type: "object", + required: ["mode", "intent", "model", "answer", "summary", "topics", "criticalPoints", "citations"], + properties: { + mode: ref("ChunkMode"), + intent: ref("RetrieveIntent"), + model: { type: "string" }, + answer: { type: "string" }, + summary: { type: "string" }, + topics: { type: "array", items: { type: "string" } }, + criticalPoints: { type: "array", items: { type: "string" } }, + citations: { type: "array", items: ref("Citation") }, + scope: ref("Scope") + } + }, + DirectAnswerRequest: { + type: "object", + required: ["query"], + properties: { + query: { type: "string" }, + model: { type: "string" }, + preloadedContext: { type: "string" } + } + }, + DirectAnswerResponse: { + type: "object", + required: ["model", "answer"], + properties: { + model: { type: "string" }, + answer: { type: "string" } + } + }, + ChatMessage: { + type: "object", + required: ["role", "content"], + properties: { + role: { type: "string", enum: ["user", "assistant"] }, + content: { type: "string" } + } + }, + ChatRequest: { + type: "object", + required: ["message"], + properties: { + message: { type: "string" }, + history: { type: "array", items: ref("ChatMessage") }, + mode: { ...ref("ChunkMode"), default: "documental" }, + model: { type: "string" }, + preloadedContext: { type: "string" }, + allowAdditionalRetrieve: { type: "boolean", default: false }, + scope: ref("Scope"), + bootstrapMeta: ref("BootstrapMeta") + } + }, + ChatResponse: { + type: "object", + required: ["model", "answer", "usedBootstrapContext", "usedAdditionalRetrieve"], + properties: { + model: { type: "string" }, + answer: { type: "string" }, + usedBootstrapContext: { type: "boolean" }, + usedAdditionalRetrieve: { type: "boolean" }, + retrieved: ref("RetrieveResponse") + } + }, + EvaluationLog: { + type: "object", + required: ["id", "trigger", "operation", "reason", "query", "createdAt", "retrievedItemsCount", "chunkIds", "documentIds", "reviewStatus", "severity"], + properties: { + id: { type: "string" }, + trigger: { type: "string", enum: ["automatic", "manual"] }, + operation: { type: "string", enum: ["retrieve", "answer", "chat"] }, + reason: { type: "string" }, + query: { type: "string" }, + mode: ref("ChunkMode"), + intent: ref("RetrieveIntent"), + scope: ref("Scope"), + model: { type: "string" }, + note: { type: "string" }, + createdAt: { type: "string", format: "date-time" }, + usedBootstrapContext: { type: "boolean" }, + bootstrapQuery: { type: "string" }, + bootstrapMode: ref("ChunkMode"), + bootstrapScope: ref("Scope"), + bootstrapModel: { type: "string" }, + bootstrapUsedModelSummary: { type: "boolean" }, + usedAdditionalRetrieve: { type: "boolean" }, + responseSummary: { type: "string" }, + retrievedItemsCount: { type: "integer" }, + chunkIds: { type: "array", items: { type: "string" } }, + documentIds: { type: "array", items: { type: "string" } }, + reviewStatus: { type: "string", enum: ["pending", "in_progress", "resolved", "ignored"] }, + severity: { type: "string", enum: ["low", "medium", "high"] }, + reviewedAt: { type: "string", format: "date-time" }, + reviewedBy: { type: "string" }, + resolutionNote: { type: "string" }, + fixReference: { type: "string" }, + supersedesLogId: { type: "string" } + } + }, + ManualLogRequest: { + type: "object", + required: ["query"], + properties: { + operation: { type: "string", enum: ["retrieve", "answer", "chat"], default: "answer" }, + reason: { type: "string", default: "manual_review_requested" }, + query: { type: "string" }, + mode: ref("ChunkMode"), + intent: ref("RetrieveIntent"), + scope: ref("Scope"), + model: { type: "string" }, + note: { type: "string" }, + usedBootstrapContext: { type: "boolean" }, + bootstrapMeta: ref("BootstrapMeta"), + usedAdditionalRetrieve: { type: "boolean" }, + responseSummary: { type: "string" }, + retrievedItems: { type: "array", items: ref("RetrievedItem") } + } + }, + UpdateLogRequest: { + type: "object", + properties: { + reviewStatus: { type: "string", enum: ["pending", "in_progress", "resolved", "ignored"] }, + severity: { type: "string", enum: ["low", "medium", "high"] }, + reviewedBy: { type: "string" }, + resolutionNote: { type: "string" }, + fixReference: { type: "string" }, + supersedesLogId: { type: "string" } + } + }, + HealthResponse: { + type: "object", + required: ["ok", "service", "environment", "embeddings", "answer", "vectorStore", "parsers", "chunking"], + properties: { + ok: { type: "boolean", const: true }, + service: { type: "string", const: "rag" }, + environment: { type: "string" }, + embeddings: ref("ProviderModel"), + answer: ref("ProviderModel"), + vectorStore: { type: "object", additionalProperties: true }, + parsers: { type: "array", items: { type: "string" } }, + chunking: { type: "object", additionalProperties: true } + } + }, + ProviderModel: { + type: "object", + required: ["provider", "model"], + properties: { + provider: { type: "string" }, + model: { type: "string" } + } + }, + AnswerModelsResponse: { + type: "object", + required: ["defaultModel", "models"], + properties: { + defaultModel: { type: "string" }, + models: { type: "array", items: { type: "string" }, maxItems: 80 } + } + }, + HelpEndpoint: { + type: "object", + required: ["method", "path", "summary", "tags"], + properties: { + method: { type: "string" }, + path: { type: "string" }, + summary: { type: "string" }, + tags: { type: "array", items: { type: "string" } } + } + }, + HelpResponse: { + type: "object", + required: ["service", "version", "description", "documentation", "authentication", "endpoints"], + properties: { + service: { type: "string" }, + version: { type: "string" }, + description: { type: "string" }, + documentation: { + type: "object", + properties: { + openapi: { type: "string" }, + playground: { type: "string" } + } + }, + authentication: { type: "string" }, + endpoints: { type: "array", items: ref("HelpEndpoint") } + } + } + } + } +} as const; + +type OpenApiOperation = { + summary?: string; + tags?: readonly string[]; +}; + +export function buildApiHelp() { + const endpoints = Object.entries(openApiDocument.paths).flatMap(([routePath, pathItem]) => + Object.entries(pathItem).map(([method, operation]) => { + const details = operation as OpenApiOperation; + return { + method: method.toUpperCase(), + path: routePath, + summary: details.summary ?? "", + tags: details.tags ?? [] + }; + }) + ); + + return { + service: openApiDocument.info.title, + version: openApiDocument.info.version, + description: openApiDocument.info.description, + documentation: { + openapi: "/openapi.json", + playground: "/playground" + }, + authentication: "None. The current API is publicly accessible; authentication is planned separately.", + endpoints + }; +} diff --git a/src/app.ts b/src/app.ts index 1372d86..52dfd10 100644 --- a/src/app.ts +++ b/src/app.ts @@ -6,6 +6,7 @@ import os from "node:os"; import path from "node:path"; import { fileURLToPath } from "node:url"; import AdmZip from "adm-zip"; +import { buildApiHelp, openApiDocument } from "./api/openapi.js"; import { env } from "./config/env.js"; import { AnswerService } from "./modules/answer/service.js"; import { IngestService } from "./modules/ingest/service.js"; @@ -61,6 +62,14 @@ export function createApp() { app.use(express.json({ limit: "5mb" })); app.use(express.static(publicDir)); + app.get("/help", (_req, res) => { + res.json(buildApiHelp()); + }); + + app.get("/openapi.json", (_req, res) => { + res.json(openApiDocument); + }); + app.get("/playground", (_req, res) => { res.sendFile(path.join(publicDir, "playground", "index.html")); });