feat(api): add OpenAPI discovery

This commit is contained in:
Paco POR-CORREO 2026-09-08 11:57:36 +02:00
parent d63a096bdc
commit f1cd87c7a0
12 changed files with 1074 additions and 514 deletions

View file

@ -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": "<resumen del bootstrap>",
"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/<id>" \
-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.

View file

@ -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

View file

@ -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.

View file

@ -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
---

View file

@ -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

92
docs/PENDIENTES_RAG.md Normal file
View file

@ -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.

View file

@ -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
---

View file

@ -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).
---

View file

@ -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

View file

@ -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.

663
src/api/openapi.ts Normal file
View file

@ -0,0 +1,663 @@
const ref = (name: string) => ({ $ref: `#/components/schemas/${name}` });
const jsonContent = (schema: Record<string, unknown>, example?: unknown) => ({
"application/json": {
schema,
...(example === undefined ? {} : { example })
}
});
const jsonResponse = (description: string, schema: Record<string, unknown>, 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
};
}

View file

@ -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"));
});