Add RAG playground and update v1 docs

This commit is contained in:
Paco POR-CORREO 2026-04-06 12:35:02 +02:00
parent 2eac6f457d
commit c641286372
15 changed files with 1038 additions and 13 deletions

View file

@ -3,6 +3,7 @@ WORKDIR /app
COPY package.json package-lock.json tsconfig.json ./
RUN npm ci
COPY src ./src
COPY public ./public
RUN npm run build
FROM node:22-bookworm-slim AS runtime
@ -11,5 +12,6 @@ ENV NODE_ENV=production
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY --from=build /app/dist ./dist
COPY public ./public
EXPOSE 3000
CMD ["node", "dist/server.js"]

392
RAG/docs/API_RAG.md Normal file
View file

@ -0,0 +1,392 @@
# API del RAG
**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
---
## Proposito
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`:
```text
https://rag.por-correo.com
```
---
## Endpoints disponibles
### 1. `GET /health`
Sirve para comprobar si el servicio esta levantado y si la conexion con `Qdrant` esta operativa.
Ejemplo:
```bash
curl -sS "https://rag.por-correo.com/health"
```
Respuesta esperada aproximada:
```json
{
"ok": true,
"service": "rag",
"environment": "production",
"embeddings": {
"provider": "openrouter",
"model": "qwen/qwen3-embedding-8b"
},
"vectorStore": {
"ok": true,
"kind": "qdrant"
}
}
```
---
### 2. `POST /ingest`
Sirve para ingerir una fuente en el RAG.
Puede usarse sobre:
- un archivo
- una carpeta
Payload base:
```json
{
"sourceType": "folder",
"sourceRef": "/ruta/a/la/carpeta",
"mode": "mechanical",
"tags": ["workspace", "global-docs"]
}
```
Campos:
- `sourceType`: `file` o `folder`
- `sourceRef`: ruta de la fuente
- `mode`: `mechanical` o `interactive`
- `tags`: etiquetas opcionales para clasificar la fuente
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"
}
```
---
### 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",
"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`
#### Ejemplo `retrieve` documental
```bash
curl -sS -X POST "https://rag.por-correo.com/retrieve" \
-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 `retrieve` bootstrap
```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"
}
}'
```
#### 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:
```json
{
"mode": "documental",
"intent": "specific",
"summary": "...",
"topics": ["PENDIENTES_GENERALES.md"],
"criticalPoints": [],
"items": [
{
"chunkId": "chk:...",
"documentId": "doc:...",
"sourceId": "src:...",
"title": "PENDIENTES_GENERALES.md",
"sectionTitle": "Resumen rapido",
"content": "...",
"score": 0.87
}
],
"followUpRefs": ["doc:..."],
"scope": {
"sourceRef": "/home/pancho/Documentos/Empresa/Desarrollo/IA/docs"
}
}
```
---
### 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
Payload base:
```json
{
"mode": "documental",
"intent": "specific",
"query": "que tenemos pendiente por hacer en este workspace",
"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",
"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"
}
}
```
---
## 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:
```json
{
"mode": "documental",
"intent": "specific",
"query": "que tenemos pendiente por hacer en este workspace",
"scope": {
"sourceRef": "/home/pancho/Documentos/Empresa/Desarrollo/IA/docs"
}
}
```
---
## Nota importante
El `scope` es clave para no mezclar fuentes distintas.
Ejemplos de `scope` utiles:
- documentacion global del workspace:
```json
{
"sourceRef": "/home/pancho/Documentos/Empresa/Desarrollo/IA/docs"
}
```
- documentacion del modulo RAG:
```json
{
"sourceRef": "/home/pancho/Documentos/Empresa/Desarrollo/IA/RAG/docs"
}
```
- codigo del modulo RAG:
```json
{
"sourceRef": "/home/pancho/Documentos/Empresa/Desarrollo/IA/RAG/src"
}
```
---
## Siguiente evolucion natural
Mas adelante esta API podra exponerse tambien mediante un `MCP`, pero a dia de hoy ya puede consumirse directamente por HTTP desde:
- modelos
- agentes
- flujos de `n8n`
- aplicaciones propias

View file

@ -4,7 +4,7 @@
**Modulo:** RAG
**Ultima actualizacion:** 2026-04-05
**Ultima modificacion por:** Agente tools IA para potenciar servicios empresariales
**Estado:** Base operativa definida
**Estado:** V1 desplegada en VPS2
---
@ -259,3 +259,24 @@ El siguiente salto natural es solo operativo:
- construir la imagen Docker sin errores de red
- montarla en EasyPanel siguiendo este patron
---
## Estado real alcanzado
El despliegue ya se ha completado en `VPS2`.
Estado confirmado:
- dominio activo: `https://rag.por-correo.com`
- servicio escuchando correctamente en produccion
- `Qdrant` operativo por red interna
- pruebas superadas de `health`, `retrieve` y `answer`
Consulta documental ya validada:
- `que tenemos pendiente por hacer en este workspace`
Consulta de codigo ya validada:
- `como se construye source_id en el rag`
Pendiente de mejora ya detectado:
- sustituir mas adelante el modelo actual de `answer` por una opcion alineada con la estrategia tecnica del proyecto

