Add RAG playground and update v1 docs
This commit is contained in:
parent
2eac6f457d
commit
c641286372
15 changed files with 1038 additions and 13 deletions
|
|
@ -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
392
RAG/docs/API_RAG.md
Normal 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
|
||||
|
|
@ -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
83
RAG/docs/PLAYGROUND.md
Normal 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
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
129
RAG/public/playground/app.js
Normal file
129
RAG/public/playground/app.js
Normal 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";
|
||||
});
|
||||
114
RAG/public/playground/index.html
Normal file
114
RAG/public/playground/index.html
Normal 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>
|
||||
142
RAG/public/playground/styles.css
Normal file
142
RAG/public/playground/styles.css
Normal 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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."
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -76,3 +76,8 @@ export interface AnswerResponse {
|
|||
}>;
|
||||
scope?: RetrieveScope;
|
||||
}
|
||||
|
||||
export interface DirectAnswerResponse {
|
||||
model: string;
|
||||
answer: string;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 |
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue