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 ./ COPY package.json package-lock.json tsconfig.json ./
RUN npm ci RUN npm ci
COPY src ./src COPY src ./src
COPY public ./public
RUN npm run build RUN npm run build
FROM node:22-bookworm-slim AS runtime FROM node:22-bookworm-slim AS runtime
@ -11,5 +12,6 @@ ENV NODE_ENV=production
COPY package.json package-lock.json ./ COPY package.json package-lock.json ./
RUN npm ci --omit=dev RUN npm ci --omit=dev
COPY --from=build /app/dist ./dist COPY --from=build /app/dist ./dist
COPY public ./public
EXPOSE 3000 EXPOSE 3000
CMD ["node", "dist/server.js"] 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 **Modulo:** RAG
**Ultima actualizacion:** 2026-04-05 **Ultima actualizacion:** 2026-04-05
**Ultima modificacion por:** Agente tools IA para potenciar servicios empresariales **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 - construir la imagen Docker sin errores de red
- montarla en EasyPanel siguiendo este patron - 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 **Proyecto:** Workspace de tools IA para empresas
**Modulo:** RAG **Modulo:** RAG
**Ultima actualizacion:** 2026-04-02 **Ultima actualizacion:** 2026-04-05
**Ultima modificacion por:** Agente tools IA para potenciar servicios empresariales **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 ## Alcance de este documento
Este documento define el que y el para que del sistema RAG base. 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 **Proyecto:** Workspace de tools IA para empresas
**Modulo:** RAG **Modulo:** RAG
**Ultima actualizacion:** 2026-04-02 **Ultima actualizacion:** 2026-04-05
**Ultima modificacion por:** Agente tools IA para potenciar servicios empresariales **Ultima modificacion por:** Agente tools IA para potenciar servicios empresariales
**Estado:** Acordado para v1 **Estado:** Acordado para v1
@ -218,4 +218,20 @@ La v1 se construira con una idea clara:
## Pendiente inmediato ## 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 express from "express";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { env } from "./config/env.js"; import { env } from "./config/env.js";
import { AnswerService } from "./modules/answer/service.js"; import { AnswerService } from "./modules/answer/service.js";
import { IngestService } from "./modules/ingest/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"; import type { ChunkMode, RetrieveIntent, RetrieveScope } from "./shared/types/rag.js";
export function createApp() { export function createApp() {
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const publicDir = path.resolve(__dirname, "../public");
const app = express(); const app = express();
const embeddingProvider = new OpenRouterEmbeddingProvider(); const embeddingProvider = new OpenRouterEmbeddingProvider();
const vectorStore = new QdrantVectorStoreClient(); const vectorStore = new QdrantVectorStoreClient();
@ -18,6 +23,11 @@ export function createApp() {
const answerService = new AnswerService(retrieveService); const answerService = new AnswerService(retrieveService);
app.use(express.json({ limit: "5mb" })); 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) => { app.get("/health", async (_req, res) => {
const vectorHealth = await vectorStore.healthcheck(); 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; return app;
} }

View file

@ -1,6 +1,6 @@
import OpenAI from "openai"; import OpenAI from "openai";
import { env } from "../../config/env.js"; 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"; import { RetrieveService } from "../retrieve/service.js";
function buildPrompt(query: string, summary: string, items: RetrievedItem[]): string { function buildPrompt(query: string, summary: string, items: RetrievedItem[]): string {
@ -75,4 +75,30 @@ export class AnswerService {
scope: retrieved.scope 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; 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. - 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`. - 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. - 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/`. - 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. - 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. - 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. - 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 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. - 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` ### `sesion_actual_opencode.md`
**Ubicacion:** `docs/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 ## Estadistica global
**Total de documentos indexados:** 15 **Total de documentos indexados:** 17

View file

@ -1,7 +1,7 @@
# Pendientes Generales # Pendientes Generales
**Proyecto:** Workspace de tools IA para empresas **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 **Ultima modificacion por:** Agente tools IA para potenciar servicios empresariales
**Estado:** Activo **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. - Elegir estrategia inicial de almacenamiento y vectorizacion.
- Definir interfaz de integracion con servicios externos. - Definir interfaz de integracion con servicios externos.
- Identificar un primer caso real de uso para validacion. - 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 ## Proximos pasos sugeridos
1. Definir el alcance minimo viable del sistema RAG base. 1. Sustituir el modelo actual de `answer` por una alternativa alineada con la estrategia del proyecto.
2. Diseñar la estructura inicial MCP del workspace. 2. Documentar la metodologia correcta para desplegar futuros servicios en EasyPanel.
3. Documentar el punto de integracion entre Retell y servicios externos como n8n. 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 | | Linea | Estado |
|-------|--------| |-------|--------|
| Sistema basico RAG reutilizable | Pendiente | | Sistema basico RAG reutilizable | En marcha |
| Estructura MCP para integracion de tools | Pendiente | | Estructura MCP para integracion de tools | Pendiente |
| Retell conectado con herramientas externas | Pendiente | | Retell conectado con herramientas externas | Pendiente |
| Posible potenciacion del RAG con Obsidian | Pendiente | | Posible potenciacion del RAG con Obsidian | Pendiente |