83
RAG/docs/PLAYGROUND.md Normal file
View file

@ -0,0 +1,83 @@
# Playground del RAG
**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:** Implementado en codigo, pendiente de redeploy
---
## Tecnologia elegida
Se ha elegido una interfaz web estatica simple, servida por el propio backend `Express` del RAG.
### Por que esta opcion
- evita crear un segundo servicio independiente solo para pruebas
- no añade otro framework de frontend ni otro pipeline de build innecesario
- permite iterar rapido sobre el RAG real usando su propia API
- es suficiente para una herramienta interna de evaluacion y ajuste
---
## Ubicacion dentro del modulo
El playground queda dentro de:
```text
RAG/public/playground/
```
Archivos creados:
- `RAG/public/playground/index.html`
- `RAG/public/playground/app.js`
- `RAG/public/playground/styles.css`
El backend lo sirve desde:
```text
/playground
```
---
## Que permite probar
1. `health`
2. `ingest`
3. `retrieve`
4. `answer`
5. `answer` sin RAG para comparar impacto del contexto
Tambien permite:
- cambiar `mode`
- cambiar `intent`
- ajustar `scope`
- usar presets para docs, docs del modulo y codigo del RAG
---
## Idea de uso
Este playground no sustituye a clientes finales ni al futuro MCP.
Su papel es:
- probar rapido el comportamiento del RAG
- comparar respuesta con y sin RAG
- validar cambios en ingesta y retrieval
- detectar donde el sistema necesita ajustes
---
## Relacion con MCP
En esta primera fase el playground usa la API HTTP del propio servicio.
Mas adelante se podra:
- mantener como herramienta interna de evaluacion
- o ampliarlo para probar tambien la capa MCP cuando exista

View file

