diff --git a/RAG/Dockerfile b/RAG/Dockerfile index d20a6b5..df49f83 100644 --- a/RAG/Dockerfile +++ b/RAG/Dockerfile @@ -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"] diff --git a/RAG/docs/API_RAG.md b/RAG/docs/API_RAG.md new file mode 100644 index 0000000..bc52c10 --- /dev/null +++ b/RAG/docs/API_RAG.md @@ -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 diff --git a/RAG/docs/DESPLIEGUE_EASYPANEL.md b/RAG/docs/DESPLIEGUE_EASYPANEL.md index 30c1025..5b43782 100644 --- a/RAG/docs/DESPLIEGUE_EASYPANEL.md +++ b/RAG/docs/DESPLIEGUE_EASYPANEL.md @@ -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 diff --git a/RAG/docs/PLAYGROUND.md b/RAG/docs/PLAYGROUND.md new file mode 100644 index 0000000..a050807 --- /dev/null +++ b/RAG/docs/PLAYGROUND.md @@ -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 diff --git a/RAG/docs/SISTEMA_RAG_BASE.md b/RAG/docs/SISTEMA_RAG_BASE.md index 2f1b35d..8824747 100644 --- a/RAG/docs/SISTEMA_RAG_BASE.md +++ b/RAG/docs/SISTEMA_RAG_BASE.md @@ -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. diff --git a/RAG/docs/STACK_TECNICO_V1.md b/RAG/docs/STACK_TECNICO_V1.md index 263a54f..9c4463c 100644 --- a/RAG/docs/STACK_TECNICO_V1.md +++ b/RAG/docs/STACK_TECNICO_V1.md @@ -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 diff --git a/RAG/public/playground/app.js b/RAG/public/playground/app.js new file mode 100644 index 0000000..f15694b --- /dev/null +++ b/RAG/public/playground/app.js @@ -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"; +}); diff --git a/RAG/public/playground/index.html b/RAG/public/playground/index.html new file mode 100644 index 0000000..79f9985 --- /dev/null +++ b/RAG/public/playground/index.html @@ -0,0 +1,114 @@ + + + + + + RAG Playground + + + +
+
+
+

RAG Playground

+

Pruebas rapidas del RAG

+

Interfaz interna para probar ingesta, retrieve, answer y comparacion sin RAG desde el mismo servicio.

+
+ +
+ +
+
+

Ingesta

+ + + + + +
+ +
+

Consulta

+ + +
+ + + +
+ +
+ + +
+ + + +
+ + + + +
+
+
+ +
+
+

Resultado principal

+
Sin ejecutar aun.
+
+
+

Comparacion sin RAG

+
Desactivada.
+
+
+ +
+

Estado / health

+
Sin comprobar.
+
+
+ + + + diff --git a/RAG/public/playground/styles.css b/RAG/public/playground/styles.css new file mode 100644 index 0000000..9e4048d --- /dev/null +++ b/RAG/public/playground/styles.css @@ -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; + } +} diff --git a/RAG/src/app.ts b/RAG/src/app.ts index ec54bc6..e5695e4 100644 --- a/RAG/src/app.ts +++ b/RAG/src/app.ts @@ -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; } diff --git a/RAG/src/modules/answer/service.ts b/RAG/src/modules/answer/service.ts index 1f05901..be661e4 100644 --- a/RAG/src/modules/answer/service.ts +++ b/RAG/src/modules/answer/service.ts @@ -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 { + 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." + }; + } } diff --git a/RAG/src/shared/types/rag.ts b/RAG/src/shared/types/rag.ts index 3fd7b42..8b4e376 100644 --- a/RAG/src/shared/types/rag.ts +++ b/RAG/src/shared/types/rag.ts @@ -76,3 +76,8 @@ export interface AnswerResponse { }>; scope?: RetrieveScope; } + +export interface DirectAnswerResponse { + model: string; + answer: string; +} diff --git a/docs/HISTORIAL_SESIONES.md b/docs/HISTORIAL_SESIONES.md index 4d645eb..965ccc7 100644 --- a/docs/HISTORIAL_SESIONES.md +++ b/docs/HISTORIAL_SESIONES.md @@ -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. --- diff --git a/docs/INDICE_DOCUMENTACION.md b/docs/INDICE_DOCUMENTACION.md index db9c724..80df2c2 100644 --- a/docs/INDICE_DOCUMENTACION.md +++ b/docs/INDICE_DOCUMENTACION.md @@ -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 diff --git a/docs/PENDIENTES_GENERALES.md b/docs/PENDIENTES_GENERALES.md index d71a9e5..3e1ee6c 100644 --- a/docs/PENDIENTES_GENERALES.md +++ b/docs/PENDIENTES_GENERALES.md @@ -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 |