@ -2,9 +2,9 @@
**Proyecto:** Workspace de tools IA para empresas
**Modulo:** RAG
**Ultima actualizacion:** 2026-04-02
**Ultima actualizacion:** 2026-04-05
**Ultima modificacion por:** Agente tools IA para potenciar servicios empresariales
**Estado:** En definicion
**Estado:** V1 operativa desplegada
---
@ -87,6 +87,28 @@ Si el sistema esta bien planteado, deberiamos poder usarlo como una capa de cont
---
## Estado actual de la v1
La v1 ya ha alcanzado un estado operativo real.
Estado confirmado:
- desplegada en `VPS2`
- 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`
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
---
## Alcance de este documento
Este documento define el que y el para que del sistema RAG base.

View file

@ -2,7 +2,7 @@
**Proyecto:** Workspace de tools IA para empresas
**Modulo:** RAG
**Ultima actualizacion:** 2026-04-02
**Ultima actualizacion:** 2026-04-05
**Ultima modificacion por:** Agente tools IA para potenciar servicios empresariales
**Estado:** Acordado para v1
@ -218,4 +218,20 @@ La v1 se construira con una idea clara:
## Pendiente inmediato
Generar la imagen Docker final del servicio y dejarlo listo para desplegar en EasyPanel.
- 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.
---
## Estado actual de despliegue
La v1 ya esta desplegada y funcionando en `VPS2` mediante EasyPanel.
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
Observacion abierta:
- aparece un warning de deprecacion relacionado con `punycode`, pero no esta bloqueando el funcionamiento del servicio

View file

@ -0,0 +1,129 @@
const healthButton = document.getElementById("healthButton");
const ingestButton = document.getElementById("ingestButton");
const queryButton = document.getElementById("queryButton");
const presetDocs = document.getElementById("presetDocs");
const presetRagDocs = document.getElementById("presetRagDocs");
const presetCode = document.getElementById("presetCode");
const healthResult = document.getElementById("healthResult");
const mainResult = document.getElementById("mainResult");
const compareResult = document.getElementById("compareResult");
const queryInput = document.getElementById("queryInput");
const queryMode = document.getElementById("queryMode");
const queryIntent = document.getElementById("queryIntent");
const queryOperation = document.getElementById("queryOperation");
const scopeSourceRef = document.getElementById("scopeSourceRef");
const scopeTags = document.getElementById("scopeTags");
const compareWithoutRag = document.getElementById("compareWithoutRag");
const ingestSourceType = document.getElementById("ingestSourceType");
const ingestSourceRef = document.getElementById("ingestSourceRef");
const ingestMode = document.getElementById("ingestMode");
const ingestTags = document.getElementById("ingestTags");
function format(value) {
return JSON.stringify(value, null, 2);
}
function splitTags(value) {
return value.split(",").map((entry) => entry.trim()).filter(Boolean);
}
async function request(url, payload, method = "POST") {
const response = await fetch(url, {
method,
headers: {
"Content-Type": "application/json"
},
body: payload ? JSON.stringify(payload) : undefined
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.error || `HTTP ${response.status}`);
}
return data;
}
healthButton.addEventListener("click", async () => {
healthResult.textContent = "Comprobando...";
try {
const response = await fetch("/health");
const data = await response.json();
healthResult.textContent = format(data);
} catch (error) {
healthResult.textContent = String(error);
}
});
ingestButton.addEventListener("click", async () => {
mainResult.textContent = "Ejecutando ingesta...";
try {
const data = await request("/ingest", {
sourceType: ingestSourceType.value,
sourceRef: ingestSourceRef.value,
mode: ingestMode.value,
tags: splitTags(ingestTags.value)
});
mainResult.textContent = format(data);
} catch (error) {
mainResult.textContent = String(error);
}
});
queryButton.addEventListener("click", async () => {
mainResult.textContent = "Consultando...";
compareResult.textContent = compareWithoutRag.checked ? "Comparando..." : "Desactivada.";
const payload = {
mode: queryMode.value,
intent: queryIntent.value,
query: queryInput.value,
scope: {
sourceRef: scopeSourceRef.value,
tags: splitTags(scopeTags.value)
}
};
try {
const endpoint = queryOperation.value === "answer" ? "/answer" : "/retrieve";
const data = await request(endpoint, payload);
mainResult.textContent = format(data);
if (compareWithoutRag.checked && queryOperation.value === "answer") {
const comparison = await request("/answer/direct", {
query: queryInput.value
});
compareResult.textContent = format(comparison);
}
} catch (error) {
mainResult.textContent = String(error);
compareResult.textContent = compareWithoutRag.checked ? String(error) : "Desactivada.";
}
});
presetDocs.addEventListener("click", () => {
queryMode.value = "documental";
queryIntent.value = "specific";
queryOperation.value = "answer";
queryInput.value = "que tenemos pendiente por hacer en este workspace";
scopeSourceRef.value = "/home/pancho/Documentos/Empresa/Desarrollo/IA/docs";
});
presetRagDocs.addEventListener("click", () => {
queryMode.value = "documental";
queryIntent.value = "bootstrap";
queryOperation.value = "retrieve";
queryInput.value = "dame un mapa inicial del modulo RAG, su arquitectura, decisiones, estado actual y documentos clave";
scopeSourceRef.value = "/home/pancho/Documentos/Empresa/Desarrollo/IA/RAG/docs";
});
presetCode.addEventListener("click", () => {
queryMode.value = "codigo";
queryIntent.value = "specific";
queryOperation.value = "answer";
queryInput.value = "como se construye source_id en el rag";
scopeSourceRef.value = "/home/pancho/Documentos/Empresa/Desarrollo/IA/RAG/src";
});

View file

@ -0,0 +1,114 @@
<!doctype html>
<html lang="es">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>RAG Playground</title>
<link rel="stylesheet" href="/playground/styles.css" />
</head>
<body>
<main class="layout">
<header class="hero">
<div>
<p class="eyebrow">RAG Playground</p>
<h1>Pruebas rapidas del RAG</h1>
<p class="lead">Interfaz interna para probar ingesta, retrieve, answer y comparacion sin RAG desde el mismo servicio.</p>
</div>
<button id="healthButton" class="secondary">Comprobar health</button>
</header>
<section class="grid">
<article class="panel">
<h2>Ingesta</h2>
<label>Tipo de fuente
<select id="ingestSourceType">
<option value="folder">folder</option>
<option value="file">file</option>
</select>
</label>
<label>Ruta de la fuente
<input id="ingestSourceRef" value="/home/pancho/Documentos/Empresa/Desarrollo/IA/docs" />
</label>
<label>Modo
<select id="ingestMode">
<option value="mechanical">mechanical</option>
<option value="interactive">interactive</option>
</select>
</label>
<label>Tags (coma separada)
<input id="ingestTags" value="workspace,global-docs" />
</label>
<button id="ingestButton">Lanzar ingesta</button>
</article>
<article class="panel panel-wide">
<h2>Consulta</h2>
<label>Pregunta
<textarea id="queryInput" rows="4">que tenemos pendiente por hacer en este workspace</textarea>
</label>
<div class="row">
<label>Modo
<select id="queryMode">
<option value="documental">documental</option>
<option value="codigo">codigo</option>
<option value="auto">auto</option>
</select>
</label>
<label>Intent
<select id="queryIntent">
<option value="specific">specific</option>
<option value="bootstrap">bootstrap</option>
</select>
</label>
<label>Operacion
<select id="queryOperation">
<option value="retrieve">retrieve</option>
<option value="answer">answer</option>
</select>
</label>
</div>
<div class="row">
<label>Scope por sourceRef
<input id="scopeSourceRef" value="/home/pancho/Documentos/Empresa/Desarrollo/IA/docs" />
</label>
<label>Tags (coma separada)
<input id="scopeTags" value="" />
</label>
</div>
<label class="checkbox">
<input type="checkbox" id="compareWithoutRag" />
Comparar tambien con respuesta sin RAG
</label>
<div class="actions">
<button id="queryButton">Ejecutar consulta</button>
<button id="presetDocs" class="secondary">Preset docs</button>
<button id="presetRagDocs" class="secondary">Preset RAG docs</button>
<button id="presetCode" class="secondary">Preset codigo</button>
</div>
</article>
</section>
<section class="grid results-grid">
<article class="panel">
<h2>Resultado principal</h2>
<pre id="mainResult">Sin ejecutar aun.</pre>
</article>
<article class="panel">
<h2>Comparacion sin RAG</h2>
<pre id="compareResult">Desactivada.</pre>
</article>
</section>
<section class="panel">
<h2>Estado / health</h2>
<pre id="healthResult">Sin comprobar.</pre>
</section>
</main>
<script src="/playground/app.js" type="module"></script>
</body>
</html>

View file

@ -0,0 +1,142 @@
:root {
color-scheme: dark;
--bg: #0d1321;
--panel: #151d31;
--panel-2: #1c2742;
--text: #e8ecf8;
--muted: #9aa6c8;
--accent: #72e0a8;
--accent-2: #7ca8ff;
--border: #2e3a5f;
}
* { box-sizing: border-box; }
body {
margin: 0;
font-family: Inter, ui-sans-serif, system-ui, sans-serif;
background: linear-gradient(180deg, #0b1020 0%, #12192b 100%);
color: var(--text);
}
.layout {
max-width: 1280px;
margin: 0 auto;
padding: 32px;
}
.hero, .panel {
background: rgba(21, 29, 49, 0.92);
border: 1px solid var(--border);
border-radius: 20px;
box-shadow: 0 18px 50px rgba(0,0,0,0.2);
}
.hero {
padding: 24px 28px;
display: flex;
justify-content: space-between;
gap: 20px;
align-items: flex-start;
margin-bottom: 24px;
}
.eyebrow {
margin: 0 0 8px;
color: var(--accent);
font-size: 12px;
text-transform: uppercase;
letter-spacing: 0.14em;
}
h1, h2 { margin: 0 0 12px; }
.lead { margin: 0; color: var(--muted); max-width: 720px; }
.grid {
display: grid;
gap: 24px;
grid-template-columns: 1fr 2fr;
margin-bottom: 24px;
}
.results-grid { grid-template-columns: 1fr 1fr; }
.panel-wide { min-width: 0; }
.panel {
padding: 24px;
}
label {
display: block;
margin-bottom: 14px;
color: var(--muted);
font-size: 14px;
}
input, textarea, select, button {
width: 100%;
border-radius: 12px;
border: 1px solid var(--border);
background: var(--panel-2);
color: var(--text);
padding: 12px 14px;
font: inherit;
}
textarea { resize: vertical; }
button {
width: auto;
cursor: pointer;
background: linear-gradient(135deg, var(--accent) 0%, #4fceff 100%);
color: #0b1020;
font-weight: 700;
border: none;
}
button.secondary {
background: transparent;
color: var(--text);
border: 1px solid var(--border);
}
.row, .actions {
display: flex;
gap: 12px;
flex-wrap: wrap;
}
.row > label {
flex: 1 1 220px;
}
.checkbox {
display: flex;
align-items: center;
gap: 10px;
}
.checkbox input {
width: auto;
}
pre {
background: #0b1020;
border: 1px solid var(--border);
border-radius: 16px;
padding: 16px;
overflow: auto;
min-height: 180px;
white-space: pre-wrap;
word-break: break-word;
}
@media (max-width: 980px) {
.grid, .results-grid {
grid-template-columns: 1fr;
}
.hero {
flex-direction: column;
}
}

View file

@ -1,4 +1,6 @@
import express from "express";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { env } from "./config/env.js";
import { AnswerService } from "./modules/answer/service.js";
import { IngestService } from "./modules/ingest/service.js";
@ -10,6 +12,9 @@ import { QdrantVectorStoreClient } from "./modules/vectorstore/client.js";
import type { ChunkMode, RetrieveIntent, RetrieveScope } from "./shared/types/rag.js";
export function createApp() {
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const publicDir = path.resolve(__dirname, "../public");
const app = express();
const embeddingProvider = new OpenRouterEmbeddingProvider();
const vectorStore = new QdrantVectorStoreClient();
@ -18,6 +23,11 @@ export function createApp() {
const answerService = new AnswerService(retrieveService);
app.use(express.json({ limit: "5mb" }));
app.use(express.static(publicDir));
app.get("/playground", (_req, res) => {
res.sendFile(path.join(publicDir, "playground", "index.html"));
});
app.get("/health", async (_req, res) => {
const vectorHealth = await vectorStore.healthcheck();
@ -82,5 +92,15 @@ export function createApp() {
}
});
app.post("/answer/direct", async (req, res) => {
try {
const query = String(req.body.query ?? "");
const result = await answerService.answerWithoutRag(query);
res.json(result);
} catch (error) {
res.status(500).json({ ok: false, error: error instanceof Error ? error.message : "Unknown direct answer error" });
}
});
return app;
}

View file

@ -1,6 +1,6 @@
import OpenAI from "openai";
import { env } from "../../config/env.js";
import type { AnswerResponse, ChunkMode, RetrieveIntent, RetrieveScope, RetrievedItem } from "../../shared/types/rag.js";
import type { AnswerResponse, ChunkMode, DirectAnswerResponse, RetrieveIntent, RetrieveScope, RetrievedItem } from "../../shared/types/rag.js";
import { RetrieveService } from "../retrieve/service.js";
function buildPrompt(query: string, summary: string, items: RetrievedItem[]): string {
@ -75,4 +75,30 @@ export class AnswerService {
scope: retrieved.scope
};
}
async answerWithoutRag(query: string): Promise<DirectAnswerResponse> {
if (!env.answerApiKey) {
throw new Error("Missing ANSWER_API_KEY for answer provider");
}
const completion = await this.client.chat.completions.create({
model: env.answerModel,
temperature: 0.2,
messages: [
{
role: "system",
content: "Responde con claridad y brevedad a la pregunta del usuario sin contexto adicional externo."
},
{
role: "user",
content: query
}
]
});
return {
model: env.answerModel,
answer: completion.choices[0]?.message?.content?.trim() || "No se pudo generar respuesta."
};
}
}

View file

@ -76,3 +76,8 @@ export interface AnswerResponse {
}>;
scope?: RetrieveScope;
}
export interface DirectAnswerResponse {
model: string;
answer: string;
}

View file

@ -56,6 +56,12 @@ Este archivo registra agentes y sesiones de trabajo de este workspace.
- Contraste del patron manual de `webfetch` con la documentacion oficial de EasyPanel, dejando definido que `RAG` deberia montarse como `App Service` con fuente Git o imagen publicada para quedar bien integrado.
- Verificacion de que Forgejo ya esta operativo en `git.por-correo.com` y que el acceso Git por SSH en puerto `2222` responde correctamente, dejando preparado el camino de despliegue integrado para `RAG`.
- Creacion del repo remoto `paco/rag-service` en Forgejo y subida correcta del estado actual del workspace mediante Git SSH.
- Despliegue correcto de `RAG` en EasyPanel como `App Service` usando Forgejo y `Dockerfile`, con dominio activo `https://rag.por-correo.com`.
- Pruebas satisfactorias en produccion de `health`, `retrieve` y `answer`, tanto en modo documental como en modo codigo.
- Deteccion y anotacion como pendiente del cambio futuro del modelo actual de `answer`.
- Creacion de `RAG/docs/API_RAG.md` como referencia operativa de la API para conectar rapidamente el servicio desde n8n, agentes y otras aplicaciones.
- Implementacion de un playground web interno servido por el propio backend del RAG para probar `health`, `ingest`, `retrieve`, `answer` y comparacion sin RAG.
- Creacion de `RAG/docs/PLAYGROUND.md` para documentar la tecnologia elegida, su ubicacion y su papel dentro del modulo.
- Reorganizacion de RAG como modulo raiz independiente con documentacion propia en `RAG/docs/`.
- Ajuste del indice documental global para reflejar la separacion entre documentacion global y documentacion por tool.
- Creacion de `docs/TASK.md` para descomponer lineas de trabajo amplias en puntos de analisis y acuerdos.
@ -71,6 +77,7 @@ Este archivo registra agentes y sesiones de trabajo de este workspace.
- Existe ya una primera implementacion funcional compilable del servicio RAG, pendiente de probar con claves reales y Qdrant activo.
- El servicio ya ha sido probado con embeddings reales y vector store remoto operativo.
- El servicio soporta ya modo documental y modo codigo con pruebas reales sobre documentacion y codigo del propio modulo.
- La v1 del RAG ya esta desplegada y operativa en VPS2 con dominio publico funcional.
---

View file

@ -272,6 +272,40 @@ Recoger dudas concretas sobre como se desplego `webfetch` en `VPS2` para reutili
---
### `RAG/docs/API_RAG.md`
**Ubicacion:** `RAG/docs/API_RAG.md`
**Proposito:**
Documentar de forma practica la API del servicio RAG, con endpoints, payloads y ejemplos listos para conectar desde n8n, agentes o aplicaciones.
**Cuando leerlo:**
- al integrar el RAG desde n8n o cualquier otro cliente HTTP
- al necesitar ejemplos listos de `health`, `ingest`, `retrieve` y `answer`
**Cuando actualizarlo:**
- cuando cambie algun endpoint o payload del servicio
- cuando se añadan nuevos modos o patrones de integracion
---
### `RAG/docs/PLAYGROUND.md`
**Ubicacion:** `RAG/docs/PLAYGROUND.md`
**Proposito:**
Documentar la tecnologia, ubicacion y utilidad del playground interno del RAG para pruebas y evaluacion.
**Cuando leerlo:**
- al querer probar el RAG con interfaz web interna
- al revisar por que se eligio esta forma de playground y no otra
**Cuando actualizarlo:**
- cuando cambie la interfaz de prueba
- cuando el playground se amplie o se conecte tambien por MCP
---
### `sesion_actual_opencode.md`
**Ubicacion:** `docs/sesion_actual_opencode.md`
@ -298,4 +332,4 @@ Instruccion universal para detectar la sesion activa de OpenCode del workspace a
## Estadistica global
**Total de documentos indexados:** 15
**Total de documentos indexados:** 17

View file

@ -1,7 +1,7 @@
# Pendientes Generales
**Proyecto:** Workspace de tools IA para empresas
**Ultima actualizacion:** 2026-04-02
**Ultima actualizacion:** 2026-04-05
**Ultima modificacion por:** Agente tools IA para potenciar servicios empresariales
**Estado:** Activo
@ -45,8 +45,20 @@ Diseñar una base RAG simple, modular y facil de conectar a otros servicios ya e
- Elegir estrategia inicial de almacenamiento y vectorizacion.
- Definir interfaz de integracion con servicios externos.
- Identificar un primer caso real de uso para validacion.
- Revisar y sustituir mas adelante el modelo actual de `answer` por una opcion alineada con la decision de no depender de OpenAI para esa capa.
**Estado:** Pendiente
**Estado:** En marcha
**Estado actual resumido:**
- La v1 del servicio RAG ya esta desplegada y operativa en `VPS2`.
- Dominio activo: `https://rag.por-correo.com`
- Modos ya funcionales: `documental` y `codigo`
- Endpoints operativos: `health`, `ingest`, `retrieve`, `answer`
**Pendientes inmediatos reales:**
- Sustituir el modelo actual de `answer` por una opcion no-OpenAI.
- Crear una metodologia reutilizable de despliegue correcto en EasyPanel para futuros servicios.
- Valorar refinados adicionales en retrieval y answer segun uso real.
---
@ -123,9 +135,9 @@ Evaluar si Obsidian puede aportar valor como capa de organizacion, fuente de con
## Proximos pasos sugeridos
1. Definir el alcance minimo viable del sistema RAG base.
2. Diseñar la estructura inicial MCP del workspace.
3. Documentar el punto de integracion entre Retell y servicios externos como n8n.
1. Sustituir el modelo actual de `answer` por una alternativa alineada con la estrategia del proyecto.
2. Documentar la metodologia correcta para desplegar futuros servicios en EasyPanel.
3. Diseñar la estructura inicial MCP del workspace.
---
@ -133,7 +145,7 @@ Evaluar si Obsidian puede aportar valor como capa de organizacion, fuente de con
| Linea | Estado |
|-------|--------|
| Sistema basico RAG reutilizable | Pendiente |
| Sistema basico RAG reutilizable | En marcha |
| Estructura MCP para integracion de tools | Pendiente |
| Retell conectado con herramientas externas | Pendiente |
| Posible potenciacion del RAG con Obsidian | Pendiente |