Add functional RAG service with code mode
This commit is contained in:
commit
7600f36f48
32 changed files with 5695 additions and 0 deletions
7
RAG/.dockerignore
Normal file
7
RAG/.dockerignore
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
npm-debug.log*
|
||||||
|
tests
|
||||||
|
docs
|
||||||
13
RAG/.env.example
Normal file
13
RAG/.env.example
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
PORT=3000
|
||||||
|
NODE_ENV=development
|
||||||
|
QDRANT_URL=http://localhost:6333
|
||||||
|
QDRANT_API_KEY=
|
||||||
|
QDRANT_COLLECTION=rag_chunks
|
||||||
|
EMBEDDING_PROVIDER=openrouter
|
||||||
|
EMBEDDING_MODEL=qwen/qwen3-embedding-8b
|
||||||
|
EMBEDDING_BASE_URL=https://openrouter.ai/api/v1
|
||||||
|
EMBEDDING_API_KEY=
|
||||||
|
ANSWER_PROVIDER=openrouter
|
||||||
|
ANSWER_MODEL=openai/gpt-4.1-mini
|
||||||
|
ANSWER_BASE_URL=https://openrouter.ai/api/v1
|
||||||
|
ANSWER_API_KEY=
|
||||||
5
RAG/.gitignore
vendored
Normal file
5
RAG/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
npm-debug.log*
|
||||||
15
RAG/Dockerfile
Normal file
15
RAG/Dockerfile
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
FROM node:22-bookworm-slim AS build
|
||||||
|
WORKDIR /app
|
||||||
|
COPY package.json package-lock.json tsconfig.json ./
|
||||||
|
RUN npm ci
|
||||||
|
COPY src ./src
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
FROM node:22-bookworm-slim AS runtime
|
||||||
|
WORKDIR /app
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
COPY package.json package-lock.json ./
|
||||||
|
RUN npm ci --omit=dev
|
||||||
|
COPY --from=build /app/dist ./dist
|
||||||
|
EXPOSE 3000
|
||||||
|
CMD ["node", "dist/server.js"]
|
||||||
498
RAG/docs/BITACORA_DISENO_RAG.md
Normal file
498
RAG/docs/BITACORA_DISENO_RAG.md
Normal file
|
|
@ -0,0 +1,498 @@
|
||||||
|
# Bitacora de diseño del RAG
|
||||||
|
|
||||||
|
**Proyecto:** Workspace de tools IA para empresas
|
||||||
|
**Modulo:** RAG
|
||||||
|
**Ultima actualizacion:** 2026-04-02
|
||||||
|
**Ultima modificacion por:** Agente tools IA para potenciar servicios empresariales
|
||||||
|
**Estado:** Activa
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Proposito
|
||||||
|
|
||||||
|
Esta bitacora registra el camino de diseño que estamos siguiendo para construir el RAG.
|
||||||
|
|
||||||
|
No se limita a guardar decisiones finales. Tambien recoge:
|
||||||
|
|
||||||
|
- como se llego a una decision
|
||||||
|
- por que se prefirio una opcion frente a otra
|
||||||
|
- que posibilidades quedan abiertas para mas adelante
|
||||||
|
- que vision general da sentido al resto de documentos del modulo
|
||||||
|
|
||||||
|
Su funcion es servir como panorama vivo del diseño del RAG.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Vision general actual
|
||||||
|
|
||||||
|
El RAG que se esta diseñando no busca ser una demo cerrada ni una herramienta pensada solo para este workspace.
|
||||||
|
|
||||||
|
La intencion es crear una base reutilizable que pueda:
|
||||||
|
|
||||||
|
- integrarse con proyectos de clientes
|
||||||
|
- potenciar agentes y soluciones con IA ya existentes
|
||||||
|
- servir tanto para conocimiento empresarial como para desarrollo de software
|
||||||
|
- crecer hacia un modelo de servicio consumible por distintas herramientas
|
||||||
|
|
||||||
|
La carpeta `docs/` del workspace solo se usara como primer caso real de prueba, no como limite conceptual del sistema.
|
||||||
|
|
||||||
|
Otro criterio ya explicitado es que las decisiones de la v1 deben tomarse como base estable y duradera del sistema. La evolucion futura debe venir por ampliacion de capacidades, no por sustituir continuamente decisiones nucleares ya cerradas.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Casos de uso principales aceptados
|
||||||
|
|
||||||
|
### 1. Agente experto en empresa
|
||||||
|
|
||||||
|
El RAG debe poder ayudar a un agente a atender clientes durante venta, postventa, soporte y seguimiento.
|
||||||
|
|
||||||
|
Eso implica que el sistema debe poder aportar contexto sobre:
|
||||||
|
|
||||||
|
- empresa
|
||||||
|
- productos
|
||||||
|
- servicios
|
||||||
|
- formas de trabajo
|
||||||
|
- empleados, areas y responsabilidades
|
||||||
|
- informacion relevante para atender correctamente al cliente
|
||||||
|
|
||||||
|
### 2. Agente experto en proyecto o desarrollo
|
||||||
|
|
||||||
|
El RAG debe poder dar a un agente una vision util de un proyecto para:
|
||||||
|
|
||||||
|
- iniciar un desarrollo desde cero
|
||||||
|
- continuar un proyecto existente
|
||||||
|
- retomar proyectos de terceros
|
||||||
|
- comprender tecnologia, arquitectura, metodos y estructura interna
|
||||||
|
- desarrollar nuevas funcionalidades bien integradas con lo que ya existe
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Decisiones y razonamiento acumulado
|
||||||
|
|
||||||
|
### El RAG no debe limitarse a pregunta y respuesta
|
||||||
|
|
||||||
|
**Decision:**
|
||||||
|
El sistema no se diseñara solo como `pregunta -> respuesta`.
|
||||||
|
|
||||||
|
**Por que:**
|
||||||
|
- los casos de uso reales requieren dar contexto util a agentes, no solo contestar preguntas
|
||||||
|
- en desarrollo y continuidad de proyectos, el mayor valor esta en cargar panorama y contexto rico
|
||||||
|
- para atencion al cliente tambien puede ser util devolver contexto y no solo respuesta final
|
||||||
|
|
||||||
|
**Consecuencia:**
|
||||||
|
La salida del RAG debe contemplar al menos recuperacion de contexto, y no solo respuesta sintetizada.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### El RAG debe poder funcionar como servicio
|
||||||
|
|
||||||
|
**Decision:**
|
||||||
|
La base de acceso principal se esta pensando como API o endpoint.
|
||||||
|
|
||||||
|
**Por que:**
|
||||||
|
- facilita que otras apps, agentes y soluciones lo consuman
|
||||||
|
- permite pruebas inmediatas desde terminal
|
||||||
|
- encaja bien con una futura capa MCP
|
||||||
|
- es mas util para integracion que pensarlo primero como libreria interna
|
||||||
|
|
||||||
|
**Posibilidad abierta:**
|
||||||
|
- MCP como forma estandar de conexion a sistemas de IA
|
||||||
|
- CLI como capa opcional de uso interno o pruebas
|
||||||
|
|
||||||
|
**Decision tecnica posterior vinculada a esta direccion:**
|
||||||
|
La v1 se orienta a un stack basado en `Node.js + TypeScript + Express + Qdrant`, con API HTTP como base principal y proveedor de embeddings desacoplado.
|
||||||
|
|
||||||
|
**Matiz importante ya detectado:**
|
||||||
|
Desacoplar el proveedor de embeddings no evita que cambiar de modelo obligue normalmente a reindexar el contenido afectado.
|
||||||
|
|
||||||
|
Lo correcto sera:
|
||||||
|
- registrar con que proveedor, modelo y dimensiones se genero cada embedding
|
||||||
|
- mantener trazabilidad de la version de indexacion
|
||||||
|
- evitar mezclar embeddings de modelos distintos dentro de la misma logica de recuperacion
|
||||||
|
|
||||||
|
**Decision cerrada posterior:**
|
||||||
|
Se adopta `Qwen3 Embedding 8B` como modelo base estable del sistema.
|
||||||
|
|
||||||
|
**Por que se cierra asi:**
|
||||||
|
- el usuario quiere que las decisiones nucleares de la v1 nazcan como base duradera
|
||||||
|
- el modelo encaja con recuperacion textual y de codigo
|
||||||
|
- ofrece una via realista de arranque ahora y despliegue local despues
|
||||||
|
- encaja mejor con la estrategia general del RAG que un modelo puramente comercial y dependiente de proveedor cerrado
|
||||||
|
|
||||||
|
**Referencia comparativa mantenida:**
|
||||||
|
`OpenAI text-embedding-3-large` se conserva como referencia de calidad y comparacion, pero no como decision principal del sistema.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### La arquitectura debe separar entrada y salida
|
||||||
|
|
||||||
|
**Decision:**
|
||||||
|
Se ha aceptado que el RAG tiene al menos dos grandes usos operativos:
|
||||||
|
|
||||||
|
- entrada de informacion
|
||||||
|
- salida de informacion por consulta o recuperacion de contexto
|
||||||
|
|
||||||
|
**Por que:**
|
||||||
|
- ambas tareas son distintas y complementarias
|
||||||
|
- ayudan a pensar el sistema con responsabilidades claras
|
||||||
|
- permiten dejar preparada una arquitectura robusta y ampliable
|
||||||
|
|
||||||
|
**Ampliacion ya detectada:**
|
||||||
|
Tambien se considera necesario un futuro subsistema de actualizacion del conocimiento.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Debe existir una via futura para actualizacion y versionado
|
||||||
|
|
||||||
|
**Decision:**
|
||||||
|
Aunque no se implementara de entrada, el sistema debe nacer preparado para manejar contenido cambiante.
|
||||||
|
|
||||||
|
**Por que:**
|
||||||
|
- el conocimiento empresarial y tecnico no es estatico
|
||||||
|
- parte de la informacion puede cambiar, quedar obsoleta o dejar de ser valida
|
||||||
|
- un RAG robusto no puede asumir que todo lo ingerido vale para siempre
|
||||||
|
|
||||||
|
**Idea asociada en exploracion:**
|
||||||
|
Una inspiracion parecida a git puede ser util para pensar el historico, la vigencia y la actualizacion incremental del conocimiento.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### El modelo no solo participa en la salida, tambien puede participar en la entrada
|
||||||
|
|
||||||
|
**Decision en formacion:**
|
||||||
|
Se deja claro que, cuando hablamos de uso de modelo, no nos referimos solo al modelo que consumira el contexto del RAG en consulta.
|
||||||
|
|
||||||
|
Tambien se contempla un modelo que participe en el proceso de entrada de informacion al RAG.
|
||||||
|
|
||||||
|
**Por que:**
|
||||||
|
- un modelo puede ayudar a entender documentos de calidad irregular
|
||||||
|
- puede identificar tema, estructura y puntos clave
|
||||||
|
- puede enriquecer la informacion que entra al sistema antes de indexarla
|
||||||
|
|
||||||
|
**Criterio adoptado:**
|
||||||
|
- no conviene depender desde el inicio de modelos caros para esta fase
|
||||||
|
- puede existir mas adelante un modelo fijo y economico dentro del pipeline de ingesta
|
||||||
|
- tambien queda abierta una fase posterior con una interfaz interactiva de alimentacion, en la que el usuario pueda elegir un modelo mas potente para procesar mejor ciertas entradas
|
||||||
|
|
||||||
|
**Direccion que sugiere esta idea:**
|
||||||
|
- fase basica: ingesta principalmente mecanica y economica
|
||||||
|
- fase posterior: ingesta asistida por modelo
|
||||||
|
- fase mas avanzada: ingesta interactiva con eleccion de modelo segun necesidad
|
||||||
|
|
||||||
|
**Matiz posterior aceptado:**
|
||||||
|
La arquitectura debe nacer preparada para recibir parametros que indiquen si la ingesta va en modo basico o en modo enriquecido/interactivo.
|
||||||
|
|
||||||
|
La idea es que:
|
||||||
|
- si no llegan parametros especiales, el sistema opere por defecto de forma mecanica y con su modelo predefinido
|
||||||
|
- si llegan parametros desde una interfaz, el sistema pueda cambiar de modelo o recibir directrices y marcado del usuario
|
||||||
|
|
||||||
|
**Consecuencia positiva de este enfoque:**
|
||||||
|
Permite que el sistema crezca hacia una ingesta mas rica sin exigir una reescritura posterior del flujo base.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Identidad del conocimiento por niveles
|
||||||
|
|
||||||
|
**Decision:**
|
||||||
|
Se ha aceptado una jerarquia de identificacion con tres niveles:
|
||||||
|
|
||||||
|
- `source_id`
|
||||||
|
- `document_id`
|
||||||
|
- `chunk_id`
|
||||||
|
|
||||||
|
**Por que:**
|
||||||
|
- no basta con identificar solo la carpeta o fuente global
|
||||||
|
- los cambios reales suelen ocurrir a nivel de documento
|
||||||
|
- los chunks son unidades de recuperacion, no la identidad principal del conocimiento
|
||||||
|
|
||||||
|
**Decision relacionada:**
|
||||||
|
Los documentos sueltos tambien tendran `source_id` mediante una fuente logica por defecto, evitando valores nulos.
|
||||||
|
|
||||||
|
**Propuesta concreta ya aceptada como base de trabajo:**
|
||||||
|
- `source_id`: `src:<tenant>:<source_kind>:<source_name>`
|
||||||
|
- `document_id`: `doc:<source_id>:<document_key>`
|
||||||
|
- `chunk_id`: `chk:<document_id>:<chunk_mode>:<chunk_index>`
|
||||||
|
|
||||||
|
**Por que esta forma parece adecuada:**
|
||||||
|
- mantiene legibilidad humana
|
||||||
|
- deja espacio para multi-tenant desde ahora sin obligar a usarlo plenamente
|
||||||
|
- separa bien fuente, documento y chunk
|
||||||
|
- permite distinguir tambien el modo de chunking en el identificador del fragmento
|
||||||
|
|
||||||
|
**Aclaracion importante sobre `tenant`:**
|
||||||
|
En este diseño, `tenant` significa el espacio logico de separacion de datos dentro del servicio RAG.
|
||||||
|
|
||||||
|
No obliga a que desde el primer dia haya varios clientes activos, pero evita que la arquitectura quede cerrada a un unico contexto.
|
||||||
|
|
||||||
|
Usar `default` al inicio permite trabajar simple hoy y quedar preparados para mañana.
|
||||||
|
|
||||||
|
**Hallazgo practico posterior:**
|
||||||
|
Una misma coleccion vectorial puede servir varias fuentes distintas siempre que queden bien separadas por identificadores y por alcance de consulta.
|
||||||
|
|
||||||
|
Ejemplo ya probado:
|
||||||
|
- `docs/` del workspace como documentacion global
|
||||||
|
- `RAG/docs/` como documentacion propia del modulo RAG
|
||||||
|
|
||||||
|
La separacion operativa actual se hace por `sourceRef` y `source_id` dentro del `scope` de consulta.
|
||||||
|
|
||||||
|
Esto demuestra en pequeno el mismo patron que luego servira para separar clientes, proyectos o dominios dentro del mismo sistema.
|
||||||
|
|
||||||
|
**Decision complementaria sobre `document_key`:**
|
||||||
|
Se adopta como regla base que el documento se identifique por una clave interna normalizada, preferiblemente relativa a la fuente.
|
||||||
|
|
||||||
|
La idea es:
|
||||||
|
- usar rutas relativas cuando existan
|
||||||
|
- usar nombres normalizados para documentos sueltos
|
||||||
|
- usar referencias estables para fuentes externas o derivadas
|
||||||
|
|
||||||
|
**Por que esta decision completa bien el esquema:**
|
||||||
|
- evita depender de rutas absolutas locales
|
||||||
|
- mantiene IDs legibles y estables
|
||||||
|
- hace posible tratar con la misma logica archivos, PDFs sueltos, APIs documentales u otras fuentes futuras
|
||||||
|
|
||||||
|
**Matiz adicional aceptado:**
|
||||||
|
`document_key` no sustituye al localizador real del fichero o recurso. Su papel es servir como identidad interna estable dentro del sistema.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Metadatos minimos aceptados
|
||||||
|
|
||||||
|
**Decision:**
|
||||||
|
Se ha aceptado como base este conjunto:
|
||||||
|
|
||||||
|
- `source_id`
|
||||||
|
- `source_type`
|
||||||
|
- `source_path` o `source_ref`
|
||||||
|
- `document_id`
|
||||||
|
- `chunk_id`
|
||||||
|
- `title`
|
||||||
|
- `updated_at`
|
||||||
|
- `hash`
|
||||||
|
- `status`
|
||||||
|
- `version`
|
||||||
|
- `tags`
|
||||||
|
|
||||||
|
**Por que:**
|
||||||
|
- equilibran simplicidad y preparacion para evolucion
|
||||||
|
- permiten trazabilidad
|
||||||
|
- preparan el sistema para cambios y actualizaciones futuras
|
||||||
|
- evitan tener que rediseñar demasiado pronto el modelo de datos
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Chunking: una sola estrategia como implementacion inicial, pero no como vision final
|
||||||
|
|
||||||
|
**Decision:**
|
||||||
|
Se empezo aceptando una estrategia comun de chunking para la v1 por simplicidad.
|
||||||
|
|
||||||
|
**Matiz importante posterior:**
|
||||||
|
Dado que el RAG tambien servira para desarrollo de software, la vision final no debe quedarse en un unico chunking universal.
|
||||||
|
|
||||||
|
**Conclusion actual:**
|
||||||
|
La arquitectura debe quedar preparada al menos para dos modos:
|
||||||
|
|
||||||
|
- `documental`
|
||||||
|
- `codigo`
|
||||||
|
|
||||||
|
**Aclaracion importante:**
|
||||||
|
El modo `codigo` no se deja de lado como direccion del sistema. Sigue siendo parte del objetivo del RAG por su uso previsto en desarrollo de software.
|
||||||
|
|
||||||
|
Lo que se prioriza primero es cerrar y construir una v1 documental estable para arrancar antes, sin renunciar a incorporar despues el modo `codigo` sobre una arquitectura ya preparada.
|
||||||
|
|
||||||
|
**Decision posterior ya implementada:**
|
||||||
|
El modo `codigo` ha quedado implementado en la v1 con:
|
||||||
|
- soporte de extensiones tecnicas habituales
|
||||||
|
- chunking por bloques top-level
|
||||||
|
- retrieve filtrable por `mode: codigo`
|
||||||
|
- answer con referencias de bloque y rango de lineas cuando aplica
|
||||||
|
|
||||||
|
**Prueba funcional ya realizada:**
|
||||||
|
se ingirio `RAG/src/` y se respondio correctamente a una consulta real sobre como se construye `source_id` en el sistema.
|
||||||
|
|
||||||
|
**Por que:**
|
||||||
|
- documentacion y codigo no se parten ni se recuperan igual de bien
|
||||||
|
- un chunking puramente documental podria aprovechar mal el codigo fuente
|
||||||
|
- el objetivo del sistema exige servir a ambos mundos
|
||||||
|
|
||||||
|
**Matiz importante ya asumido:**
|
||||||
|
Cambiar de forma relevante la estrategia de chunking en el futuro implicara normalmente reprocessar e indexar de nuevo la informacion afectada.
|
||||||
|
|
||||||
|
Esto no invalida avanzar con una v1 simple, pero refuerza la idea de elegir una base suficientemente solida desde ahora.
|
||||||
|
|
||||||
|
**Decision ya cerrada para el modo documental de la v1:**
|
||||||
|
- cortar primero por estructura natural
|
||||||
|
- subdividir por parrafos cuando haga falta
|
||||||
|
- usar corte por longitud solo como ultimo recurso
|
||||||
|
- trabajar con una referencia aproximada por caracteres
|
||||||
|
- aplicar overlap pequeno y fijo
|
||||||
|
|
||||||
|
**Valores elegidos para arrancar:**
|
||||||
|
- objetivo aproximado: 1500 caracteres
|
||||||
|
- maximo aproximado: 2200 caracteres
|
||||||
|
- overlap aproximado: 200 caracteres
|
||||||
|
|
||||||
|
**Por que se cierran ya estos valores:**
|
||||||
|
- permiten construir la v1 sin seguir aplazando decisiones basicas
|
||||||
|
- mantienen una complejidad baja
|
||||||
|
- ofrecen una base razonablemente estable para documentacion normal
|
||||||
|
- dejan abierta una futura variante `documental_largo` para libros o contenido tecnico extenso
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Seleccion de modo: enfoque hibrido
|
||||||
|
|
||||||
|
**Decision:**
|
||||||
|
La seleccion del modo debe ser hibrida.
|
||||||
|
|
||||||
|
**Forma acordada:**
|
||||||
|
- el consumidor puede indicar el modo explicitamente
|
||||||
|
- si no lo hace, el sistema intenta inferirlo
|
||||||
|
|
||||||
|
**Por que:**
|
||||||
|
- combina control y comodidad
|
||||||
|
- evita obligar a multiples interfaces separadas demasiado pronto
|
||||||
|
- permite a un agente elegir segun el contenido o dejar que el sistema ayude
|
||||||
|
|
||||||
|
**Modo base previsto:**
|
||||||
|
- `documental`
|
||||||
|
- `codigo`
|
||||||
|
- `auto`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### La salida del RAG debe contemplar contexto, no solo respuestas
|
||||||
|
|
||||||
|
**Decision:**
|
||||||
|
Se consideran validos al menos estos tipos de salida conceptuales:
|
||||||
|
|
||||||
|
- `retrieve`
|
||||||
|
- `answer`
|
||||||
|
- opcion intermedia de contexto mas sintesis
|
||||||
|
|
||||||
|
**Por que:**
|
||||||
|
- el mayor valor para agentes de desarrollo esta en recuperar contexto rico y trazable
|
||||||
|
- atencion al cliente puede beneficiarse de contexto mas respuesta sintetizada
|
||||||
|
- una salida solo orientada a respuesta dejaría corto el sistema para los usos previstos
|
||||||
|
|
||||||
|
**Decision posterior ya implementada:**
|
||||||
|
`answer` se monta sobre `retrieve`, no como un camino independiente.
|
||||||
|
|
||||||
|
**Por que:**
|
||||||
|
- mantiene una arquitectura mas limpia
|
||||||
|
- obliga a que la respuesta final siga dependiendo del contexto recuperado
|
||||||
|
- hace visible que mejorar `retrieve` mejora tambien `answer`
|
||||||
|
|
||||||
|
**Ajuste posterior relevante:**
|
||||||
|
`retrieve specific` se ha afinado para preguntas operativas frecuentes mediante subconsultas y reordenacion por intencion.
|
||||||
|
|
||||||
|
**Efecto visible:**
|
||||||
|
mejora respuestas como la consulta de pendientes del workspace, que ahora ya baja a lineas concretas del backlog en lugar de quedarse solo en la descripcion general del documento.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Retrieve inicial y retrieve especifico
|
||||||
|
|
||||||
|
**Decision en formacion:**
|
||||||
|
Se ve mucho valor en separar:
|
||||||
|
|
||||||
|
- un `retrieve inicial` o de arranque
|
||||||
|
- un `retrieve especifico` para profundizar
|
||||||
|
|
||||||
|
**Idea del retrieve inicial:**
|
||||||
|
Debe comportarse como un mapa del dominio, no como una respuesta en detalle.
|
||||||
|
|
||||||
|
**Ese mapa deberia incluir:**
|
||||||
|
- vision general
|
||||||
|
- temas principales
|
||||||
|
- puntos criticos
|
||||||
|
- estructura o areas
|
||||||
|
- referencias para profundizar
|
||||||
|
|
||||||
|
**Aporte adicional aceptado:**
|
||||||
|
Algunas piezas podran marcarse explicitamente como criticas para que el sistema les de prioridad en ese contexto inicial.
|
||||||
|
|
||||||
|
**Por que:**
|
||||||
|
- evita saturar al modelo con demasiado detalle desde el principio
|
||||||
|
- da orientacion general al agente
|
||||||
|
- permite usar consultas posteriores para profundizar solo donde haga falta
|
||||||
|
|
||||||
|
**Decision posterior ya estructurada:**
|
||||||
|
El `retrieve` inicial queda entendido como un paquete de contexto de arranque con esta estructura base:
|
||||||
|
|
||||||
|
- `mode`
|
||||||
|
- `intent`
|
||||||
|
- `summary`
|
||||||
|
- `topics`
|
||||||
|
- `critical_points`
|
||||||
|
- `items`
|
||||||
|
- `follow_up_refs`
|
||||||
|
- `scope`
|
||||||
|
|
||||||
|
**Sentido de esta estructura:**
|
||||||
|
- combina panorama general y trazabilidad
|
||||||
|
- sirve tanto a agentes conversacionales como a agentes de desarrollo
|
||||||
|
- permite pasar de una carga inicial de contexto a una profundizacion posterior sin perder el mapa del dominio
|
||||||
|
|
||||||
|
**Matiz posterior aceptado:**
|
||||||
|
El `retrieve`, y en especial el `bootstrap`, debe poder recibir un alcance explicito para cargar solo el contexto de una fuente concreta, por ejemplo un workspace o proyecto determinado.
|
||||||
|
|
||||||
|
**Decision posterior ya implementada:**
|
||||||
|
El `bootstrap` deja de comportarse como una sola busqueda ampliada y pasa a usar varias subconsultas internas para construir un mapa inicial mas util.
|
||||||
|
|
||||||
|
**Por que:**
|
||||||
|
- mejora la cobertura del contexto de arranque
|
||||||
|
- recupera mejor documentos base como indice, README, pendientes o historial
|
||||||
|
- se ajusta mejor al objetivo de volver rapidamente experto a un agente sobre un workspace o proyecto concreto
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Posibilidades abiertas que siguen vivas
|
||||||
|
|
||||||
|
- integrar el RAG con MCP como canal de conexion a sistemas de IA
|
||||||
|
- potenciar el RAG con Obsidian como posible fuente, capa de organizacion o entorno complementario de conocimiento
|
||||||
|
- usarlo como servicio comun para distintos clientes y aplicaciones
|
||||||
|
- permitir mejoras del motor que beneficien a todos los consumidores del servicio
|
||||||
|
- evolucionar hacia algun tipo de historico o versionado inspirado en ideas parecidas a git
|
||||||
|
- introducir estrategias especificas de chunking por tipo de documento o fuente
|
||||||
|
- ampliar el modo documental con soporte especializado para PDFs complejos o escaneados mediante OCR si llegara a hacer falta
|
||||||
|
- definir memoria base de sesion mas retrieves dinamicos posteriores
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Los PDFs entran en la v1
|
||||||
|
|
||||||
|
**Decision:**
|
||||||
|
Los PDFs no se aplazan. Deben estar soportados desde la primera version del sistema.
|
||||||
|
|
||||||
|
**Por que:**
|
||||||
|
- en entornos reales de empresa y proyectos, mucha informacion relevante llega en PDF
|
||||||
|
- dejarlos fuera desde el inicio recortaria demasiado el valor practico de la v1
|
||||||
|
- el objetivo del RAG es ser util desde pronto en escenarios reales, no solo con Markdown o texto plano
|
||||||
|
|
||||||
|
**Matiz importante:**
|
||||||
|
El soporte inicial se entiende para PDFs con texto extraible. PDFs escaneados o de mala calidad podran requerir despues una capa adicional de OCR o tratamiento especializado.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pendientes inmediatos del diseño
|
||||||
|
|
||||||
|
1. Definir como se construiran exactamente `source_id`, `document_id` y `chunk_id`.
|
||||||
|
2. Definir la estrategia concreta del chunking inicial.
|
||||||
|
3. Definir que debe devolver exactamente `retrieve`.
|
||||||
|
4. Definir que bloques tendra el `retrieve inicial` tipo mapa de dominio.
|
||||||
|
5. Definir como se accedera a los servicios minimos del RAG desde la API.
|
||||||
|
6. Definir si la ingesta asistida por modelo estara presente desde la v1 o quedara para la siguiente fase.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Rol de esta bitacora dentro del modulo
|
||||||
|
|
||||||
|
Los otros documentos del modulo deben ir recogiendo decisiones concretas por areas.
|
||||||
|
|
||||||
|
Esta bitacora, en cambio, debe conservar la panoramica del camino seguido:
|
||||||
|
|
||||||
|
- que se quiere construir
|
||||||
|
- para que se quiere construir
|
||||||
|
- como se esta razonando el diseño
|
||||||
|
- que se decide ahora
|
||||||
|
- que se deja abierto para mas adelante
|
||||||
533
RAG/docs/INGESTA.md
Normal file
533
RAG/docs/INGESTA.md
Normal file
|
|
@ -0,0 +1,533 @@
|
||||||
|
# Ingesta
|
||||||
|
|
||||||
|
**Proyecto:** Workspace de tools IA para empresas
|
||||||
|
**Modulo:** RAG
|
||||||
|
**Ultima actualizacion:** 2026-04-02
|
||||||
|
**Ultima modificacion por:** Agente tools IA para potenciar servicios empresariales
|
||||||
|
**Estado:** En definicion
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Proposito
|
||||||
|
|
||||||
|
Definir que es la ingesta dentro del sistema RAG, que responsabilidades tiene y que debe dejar preparado para el resto de modulos.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Que es la ingesta
|
||||||
|
|
||||||
|
La ingesta es el punto de entrada del conocimiento al RAG.
|
||||||
|
|
||||||
|
Su funcion es recibir fuentes de informacion y convertirlas en entradas manejables para el sistema, dejandolas listas para su posterior procesado, indexacion y recuperacion.
|
||||||
|
|
||||||
|
No debe encargarse de todo el pipeline. Su objetivo es incorporar y registrar correctamente las fuentes.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Responsabilidades de la ingesta
|
||||||
|
|
||||||
|
La ingesta debe ocuparse de:
|
||||||
|
|
||||||
|
1. Recibir una fuente de informacion.
|
||||||
|
2. Identificar su tipo.
|
||||||
|
3. Leer su contenido o referencia.
|
||||||
|
4. Registrar metadatos basicos.
|
||||||
|
5. Entregar el resultado a la fase de procesado.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tipos de fuente previstos
|
||||||
|
|
||||||
|
La primera version puede empezar por documentos, pero la ingesta debe nacer pensada para admitir mas tipos de fuente.
|
||||||
|
|
||||||
|
Fuentes previstas:
|
||||||
|
- archivos locales
|
||||||
|
- carpetas
|
||||||
|
- documentacion de proyecto
|
||||||
|
- codigo fuente de proyecto
|
||||||
|
- repositorios
|
||||||
|
- endpoints o APIs
|
||||||
|
- bases de datos
|
||||||
|
- otras fuentes conectables en el futuro
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Alcance de la primera version
|
||||||
|
|
||||||
|
La primera version de la ingesta debe centrarse en:
|
||||||
|
|
||||||
|
- leer archivos de documentacion
|
||||||
|
- leer PDFs con texto extraible
|
||||||
|
- leer codigo fuente del proyecto
|
||||||
|
- recorrer carpetas concretas
|
||||||
|
- registrar que fuente entro al sistema
|
||||||
|
- dejar preparada la extension a nuevas fuentes mas adelante
|
||||||
|
|
||||||
|
Caso practico inicial:
|
||||||
|
- carpeta `docs/` del workspace como primera fuente real de prueba
|
||||||
|
- carpeta `RAG/src/` como primera fuente real de prueba para el modo `codigo`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Salida esperada de la ingesta
|
||||||
|
|
||||||
|
La ingesta no debe devolver aun conocimiento recuperable final. Debe devolver una representacion normalizada de la fuente para el siguiente modulo.
|
||||||
|
|
||||||
|
Esa salida deberia incluir como minimo:
|
||||||
|
|
||||||
|
- identificador de fuente
|
||||||
|
- tipo de fuente
|
||||||
|
- origen o ruta
|
||||||
|
- contenido bruto o accesible
|
||||||
|
- fecha de lectura
|
||||||
|
- metadatos basicos disponibles
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Metadatos minimos aceptados
|
||||||
|
|
||||||
|
La primera propuesta aceptada para metadatos minimos del sistema es esta:
|
||||||
|
|
||||||
|
- `source_id`
|
||||||
|
- `source_type`
|
||||||
|
- `source_path` o `source_ref`
|
||||||
|
- `document_id`
|
||||||
|
- `chunk_id`
|
||||||
|
- `title`
|
||||||
|
- `updated_at`
|
||||||
|
- `hash`
|
||||||
|
- `status`
|
||||||
|
- `version`
|
||||||
|
- `tags`
|
||||||
|
|
||||||
|
Metadatos recomendados adicionales para la capa de embeddings:
|
||||||
|
|
||||||
|
- `embedding_provider`
|
||||||
|
- `embedding_model`
|
||||||
|
- `embedding_dimensions`
|
||||||
|
|
||||||
|
No todos tendran el mismo peso en la v1, pero conviene que el diseño nazca preparado para soportarlos.
|
||||||
|
|
||||||
|
**Por que se adopta este conjunto:**
|
||||||
|
|
||||||
|
- `source_id`, `document_id` y `chunk_id` permiten trazabilidad y actualizacion por niveles.
|
||||||
|
- `source_type` y `source_path` o `source_ref` permiten entender de donde salio cada entrada.
|
||||||
|
- `title` mejora legibilidad, depuracion y futuras recuperaciones.
|
||||||
|
- `updated_at`, `hash`, `status` y `version` preparan el sistema para cambios, invalidez y evolucion de contenido.
|
||||||
|
- `tags` deja abierta una capa ligera de clasificacion sin obligar a una taxonomia compleja en la v1.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Jerarquia de identificacion
|
||||||
|
|
||||||
|
Se adopta una jerarquia simple de 3 niveles:
|
||||||
|
|
||||||
|
1. `source`
|
||||||
|
- origen logico de ingesta
|
||||||
|
- ejemplos: carpeta `docs/`, carpeta `src/`, repositorio, API, tabla o coleccion externa
|
||||||
|
|
||||||
|
2. `document`
|
||||||
|
- unidad concreta dentro de la fuente
|
||||||
|
- ejemplos: `docs/HISTORIAL_SESIONES.md`, `src/app/main.ts`, `manual_cliente.pdf`
|
||||||
|
|
||||||
|
3. `chunk`
|
||||||
|
- fragmento recuperable del documento
|
||||||
|
|
||||||
|
Esta separacion permite actualizar una pieza concreta sin tener que tratar toda la fuente como si hubiera cambiado por completo.
|
||||||
|
|
||||||
|
**Por que se adopta esta jerarquia:**
|
||||||
|
|
||||||
|
- una carpeta o repositorio no debe ser la unica unidad de control, porque los cambios reales suelen ocurrir a nivel de documento.
|
||||||
|
- un mismo documento debe poder actualizarse sin reingestar toda la fuente completa.
|
||||||
|
- el chunk debe ser la unidad de recuperacion, no de identidad principal del conocimiento.
|
||||||
|
- esta estructura se parece a un sistema de versionado por niveles, y deja abierta la puerta a historico, reindexacion parcial y auditoria.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Criterio para los identificadores
|
||||||
|
|
||||||
|
Se trabajara con estos tres identificadores:
|
||||||
|
|
||||||
|
- `source_id`: identifica la fuente logica
|
||||||
|
- `document_id`: identifica el documento concreto
|
||||||
|
- `chunk_id`: identifica cada fragmento recuperable
|
||||||
|
|
||||||
|
La carpeta `docs/` puede ser una `source`, pero `docs/HISTORIAL_SESIONES.md` debe tener su propio `document_id` para poder detectar cambios, reindexar y mantener trazabilidad fina.
|
||||||
|
|
||||||
|
**Por que se decide asi:**
|
||||||
|
|
||||||
|
- si `source_id` absorbiera toda la identidad, perderiamos control fino sobre los documentos internos.
|
||||||
|
- si solo identificaramos chunks, perderiamos una unidad intermedia clave para actualizacion, limpieza y observabilidad.
|
||||||
|
- separar estos tres identificadores permite mantener el sistema simple pero preparado para crecer.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Propuesta concreta para construir los identificadores
|
||||||
|
|
||||||
|
Se adopta esta propuesta inicial.
|
||||||
|
|
||||||
|
### `source_id`
|
||||||
|
|
||||||
|
**Formato:**
|
||||||
|
`src:<tenant>:<source_kind>:<source_name>`
|
||||||
|
|
||||||
|
**Ejemplos:**
|
||||||
|
- `src:default:folder:workspace-docs`
|
||||||
|
- `src:default:folder:project-code`
|
||||||
|
- `src:default:manual:client-upload`
|
||||||
|
- `src:cliente-a:repo:backend-api`
|
||||||
|
- `src:cliente-a:api:java-docs`
|
||||||
|
|
||||||
|
**Que representa:**
|
||||||
|
- el origen logico de ingesta
|
||||||
|
- no un archivo concreto
|
||||||
|
- debe mantenerse estable aunque cambien documentos internos
|
||||||
|
|
||||||
|
**Que significa `tenant`:**
|
||||||
|
- es el espacio logico de separacion de datos
|
||||||
|
- sirve para distinguir clientes, proyectos, entornos o contextos aislados dentro del mismo RAG
|
||||||
|
- en la v1 puede usarse simplemente `default` cuando aun no necesitemos separar nada
|
||||||
|
|
||||||
|
**Para que nos sirve en nuestro sistema:**
|
||||||
|
- deja preparado el RAG para dar servicio a varios clientes o aplicaciones sin mezclar su informacion
|
||||||
|
- permite que una misma arquitectura y un mismo servicio RAG trabajen para varios consumidores
|
||||||
|
- facilita crecer hacia un modelo centralizado en el que mejoramos el RAG una vez y varios clientes o apps se benefician
|
||||||
|
|
||||||
|
**Ejemplo practico de multi-tenant:**
|
||||||
|
- `src:cliente-a:folder:workspace-docs`
|
||||||
|
- `src:cliente-b:folder:workspace-docs`
|
||||||
|
|
||||||
|
Ambos pueden tener un archivo llamado `docs/manual.md`, pero seguiran siendo distintos porque pertenecen a tenants diferentes.
|
||||||
|
|
||||||
|
Esto evita mezclar conocimiento de clientes o proyectos distintos dentro del mismo sistema.
|
||||||
|
|
||||||
|
**Reglas:**
|
||||||
|
- `tenant`: `default` si no hay cliente o espacio separado aun
|
||||||
|
- `source_kind`: tipo logico de fuente, por ejemplo `folder`, `repo`, `manual`, `api`, `db`
|
||||||
|
- `source_name`: nombre corto, legible y estable
|
||||||
|
|
||||||
|
### `document_id`
|
||||||
|
|
||||||
|
**Formato:**
|
||||||
|
`doc:<source_id>:<document_key>`
|
||||||
|
|
||||||
|
**Ejemplos:**
|
||||||
|
- `doc:src:default:folder:workspace-docs:docs/historial_sesiones.md`
|
||||||
|
- `doc:src:default:folder:project-code:src/app/main.ts`
|
||||||
|
- `doc:src:default:manual:client-upload:manual_cliente.pdf`
|
||||||
|
- `doc:src:cliente-a:api:java-docs:list`
|
||||||
|
|
||||||
|
**Que representa:**
|
||||||
|
- el documento o unidad concreta dentro de una fuente
|
||||||
|
- es la unidad principal para detectar cambios, desapariciones o reingesta selectiva
|
||||||
|
|
||||||
|
**Reglas:**
|
||||||
|
- usar ruta relativa si existe
|
||||||
|
- si no existe ruta, usar nombre normalizado o referencia estable
|
||||||
|
- evitar espacios y diferencias cosmeticas innecesarias en el identificador
|
||||||
|
|
||||||
|
### `chunk_id`
|
||||||
|
|
||||||
|
**Formato:**
|
||||||
|
`chk:<document_id>:<chunk_mode>:<chunk_index>`
|
||||||
|
|
||||||
|
**Ejemplos:**
|
||||||
|
- `chk:doc:src:default:folder:workspace-docs:docs/historial_sesiones.md:documental:0001`
|
||||||
|
- `chk:doc:src:default:folder:project-code:src/app/main.ts:codigo:0003`
|
||||||
|
|
||||||
|
**Que representa:**
|
||||||
|
- cada fragmento recuperable del documento
|
||||||
|
|
||||||
|
**Reglas:**
|
||||||
|
- `chunk_mode`: permite distinguir si el chunk fue generado en modo `documental` o `codigo`
|
||||||
|
- `chunk_index`: indice secuencial con padding para mantener orden legible
|
||||||
|
|
||||||
|
**Ejemplo practico simple:**
|
||||||
|
|
||||||
|
Supongamos esta fuente:
|
||||||
|
|
||||||
|
- `source_id`: `src:default:folder:workspace-docs`
|
||||||
|
|
||||||
|
Y dentro de ella dos documentos:
|
||||||
|
|
||||||
|
- `document_id`: `doc:src:default:folder:workspace-docs:docs/readme.md`
|
||||||
|
- `document_id`: `doc:src:default:folder:workspace-docs:docs/historial_sesiones.md`
|
||||||
|
|
||||||
|
Si `docs/readme.md` se parte en 3 chunks documentales:
|
||||||
|
|
||||||
|
- `chk:doc:src:default:folder:workspace-docs:docs/readme.md:documental:0001`
|
||||||
|
- `chk:doc:src:default:folder:workspace-docs:docs/readme.md:documental:0002`
|
||||||
|
- `chk:doc:src:default:folder:workspace-docs:docs/readme.md:documental:0003`
|
||||||
|
|
||||||
|
Y `docs/historial_sesiones.md` se parte en 2 chunks documentales:
|
||||||
|
|
||||||
|
- `chk:doc:src:default:folder:workspace-docs:docs/historial_sesiones.md:documental:0001`
|
||||||
|
- `chk:doc:src:default:folder:workspace-docs:docs/historial_sesiones.md:documental:0002`
|
||||||
|
|
||||||
|
El `0001` no compite con el `0001` del otro documento.
|
||||||
|
|
||||||
|
El indice solo tiene sentido dentro de su propio `document_id`.
|
||||||
|
|
||||||
|
Eso permite:
|
||||||
|
- saber a que documento pertenece cada chunk
|
||||||
|
- conservar el orden interno del documento
|
||||||
|
- distinguir chunks de distintos modos si algun dia un mismo documento se procesa de manera diferente
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Normalizacion recomendada
|
||||||
|
|
||||||
|
Para mantener consistencia:
|
||||||
|
|
||||||
|
- usar minusculas cuando no rompa semantica importante
|
||||||
|
- reemplazar espacios por guion bajo o guion medio segun convenga
|
||||||
|
- usar rutas relativas como referencia principal cuando existan
|
||||||
|
- evitar caracteres redundantes o variables del entorno que rompan estabilidad
|
||||||
|
- no incluir hashes dentro del identificador principal
|
||||||
|
|
||||||
|
El `hash` debe vivir como metadato, no como parte del ID. Asi el identificador se mantiene estable y el hash sirve para detectar cambios.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Propuesta para `document_key`
|
||||||
|
|
||||||
|
Se adopta esta regla general:
|
||||||
|
|
||||||
|
- si el documento pertenece a una fuente con estructura de rutas, usar su ruta relativa normalizada dentro de la fuente
|
||||||
|
- si el documento no tiene ruta natural, usar un nombre o referencia estable normalizada
|
||||||
|
- si la fuente es derivada o externa, usar una referencia corta y estable que identifique la unidad documental
|
||||||
|
|
||||||
|
### Formato esperado
|
||||||
|
|
||||||
|
`document_key` debe ser:
|
||||||
|
|
||||||
|
- legible
|
||||||
|
- estable
|
||||||
|
- relativo a la fuente cuando sea posible
|
||||||
|
- suficientemente especifico para no colisionar dentro de una misma `source`
|
||||||
|
|
||||||
|
### Reglas de normalizacion
|
||||||
|
|
||||||
|
1. Usar ruta relativa respecto de la fuente cuando exista.
|
||||||
|
2. Unificar separadores a `/`.
|
||||||
|
3. Eliminar prefijos absolutos del sistema.
|
||||||
|
4. Mantener la extension del archivo si aplica.
|
||||||
|
5. Convertir a minusculas cuando no rompa semantica importante.
|
||||||
|
6. Reemplazar espacios por `_`.
|
||||||
|
7. Evitar caracteres redundantes o decorativos.
|
||||||
|
8. No incluir hashes, timestamps volatiles ni parametros temporales.
|
||||||
|
|
||||||
|
### Ejemplos
|
||||||
|
|
||||||
|
#### Fuente tipo carpeta
|
||||||
|
|
||||||
|
- fuente: `src:default:folder:workspace-docs`
|
||||||
|
- archivo real: `/home/pancho/Documentos/Empresa/Desarrollo/IA/docs/HISTORIAL_SESIONES.md`
|
||||||
|
- `document_key`: `docs/historial_sesiones.md`
|
||||||
|
|
||||||
|
#### Fuente tipo repo
|
||||||
|
|
||||||
|
- fuente: `src:default:repo:backend-api`
|
||||||
|
- archivo real: `/repo/src/app/main.ts`
|
||||||
|
- `document_key`: `src/app/main.ts`
|
||||||
|
|
||||||
|
#### Documento suelto manual
|
||||||
|
|
||||||
|
- fuente: `src:default:manual:client-upload`
|
||||||
|
- archivo real: `Manual Cliente Final.pdf`
|
||||||
|
- `document_key`: `manual_cliente_final.pdf`
|
||||||
|
|
||||||
|
#### Fuente derivada o externa
|
||||||
|
|
||||||
|
- fuente: `src:default:api:java-docs`
|
||||||
|
- unidad documental: documentacion de `ArrayList`
|
||||||
|
- `document_key`: `java/util/arraylist`
|
||||||
|
|
||||||
|
### Por que se decide asi
|
||||||
|
|
||||||
|
- mantiene trazabilidad sin depender de rutas absolutas del entorno
|
||||||
|
- evita que mover el proyecto de maquina o carpeta rompa la identidad del documento
|
||||||
|
- funciona tanto para archivos estructurados como para documentos sueltos o fuentes externas
|
||||||
|
- deja el sistema preparado para futuras ingestas derivadas sin cambiar el modelo base
|
||||||
|
|
||||||
|
**Aclaracion importante:**
|
||||||
|
|
||||||
|
- `document_key` es una identidad interna estable
|
||||||
|
- no debe ser la unica referencia para recargar el fichero real
|
||||||
|
- para reingesta o recarga debe mantenerse tambien el localizador original del recurso
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Documentos sueltos y fuentes por defecto
|
||||||
|
|
||||||
|
El sistema debe soportar tambien documentos sueltos que no pertenezcan de forma natural a una carpeta o coleccion preexistente.
|
||||||
|
|
||||||
|
Ejemplo:
|
||||||
|
- un PDF entregado por un cliente
|
||||||
|
|
||||||
|
En esos casos:
|
||||||
|
|
||||||
|
- el documento seguira teniendo su propio `document_id`
|
||||||
|
- no se dejara `source_id` vacio o nulo
|
||||||
|
- se usara una fuente logica por defecto para agrupar este tipo de entradas
|
||||||
|
|
||||||
|
La idea es que el sistema pueda distinguir entre:
|
||||||
|
|
||||||
|
- documentos nacidos dentro de una fuente estructurada
|
||||||
|
- documentos incorporados como entradas sueltas
|
||||||
|
|
||||||
|
Esto evita nulos, mantiene consistencia y deja abierta la posibilidad de definir despues varias fuentes logicas para documentos manuales, aportados por cliente o cargados desde otras vias.
|
||||||
|
|
||||||
|
**Por que se decide asi:**
|
||||||
|
|
||||||
|
- un documento suelto sigue necesitando un origen logico dentro del sistema.
|
||||||
|
- evitar `null` en `source_id` simplifica reglas internas, validaciones y futuras integraciones.
|
||||||
|
- agrupar documentos sueltos bajo una fuente logica por defecto permite empezar simple y luego especializar si hace falta.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Vision de integracion futura
|
||||||
|
|
||||||
|
Este diseño de ingesta tambien debe facilitar que el RAG pueda integrarse mas adelante con herramientas y entornos de trabajo del propio equipo, por ejemplo OpenCode por proyecto.
|
||||||
|
|
||||||
|
La idea es que, aunque la documentacion exista fisicamente en disco, el RAG pueda ofrecer una capa practica de acceso al conocimiento del proyecto a medida que ese conocimiento crezca.
|
||||||
|
|
||||||
|
Esto refuerza varias decisiones tomadas en este modulo:
|
||||||
|
|
||||||
|
- trazabilidad por fuente, documento y chunk
|
||||||
|
- soporte para carpetas completas y documentos sueltos
|
||||||
|
- preparacion para actualizacion de contenido
|
||||||
|
- diseño desacoplado del tipo concreto de consumidor
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Relacion con el modulo de actualizacion
|
||||||
|
|
||||||
|
La ingesta debe quedar preparada para convivir con un futuro sistema de actualizacion.
|
||||||
|
|
||||||
|
Eso implica que, desde el inicio, conviene contemplar:
|
||||||
|
|
||||||
|
- deteccion de nuevas fuentes
|
||||||
|
- deteccion de cambios en fuentes ya conocidas
|
||||||
|
- posibilidad de releer una fuente
|
||||||
|
- trazabilidad de cuando y desde donde entro una version concreta
|
||||||
|
|
||||||
|
No es necesario implementar todo esto en la primera version, pero si diseñarlo para no bloquear su evolucion posterior.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Principios de diseño
|
||||||
|
|
||||||
|
- Simple en la primera version.
|
||||||
|
- Desacoplada del tipo concreto de fuente.
|
||||||
|
- Preparada para crecimiento.
|
||||||
|
- Trazable desde el origen.
|
||||||
|
- Compatible con futuras actualizaciones y versionado.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Uso de modelo durante la ingesta
|
||||||
|
|
||||||
|
Se acepta que el sistema RAG podra usar un modelo tambien en la fase de entrada, no solo en la fase de salida o consumo.
|
||||||
|
|
||||||
|
Su funcion no seria responder al usuario final, sino ayudar a:
|
||||||
|
|
||||||
|
- entender mejor el contenido entrante
|
||||||
|
- detectar tema principal y puntos clave
|
||||||
|
- extraer estructura util
|
||||||
|
- identificar elementos criticos o destacables
|
||||||
|
- preparar mejor el conocimiento que luego consumira el RAG
|
||||||
|
|
||||||
|
**Criterio actual:**
|
||||||
|
|
||||||
|
- en la primera fase no debemos depender obligatoriamente de modelos caros para esta tarea
|
||||||
|
- mas adelante puede existir un modelo fijo de uso interno dentro del pipeline de ingesta
|
||||||
|
- tambien queda abierta una futura interfaz interactiva de alimentacion en la que el usuario pueda escoger un modelo mas potente si quiere enriquecer el procesamiento
|
||||||
|
|
||||||
|
**Implicacion de diseño:**
|
||||||
|
|
||||||
|
La ingesta no debe quedar pensada solo como lectura mecanica de archivos. Debe poder evolucionar hacia una ingesta asistida por modelo.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Modos de ingesta previstos
|
||||||
|
|
||||||
|
Se adopta este criterio de funcionamiento:
|
||||||
|
|
||||||
|
- por defecto, la ingesta funcionara en modo mecanico
|
||||||
|
- ese modo seguira pudiendo usar un modelo predefinido dentro del pipeline si asi se define
|
||||||
|
- la arquitectura debe quedar preparada desde el inicio para recibir parametros de modo o de interaccion
|
||||||
|
- si esos parametros existen, la ingesta podra comportarse como una ingesta enriquecida o interactiva
|
||||||
|
|
||||||
|
Esto permite que la primera version funcione sin rehacer la arquitectura cuando se amplie.
|
||||||
|
|
||||||
|
### Modo por defecto
|
||||||
|
|
||||||
|
Si no se reciben parametros especiales desde una interfaz:
|
||||||
|
|
||||||
|
- la ingesta actua de forma automatica
|
||||||
|
- usa el modelo predefinido si corresponde
|
||||||
|
- no espera decisiones del usuario
|
||||||
|
|
||||||
|
### Modo interactivo o enriquecido
|
||||||
|
|
||||||
|
Si se reciben parametros desde una interfaz o flujo enriquecido:
|
||||||
|
|
||||||
|
- la ingesta puede detectar que no esta en modo basico
|
||||||
|
- puede permitir cambio de modelo
|
||||||
|
- puede recibir instrucciones, marcas o directrices del usuario
|
||||||
|
- puede priorizar o etiquetar informacion importante antes de indexar
|
||||||
|
|
||||||
|
### Objetivo de este diseño
|
||||||
|
|
||||||
|
Separar desde el inicio:
|
||||||
|
|
||||||
|
- funcionamiento base estable y automatizable
|
||||||
|
- enriquecimiento opcional sin romper el flujo normal de ingesta
|
||||||
|
|
||||||
|
Esto evita tener que rehacer el codigo cuando el sistema evolucione.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Enriquecimiento ligero opcional
|
||||||
|
|
||||||
|
Se deja abierta una via para un enriquecimiento ligero opcional desde etapas tempranas, siempre que no entorpezca la ingesta.
|
||||||
|
|
||||||
|
La forma mas simple de hacerlo seria:
|
||||||
|
|
||||||
|
- mantener la ingesta mecanica como camino principal
|
||||||
|
- ejecutar el enriquecimiento solo si llega un parametro explicito
|
||||||
|
- limitar ese enriquecimiento a tareas pequenas y acotadas
|
||||||
|
|
||||||
|
Ejemplos adecuados para ese enriquecimiento ligero:
|
||||||
|
|
||||||
|
- extraer tema principal
|
||||||
|
- proponer palabras clave
|
||||||
|
- detectar posibles puntos criticos
|
||||||
|
- generar una descripcion corta del documento
|
||||||
|
|
||||||
|
Ejemplos que no conviene meter en esta fase ligera:
|
||||||
|
|
||||||
|
- analisis profundo costoso
|
||||||
|
- interacciones largas con usuario
|
||||||
|
- multiples rondas de refinado
|
||||||
|
- procesos que bloqueen innecesariamente la ingesta base
|
||||||
|
|
||||||
|
**Criterio recomendado:**
|
||||||
|
|
||||||
|
El enriquecimiento ligero debe ser:
|
||||||
|
|
||||||
|
- opcional
|
||||||
|
- rapido
|
||||||
|
- barato
|
||||||
|
- no bloqueante
|
||||||
|
- facil de omitir sin afectar al pipeline principal
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Preguntas a cerrar
|
||||||
|
|
||||||
|
1. Si la ingesta trabajara por archivos individuales, carpetas, colecciones o todo a la vez.
|
||||||
|
2. Como construiremos exactamente `source_id`, `document_id` y `chunk_id`.
|
||||||
|
3. Que nombre tendran las fuentes logicas por defecto para documentos sueltos.
|
||||||
|
4. Como distinguiremos entre fuente nueva, fuente actualizada y fuente obsoleta.
|
||||||
257
RAG/docs/PROCESADO.md
Normal file
257
RAG/docs/PROCESADO.md
Normal file
|
|
@ -0,0 +1,257 @@
|
||||||
|
# Procesado
|
||||||
|
|
||||||
|
**Proyecto:** Workspace de tools IA para empresas
|
||||||
|
**Modulo:** RAG
|
||||||
|
**Ultima actualizacion:** 2026-04-02
|
||||||
|
**Ultima modificacion por:** Agente tools IA para potenciar servicios empresariales
|
||||||
|
**Estado:** En definicion
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Proposito
|
||||||
|
|
||||||
|
Definir el modulo de procesado del sistema RAG, encargado de transformar la salida de ingesta en contenido preparado para indexacion y recuperacion.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Que es el procesado
|
||||||
|
|
||||||
|
El procesado toma las fuentes ya leidas por la ingesta y las prepara para el resto del pipeline.
|
||||||
|
|
||||||
|
Su trabajo incluye, segun el tipo de contenido:
|
||||||
|
|
||||||
|
- limpieza basica
|
||||||
|
- normalizacion
|
||||||
|
- extraccion de estructura util
|
||||||
|
- preparacion de metadatos
|
||||||
|
- division en chunks
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Responsabilidades del procesado
|
||||||
|
|
||||||
|
1. Recibir la salida de la ingesta.
|
||||||
|
2. Normalizar el contenido a una representacion interna util.
|
||||||
|
3. Conservar contexto estructural relevante.
|
||||||
|
4. Generar chunks recuperables.
|
||||||
|
5. Entregar el resultado listo para indexacion.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Decision inicial sobre chunking
|
||||||
|
|
||||||
|
Para la v1 del sistema se adopta una estrategia comun de chunking para todos los documentos.
|
||||||
|
|
||||||
|
No se implementaran aun estrategias distintas por tipo de documento.
|
||||||
|
|
||||||
|
Decision cerrada para esta v1 documental:
|
||||||
|
|
||||||
|
- se adopta un modo `documental` estable como base
|
||||||
|
- se deja abierta una futura variante para documentos extensos, por ejemplo `documental_largo` o `documental_jerarquico`
|
||||||
|
|
||||||
|
Decision ya implementada adicional:
|
||||||
|
|
||||||
|
- se implementa tambien un modo `codigo`
|
||||||
|
- su chunking se hace por bloques top-level y no por parrafos
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Por que se decide asi
|
||||||
|
|
||||||
|
- reduce complejidad en la primera version
|
||||||
|
- permite validar antes el valor real del RAG
|
||||||
|
- evita abrir demasiadas variantes demasiado pronto
|
||||||
|
- deja la puerta abierta a estrategias especificas cuando haya evidencia real de necesidad
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Vision futura
|
||||||
|
|
||||||
|
Mas adelante el sistema podra permitir chunking distinto segun:
|
||||||
|
|
||||||
|
- tipo de documento
|
||||||
|
- tipo de fuente
|
||||||
|
- uso esperado del contenido
|
||||||
|
- formato tecnico o estructura interna
|
||||||
|
|
||||||
|
Ejemplos:
|
||||||
|
- documentacion general
|
||||||
|
- historiales o logs
|
||||||
|
- PDFs tecnicos
|
||||||
|
- codigo fuente
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Principio adoptado
|
||||||
|
|
||||||
|
Regla actual:
|
||||||
|
- una estrategia comun, simple y estable para la v1
|
||||||
|
|
||||||
|
Regla futura:
|
||||||
|
- estrategias adaptables por tipo de documento o fuente si el beneficio supera el coste de complejidad
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Politica oficial de chunking documental v1
|
||||||
|
|
||||||
|
Se adopta esta estrategia para el modo `documental`:
|
||||||
|
|
||||||
|
1. Intentar partir primero por estructura natural del documento.
|
||||||
|
2. Usar como unidades preferentes:
|
||||||
|
- titulos
|
||||||
|
- subtitulos
|
||||||
|
- bloques de parrafos relacionados
|
||||||
|
3. Si una seccion o bloque supera el tamano maximo permitido, subdividirla por parrafos.
|
||||||
|
4. Si aun asi sigue siendo demasiado grande, cortar por longitud sin romper mas de lo necesario.
|
||||||
|
5. Mantener metadatos estructurales del bloque, por ejemplo titulo o seccion de origen.
|
||||||
|
6. Aplicar overlap pequeno y fijo entre chunks contiguos.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Criterio de tamano para la v1
|
||||||
|
|
||||||
|
Para mantener la implementacion simple y estable, la v1 trabajara con una medida aproximada por caracteres, no por tokens.
|
||||||
|
|
||||||
|
Propuesta cerrada:
|
||||||
|
|
||||||
|
- tamano objetivo por chunk: alrededor de 1500 caracteres
|
||||||
|
- tamano maximo por chunk: alrededor de 2200 caracteres
|
||||||
|
- overlap aproximado: alrededor de 200 caracteres
|
||||||
|
|
||||||
|
### Como se aplican estos valores
|
||||||
|
|
||||||
|
- si un bloque natural cabe dentro del rango objetivo, se conserva como un solo chunk
|
||||||
|
- si supera el objetivo, se intenta dividir por parrafos
|
||||||
|
- si supera el maximo incluso despues de agrupar o dividir por parrafos, se corta por longitud controlada
|
||||||
|
- el overlap se toma del final del chunk anterior para conservar continuidad
|
||||||
|
|
||||||
|
### Por que se decide asi
|
||||||
|
|
||||||
|
- caracteres es una medida mas simple de implementar en la v1
|
||||||
|
- estos valores son suficientemente pequenos para no cargar demasiado cada fragmento
|
||||||
|
- siguen siendo suficientemente amplios para conservar contexto util en documentacion normal
|
||||||
|
- el overlap pequeno ayuda a no perder continuidad entre ideas cercanas
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Limitaciones conocidas del modo documental v1
|
||||||
|
|
||||||
|
- funciona especialmente bien para documentacion general, notas, manuales, PDFs con texto extraible y contenido textual estructurado
|
||||||
|
- puede quedarse corto para libros tecnicos, documentos muy largos o contenido extremadamente denso
|
||||||
|
- para esos casos se deja prevista una futura variante especializada, sin romper el modo `documental` base
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Politica oficial de chunking de codigo v1
|
||||||
|
|
||||||
|
Se adopta esta estrategia para el modo `codigo`:
|
||||||
|
|
||||||
|
1. Detectar bloques top-level relevantes del fichero.
|
||||||
|
2. Usar como unidades preferentes:
|
||||||
|
- funciones
|
||||||
|
- clases
|
||||||
|
- interfaces
|
||||||
|
- tipos
|
||||||
|
- bloques exportados o definidos al nivel principal
|
||||||
|
3. Si un bloque supera el tamano maximo, subdividirlo por longitud controlada.
|
||||||
|
4. Conservar metadatos utiles del bloque:
|
||||||
|
- `sectionTitle`
|
||||||
|
- `startLine`
|
||||||
|
- `endLine`
|
||||||
|
|
||||||
|
### Por que se decide asi
|
||||||
|
|
||||||
|
- el codigo no debe partirse como si fueran parrafos documentales
|
||||||
|
- interesa recuperar unidades semanticas utiles para preguntas tecnicas
|
||||||
|
- mantener rango de lineas y cabecera del bloque mejora la trazabilidad practica
|
||||||
|
|
||||||
|
### Soporte actual de extensiones de codigo
|
||||||
|
|
||||||
|
- `ts`
|
||||||
|
- `tsx`
|
||||||
|
- `js`
|
||||||
|
- `jsx`
|
||||||
|
- `mjs`
|
||||||
|
- `cjs`
|
||||||
|
- `py`
|
||||||
|
- `json`
|
||||||
|
- `yml`
|
||||||
|
- `yaml`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Impacto de cambiar la estrategia de chunking
|
||||||
|
|
||||||
|
La forma de chunking condiciona directamente como queda representado el conocimiento dentro del RAG.
|
||||||
|
|
||||||
|
Por eso, si en el futuro cambiamos de manera relevante la estrategia de chunking, lo normal sera tener que reprocesar e indexar de nuevo los documentos afectados.
|
||||||
|
|
||||||
|
### Que implica esto
|
||||||
|
|
||||||
|
- si cambia la forma de partir un documento, sus chunks dejan de ser equivalentes a los anteriores
|
||||||
|
- los embeddings asociados a esos chunks tambien dejan de representar exactamente la nueva estructura
|
||||||
|
- por tanto, lo correcto suele ser rehacer chunking e indexacion para esa parte del conocimiento
|
||||||
|
|
||||||
|
### Que no significa necesariamente
|
||||||
|
|
||||||
|
- no siempre hara falta rehacer todo el RAG completo
|
||||||
|
- puede bastar con reingestar solo determinadas fuentes, tipos documentales o tenants
|
||||||
|
|
||||||
|
### Criterio recomendado
|
||||||
|
|
||||||
|
- en la v1 conviene usar una estrategia estable y suficientemente buena
|
||||||
|
- no buscar la perfeccion absoluta desde el inicio
|
||||||
|
- dejar preparado el sistema para reindexacion parcial cuando haya mejoras posteriores
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Convivencia de estrategias distintas
|
||||||
|
|
||||||
|
En el futuro podran convivir estrategias distintas de chunking, por ejemplo `documental` y `codigo`.
|
||||||
|
|
||||||
|
Pero esa convivencia debe hacerse de forma controlada y explicita, no mezclando sin criterio chunks equivalentes del mismo tipo documental.
|
||||||
|
|
||||||
|
Ejemplo sano:
|
||||||
|
- documentos de negocio en modo `documental`
|
||||||
|
- codigo fuente en modo `codigo`
|
||||||
|
|
||||||
|
Ejemplo menos sano:
|
||||||
|
- misma clase de documentos documentales partida a la vez con dos reglas distintas sin control de version o motivo claro
|
||||||
|
|
||||||
|
Por eso, lo que decidimos ahora es importante: fija la base de la v1 y condiciona como se representara inicialmente el conocimiento.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Overlap entre chunks
|
||||||
|
|
||||||
|
`Overlap` significa repetir una pequena parte del contenido entre un chunk y el siguiente.
|
||||||
|
|
||||||
|
### Ejemplo simple
|
||||||
|
|
||||||
|
Si un texto se parte asi:
|
||||||
|
|
||||||
|
- chunk 1: parrafos 1, 2 y 3
|
||||||
|
- chunk 2: parrafos 3, 4 y 5
|
||||||
|
|
||||||
|
entonces el parrafo 3 actua como zona de solapamiento entre ambos chunks.
|
||||||
|
|
||||||
|
### Para que sirve
|
||||||
|
|
||||||
|
- evita que una idea importante quede cortada justo en el limite entre chunks
|
||||||
|
- conserva mejor continuidad entre fragmentos vecinos
|
||||||
|
- mejora la recuperacion cuando una consulta depende de contexto compartido entre dos trozos
|
||||||
|
|
||||||
|
### Recomendacion para la v1
|
||||||
|
|
||||||
|
- usar overlap pequeno
|
||||||
|
- suficiente para conservar continuidad
|
||||||
|
- sin duplicar demasiado contenido ni disparar el coste de indexacion
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Preguntas a cerrar
|
||||||
|
|
||||||
|
1. Como sera exactamente la estrategia de chunking comun de la v1.
|
||||||
|
2. Si los chunks se construiran por estructura del documento, por longitud o con un enfoque mixto.
|
||||||
|
3. Si habra overlap entre chunks desde la primera version.
|
||||||
|
4. Que informacion estructural debe conservar cada chunk.
|
||||||
286
RAG/docs/SALIDA.md
Normal file
286
RAG/docs/SALIDA.md
Normal file
|
|
@ -0,0 +1,286 @@
|
||||||
|
# Salida
|
||||||
|
|
||||||
|
**Proyecto:** Workspace de tools IA para empresas
|
||||||
|
**Modulo:** RAG
|
||||||
|
**Ultima actualizacion:** 2026-04-02
|
||||||
|
**Ultima modificacion por:** Agente tools IA para potenciar servicios empresariales
|
||||||
|
**Estado:** En definicion
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Proposito
|
||||||
|
|
||||||
|
Definir como sale el conocimiento del sistema RAG hacia los consumidores, especialmente en forma de contexto util para agentes, aplicaciones y soluciones con IA.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Vision general
|
||||||
|
|
||||||
|
La salida del RAG no se limita a responder preguntas.
|
||||||
|
|
||||||
|
El sistema debe poder:
|
||||||
|
|
||||||
|
- recuperar contexto util
|
||||||
|
- ofrecer un mapa inicial de conocimiento
|
||||||
|
- devolver respuesta apoyada en contexto cuando haga falta
|
||||||
|
|
||||||
|
Esto responde a los dos grandes casos de uso aceptados:
|
||||||
|
|
||||||
|
- agentes expertos en empresa
|
||||||
|
- agentes expertos en proyecto o desarrollo
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tipos de salida contemplados
|
||||||
|
|
||||||
|
Se consideran validos estos tipos de salida conceptuales:
|
||||||
|
|
||||||
|
1. `retrieve`
|
||||||
|
- devuelve contexto recuperado
|
||||||
|
|
||||||
|
2. `answer`
|
||||||
|
- devuelve una respuesta apoyada en contexto
|
||||||
|
|
||||||
|
3. `context_plus_summary`
|
||||||
|
- devuelve contexto mas una sintesis util
|
||||||
|
|
||||||
|
En la practica, `retrieve` es la pieza clave para la v1, porque es la base que permite contextualizar agentes y otras herramientas.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Modo `answer`
|
||||||
|
|
||||||
|
`answer` se apoya en `retrieve`.
|
||||||
|
|
||||||
|
Flujo aplicado:
|
||||||
|
|
||||||
|
1. recuperar contexto relevante
|
||||||
|
2. construir un prompt con resumen y citas del contexto recuperado
|
||||||
|
3. pedir a un modelo de respuesta que conteste usando solo ese contexto
|
||||||
|
4. devolver la respuesta junto con citas y trazabilidad minima
|
||||||
|
|
||||||
|
### Estructura esperada
|
||||||
|
|
||||||
|
- `mode`
|
||||||
|
- `intent`
|
||||||
|
- `answer`
|
||||||
|
- `summary`
|
||||||
|
- `topics`
|
||||||
|
- `criticalPoints`
|
||||||
|
- `citations`
|
||||||
|
- `scope`
|
||||||
|
|
||||||
|
### Por que se diseña asi
|
||||||
|
|
||||||
|
- mantiene a `retrieve` como nucleo del sistema
|
||||||
|
- evita que la respuesta se desconecte del contexto recuperado
|
||||||
|
- permite que el mismo RAG sirva tanto para cargar contexto como para responder directamente
|
||||||
|
- conserva trazabilidad para revisar de donde sale la respuesta
|
||||||
|
|
||||||
|
### Limite importante
|
||||||
|
|
||||||
|
La calidad de `answer` depende directamente de la calidad de `retrieve`.
|
||||||
|
|
||||||
|
Si la recuperacion no trae el fragmento concreto adecuado, la respuesta puede quedar demasiado prudente o incompleta aunque el modo `answer` funcione correctamente.
|
||||||
|
|
||||||
|
En modo `codigo`, `answer` incluye tambien `sectionTitle` y rango de lineas cuando estan disponibles.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Mejora aplicada a `retrieve specific`
|
||||||
|
|
||||||
|
Para consultas operativas frecuentes, `retrieve specific` ya no se limita a una sola busqueda semantica directa.
|
||||||
|
|
||||||
|
Se ha mejorado con:
|
||||||
|
|
||||||
|
- subconsultas internas segun la intencion detectada
|
||||||
|
- reordenacion por alineacion con la pregunta
|
||||||
|
- refuerzo de documentos especialmente relevantes para ciertos casos, como backlog, reglas, historial o indice documental
|
||||||
|
|
||||||
|
**Objetivo de esta mejora:**
|
||||||
|
- que preguntas practicas como "que tenemos pendiente" o "cuales son las reglas" lleguen antes a los fragmentos verdaderamente utiles
|
||||||
|
- mejorar `answer` sin tener que cambiar su arquitectura
|
||||||
|
|
||||||
|
Tambien se ha ampliado para consultas tecnicas o conceptuales del modo `codigo`, por ejemplo preguntas sobre funciones, IDs, servicios, endpoints o flujo interno.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Retrieve inicial y retrieve especifico
|
||||||
|
|
||||||
|
Se adopta esta separacion conceptual:
|
||||||
|
|
||||||
|
1. `retrieve` inicial o de arranque
|
||||||
|
- entrega un mapa general del dominio
|
||||||
|
- orienta al agente sobre el panorama del conocimiento disponible
|
||||||
|
- no busca profundizar en exceso
|
||||||
|
|
||||||
|
2. `retrieve` especifico
|
||||||
|
- profundiza en un tema, duda o necesidad concreta
|
||||||
|
- recupera contenido mas cercano a la consulta puntual
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Estructura acordada para `retrieve` inicial
|
||||||
|
|
||||||
|
Se adopta esta estructura base para el `retrieve` inicial.
|
||||||
|
|
||||||
|
### 1. `mode`
|
||||||
|
|
||||||
|
Indica el modo de recuperacion aplicado.
|
||||||
|
|
||||||
|
Valores previstos:
|
||||||
|
- `documental`
|
||||||
|
- `codigo`
|
||||||
|
- `auto`
|
||||||
|
|
||||||
|
**Por que se incluye:**
|
||||||
|
- permite trazabilidad de como se hizo la recuperacion
|
||||||
|
- ayuda al consumidor a entender el tipo de lectura aplicada
|
||||||
|
|
||||||
|
### 2. `intent`
|
||||||
|
|
||||||
|
Indica la intencion de la recuperacion.
|
||||||
|
|
||||||
|
Valores previstos:
|
||||||
|
- `bootstrap`
|
||||||
|
- `specific`
|
||||||
|
|
||||||
|
Para el `retrieve` inicial, el valor esperado es `bootstrap`.
|
||||||
|
|
||||||
|
**Por que se incluye:**
|
||||||
|
- separa claramente el mapa inicial de una consulta puntual
|
||||||
|
- evita confundir una carga de contexto general con una busqueda de detalle
|
||||||
|
|
||||||
|
### 3. `summary`
|
||||||
|
|
||||||
|
Resumen corto del contexto recuperado.
|
||||||
|
|
||||||
|
**Por que se incluye:**
|
||||||
|
- da una lectura rapida del panorama general
|
||||||
|
- permite al agente orientarse sin tener que leer todos los fragmentos completos de inmediato
|
||||||
|
|
||||||
|
### 4. `topics`
|
||||||
|
|
||||||
|
Lista de temas principales detectados.
|
||||||
|
|
||||||
|
**Por que se incluye:**
|
||||||
|
- ayuda a que el agente tenga un mapa tematico del dominio
|
||||||
|
- sirve como guia para decidir en que profundizar despues
|
||||||
|
|
||||||
|
### 5. `critical_points`
|
||||||
|
|
||||||
|
Lista de puntos criticos o prioritarios.
|
||||||
|
|
||||||
|
Estos puntos pueden salir de dos vias:
|
||||||
|
- inferidos por el sistema
|
||||||
|
- marcados explicitamente como criticos en las fuentes o durante la ingesta
|
||||||
|
|
||||||
|
**Por que se incluye:**
|
||||||
|
- algunos temas deben mantenerse visibles aunque el resumen general sea breve
|
||||||
|
- permite priorizar conocimiento especialmente sensible o importante
|
||||||
|
|
||||||
|
### 6. `items`
|
||||||
|
|
||||||
|
Lista de fragmentos o piezas recuperadas.
|
||||||
|
|
||||||
|
Cada item debe poder incluir como minimo:
|
||||||
|
- `chunk_id`
|
||||||
|
- `document_id`
|
||||||
|
- `source_id`
|
||||||
|
- `title`
|
||||||
|
- `content`
|
||||||
|
- `score`
|
||||||
|
|
||||||
|
**Por que se incluye:**
|
||||||
|
- es la parte trazable y reutilizable del contexto
|
||||||
|
- permite que un agente o sistema no dependa solo de una sintesis
|
||||||
|
- mantiene acceso a las piezas concretas del conocimiento recuperado
|
||||||
|
|
||||||
|
### 7. `follow_up_refs`
|
||||||
|
|
||||||
|
Referencias o pistas para profundizar despues.
|
||||||
|
|
||||||
|
Pueden apuntar a:
|
||||||
|
- temas
|
||||||
|
- documentos
|
||||||
|
- secciones
|
||||||
|
- preguntas sugeridas
|
||||||
|
|
||||||
|
**Por que se incluye:**
|
||||||
|
- convierte el `retrieve` inicial en una guia de navegacion
|
||||||
|
- facilita pasar de un mapa general a una consulta especifica sin perder el hilo
|
||||||
|
|
||||||
|
### 8. `scope`
|
||||||
|
|
||||||
|
Permite acotar la recuperacion a una fuente, workspace, proyecto o conjunto de tags concreto.
|
||||||
|
|
||||||
|
Campos previstos:
|
||||||
|
- `sourceId`
|
||||||
|
- `sourceRef`
|
||||||
|
- `tags`
|
||||||
|
|
||||||
|
**Por que se incluye:**
|
||||||
|
- evita que el bootstrap o la consulta especifica carguen contexto de todo el sistema cuando solo interesa un workspace o proyecto
|
||||||
|
- prepara el RAG para convivir con multiples fuentes sin mezclar panoramas
|
||||||
|
- hace util el retrieve inicial como carga contextual enfocada, no solo global
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Que representa realmente este `retrieve` inicial
|
||||||
|
|
||||||
|
No es una respuesta final.
|
||||||
|
|
||||||
|
Es un paquete de contexto de arranque pensado para:
|
||||||
|
|
||||||
|
- dar orientacion general
|
||||||
|
- volver experto al agente en ese dominio durante la sesion de trabajo
|
||||||
|
- servir como base para posteriores consultas especificas
|
||||||
|
|
||||||
|
Su papel es parecido al de una carga inicial de contexto o mapa operativo del conocimiento.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Estrategia aplicada al `retrieve` inicial
|
||||||
|
|
||||||
|
El `bootstrap` no debe limitarse a ejecutar una sola busqueda semantica amplia.
|
||||||
|
|
||||||
|
Se adopta una estrategia de recuperacion orientada a mapa inicial:
|
||||||
|
|
||||||
|
- lanzar varias subconsultas relacionadas con panorama general, documentacion principal, pendientes y reglas del dominio
|
||||||
|
- fusionar resultados sin duplicar chunks
|
||||||
|
- ordenar los fragmentos priorizando relevancia y utilidad panoramica
|
||||||
|
- sintetizar un resumen final que ayude a orientarse rapido
|
||||||
|
|
||||||
|
**Por que se decide asi:**
|
||||||
|
- un retrieve inicial necesita mas cobertura que una consulta puntual
|
||||||
|
- una sola consulta suele dejar fuera piezas clave del mapa general
|
||||||
|
- combinar subconsultas mejora la calidad del contexto de arranque sin cambiar el modelo de embeddings ni la base vectorial
|
||||||
|
|
||||||
|
**Resultado esperado:**
|
||||||
|
- mejor identificacion de documentos base
|
||||||
|
- mejor panorama del workspace o proyecto
|
||||||
|
- referencias mas utiles para profundizar despues
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Por que se ha elegido esta estructura
|
||||||
|
|
||||||
|
- porque el RAG debe servir para mucho mas que preguntas aisladas
|
||||||
|
- porque el usuario quiere que un agente pueda arrancar con una vision rica del tema o proyecto
|
||||||
|
- porque un mapa inicial ayuda a profundizar despues sin saturar contexto desde el principio
|
||||||
|
- porque mezcla panorama, criticidad, trazabilidad y capacidad de profundizacion posterior
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ajustes futuros posibles
|
||||||
|
|
||||||
|
Aunque esta estructura queda aceptada para la v1, se asume que en la practica podria requerir ajustes.
|
||||||
|
|
||||||
|
Posibles cambios futuros:
|
||||||
|
- afinar el nivel de detalle de `summary`
|
||||||
|
- separar mejor `topics` y `critical_points`
|
||||||
|
- enriquecer `items` con mas metadatos
|
||||||
|
- hacer que `follow_up_refs` sea mas estructurado
|
||||||
|
- incorporar niveles de confianza o explicacion de relevancia
|
||||||
|
|
||||||
|
La estructura actual se acepta como una base suficientemente clara para empezar a construir y evaluar.
|
||||||
102
RAG/docs/SISTEMA_RAG_BASE.md
Normal file
102
RAG/docs/SISTEMA_RAG_BASE.md
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
# Sistema RAG base
|
||||||
|
|
||||||
|
**Proyecto:** Workspace de tools IA para empresas
|
||||||
|
**Modulo:** RAG
|
||||||
|
**Ultima actualizacion:** 2026-04-02
|
||||||
|
**Ultima modificacion por:** Agente tools IA para potenciar servicios empresariales
|
||||||
|
**Estado:** En definicion
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Proposito
|
||||||
|
|
||||||
|
Definir un sistema RAG base, bien planteado y reutilizable, que pueda integrarse con rapidez en proyectos de clientes y tambien servir como base para tools internas del workspace.
|
||||||
|
|
||||||
|
La prueba inicial con la carpeta `docs/` de este workspace se utilizara solo como ejemplo practico y entorno real de validacion, no como limite del sistema.
|
||||||
|
|
||||||
|
Desde la v1, el sistema debe contemplar tambien PDFs como parte valida de las fuentes documentales de entrada.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Que es lo que queremos lograr
|
||||||
|
|
||||||
|
Queremos construir una base RAG que permita:
|
||||||
|
|
||||||
|
- indexar informacion relevante de un proyecto
|
||||||
|
- recuperar contexto util en funcion de una consulta
|
||||||
|
- entregar ese contexto a un agente o servicio para mejorar sus respuestas o decisiones
|
||||||
|
- integrarse con rapidez en otros proyectos sin rehacer la arquitectura cada vez
|
||||||
|
|
||||||
|
En terminos practicos, el objetivo es disponer de una pieza reutilizable que convierta documentacion, conocimiento interno y otras fuentes en contexto accesible para agentes y servicios de IA.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Vision del sistema
|
||||||
|
|
||||||
|
Este sistema RAG debe quedar preparado para trabajar con diferentes tipos de fuentes, por ejemplo:
|
||||||
|
|
||||||
|
- documentacion de proyecto
|
||||||
|
- bases de conocimiento internas
|
||||||
|
- contenido operativo de clientes
|
||||||
|
- archivos tecnicos
|
||||||
|
- datos exportados desde otros sistemas
|
||||||
|
|
||||||
|
La carpeta `docs/` del workspace servira como primer caso real de prueba porque permite validar resultados con un conjunto de informacion pequeno, claro y facil de revisar.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Objetivos principales
|
||||||
|
|
||||||
|
1. Diseñar una base RAG reutilizable y desacoplada del proyecto de prueba.
|
||||||
|
2. Permitir que pueda conectarse a otros servicios de IA o agentes con una integracion sencilla.
|
||||||
|
3. Hacer que el sistema funcione con fuentes reales, empezando por `docs/`.
|
||||||
|
4. Facilitar continuidad entre sesiones, agentes y proyectos gracias a recuperacion de contexto relevante.
|
||||||
|
5. Dejar lista una arquitectura que luego pueda crecer hacia casos mas complejos.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Caso de prueba inicial
|
||||||
|
|
||||||
|
La primera validacion se hara con la carpeta `docs/` de este workspace.
|
||||||
|
|
||||||
|
La idea es comprobar si un agente puede:
|
||||||
|
|
||||||
|
- localizar rapidamente informacion relevante
|
||||||
|
- entender que se ha hecho y que queda pendiente
|
||||||
|
- responder con base documental sin releer todo manualmente
|
||||||
|
- usar la documentacion del workspace como conocimiento ampliado en tiempo de consulta
|
||||||
|
|
||||||
|
Esto servira para evaluar si la base conceptual y tecnica del RAG esta bien construida.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Principios del sistema
|
||||||
|
|
||||||
|
- Reutilizable: debe servir para este workspace y para proyectos de clientes.
|
||||||
|
- Modular: ingesta, indexacion, recuperacion y consumo deben poder evolucionar por separado.
|
||||||
|
- Simple al inicio: la primera version debe ser pequena y validable.
|
||||||
|
- Escalable: debe poder crecer a nuevas fuentes y nuevos casos de uso.
|
||||||
|
- Trazable: el contexto recuperado debe indicar de donde sale.
|
||||||
|
- Facil de integrar: debe poder conectarse rapidamente a agentes, tools o servicios.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Resultado esperado
|
||||||
|
|
||||||
|
Si el sistema esta bien planteado, deberiamos poder usarlo como una capa de contexto ampliado para agentes y servicios, logrando que consulten conocimiento relevante de forma rapida y util, sin depender de tener toda la informacion cargada manualmente en cada sesion.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Alcance de este documento
|
||||||
|
|
||||||
|
Este documento define el que y el para que del sistema RAG base.
|
||||||
|
|
||||||
|
En documentos posteriores de esta carpeta se podran detallar, por separado:
|
||||||
|
|
||||||
|
- arquitectura propuesta
|
||||||
|
- flujo de ingesta
|
||||||
|
- estrategia de chunking
|
||||||
|
- embeddings y almacenamiento
|
||||||
|
- interfaz de consulta
|
||||||
|
- pruebas con `docs/`
|
||||||
|
- criterios de evaluacion de resultados
|
||||||
221
RAG/docs/STACK_TECNICO_V1.md
Normal file
221
RAG/docs/STACK_TECNICO_V1.md
Normal file
|
|
@ -0,0 +1,221 @@
|
||||||
|
# Stack tecnico v1
|
||||||
|
|
||||||
|
**Proyecto:** Workspace de tools IA para empresas
|
||||||
|
**Modulo:** RAG
|
||||||
|
**Ultima actualizacion:** 2026-04-02
|
||||||
|
**Ultima modificacion por:** Agente tools IA para potenciar servicios empresariales
|
||||||
|
**Estado:** Acordado para v1
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Proposito
|
||||||
|
|
||||||
|
Definir el stack tecnico minimo con el que se construira la primera version funcional del RAG.
|
||||||
|
|
||||||
|
Las decisiones tomadas aqui deben entenderse como base estable del sistema, no como elecciones provisionales pensadas para ser sustituidas inmediatamente en una siguiente version.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Stack acordado
|
||||||
|
|
||||||
|
### Runtime y lenguaje
|
||||||
|
|
||||||
|
- `Node.js`
|
||||||
|
- `TypeScript`
|
||||||
|
|
||||||
|
**Por que:**
|
||||||
|
- equilibrio entre velocidad de desarrollo y estructura
|
||||||
|
- buen encaje con APIs, tools y futuras integraciones
|
||||||
|
- facilita evolucion posterior hacia MCP u otras capas de consumo
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Backend HTTP
|
||||||
|
|
||||||
|
- `Express`
|
||||||
|
|
||||||
|
**Por que:**
|
||||||
|
- simplicidad de arranque
|
||||||
|
- ecosistema amplio
|
||||||
|
- suficiente para esta v1
|
||||||
|
- el rendimiento del framework no es el cuello de botella principal del sistema ahora mismo
|
||||||
|
|
||||||
|
**Encaje operativo:**
|
||||||
|
- compatible con despliegue local y en VPS
|
||||||
|
- adecuado para montarlo como servicio en EasyPanel dentro de un contenedor propio
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Vector store
|
||||||
|
|
||||||
|
- `Qdrant`
|
||||||
|
|
||||||
|
**Por que:**
|
||||||
|
- pensado especificamente para busqueda vectorial
|
||||||
|
- mas adecuado para un RAG real que usar solo una base de datos generalista
|
||||||
|
- deja una base mejor para evolucion posterior que una solucion demasiado provisional
|
||||||
|
|
||||||
|
**Matiz:**
|
||||||
|
- `SQLite` podria servir como apoyo auxiliar para ciertos metadatos o control local, pero no se toma como nucleo del retrieval vectorial
|
||||||
|
- en un escenario con EasyPanel, `Qdrant` encaja bien como servicio separado o contenedor adjunto al servicio principal del RAG
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Embeddings
|
||||||
|
|
||||||
|
- proveedor desacoplado mediante una interfaz interna del sistema
|
||||||
|
|
||||||
|
**Por que:**
|
||||||
|
- evita atarnos a un unico proveedor desde el inicio
|
||||||
|
- permite cambiar por coste, calidad o disponibilidad sin rehacer el nucleo del RAG
|
||||||
|
- facilita pruebas comparativas en el futuro
|
||||||
|
|
||||||
|
**Aclaracion importante:**
|
||||||
|
Esto debe entenderse como una capa de abstraccion o adaptador interno para proveedores de embeddings, no como una dependencia directa del resto del sistema hacia un proveedor concreto.
|
||||||
|
|
||||||
|
**Decision cerrada para la base estable del sistema:**
|
||||||
|
- `Qwen3 Embedding 8B`
|
||||||
|
|
||||||
|
**Por que se elige este modelo:**
|
||||||
|
- encaja con el objetivo de que la v1 ya sea una base estable y no una eleccion pensada para cambiar pronto
|
||||||
|
- sirve tanto para recuperacion textual como para recuperacion de codigo
|
||||||
|
- ofrece capacidades multilingues y manejo de texto largo
|
||||||
|
- esta disponible ahora en OpenRouter para poder arrancar
|
||||||
|
- puede desplegarse de forma local mas adelante, manteniendo coherencia con la arquitectura deseada
|
||||||
|
|
||||||
|
**Referencia de comparacion, no decision principal:**
|
||||||
|
- `OpenAI text-embedding-3-large` se considera una referencia valida de calidad comercial, pero no se adopta como base del sistema porque no encaja igual de bien con el objetivo de despliegue local futuro
|
||||||
|
|
||||||
|
**Consecuencia de cambiar de modelo de embeddings:**
|
||||||
|
- los vectores generados por modelos distintos no deben mezclarse como si vivieran en el mismo espacio semantico
|
||||||
|
- si se cambia de modelo de embeddings, normalmente hay que re-embeddear el contenido afectado
|
||||||
|
- esto no obliga a perder trazabilidad si el sistema guarda que modelo y que version genero cada embedding
|
||||||
|
|
||||||
|
**Criterio de diseño adoptado:**
|
||||||
|
- cada embedding debe registrar al menos su `embedding_provider`, `embedding_model` y `embedding_dimensions`
|
||||||
|
- el sistema debe poder saber con que modelo fue indexado cada documento o chunk
|
||||||
|
- el retrieval debe consultar dentro de un indice o coleccion coherente con el modelo de embeddings usado
|
||||||
|
|
||||||
|
**Configuracion base prevista:**
|
||||||
|
- modelo base: `Qwen3 Embedding 8B`
|
||||||
|
- acceso inicial: proveedor externo compatible
|
||||||
|
- acceso futuro previsto: despliegue local en red propia manteniendo la misma abstraccion del proveedor
|
||||||
|
|
||||||
|
**Direccion recomendada:**
|
||||||
|
- tratar cada combinacion relevante de proveedor y modelo como una version de indexacion
|
||||||
|
- permitir reindexacion controlada cuando se quiera migrar a otro modelo
|
||||||
|
- evitar mezclar embeddings de modelos distintos en la misma logica de recuperacion
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Parsing documental
|
||||||
|
|
||||||
|
Soporte inicial previsto para:
|
||||||
|
|
||||||
|
- `md`
|
||||||
|
- `txt`
|
||||||
|
- `pdf`
|
||||||
|
- codigo fuente del proyecto
|
||||||
|
|
||||||
|
**Por que:**
|
||||||
|
- cubre documentacion habitual del workspace y de escenarios reales de empresa
|
||||||
|
- el soporte de PDF es obligatorio desde la v1
|
||||||
|
|
||||||
|
**Matiz:**
|
||||||
|
- la v1 contempla PDFs con texto extraible
|
||||||
|
- OCR para PDFs escaneados o de baja calidad queda como ampliacion futura
|
||||||
|
|
||||||
|
Extensiones de codigo ya soportadas:
|
||||||
|
- `ts`
|
||||||
|
- `tsx`
|
||||||
|
- `js`
|
||||||
|
- `jsx`
|
||||||
|
- `mjs`
|
||||||
|
- `cjs`
|
||||||
|
- `py`
|
||||||
|
- `json`
|
||||||
|
- `yml`
|
||||||
|
- `yaml`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Chunking y pipeline interno
|
||||||
|
|
||||||
|
- implementacion propia en TypeScript
|
||||||
|
|
||||||
|
**Por que:**
|
||||||
|
- el comportamiento del chunking forma parte central del diseño del sistema
|
||||||
|
- ya se han acordado reglas propias que conviene controlar directamente
|
||||||
|
- evita depender demasiado pronto de una abstraccion externa que no siga nuestras decisiones
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Forma de acceso
|
||||||
|
|
||||||
|
- API HTTP como base principal
|
||||||
|
|
||||||
|
**Por que:**
|
||||||
|
- permite que otras apps, agentes y sistemas consuman el RAG con facilidad
|
||||||
|
- permite pruebas inmediatas desde terminal
|
||||||
|
- encaja bien con una futura capa MCP
|
||||||
|
|
||||||
|
Endpoints funcionales actuales:
|
||||||
|
- `GET /health`
|
||||||
|
- `POST /ingest`
|
||||||
|
- `POST /retrieve`
|
||||||
|
- `POST /answer`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Configuracion local de claves
|
||||||
|
|
||||||
|
Se deja previsto un archivo local `RAG/.env.local` para trabajar claves y configuraciones sensibles sin incluirlas en el repositorio.
|
||||||
|
|
||||||
|
Uso previsto:
|
||||||
|
- `EMBEDDING_API_KEY` para OpenRouter o proveedor compatible
|
||||||
|
- credenciales de `Qdrant` si aplican
|
||||||
|
- futuras claves de servicios asociados
|
||||||
|
|
||||||
|
Este archivo esta ignorado por Git.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Encaje con EasyPanel
|
||||||
|
|
||||||
|
La estructura tecnica inicial se ha planteado para que el modulo pueda ejecutarse:
|
||||||
|
|
||||||
|
- en local durante desarrollo
|
||||||
|
- en un VPS Debian gestionado desde EasyPanel
|
||||||
|
|
||||||
|
Direccion operativa recomendada:
|
||||||
|
|
||||||
|
- un contenedor para la API del RAG
|
||||||
|
- un servicio de `Qdrant` separado o adjunto segun convenga
|
||||||
|
- variables de entorno gestionadas desde EasyPanel
|
||||||
|
|
||||||
|
Esto mantiene simple el despliegue y evita acoplar el codigo a una unica maquina o ruta local.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Direccion general del stack
|
||||||
|
|
||||||
|
La v1 se construira con una idea clara:
|
||||||
|
|
||||||
|
- base simple de poner en marcha
|
||||||
|
- lo bastante seria para servir en casos reales
|
||||||
|
- preparada para evolucionar sin rehacer todo al cambiar proveedor o ampliar capacidades
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Decisiones ya cerradas que condicionan este stack
|
||||||
|
|
||||||
|
- el RAG se pensara como servicio
|
||||||
|
- PDFs entran desde la v1
|
||||||
|
- el modo `codigo` sigue siendo parte de la direccion del sistema aunque no sea la primera implementacion
|
||||||
|
- la salida del RAG debe servir tambien para contexto, no solo para respuestas finales
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pendiente inmediato
|
||||||
|
|
||||||
|
Generar la imagen Docker final del servicio y dejarlo listo para desplegar en EasyPanel.
|
||||||
1836
RAG/package-lock.json
generated
Normal file
1836
RAG/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
26
RAG/package.json
Normal file
26
RAG/package.json
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
{
|
||||||
|
"name": "rag-service",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "tsx watch src/server.ts",
|
||||||
|
"build": "tsc -p tsconfig.json",
|
||||||
|
"start": "node dist/server.js",
|
||||||
|
"check": "tsc --noEmit -p tsconfig.json"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@qdrant/js-client-rest": "^1.15.0",
|
||||||
|
"dotenv": "^16.4.5",
|
||||||
|
"express": "^4.21.2",
|
||||||
|
"openai": "^4.104.0",
|
||||||
|
"pdf-parse": "^1.1.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/express": "^5.0.1",
|
||||||
|
"@types/node": "^22.15.3",
|
||||||
|
"@types/pdf-parse": "^1.1.4",
|
||||||
|
"tsx": "^4.19.3",
|
||||||
|
"typescript": "^5.8.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
86
RAG/src/app.ts
Normal file
86
RAG/src/app.ts
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
import express from "express";
|
||||||
|
import { env } from "./config/env.js";
|
||||||
|
import { AnswerService } from "./modules/answer/service.js";
|
||||||
|
import { IngestService } from "./modules/ingest/service.js";
|
||||||
|
import { OpenRouterEmbeddingProvider } from "./modules/embeddings/provider.js";
|
||||||
|
import { documentalChunkingPolicy } from "./modules/process/chunking.js";
|
||||||
|
import { RetrieveService } from "./modules/retrieve/service.js";
|
||||||
|
import { supportedParserExtensions } from "./modules/parsers/parser-registry.js";
|
||||||
|
import { QdrantVectorStoreClient } from "./modules/vectorstore/client.js";
|
||||||
|
import type { ChunkMode, RetrieveIntent, RetrieveScope } from "./shared/types/rag.js";
|
||||||
|
|
||||||
|
export function createApp() {
|
||||||
|
const app = express();
|
||||||
|
const embeddingProvider = new OpenRouterEmbeddingProvider();
|
||||||
|
const vectorStore = new QdrantVectorStoreClient();
|
||||||
|
const ingestService = new IngestService(embeddingProvider, vectorStore);
|
||||||
|
const retrieveService = new RetrieveService(embeddingProvider, vectorStore);
|
||||||
|
const answerService = new AnswerService(retrieveService);
|
||||||
|
|
||||||
|
app.use(express.json({ limit: "5mb" }));
|
||||||
|
|
||||||
|
app.get("/health", async (_req, res) => {
|
||||||
|
const vectorHealth = await vectorStore.healthcheck();
|
||||||
|
res.json({
|
||||||
|
ok: true,
|
||||||
|
service: "rag",
|
||||||
|
environment: env.nodeEnv,
|
||||||
|
embeddings: {
|
||||||
|
provider: embeddingProvider.providerName,
|
||||||
|
model: embeddingProvider.modelName
|
||||||
|
},
|
||||||
|
vectorStore: vectorHealth,
|
||||||
|
parsers: supportedParserExtensions(),
|
||||||
|
chunking: documentalChunkingPolicy
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post("/ingest", async (req, res) => {
|
||||||
|
try {
|
||||||
|
const result = await ingestService.ingest(req.body);
|
||||||
|
res.status(202).json(result);
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ ok: false, error: error instanceof Error ? error.message : "Unknown ingest error" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post("/retrieve", async (req, res) => {
|
||||||
|
try {
|
||||||
|
const mode = (req.body.mode ?? "auto") as ChunkMode;
|
||||||
|
const intent = (req.body.intent ?? "specific") as RetrieveIntent;
|
||||||
|
const query = String(req.body.query ?? "");
|
||||||
|
const scope: RetrieveScope | undefined = req.body.scope
|
||||||
|
? {
|
||||||
|
sourceId: req.body.scope.sourceId,
|
||||||
|
sourceRef: req.body.scope.sourceRef,
|
||||||
|
tags: Array.isArray(req.body.scope.tags) ? req.body.scope.tags.map(String) : undefined
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
const result = await retrieveService.retrieve(mode, intent, query, scope);
|
||||||
|
res.json(result);
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ ok: false, error: error instanceof Error ? error.message : "Unknown retrieve error" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post("/answer", async (req, res) => {
|
||||||
|
try {
|
||||||
|
const mode = (req.body.mode ?? "auto") as ChunkMode;
|
||||||
|
const intent = (req.body.intent ?? "specific") as RetrieveIntent;
|
||||||
|
const query = String(req.body.query ?? "");
|
||||||
|
const scope: RetrieveScope | undefined = req.body.scope
|
||||||
|
? {
|
||||||
|
sourceId: req.body.scope.sourceId,
|
||||||
|
sourceRef: req.body.scope.sourceRef,
|
||||||
|
tags: Array.isArray(req.body.scope.tags) ? req.body.scope.tags.map(String) : undefined
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
const result = await answerService.answer(mode, intent, query, scope);
|
||||||
|
res.json(result);
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ ok: false, error: error instanceof Error ? error.message : "Unknown answer error" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return app;
|
||||||
|
}
|
||||||
28
RAG/src/config/env.ts
Normal file
28
RAG/src/config/env.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { config as loadEnv } from "dotenv";
|
||||||
|
|
||||||
|
loadEnv();
|
||||||
|
loadEnv({ path: ".env.local", override: true });
|
||||||
|
|
||||||
|
function requireEnv(name: string, fallback?: string): string {
|
||||||
|
const value = process.env[name] ?? fallback;
|
||||||
|
if (!value) {
|
||||||
|
throw new Error(`Missing environment variable: ${name}`);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const env = {
|
||||||
|
nodeEnv: process.env.NODE_ENV ?? "development",
|
||||||
|
port: Number(process.env.PORT ?? 3000),
|
||||||
|
qdrantUrl: requireEnv("QDRANT_URL", "http://localhost:6333"),
|
||||||
|
qdrantApiKey: process.env.QDRANT_API_KEY ?? "",
|
||||||
|
qdrantCollection: requireEnv("QDRANT_COLLECTION", "rag_chunks"),
|
||||||
|
embeddingProvider: requireEnv("EMBEDDING_PROVIDER", "openrouter"),
|
||||||
|
embeddingModel: requireEnv("EMBEDDING_MODEL", "qwen/qwen3-embedding-8b"),
|
||||||
|
embeddingBaseUrl: requireEnv("EMBEDDING_BASE_URL", "https://openrouter.ai/api/v1"),
|
||||||
|
embeddingApiKey: process.env.EMBEDDING_API_KEY ?? "",
|
||||||
|
answerProvider: requireEnv("ANSWER_PROVIDER", "openrouter"),
|
||||||
|
answerModel: requireEnv("ANSWER_MODEL", "openai/gpt-4.1-mini"),
|
||||||
|
answerBaseUrl: requireEnv("ANSWER_BASE_URL", "https://openrouter.ai/api/v1"),
|
||||||
|
answerApiKey: process.env.ANSWER_API_KEY ?? process.env.EMBEDDING_API_KEY ?? ""
|
||||||
|
} as const;
|
||||||
78
RAG/src/modules/answer/service.ts
Normal file
78
RAG/src/modules/answer/service.ts
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
import OpenAI from "openai";
|
||||||
|
import { env } from "../../config/env.js";
|
||||||
|
import type { AnswerResponse, ChunkMode, RetrieveIntent, RetrieveScope, RetrievedItem } from "../../shared/types/rag.js";
|
||||||
|
import { RetrieveService } from "../retrieve/service.js";
|
||||||
|
|
||||||
|
function buildPrompt(query: string, summary: string, items: RetrievedItem[]): string {
|
||||||
|
const context = items
|
||||||
|
.map((item, index) => [
|
||||||
|
`Fuente ${index + 1}: ${item.title}`,
|
||||||
|
item.sectionTitle ? `section_title: ${item.sectionTitle}` : undefined,
|
||||||
|
`chunk_id: ${item.chunkId}`,
|
||||||
|
`document_id: ${item.documentId}`,
|
||||||
|
item.startLine ? `line_range: ${item.startLine}-${item.endLine ?? item.startLine}` : undefined,
|
||||||
|
item.content
|
||||||
|
].filter(Boolean).join("\n"))
|
||||||
|
.join("\n\n---\n\n");
|
||||||
|
|
||||||
|
return [
|
||||||
|
"Responde usando solo el contexto recuperado.",
|
||||||
|
"Si el contexto no es suficiente, dilo claramente.",
|
||||||
|
"Prioriza exactitud y brevedad.",
|
||||||
|
`Resumen del contexto: ${summary}`,
|
||||||
|
`Pregunta: ${query}`,
|
||||||
|
"Contexto recuperado:",
|
||||||
|
context
|
||||||
|
].join("\n\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AnswerService {
|
||||||
|
private readonly client: OpenAI;
|
||||||
|
|
||||||
|
constructor(private readonly retrieveService: RetrieveService) {
|
||||||
|
this.client = new OpenAI({
|
||||||
|
apiKey: env.answerApiKey,
|
||||||
|
baseURL: env.answerBaseUrl
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async answer(mode: ChunkMode, intent: RetrieveIntent, query: string, scope?: RetrieveScope): Promise<AnswerResponse> {
|
||||||
|
if (!env.answerApiKey) {
|
||||||
|
throw new Error("Missing ANSWER_API_KEY for answer provider");
|
||||||
|
}
|
||||||
|
|
||||||
|
const retrieved = await this.retrieveService.retrieve(mode, intent, query, scope);
|
||||||
|
const completion = await this.client.chat.completions.create({
|
||||||
|
model: env.answerModel,
|
||||||
|
temperature: 0.2,
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
role: "system",
|
||||||
|
content: "Eres un asistente RAG. Respondes con precision usando solamente el contexto recuperado. Si falta informacion, lo indicas sin inventar datos."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: "user",
|
||||||
|
content: buildPrompt(query, retrieved.summary, retrieved.items)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
mode: retrieved.mode,
|
||||||
|
intent: retrieved.intent,
|
||||||
|
answer: completion.choices[0]?.message?.content?.trim() || "No se pudo generar respuesta.",
|
||||||
|
summary: retrieved.summary,
|
||||||
|
topics: retrieved.topics,
|
||||||
|
criticalPoints: retrieved.criticalPoints,
|
||||||
|
citations: retrieved.items.map((item) => ({
|
||||||
|
chunkId: item.chunkId,
|
||||||
|
documentId: item.documentId,
|
||||||
|
title: item.title,
|
||||||
|
sectionTitle: item.sectionTitle,
|
||||||
|
startLine: item.startLine,
|
||||||
|
endLine: item.endLine
|
||||||
|
})),
|
||||||
|
scope: retrieved.scope
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
39
RAG/src/modules/embeddings/provider.ts
Normal file
39
RAG/src/modules/embeddings/provider.ts
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
import OpenAI from "openai";
|
||||||
|
import { env } from "../../config/env.js";
|
||||||
|
|
||||||
|
export interface EmbeddingProvider {
|
||||||
|
readonly providerName: string;
|
||||||
|
readonly modelName: string;
|
||||||
|
readonly dimensions?: number;
|
||||||
|
embed(input: string[]): Promise<number[][]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class OpenRouterEmbeddingProvider implements EmbeddingProvider {
|
||||||
|
readonly providerName: string;
|
||||||
|
readonly modelName: string;
|
||||||
|
readonly dimensions?: number;
|
||||||
|
private readonly client: OpenAI;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.providerName = env.embeddingProvider;
|
||||||
|
this.modelName = env.embeddingModel;
|
||||||
|
this.client = new OpenAI({
|
||||||
|
apiKey: env.embeddingApiKey,
|
||||||
|
baseURL: env.embeddingBaseUrl
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async embed(input: string[]): Promise<number[][]> {
|
||||||
|
if (!env.embeddingApiKey) {
|
||||||
|
throw new Error("Missing EMBEDDING_API_KEY for embedding provider");
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await this.client.embeddings.create({
|
||||||
|
model: this.modelName,
|
||||||
|
input,
|
||||||
|
encoding_format: "float"
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data.map((item) => item.embedding);
|
||||||
|
}
|
||||||
|
}
|
||||||
91
RAG/src/modules/ingest/service.ts
Normal file
91
RAG/src/modules/ingest/service.ts
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
import path from "node:path";
|
||||||
|
import { parseDocument, isSupportedDocument } from "../parsers/parser-registry.js";
|
||||||
|
import { chunkDocument, codeChunkingPolicy, documentalChunkingPolicy } from "../process/chunking.js";
|
||||||
|
import type { EmbeddingProvider } from "../embeddings/provider.js";
|
||||||
|
import type { VectorStoreClient } from "../vectorstore/client.js";
|
||||||
|
import type { IngestResult, IngestSourceInput, IngestedChunk } from "../../shared/types/rag.js";
|
||||||
|
import { buildChunkId, buildDocumentId, buildQdrantPointId, buildSourceId, normalizeDocumentKey } from "../../shared/utils/ids.js";
|
||||||
|
import { listFilesRecursively } from "../../shared/utils/files.js";
|
||||||
|
import { env } from "../../config/env.js";
|
||||||
|
|
||||||
|
export class IngestService {
|
||||||
|
constructor(
|
||||||
|
private readonly embeddingProvider: EmbeddingProvider,
|
||||||
|
private readonly vectorStore: VectorStoreClient
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async ingest(source: IngestSourceInput): Promise<IngestResult> {
|
||||||
|
const sourceId = source.sourceId ?? buildSourceId(source.sourceType, source.sourceRef);
|
||||||
|
const discoveredFiles = await this.resolveInputFiles(source);
|
||||||
|
const supportedFiles = discoveredFiles.filter(isSupportedDocument);
|
||||||
|
let documentsProcessed = 0;
|
||||||
|
let chunksStored = 0;
|
||||||
|
|
||||||
|
for (const filePath of supportedFiles) {
|
||||||
|
const parsed = await parseDocument(filePath);
|
||||||
|
if (!parsed.content.trim()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const documentKey = normalizeDocumentKey(
|
||||||
|
source.sourceType === "folder"
|
||||||
|
? path.relative(path.resolve(source.sourceRef), path.resolve(filePath))
|
||||||
|
: path.basename(filePath)
|
||||||
|
);
|
||||||
|
const documentId = buildDocumentId(sourceId, documentKey);
|
||||||
|
const chunkingPolicy = parsed.chunkMode === "codigo" ? codeChunkingPolicy : documentalChunkingPolicy;
|
||||||
|
const chunks = chunkDocument(parsed.title, parsed.content, chunkingPolicy);
|
||||||
|
const embeddings = await this.embeddingProvider.embed(chunks.map((chunk) => chunk.content));
|
||||||
|
|
||||||
|
const qdrantChunks: IngestedChunk[] = chunks.map((chunk, index) => {
|
||||||
|
const chunkId = buildChunkId(documentId, chunkingPolicy.mode, chunk.index);
|
||||||
|
return {
|
||||||
|
id: buildQdrantPointId(chunkId),
|
||||||
|
vector: embeddings[index],
|
||||||
|
payload: {
|
||||||
|
chunk_id: chunkId,
|
||||||
|
source_id: sourceId,
|
||||||
|
source_type: source.sourceType,
|
||||||
|
source_ref: source.sourceRef,
|
||||||
|
document_id: documentId,
|
||||||
|
document_key: documentKey,
|
||||||
|
title: parsed.title,
|
||||||
|
mime_type: parsed.mimeType,
|
||||||
|
section_title: chunk.sectionTitle,
|
||||||
|
chunk_mode: chunkingPolicy.mode,
|
||||||
|
chunk_index: chunk.index,
|
||||||
|
start_line: chunk.startLine,
|
||||||
|
end_line: chunk.endLine,
|
||||||
|
content: chunk.content,
|
||||||
|
embedding_provider: this.embeddingProvider.providerName,
|
||||||
|
embedding_model: this.embeddingProvider.modelName,
|
||||||
|
embedding_dimensions: embeddings[index]?.length ?? 0,
|
||||||
|
status: "active",
|
||||||
|
updated_at: new Date().toISOString(),
|
||||||
|
tags: source.tags ?? []
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.vectorStore.upsert(qdrantChunks);
|
||||||
|
documentsProcessed += 1;
|
||||||
|
chunksStored += qdrantChunks.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
accepted: true,
|
||||||
|
source,
|
||||||
|
filesDiscovered: supportedFiles.length,
|
||||||
|
documentsProcessed,
|
||||||
|
chunksStored,
|
||||||
|
collectionName: env.qdrantCollection
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private async resolveInputFiles(source: IngestSourceInput): Promise<string[]> {
|
||||||
|
if (source.sourceType === "file") {
|
||||||
|
return [source.sourceRef];
|
||||||
|
}
|
||||||
|
return listFilesRecursively(source.sourceRef);
|
||||||
|
}
|
||||||
|
}
|
||||||
68
RAG/src/modules/parsers/parser-registry.ts
Normal file
68
RAG/src/modules/parsers/parser-registry.ts
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
import { readFile } from "node:fs/promises";
|
||||||
|
import path from "node:path";
|
||||||
|
import pdf from "pdf-parse";
|
||||||
|
import type { ChunkingMode } from "../process/chunking.js";
|
||||||
|
|
||||||
|
export interface ParsedDocument {
|
||||||
|
title: string;
|
||||||
|
content: string;
|
||||||
|
mimeType: string;
|
||||||
|
chunkMode: ChunkingMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
const documentalExtensions = [".md", ".txt", ".pdf"] as const;
|
||||||
|
const codeExtensions = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".py", ".json", ".yml", ".yaml"] as const;
|
||||||
|
const parserExtensions = [...documentalExtensions, ...codeExtensions] as const;
|
||||||
|
|
||||||
|
export function supportedParserExtensions(): string[] {
|
||||||
|
return [...parserExtensions];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isSupportedDocument(filePath: string): boolean {
|
||||||
|
return parserExtensions.includes(path.extname(filePath).toLowerCase() as (typeof parserExtensions)[number]);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function inferChunkMode(filePath: string): ChunkingMode {
|
||||||
|
const extension = path.extname(filePath).toLowerCase();
|
||||||
|
if (codeExtensions.includes(extension as (typeof codeExtensions)[number])) {
|
||||||
|
return "codigo";
|
||||||
|
}
|
||||||
|
return "documental";
|
||||||
|
}
|
||||||
|
|
||||||
|
function inferMimeType(extension: string, chunkMode: ChunkingMode): string {
|
||||||
|
if (extension === ".pdf") {
|
||||||
|
return "application/pdf";
|
||||||
|
}
|
||||||
|
if (extension === ".md") {
|
||||||
|
return "text/markdown";
|
||||||
|
}
|
||||||
|
if (chunkMode === "codigo") {
|
||||||
|
return "text/x-code";
|
||||||
|
}
|
||||||
|
return "text/plain";
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function parseDocument(filePath: string): Promise<ParsedDocument> {
|
||||||
|
const extension = path.extname(filePath).toLowerCase();
|
||||||
|
const chunkMode = inferChunkMode(filePath);
|
||||||
|
|
||||||
|
if (extension === ".pdf") {
|
||||||
|
const buffer = await readFile(filePath);
|
||||||
|
const result = await pdf(buffer);
|
||||||
|
return {
|
||||||
|
title: path.basename(filePath),
|
||||||
|
content: result.text.trim(),
|
||||||
|
mimeType: inferMimeType(extension, chunkMode),
|
||||||
|
chunkMode
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = await readFile(filePath, "utf8");
|
||||||
|
return {
|
||||||
|
title: path.basename(filePath),
|
||||||
|
content,
|
||||||
|
mimeType: inferMimeType(extension, chunkMode),
|
||||||
|
chunkMode
|
||||||
|
};
|
||||||
|
}
|
||||||
325
RAG/src/modules/process/chunking.ts
Normal file
325
RAG/src/modules/process/chunking.ts
Normal file
|
|
@ -0,0 +1,325 @@
|
||||||
|
export type ChunkingMode = "documental" | "codigo";
|
||||||
|
|
||||||
|
export interface ChunkingPolicy {
|
||||||
|
mode: ChunkingMode;
|
||||||
|
targetCharacters: number;
|
||||||
|
maxCharacters: number;
|
||||||
|
overlapCharacters: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChunkedDocument {
|
||||||
|
index: number;
|
||||||
|
title: string;
|
||||||
|
content: string;
|
||||||
|
sectionTitle: string;
|
||||||
|
startLine: number;
|
||||||
|
endLine: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const documentalChunkingPolicy: ChunkingPolicy = {
|
||||||
|
mode: "documental",
|
||||||
|
targetCharacters: 1500,
|
||||||
|
maxCharacters: 2200,
|
||||||
|
overlapCharacters: 200
|
||||||
|
};
|
||||||
|
|
||||||
|
export const codeChunkingPolicy: ChunkingPolicy = {
|
||||||
|
mode: "codigo",
|
||||||
|
targetCharacters: 1800,
|
||||||
|
maxCharacters: 2600,
|
||||||
|
overlapCharacters: 160
|
||||||
|
};
|
||||||
|
|
||||||
|
function normalizeLineBreaks(content: string): string {
|
||||||
|
return content.replace(/\r\n/g, "\n").trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function splitIntoSections(content: string): Array<{ sectionTitle: string; body: string; startLine: number }> {
|
||||||
|
const normalized = normalizeLineBreaks(content);
|
||||||
|
const lines = normalized.split("\n");
|
||||||
|
const sections: Array<{ sectionTitle: string; body: string; startLine: number }> = [];
|
||||||
|
let currentTitle = "General";
|
||||||
|
let currentLines: string[] = [];
|
||||||
|
let currentStartLine = 1;
|
||||||
|
|
||||||
|
for (let i = 0; i < lines.length; i += 1) {
|
||||||
|
const line = lines[i];
|
||||||
|
const trimmed = line.trim();
|
||||||
|
if (/^#{1,6}\s+/.test(trimmed)) {
|
||||||
|
if (currentLines.length > 0) {
|
||||||
|
sections.push({ sectionTitle: currentTitle, body: currentLines.join("\n").trim(), startLine: currentStartLine });
|
||||||
|
}
|
||||||
|
currentTitle = trimmed.replace(/^#{1,6}\s+/, "").trim() || "General";
|
||||||
|
currentLines = [];
|
||||||
|
currentStartLine = i + 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
currentLines.push(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentLines.length > 0) {
|
||||||
|
sections.push({ sectionTitle: currentTitle, body: currentLines.join("\n").trim(), startLine: currentStartLine });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sections.length === 0 && normalized) {
|
||||||
|
sections.push({ sectionTitle: "General", body: normalized, startLine: 1 });
|
||||||
|
}
|
||||||
|
|
||||||
|
return sections.filter((section) => section.body.trim().length > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function splitSectionIntoParagraphs(sectionBody: string): string[] {
|
||||||
|
return sectionBody
|
||||||
|
.split(/\n\s*\n/g)
|
||||||
|
.map((paragraph) => paragraph.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
|
function chunkOversizedText(text: string, maxCharacters: number, overlapCharacters: number): string[] {
|
||||||
|
const chunks: string[] = [];
|
||||||
|
let start = 0;
|
||||||
|
const cleaned = text.trim();
|
||||||
|
|
||||||
|
while (start < cleaned.length) {
|
||||||
|
const end = Math.min(start + maxCharacters, cleaned.length);
|
||||||
|
const slice = cleaned.slice(start, end).trim();
|
||||||
|
if (slice) {
|
||||||
|
chunks.push(slice);
|
||||||
|
}
|
||||||
|
if (end >= cleaned.length) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
start = Math.max(end - overlapCharacters, start + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return chunks;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildDocumentalChunks(title: string, content: string, policy: ChunkingPolicy): ChunkedDocument[] {
|
||||||
|
const sections = splitIntoSections(content);
|
||||||
|
const chunks: ChunkedDocument[] = [];
|
||||||
|
let index = 0;
|
||||||
|
|
||||||
|
for (const section of sections) {
|
||||||
|
const sectionLineCount = section.body.split("\n").length;
|
||||||
|
if (section.body.length <= policy.maxCharacters) {
|
||||||
|
chunks.push({
|
||||||
|
index: index++,
|
||||||
|
title,
|
||||||
|
sectionTitle: section.sectionTitle,
|
||||||
|
content: section.body,
|
||||||
|
startLine: section.startLine,
|
||||||
|
endLine: section.startLine + sectionLineCount - 1
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const paragraphs = splitSectionIntoParagraphs(section.body);
|
||||||
|
let buffer = "";
|
||||||
|
let bufferStartLine = section.startLine;
|
||||||
|
let consumedLines = 0;
|
||||||
|
|
||||||
|
for (const paragraph of paragraphs) {
|
||||||
|
const paragraphLines = paragraph.split("\n").length;
|
||||||
|
const candidate = buffer ? `${buffer}\n\n${paragraph}` : paragraph;
|
||||||
|
if (candidate.length <= policy.targetCharacters) {
|
||||||
|
buffer = candidate;
|
||||||
|
consumedLines += paragraphLines + (buffer ? 1 : 0);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buffer) {
|
||||||
|
chunks.push({
|
||||||
|
index: index++,
|
||||||
|
title,
|
||||||
|
sectionTitle: section.sectionTitle,
|
||||||
|
content: buffer,
|
||||||
|
startLine: bufferStartLine,
|
||||||
|
endLine: bufferStartLine + buffer.split("\n").length - 1
|
||||||
|
});
|
||||||
|
bufferStartLine = bufferStartLine + buffer.split("\n").length + 1;
|
||||||
|
buffer = paragraph;
|
||||||
|
consumedLines = paragraphLines;
|
||||||
|
} else {
|
||||||
|
for (const piece of chunkOversizedText(paragraph, policy.maxCharacters, policy.overlapCharacters)) {
|
||||||
|
chunks.push({
|
||||||
|
index: index++,
|
||||||
|
title,
|
||||||
|
sectionTitle: section.sectionTitle,
|
||||||
|
content: piece,
|
||||||
|
startLine: bufferStartLine,
|
||||||
|
endLine: bufferStartLine + piece.split("\n").length - 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
bufferStartLine += paragraphLines;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buffer) {
|
||||||
|
if (buffer.length > policy.maxCharacters) {
|
||||||
|
for (const piece of chunkOversizedText(buffer, policy.maxCharacters, policy.overlapCharacters)) {
|
||||||
|
chunks.push({
|
||||||
|
index: index++,
|
||||||
|
title,
|
||||||
|
sectionTitle: section.sectionTitle,
|
||||||
|
content: piece,
|
||||||
|
startLine: bufferStartLine,
|
||||||
|
endLine: bufferStartLine + piece.split("\n").length - 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
chunks.push({
|
||||||
|
index: index++,
|
||||||
|
title,
|
||||||
|
sectionTitle: section.sectionTitle,
|
||||||
|
content: buffer,
|
||||||
|
startLine: bufferStartLine,
|
||||||
|
endLine: bufferStartLine + buffer.split("\n").length - 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return chunks;
|
||||||
|
}
|
||||||
|
|
||||||
|
function looksLikeTopLevelCodeStart(line: string): boolean {
|
||||||
|
const trimmed = line.trim();
|
||||||
|
if (!trimmed) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
/^export\s+(async\s+)?function\s+/,
|
||||||
|
/^(async\s+)?function\s+/,
|
||||||
|
/^export\s+class\s+/,
|
||||||
|
/^class\s+/,
|
||||||
|
/^export\s+(const|let|var)\s+[A-Za-z0-9_$]+\s*=\s*(async\s*)?(\([^)]*\)|[A-Za-z0-9_$]+)\s*=>/,
|
||||||
|
/^(const|let|var)\s+[A-Za-z0-9_$]+\s*=\s*(async\s*)?(\([^)]*\)|[A-Za-z0-9_$]+)\s*=>/,
|
||||||
|
/^export\s+default\s+/,
|
||||||
|
/^interface\s+/,
|
||||||
|
/^type\s+/,
|
||||||
|
/^enum\s+/,
|
||||||
|
/^def\s+/,
|
||||||
|
/^class\s+/,
|
||||||
|
/^@/,
|
||||||
|
/^if\s+__name__\s*==\s*["']__main__["']:/
|
||||||
|
].some((pattern) => pattern.test(trimmed));
|
||||||
|
}
|
||||||
|
|
||||||
|
function inferCodeSectionTitle(block: string): string {
|
||||||
|
const firstMeaningful = block
|
||||||
|
.split("\n")
|
||||||
|
.map((line) => line.trim())
|
||||||
|
.find(Boolean);
|
||||||
|
|
||||||
|
return firstMeaningful?.slice(0, 120) || "code_block";
|
||||||
|
}
|
||||||
|
|
||||||
|
function splitCodeIntoBlocks(content: string): Array<{ sectionTitle: string; body: string; startLine: number }> {
|
||||||
|
const normalized = content.replace(/\r\n/g, "\n");
|
||||||
|
const lines = normalized.split("\n");
|
||||||
|
const blocks: Array<{ sectionTitle: string; body: string; startLine: number }> = [];
|
||||||
|
let currentLines: string[] = [];
|
||||||
|
let currentStartLine = 1;
|
||||||
|
|
||||||
|
for (let i = 0; i < lines.length; i += 1) {
|
||||||
|
const line = lines[i];
|
||||||
|
const isNewBlock = currentLines.length > 0 && looksLikeTopLevelCodeStart(line);
|
||||||
|
if (isNewBlock) {
|
||||||
|
const body = currentLines.join("\n").trim();
|
||||||
|
if (body) {
|
||||||
|
blocks.push({
|
||||||
|
sectionTitle: inferCodeSectionTitle(body),
|
||||||
|
body,
|
||||||
|
startLine: currentStartLine
|
||||||
|
});
|
||||||
|
}
|
||||||
|
currentLines = [line];
|
||||||
|
currentStartLine = i + 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentLines.push(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
const finalBody = currentLines.join("\n").trim();
|
||||||
|
if (finalBody) {
|
||||||
|
blocks.push({
|
||||||
|
sectionTitle: inferCodeSectionTitle(finalBody),
|
||||||
|
body: finalBody,
|
||||||
|
startLine: currentStartLine
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return blocks;
|
||||||
|
}
|
||||||
|
|
||||||
|
function splitCodeBlockByLength(block: string, policy: ChunkingPolicy): string[] {
|
||||||
|
const lines = block.split("\n");
|
||||||
|
const chunks: string[] = [];
|
||||||
|
let buffer: string[] = [];
|
||||||
|
let bufferLength = 0;
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
const nextLength = bufferLength + line.length + 1;
|
||||||
|
if (buffer.length > 0 && nextLength > policy.targetCharacters) {
|
||||||
|
chunks.push(buffer.join("\n").trim());
|
||||||
|
const overlapLines = buffer.join("\n").slice(-policy.overlapCharacters).split("\n").filter(Boolean);
|
||||||
|
buffer = overlapLines.length > 0 ? overlapLines : [];
|
||||||
|
bufferLength = buffer.join("\n").length;
|
||||||
|
}
|
||||||
|
buffer.push(line);
|
||||||
|
bufferLength += line.length + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buffer.length > 0) {
|
||||||
|
chunks.push(buffer.join("\n").trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
return chunks.filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildCodeChunks(title: string, content: string, policy: ChunkingPolicy): ChunkedDocument[] {
|
||||||
|
const blocks = splitCodeIntoBlocks(content);
|
||||||
|
const chunks: ChunkedDocument[] = [];
|
||||||
|
let index = 0;
|
||||||
|
|
||||||
|
for (const block of blocks) {
|
||||||
|
if (block.body.length <= policy.maxCharacters) {
|
||||||
|
chunks.push({
|
||||||
|
index: index++,
|
||||||
|
title,
|
||||||
|
sectionTitle: block.sectionTitle,
|
||||||
|
content: block.body,
|
||||||
|
startLine: block.startLine,
|
||||||
|
endLine: block.startLine + block.body.split("\n").length - 1
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let currentStartLine = block.startLine;
|
||||||
|
for (const piece of splitCodeBlockByLength(block.body, policy)) {
|
||||||
|
const lineCount = piece.split("\n").length;
|
||||||
|
chunks.push({
|
||||||
|
index: index++,
|
||||||
|
title,
|
||||||
|
sectionTitle: block.sectionTitle,
|
||||||
|
content: piece,
|
||||||
|
startLine: currentStartLine,
|
||||||
|
endLine: currentStartLine + lineCount - 1
|
||||||
|
});
|
||||||
|
currentStartLine += lineCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return chunks;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function chunkDocument(title: string, content: string, policy: ChunkingPolicy): ChunkedDocument[] {
|
||||||
|
if (policy.mode === "codigo") {
|
||||||
|
return buildCodeChunks(title, content, policy);
|
||||||
|
}
|
||||||
|
|
||||||
|
return buildDocumentalChunks(title, content, policy);
|
||||||
|
}
|
||||||
238
RAG/src/modules/retrieve/service.ts
Normal file
238
RAG/src/modules/retrieve/service.ts
Normal file
|
|
@ -0,0 +1,238 @@
|
||||||
|
import type { EmbeddingProvider } from "../embeddings/provider.js";
|
||||||
|
import type { VectorStoreClient } from "../vectorstore/client.js";
|
||||||
|
import type { ChunkMode, RetrieveIntent, RetrieveResponse, RetrieveScope } from "../../shared/types/rag.js";
|
||||||
|
|
||||||
|
function unique(values: string[]): string[] {
|
||||||
|
return [...new Set(values.filter(Boolean))];
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeQuery(value: string): string {
|
||||||
|
return value.trim().replace(/\s+/g, " ");
|
||||||
|
}
|
||||||
|
|
||||||
|
function scoreBoostFromContent(content: string): number {
|
||||||
|
let boost = 0;
|
||||||
|
if (/regla|importante|critico|critica/i.test(content)) {
|
||||||
|
boost += 0.08;
|
||||||
|
}
|
||||||
|
if (/pendiente|backlog|task|objetivo/i.test(content)) {
|
||||||
|
boost += 0.06;
|
||||||
|
}
|
||||||
|
if (/workspace|proyecto|documentacion/i.test(content)) {
|
||||||
|
boost += 0.04;
|
||||||
|
}
|
||||||
|
if (/objetivo|objetivos principales|principios del sistema|resultado esperado|vision del sistema/i.test(content)) {
|
||||||
|
boost += 0.16;
|
||||||
|
}
|
||||||
|
if (/\*\*proyecto:\*\*|\*\*modulo:\*\*|\*\*ultima actualizacion:\*\*/i.test(content)) {
|
||||||
|
boost -= 0.18;
|
||||||
|
}
|
||||||
|
return boost;
|
||||||
|
}
|
||||||
|
|
||||||
|
function scoreBoostFromTitle(title: string): number {
|
||||||
|
let boost = 0;
|
||||||
|
if (/pendientes_generales/i.test(title)) {
|
||||||
|
boost += 0.1;
|
||||||
|
}
|
||||||
|
if (/ids/i.test(title)) {
|
||||||
|
boost += 0.12;
|
||||||
|
}
|
||||||
|
if (/service|client|chunking|provider|server|app/i.test(title)) {
|
||||||
|
boost += 0.05;
|
||||||
|
}
|
||||||
|
if (/readme|indice_documentacion|historial_sesiones|task/i.test(title)) {
|
||||||
|
boost += 0.03;
|
||||||
|
}
|
||||||
|
return boost;
|
||||||
|
}
|
||||||
|
|
||||||
|
function scoreBoostFromQueryAlignment(query: string, item: RetrieveResponse["items"][number]): number {
|
||||||
|
const source = `${item.title} ${item.content}`.toLowerCase();
|
||||||
|
const normalizedQuery = query.toLowerCase();
|
||||||
|
let boost = 0;
|
||||||
|
|
||||||
|
if (/pendiente|backlog|por hacer|siguiente/i.test(normalizedQuery) && /pendiente|backlog|lineas de trabajo|proximos pasos/i.test(source)) {
|
||||||
|
boost += 0.18;
|
||||||
|
}
|
||||||
|
if (/regla|norma|protocolo|instruccion/i.test(normalizedQuery) && /regla|norma|protocolo|instruccion/i.test(source)) {
|
||||||
|
boost += 0.18;
|
||||||
|
}
|
||||||
|
if (/sesion|historial|registro/i.test(normalizedQuery) && /sesion|historial|registro/i.test(source)) {
|
||||||
|
boost += 0.16;
|
||||||
|
}
|
||||||
|
if (/indice|documentacion|mapa/i.test(normalizedQuery) && /indice|documentacion|mapa/i.test(source)) {
|
||||||
|
boost += 0.14;
|
||||||
|
}
|
||||||
|
if (/funcion|metodo|clase|endpoint|service|client|chunk|codigo|linea|source_id|document_id|qdrant|embedding/i.test(normalizedQuery) && /function|class|endpoint|service|client|chunk|codigo|source_id|document_id|qdrant|embedding|def\s+/i.test(source)) {
|
||||||
|
boost += 0.2;
|
||||||
|
}
|
||||||
|
if (/source_id|document_id|chunk_id|ids/i.test(normalizedQuery) && /buildsourceid|builddocumentid|buildchunkid|ids\.ts|source_id|document_id|chunk_id/i.test(source)) {
|
||||||
|
boost += 0.28;
|
||||||
|
}
|
||||||
|
if (/source_id/i.test(normalizedQuery) && /buildsourceid|source_id/i.test(source)) {
|
||||||
|
boost += 0.22;
|
||||||
|
}
|
||||||
|
if (/document_id/i.test(normalizedQuery) && /builddocumentid|document_id/i.test(source)) {
|
||||||
|
boost += 0.22;
|
||||||
|
}
|
||||||
|
if (/chunk_id/i.test(normalizedQuery) && /buildchunkid|chunk_id/i.test(source)) {
|
||||||
|
boost += 0.22;
|
||||||
|
}
|
||||||
|
|
||||||
|
return boost;
|
||||||
|
}
|
||||||
|
|
||||||
|
function rankItems(query: string, items: RetrieveResponse["items"]): RetrieveResponse["items"] {
|
||||||
|
return [...items].sort((left, right) => {
|
||||||
|
const leftScore = left.score + scoreBoostFromTitle(left.title) + scoreBoostFromContent(left.content) + scoreBoostFromQueryAlignment(query, left);
|
||||||
|
const rightScore = right.score + scoreBoostFromTitle(right.title) + scoreBoostFromContent(right.content) + scoreBoostFromQueryAlignment(query, right);
|
||||||
|
return rightScore - leftScore;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildBootstrapQueries(query: string): string[] {
|
||||||
|
const cleaned = normalizeQuery(query);
|
||||||
|
return unique([
|
||||||
|
cleaned,
|
||||||
|
`${cleaned} mapa general`,
|
||||||
|
`${cleaned} documentacion principal`,
|
||||||
|
`${cleaned} pendientes y lineas de trabajo`,
|
||||||
|
`${cleaned} reglas y estructura del workspace`
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildSpecificQueries(query: string): string[] {
|
||||||
|
const cleaned = normalizeQuery(query);
|
||||||
|
const queries = [cleaned];
|
||||||
|
|
||||||
|
if (/pendiente|backlog|por hacer|siguiente/i.test(cleaned)) {
|
||||||
|
queries.push(`${cleaned} pendientes backlog proximos pasos`);
|
||||||
|
queries.push(`${cleaned} lineas de trabajo prioritarias`);
|
||||||
|
queries.push(`${cleaned} sistema basico RAG estructura MCP Retell`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/regla|norma|protocolo|instruccion/i.test(cleaned)) {
|
||||||
|
queries.push(`${cleaned} reglas protocolo instrucciones`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/sesion|historial|registro/i.test(cleaned)) {
|
||||||
|
queries.push(`${cleaned} historial sesiones registro`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/indice|documentacion|mapa/i.test(cleaned)) {
|
||||||
|
queries.push(`${cleaned} indice documentacion mapa`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/caracteristica|como funciona|objetivo|modulo|arquitectura|stack/i.test(cleaned)) {
|
||||||
|
queries.push(`${cleaned} sistema rag base arquitectura stack tecnico`);
|
||||||
|
queries.push(`${cleaned} ingesta procesado salida embeddings qdrant`);
|
||||||
|
queries.push(`${cleaned} objetivo principios casos de uso`);
|
||||||
|
queries.push(`${cleaned} vision del sistema objetivos principales resultado esperado`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/funcion|metodo|clase|endpoint|service|client|source_id|document_id|chunk|qdrant|embedding|codigo/i.test(cleaned)) {
|
||||||
|
queries.push(`${cleaned} funcion metodo clase service client`);
|
||||||
|
queries.push(`${cleaned} endpoint api qdrant embeddings retrieve answer ingest`);
|
||||||
|
queries.push(`${cleaned} source_id document_id chunk_id codigo`);
|
||||||
|
queries.push(`${cleaned} buildSourceId buildDocumentId buildChunkId ids.ts`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return unique(queries);
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildSpecificSummary(topics: string[], itemsCount: number): string {
|
||||||
|
if (itemsCount === 0) {
|
||||||
|
return "No se recupero contexto relevante para la consulta.";
|
||||||
|
}
|
||||||
|
|
||||||
|
return `Se recuperaron ${itemsCount} fragmentos relevantes sobre: ${topics.slice(0, 4).join(", ") || "tema consultado"}.`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildBootstrapSummary(topics: string[], criticalPoints: string[], followUpRefs: string[], itemsCount: number): string {
|
||||||
|
if (itemsCount === 0) {
|
||||||
|
return "No se recupero contexto suficiente para construir el mapa inicial del dominio.";
|
||||||
|
}
|
||||||
|
|
||||||
|
const themeText = topics.slice(0, 4).join(", ") || "los documentos principales";
|
||||||
|
const criticalText = criticalPoints.slice(0, 3).join(", ") || "sin puntos criticos destacados";
|
||||||
|
return `Mapa inicial construido con ${itemsCount} fragmentos. Temas base: ${themeText}. Puntos a tener presentes: ${criticalText}. Referencias principales para profundizar: ${followUpRefs.slice(0, 4).join(", ") || "no disponibles"}.`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function embedQuery(provider: EmbeddingProvider, query: string): Promise<number[]> {
|
||||||
|
const [vector] = await provider.embed([query]);
|
||||||
|
return vector;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class RetrieveService {
|
||||||
|
constructor(
|
||||||
|
private readonly embeddingProvider: EmbeddingProvider,
|
||||||
|
private readonly vectorStore: VectorStoreClient
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async retrieve(mode: ChunkMode, intent: RetrieveIntent, query: string, scope?: RetrieveScope): Promise<RetrieveResponse> {
|
||||||
|
const items = intent === "bootstrap"
|
||||||
|
? await this.retrieveBootstrap(mode, query, scope)
|
||||||
|
: await this.retrieveSpecific(mode, query, scope);
|
||||||
|
|
||||||
|
const topics = unique(items.map((item) => item.title)).slice(0, 8);
|
||||||
|
const criticalPoints = items
|
||||||
|
.filter((item) => /critico|critical|importante|pendiente/i.test(item.content))
|
||||||
|
.map((item) => item.title)
|
||||||
|
.filter(Boolean)
|
||||||
|
.slice(0, 5);
|
||||||
|
const followUpRefs = unique(items.map((item) => item.documentId)).slice(0, 8);
|
||||||
|
const summary = intent === "bootstrap"
|
||||||
|
? buildBootstrapSummary(topics, criticalPoints, followUpRefs, items.length)
|
||||||
|
: buildSpecificSummary(topics, items.length);
|
||||||
|
|
||||||
|
return {
|
||||||
|
mode,
|
||||||
|
intent,
|
||||||
|
summary,
|
||||||
|
topics,
|
||||||
|
criticalPoints,
|
||||||
|
items,
|
||||||
|
followUpRefs,
|
||||||
|
scope
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private async retrieveSpecific(mode: ChunkMode, query: string, scope?: RetrieveScope) {
|
||||||
|
const queries = buildSpecificQueries(query);
|
||||||
|
const merged = new Map<string, RetrieveResponse["items"][number]>();
|
||||||
|
|
||||||
|
for (const subquery of queries) {
|
||||||
|
const queryVector = await embedQuery(this.embeddingProvider, subquery);
|
||||||
|
const results = await this.vectorStore.search(queryVector, 6, mode, scope);
|
||||||
|
|
||||||
|
for (const item of results) {
|
||||||
|
const existing = merged.get(item.chunkId);
|
||||||
|
if (!existing || item.score > existing.score) {
|
||||||
|
merged.set(item.chunkId, item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rankItems(query, [...merged.values()]).slice(0, 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async retrieveBootstrap(mode: ChunkMode, query: string, scope?: RetrieveScope) {
|
||||||
|
const queries = buildBootstrapQueries(query);
|
||||||
|
const merged = new Map<string, RetrieveResponse["items"][number]>();
|
||||||
|
|
||||||
|
for (const subquery of queries) {
|
||||||
|
const queryVector = await embedQuery(this.embeddingProvider, subquery);
|
||||||
|
const results = await this.vectorStore.search(queryVector, 6, mode, scope);
|
||||||
|
|
||||||
|
for (const item of results) {
|
||||||
|
const existing = merged.get(item.chunkId);
|
||||||
|
if (!existing || item.score > existing.score) {
|
||||||
|
merged.set(item.chunkId, item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rankItems(query, [...merged.values()]).slice(0, 12);
|
||||||
|
}
|
||||||
|
}
|
||||||
136
RAG/src/modules/vectorstore/client.ts
Normal file
136
RAG/src/modules/vectorstore/client.ts
Normal file
|
|
@ -0,0 +1,136 @@
|
||||||
|
import { QdrantClient } from "@qdrant/js-client-rest";
|
||||||
|
import { env } from "../../config/env.js";
|
||||||
|
import type { IngestedChunk, RetrieveScope, RetrievedItem } from "../../shared/types/rag.js";
|
||||||
|
|
||||||
|
function buildQdrantClient(): QdrantClient {
|
||||||
|
const url = new URL(env.qdrantUrl);
|
||||||
|
return new QdrantClient({
|
||||||
|
host: url.hostname,
|
||||||
|
port: Number(url.port || (url.protocol === "https:" ? 443 : 6333)),
|
||||||
|
https: url.protocol === "https:",
|
||||||
|
apiKey: env.qdrantApiKey || undefined,
|
||||||
|
checkCompatibility: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface VectorStoreClient {
|
||||||
|
readonly kind: string;
|
||||||
|
healthcheck(): Promise<{ ok: boolean; kind: string }>;
|
||||||
|
ensureCollection(vectorSize: number): Promise<void>;
|
||||||
|
upsert(chunks: IngestedChunk[]): Promise<void>;
|
||||||
|
search(queryVector: number[], limit: number, mode?: string, scope?: RetrieveScope): Promise<RetrievedItem[]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildSearchFilter(mode?: string, scope?: RetrieveScope) {
|
||||||
|
const must: Array<Record<string, unknown>> = [];
|
||||||
|
|
||||||
|
if (mode && mode !== "auto") {
|
||||||
|
must.push({
|
||||||
|
key: "chunk_mode",
|
||||||
|
match: { value: mode }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scope?.sourceId) {
|
||||||
|
must.push({
|
||||||
|
key: "source_id",
|
||||||
|
match: { value: scope.sourceId }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scope?.sourceRef) {
|
||||||
|
must.push({
|
||||||
|
key: "source_ref",
|
||||||
|
match: { value: scope.sourceRef }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scope?.tags?.length) {
|
||||||
|
for (const tag of scope.tags) {
|
||||||
|
must.push({
|
||||||
|
key: "tags",
|
||||||
|
match: { any: [tag] }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return must.length > 0 ? { must } : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class QdrantVectorStoreClient implements VectorStoreClient {
|
||||||
|
readonly kind = "qdrant";
|
||||||
|
private readonly client: QdrantClient;
|
||||||
|
private collectionReady = false;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.client = buildQdrantClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
async healthcheck(): Promise<{ ok: boolean; kind: string }> {
|
||||||
|
try {
|
||||||
|
await this.client.getCollections();
|
||||||
|
return { ok: true, kind: this.kind };
|
||||||
|
} catch {
|
||||||
|
return { ok: false, kind: this.kind };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async ensureCollection(vectorSize: number): Promise<void> {
|
||||||
|
if (this.collectionReady) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const collections = await this.client.getCollections();
|
||||||
|
const exists = collections.collections.some((collection) => collection.name === env.qdrantCollection);
|
||||||
|
|
||||||
|
if (!exists) {
|
||||||
|
await this.client.createCollection(env.qdrantCollection, {
|
||||||
|
vectors: {
|
||||||
|
size: vectorSize,
|
||||||
|
distance: "Cosine"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.collectionReady = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async upsert(chunks: IngestedChunk[]): Promise<void> {
|
||||||
|
if (chunks.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.ensureCollection(chunks[0].vector.length);
|
||||||
|
await this.client.upsert(env.qdrantCollection, {
|
||||||
|
wait: true,
|
||||||
|
points: chunks.map((chunk) => ({
|
||||||
|
id: chunk.id,
|
||||||
|
vector: chunk.vector,
|
||||||
|
payload: chunk.payload
|
||||||
|
}))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async search(queryVector: number[], limit: number, mode?: string, scope?: RetrieveScope): Promise<RetrievedItem[]> {
|
||||||
|
await this.ensureCollection(queryVector.length);
|
||||||
|
const result = await this.client.search(env.qdrantCollection, {
|
||||||
|
vector: queryVector,
|
||||||
|
limit,
|
||||||
|
with_payload: true,
|
||||||
|
filter: buildSearchFilter(mode, scope)
|
||||||
|
});
|
||||||
|
|
||||||
|
return result.map((point) => ({
|
||||||
|
chunkId: String(point.payload?.chunk_id ?? point.id),
|
||||||
|
documentId: String(point.payload?.document_id ?? ""),
|
||||||
|
sourceId: String(point.payload?.source_id ?? ""),
|
||||||
|
title: String(point.payload?.title ?? ""),
|
||||||
|
sectionTitle: point.payload?.section_title ? String(point.payload.section_title) : undefined,
|
||||||
|
content: String(point.payload?.content ?? ""),
|
||||||
|
score: Number(point.score ?? 0),
|
||||||
|
chunkMode: point.payload?.chunk_mode ? String(point.payload.chunk_mode) as "documental" | "codigo" : undefined,
|
||||||
|
startLine: point.payload?.start_line ? Number(point.payload.start_line) : undefined,
|
||||||
|
endLine: point.payload?.end_line ? Number(point.payload.end_line) : undefined
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
8
RAG/src/server.ts
Normal file
8
RAG/src/server.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { createApp } from "./app.js";
|
||||||
|
import { env } from "./config/env.js";
|
||||||
|
|
||||||
|
const app = createApp();
|
||||||
|
|
||||||
|
app.listen(env.port, () => {
|
||||||
|
console.log(`RAG service listening on port ${env.port}`);
|
||||||
|
});
|
||||||
78
RAG/src/shared/types/rag.ts
Normal file
78
RAG/src/shared/types/rag.ts
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
export type ChunkMode = "documental" | "codigo" | "auto";
|
||||||
|
|
||||||
|
export type RetrieveIntent = "bootstrap" | "specific";
|
||||||
|
|
||||||
|
export type IngestMode = "mechanical" | "interactive";
|
||||||
|
|
||||||
|
export type SourceType = "file" | "folder";
|
||||||
|
|
||||||
|
export interface IngestSourceInput {
|
||||||
|
sourceId?: string;
|
||||||
|
sourceType: SourceType;
|
||||||
|
sourceRef: string;
|
||||||
|
mode?: IngestMode;
|
||||||
|
tags?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IngestedChunk {
|
||||||
|
id: string;
|
||||||
|
vector: number[];
|
||||||
|
payload: Record<string, unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IngestResult {
|
||||||
|
accepted: true;
|
||||||
|
source: IngestSourceInput;
|
||||||
|
filesDiscovered: number;
|
||||||
|
documentsProcessed: number;
|
||||||
|
chunksStored: number;
|
||||||
|
collectionName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RetrievedItem {
|
||||||
|
chunkId: string;
|
||||||
|
documentId: string;
|
||||||
|
sourceId: string;
|
||||||
|
title: string;
|
||||||
|
sectionTitle?: string;
|
||||||
|
content: string;
|
||||||
|
score: number;
|
||||||
|
chunkMode?: ChunkMode;
|
||||||
|
startLine?: number;
|
||||||
|
endLine?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RetrieveScope {
|
||||||
|
sourceId?: string;
|
||||||
|
sourceRef?: string;
|
||||||
|
tags?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RetrieveResponse {
|
||||||
|
mode: ChunkMode;
|
||||||
|
intent: RetrieveIntent;
|
||||||
|
summary: string;
|
||||||
|
topics: string[];
|
||||||
|
criticalPoints: string[];
|
||||||
|
items: RetrievedItem[];
|
||||||
|
followUpRefs: string[];
|
||||||
|
scope?: RetrieveScope;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AnswerResponse {
|
||||||
|
mode: ChunkMode;
|
||||||
|
intent: RetrieveIntent;
|
||||||
|
answer: string;
|
||||||
|
summary: string;
|
||||||
|
topics: string[];
|
||||||
|
criticalPoints: string[];
|
||||||
|
citations: Array<{
|
||||||
|
chunkId: string;
|
||||||
|
documentId: string;
|
||||||
|
title: string;
|
||||||
|
sectionTitle?: string;
|
||||||
|
startLine?: number;
|
||||||
|
endLine?: number;
|
||||||
|
}>;
|
||||||
|
scope?: RetrieveScope;
|
||||||
|
}
|
||||||
17
RAG/src/shared/utils/files.ts
Normal file
17
RAG/src/shared/utils/files.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { readdir } from "node:fs/promises";
|
||||||
|
import path from "node:path";
|
||||||
|
|
||||||
|
export async function listFilesRecursively(inputPath: string): Promise<string[]> {
|
||||||
|
const entries = await readdir(inputPath, { withFileTypes: true });
|
||||||
|
const files = await Promise.all(
|
||||||
|
entries.map(async (entry) => {
|
||||||
|
const resolved = path.join(inputPath, entry.name);
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
return listFilesRecursively(resolved);
|
||||||
|
}
|
||||||
|
return [resolved];
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return files.flat();
|
||||||
|
}
|
||||||
38
RAG/src/shared/utils/ids.ts
Normal file
38
RAG/src/shared/utils/ids.ts
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
import path from "node:path";
|
||||||
|
import { createHash } from "node:crypto";
|
||||||
|
|
||||||
|
function asciiNormalize(value: string): string {
|
||||||
|
return value
|
||||||
|
.normalize("NFD")
|
||||||
|
.replace(/[\u0300-\u036f]/g, "")
|
||||||
|
.replace(/ñ/gi, (char) => (char === "Ñ" ? "N" : "n"));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeDocumentKey(value: string): string {
|
||||||
|
return asciiNormalize(value)
|
||||||
|
.replace(/\\/g, "/")
|
||||||
|
.replace(/\s+/g, "_")
|
||||||
|
.replace(/_+/g, "_")
|
||||||
|
.replace(/[^a-zA-Z0-9_./-]/g, "")
|
||||||
|
.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildSourceId(sourceType: string, sourceRef: string): string {
|
||||||
|
const readable = normalizeDocumentKey(path.basename(sourceRef) || sourceRef).replace(/\.[^.]+$/, "") || "source";
|
||||||
|
const suffix = createHash("sha1").update(sourceRef).digest("hex").slice(0, 8);
|
||||||
|
const sourceName = `${readable}_${suffix}`;
|
||||||
|
return `src:default:${sourceType}:${sourceName}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildDocumentId(sourceId: string, documentKey: string): string {
|
||||||
|
return `doc:${sourceId}:${documentKey}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildChunkId(documentId: string, chunkMode: string, chunkIndex: number): string {
|
||||||
|
return `chk:${documentId}:${chunkMode}:${String(chunkIndex + 1).padStart(4, "0")}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildQdrantPointId(value: string): string {
|
||||||
|
const hex = createHash("sha1").update(value).digest("hex").slice(0, 32);
|
||||||
|
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
|
||||||
|
}
|
||||||
14
RAG/tsconfig.json
Normal file
14
RAG/tsconfig.json
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"module": "NodeNext",
|
||||||
|
"moduleResolution": "NodeNext",
|
||||||
|
"outDir": "dist",
|
||||||
|
"rootDir": "src",
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"skipLibCheck": true
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts"]
|
||||||
|
}
|
||||||
74
docs/HISTORIAL_SESIONES.md
Normal file
74
docs/HISTORIAL_SESIONES.md
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
# Historial de sesiones
|
||||||
|
|
||||||
|
## Proyecto: Workspace de tools IA para empresas
|
||||||
|
|
||||||
|
Este archivo registra agentes y sesiones de trabajo de este workspace.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Indice de agentes
|
||||||
|
|
||||||
|
| Agente | Responsabilidad | Identificador |
|
||||||
|
|--------|-----------------|---------------|
|
||||||
|
| **Agente tools IA para potenciar servicios empresariales** | Desarrollo de tools, herramientas, skills, RAGs, MCPs y utilidades para potenciar soluciones con IA para empresas | `session_id OpenCode por workspace cuando aplique` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Sesiones de trabajo
|
||||||
|
|
||||||
|
### Sesion 1 (2026-04-02) - Agente tools IA para potenciar servicios empresariales
|
||||||
|
**Agente:** **Agente tools IA para potenciar servicios empresariales**
|
||||||
|
**Modelo:** gpt-5.4
|
||||||
|
**Conversation ID:** `N/D (OpenCode no lo expone en este entorno)`
|
||||||
|
**Session ID OpenCode:** `ses_2b208e826ffeqyzvUVG7Tal0r0`
|
||||||
|
**Titulo de sesion:** `Registro de agente para tools IA empresariales`
|
||||||
|
**Directorio:** `/home/pancho/Documentos/Empresa/Desarrollo/IA`
|
||||||
|
|
||||||
|
#### Trabajo realizado:
|
||||||
|
- Registro del agente para este workspace.
|
||||||
|
- Lectura y alineacion inicial de la documentacion base.
|
||||||
|
- Creacion de `docs/PENDIENTES_GENERALES.md` con las primeras lineas de trabajo.
|
||||||
|
- Limpieza de documentacion heredada de otros proyectos en archivos base del workspace.
|
||||||
|
- Creacion inicial del documento marco del sistema RAG reutilizable.
|
||||||
|
- Definicion inicial del modulo de ingesta en `RAG/docs/INGESTA.md`.
|
||||||
|
- Definicion inicial del modulo de procesado en `RAG/docs/PROCESADO.md`, incluyendo la decision de una estrategia de chunking comun para la v1.
|
||||||
|
- Creacion de `RAG/docs/BITACORA_DISENO_RAG.md` para conservar el camino de decisiones, razones y posibilidades abiertas del diseño del sistema.
|
||||||
|
- Definicion inicial del modulo de salida en `RAG/docs/SALIDA.md`, incluyendo la estructura acordada para el `retrieve` inicial como paquete de contexto de arranque.
|
||||||
|
- Ajuste del alcance de la v1 para dejar explicito que los PDFs deben estar soportados desde el inicio y que el modo `codigo` sigue siendo parte de la direccion del sistema.
|
||||||
|
- Definicion de `RAG/docs/STACK_TECNICO_V1.md` con el stack tecnico minimo acordado para construir la primera version funcional del sistema.
|
||||||
|
- Aclaracion del diseño de embeddings desacoplados, dejando documentado que los cambios de modelo requieren reindexacion coherente y trazabilidad por proveedor/modelo.
|
||||||
|
- Cierre de la decision de embeddings fijando `Qwen3 Embedding 8B` como modelo base estable del sistema, con arranque via proveedor compatible y objetivo futuro de despliegue local.
|
||||||
|
- Creacion del scaffold inicial de `RAG/` con API Express, modulos base, Dockerfile, configuracion por entorno y orientacion de despliegue compatible con EasyPanel.
|
||||||
|
- Implementacion funcional inicial del pipeline con parsers `md/txt/pdf`, chunking documental, proveedor real de embeddings via OpenRouter, cliente Qdrant y endpoints operativos de ingesta y retrieve.
|
||||||
|
- Creacion de `RAG/.env.local` como fichero local de claves y configuracion sensible fuera del control de versiones.
|
||||||
|
- Prueba real completada contra `Qdrant` remoto en EasyPanel, con ingesta funcional de `docs/` y retrieves reales en modos `specific` y `bootstrap`.
|
||||||
|
- Ampliacion del retrieve para aceptar `scope` por fuente, referencia o tags, dejando preparado el bootstrap enfocado por workspace o proyecto.
|
||||||
|
- Mejora del `retrieve bootstrap` mediante subconsultas internas y sintesis orientada a mapa inicial del dominio.
|
||||||
|
- Implementacion funcional de `POST /answer`, apoyado en `retrieve` y con respuesta generada por modelo usando solo el contexto recuperado.
|
||||||
|
- Mejora de `retrieve specific` para preguntas operativas frecuentes, logrando respuestas mas concretas sobre backlog y estado del workspace.
|
||||||
|
- Limpieza de la coleccion `rag_chunks`, reingesta separada de `docs/` y `RAG/docs/`, y prueba satisfactoria de consultas acotadas por `scope` a cada fuente.
|
||||||
|
- Implementacion completa del modo `codigo`, incluyendo ingesta de `RAG/src/`, chunking semantico por bloques top-level, retrieve filtrado y respuesta con referencias de lineas.
|
||||||
|
- Prueba satisfactoria del modo `codigo` con una consulta real sobre la construccion de `source_id` en el sistema.
|
||||||
|
- 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.
|
||||||
|
|
||||||
|
#### Estado final:
|
||||||
|
- Documentacion base alineada con este workspace.
|
||||||
|
- Agente registrado.
|
||||||
|
- Backlog inicial disponible para continuar el trabajo.
|
||||||
|
- Carpeta `RAG/docs/` creada y primera documentacion RAG registrada con la estructura correcta del workspace.
|
||||||
|
- Existe ya un documento global para partir planificaciones amplias en tasks de trabajo.
|
||||||
|
- Existe ya una base tecnica compilable del modulo `RAG/` lista para continuar con implementacion funcional.
|
||||||
|
- 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.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Agentes activos
|
||||||
|
|
||||||
|
### Agente tools IA para potenciar servicios empresariales
|
||||||
|
- **Responsabilidad:** Desarrollo de tools, herramientas, skills, RAGs, MCPs y utilidades para potenciar soluciones con IA para empresas.
|
||||||
|
- **Estado:** Activo
|
||||||
|
- **Trabajo principal:** Desarrollo de herramientas reutilizables e integraciones para potenciar otros servicios de IA empresariales.
|
||||||
250
docs/INDICE_DOCUMENTACION.md
Normal file
250
docs/INDICE_DOCUMENTACION.md
Normal file
|
|
@ -0,0 +1,250 @@
|
||||||
|
# Indice de documentacion
|
||||||
|
|
||||||
|
**Proyecto:** Workspace de tools IA para empresas
|
||||||
|
**Ultima actualizacion:** 2026-04-02
|
||||||
|
**Ultima modificacion por:** Agente tools IA para potenciar servicios empresariales
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Proposito
|
||||||
|
|
||||||
|
Este documento es el mapa de la documentacion activa del workspace.
|
||||||
|
|
||||||
|
Usalo para:
|
||||||
|
- saber que documentos existen
|
||||||
|
- evitar documentacion duplicada
|
||||||
|
- identificar donde registrar cambios del workspace
|
||||||
|
- mantener limpia la documentacion de este proyecto
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Protocolo rapido
|
||||||
|
|
||||||
|
1. Lee `docs/README.md` antes de crear o reorganizar documentacion.
|
||||||
|
2. Verifica aqui si el documento ya existe o si su contenido pertenece a otro archivo.
|
||||||
|
3. Si creas un documento nuevo, anadelo aqui en la misma sesion.
|
||||||
|
4. Si limpias documentacion heredada, actualiza este indice para reflejar el estado real.
|
||||||
|
|
||||||
|
Convencion de estructura:
|
||||||
|
- `docs/` en raiz contiene solo documentacion global del workspace.
|
||||||
|
- cada tool o modulo independiente debe vivir en su propia carpeta raiz.
|
||||||
|
- la documentacion propia de cada tool debe vivir dentro de su carpeta, por ejemplo `RAG/docs/`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Documentos activos
|
||||||
|
|
||||||
|
### `README.md`
|
||||||
|
|
||||||
|
**Ubicacion:** `docs/README.md`
|
||||||
|
|
||||||
|
**Proposito:**
|
||||||
|
Reglas base para agentes y criterios de trabajo del workspace.
|
||||||
|
|
||||||
|
**Cuando leerlo:**
|
||||||
|
- al empezar a trabajar en este workspace
|
||||||
|
- antes de crear o modificar documentacion
|
||||||
|
|
||||||
|
**Cuando actualizarlo:**
|
||||||
|
- si cambian las reglas del workspace
|
||||||
|
- si se incorporan nuevas normas de documentacion o trabajo
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `INDICE_DOCUMENTACION.md`
|
||||||
|
|
||||||
|
**Ubicacion:** `docs/INDICE_DOCUMENTACION.md`
|
||||||
|
|
||||||
|
**Proposito:**
|
||||||
|
Inventario maestro de la documentacion vigente.
|
||||||
|
|
||||||
|
**Cuando leerlo:**
|
||||||
|
- antes de crear un nuevo documento
|
||||||
|
- al revisar estructura documental del workspace
|
||||||
|
|
||||||
|
**Cuando actualizarlo:**
|
||||||
|
- cuando se crea, elimina o renombra un documento en `docs/`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `HISTORIAL_SESIONES.md`
|
||||||
|
|
||||||
|
**Ubicacion:** `docs/HISTORIAL_SESIONES.md`
|
||||||
|
|
||||||
|
**Proposito:**
|
||||||
|
Registro de agentes y sesiones de trabajo de este workspace.
|
||||||
|
|
||||||
|
**Cuando leerlo:**
|
||||||
|
- al identificar un agente
|
||||||
|
- al revisar continuidad del trabajo anterior
|
||||||
|
|
||||||
|
**Cuando actualizarlo:**
|
||||||
|
- al registrar una nueva sesion relevante
|
||||||
|
- al dar de alta o ajustar un agente activo
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `PENDIENTES_GENERALES.md`
|
||||||
|
|
||||||
|
**Ubicacion:** `docs/PENDIENTES_GENERALES.md`
|
||||||
|
|
||||||
|
**Proposito:**
|
||||||
|
Backlog principal de tools, experimentos, investigaciones e integraciones del workspace.
|
||||||
|
|
||||||
|
**Cuando leerlo:**
|
||||||
|
- al decidir en que trabajar a continuacion
|
||||||
|
- al revisar prioridades del workspace
|
||||||
|
|
||||||
|
**Cuando actualizarlo:**
|
||||||
|
- cuando aparece una nueva linea de trabajo
|
||||||
|
- cuando cambia el enfoque o estado de una iniciativa
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `TASK.md`
|
||||||
|
|
||||||
|
**Ubicacion:** `docs/TASK.md`
|
||||||
|
|
||||||
|
**Proposito:**
|
||||||
|
Descomponer lineas de trabajo amplias en bloques de analisis, decisiones y acuerdos para avanzar sin dejar puntos importantes fuera.
|
||||||
|
|
||||||
|
**Cuando leerlo:**
|
||||||
|
- cuando una linea de trabajo requiera varias conversaciones o decisiones encadenadas
|
||||||
|
- al continuar una planificacion tecnica ya iniciada
|
||||||
|
|
||||||
|
**Cuando actualizarlo:**
|
||||||
|
- cuando se abra una nueva task de analisis relevante
|
||||||
|
- cuando cambien los puntos a convenir de una task activa
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `RAG/docs/SISTEMA_RAG_BASE.md`
|
||||||
|
|
||||||
|
**Ubicacion:** `RAG/docs/SISTEMA_RAG_BASE.md`
|
||||||
|
|
||||||
|
**Proposito:**
|
||||||
|
Documento marco del sistema RAG base reutilizable que se quiere construir para este workspace y para futuros proyectos.
|
||||||
|
|
||||||
|
**Cuando leerlo:**
|
||||||
|
- al iniciar el trabajo sobre la linea RAG
|
||||||
|
- al necesitar recordar objetivos, alcance y enfoque del sistema
|
||||||
|
|
||||||
|
**Cuando actualizarlo:**
|
||||||
|
- cuando cambie la vision del sistema RAG
|
||||||
|
- cuando se definan o ajusten objetivos clave del diseño base
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `RAG/docs/INGESTA.md`
|
||||||
|
|
||||||
|
**Ubicacion:** `RAG/docs/INGESTA.md`
|
||||||
|
|
||||||
|
**Proposito:**
|
||||||
|
Definir el modulo de ingesta del sistema RAG, su alcance, responsabilidades y preparacion para futuras actualizaciones.
|
||||||
|
|
||||||
|
**Cuando leerlo:**
|
||||||
|
- al diseñar la entrada de conocimiento al RAG
|
||||||
|
- al decidir como se incorporan y registran las fuentes
|
||||||
|
|
||||||
|
**Cuando actualizarlo:**
|
||||||
|
- cuando cambie el alcance de la ingesta
|
||||||
|
- cuando se acuerden metadatos, tipos de fuente o comportamiento base
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `RAG/docs/PROCESADO.md`
|
||||||
|
|
||||||
|
**Ubicacion:** `RAG/docs/PROCESADO.md`
|
||||||
|
|
||||||
|
**Proposito:**
|
||||||
|
Definir el modulo de procesado del RAG, especialmente la preparacion del contenido y la estrategia de chunking.
|
||||||
|
|
||||||
|
**Cuando leerlo:**
|
||||||
|
- al definir como se transforma el contenido ingerido antes de indexarlo
|
||||||
|
- al trabajar decisiones sobre chunking y estructura recuperable
|
||||||
|
|
||||||
|
**Cuando actualizarlo:**
|
||||||
|
- cuando se acuerde o cambie la estrategia de chunking
|
||||||
|
- cuando se amplie el tratamiento por tipo de documento o fuente
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `RAG/docs/BITACORA_DISENO_RAG.md`
|
||||||
|
|
||||||
|
**Ubicacion:** `RAG/docs/BITACORA_DISENO_RAG.md`
|
||||||
|
|
||||||
|
**Proposito:**
|
||||||
|
Mantener una vision panoramica del camino de diseño del RAG, incluyendo decisiones, razonamiento, contexto y opciones abiertas.
|
||||||
|
|
||||||
|
**Cuando leerlo:**
|
||||||
|
- al retomar la conversacion de diseño del RAG
|
||||||
|
- al necesitar entender por que se eligio una direccion concreta
|
||||||
|
- al querer ver la vision general que da sentido al resto de documentos del modulo
|
||||||
|
|
||||||
|
**Cuando actualizarlo:**
|
||||||
|
- cuando se cierre una decision relevante de diseño
|
||||||
|
- cuando aparezca un cambio de rumbo, matiz importante u opcion abierta que merezca conservarse
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `RAG/docs/SALIDA.md`
|
||||||
|
|
||||||
|
**Ubicacion:** `RAG/docs/SALIDA.md`
|
||||||
|
|
||||||
|
**Proposito:**
|
||||||
|
Definir la salida del sistema RAG, especialmente la recuperacion de contexto y la estructura del retrieve inicial.
|
||||||
|
|
||||||
|
**Cuando leerlo:**
|
||||||
|
- al diseñar como consumiran el RAG agentes, apps o herramientas
|
||||||
|
- al revisar que debe devolver `retrieve` y como debe servir de contexto de arranque
|
||||||
|
|
||||||
|
**Cuando actualizarlo:**
|
||||||
|
- cuando cambie la estructura de salida del RAG
|
||||||
|
- cuando se ajusten los modos de retrieve, answer o sintesis
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `RAG/docs/STACK_TECNICO_V1.md`
|
||||||
|
|
||||||
|
**Ubicacion:** `RAG/docs/STACK_TECNICO_V1.md`
|
||||||
|
|
||||||
|
**Proposito:**
|
||||||
|
Documentar el stack tecnico minimo acordado para construir la primera version funcional del RAG.
|
||||||
|
|
||||||
|
**Cuando leerlo:**
|
||||||
|
- al iniciar la implementacion tecnica del RAG
|
||||||
|
- al revisar decisiones de backend, vector store, parsing y forma de acceso
|
||||||
|
|
||||||
|
**Cuando actualizarlo:**
|
||||||
|
- cuando cambie alguna decision tecnica base del stack
|
||||||
|
- cuando se cierre o cambie el proveedor inicial de embeddings
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `sesion_actual_opencode.md`
|
||||||
|
|
||||||
|
**Ubicacion:** `docs/sesion_actual_opencode.md`
|
||||||
|
|
||||||
|
**Proposito:**
|
||||||
|
Instruccion universal para detectar la sesion activa de OpenCode del workspace actual.
|
||||||
|
|
||||||
|
**Cuando leerlo:**
|
||||||
|
- al necesitar identificar `session_id`, titulo y directorio de la sesion actual
|
||||||
|
|
||||||
|
**Cuando actualizarlo:**
|
||||||
|
- solo si el usuario lo pide expresamente o cambia el mecanismo canonico de deteccion
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Estado actual de la documentacion
|
||||||
|
|
||||||
|
- La documentacion global del workspace esta concentrada en `docs/`.
|
||||||
|
- La documentacion especifica de cada tool debe vivir dentro de su propio modulo raiz.
|
||||||
|
- Se ha eliminado el arrastre documental de proyectos anteriores en los archivos base.
|
||||||
|
- Cualquier documento nuevo debe responder al objetivo de tools IA para empresas.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Estadistica global
|
||||||
|
|
||||||
|
**Total de documentos indexados:** 12
|
||||||
139
docs/PENDIENTES_GENERALES.md
Normal file
139
docs/PENDIENTES_GENERALES.md
Normal file
|
|
@ -0,0 +1,139 @@
|
||||||
|
# Pendientes Generales
|
||||||
|
|
||||||
|
**Proyecto:** Workspace de tools IA para empresas
|
||||||
|
**Ultima actualizacion:** 2026-04-02
|
||||||
|
**Ultima modificacion por:** Agente tools IA para potenciar servicios empresariales
|
||||||
|
**Estado:** Activo
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Proposito
|
||||||
|
|
||||||
|
Este documento centraliza las lineas de trabajo, experimentos, investigaciones y tools pendientes del workspace para dar continuidad al objetivo principal:
|
||||||
|
|
||||||
|
- Desarrollar tools, herramientas, skills y componentes reutilizables para potenciar soluciones con IA para empresas.
|
||||||
|
- Priorizar piezas que puedan integrarse facilmente con servicios ya en funcionamiento.
|
||||||
|
- Mantener visible el estado de las iniciativas activas y sus siguientes pasos.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Como usar este documento
|
||||||
|
|
||||||
|
Este archivo funciona como listado rapido de ideas, lineas de trabajo y frentes pendientes del workspace.
|
||||||
|
|
||||||
|
Cuando el usuario pregunte que hay pendiente por hacer, este documento debe permitir responder con un panorama rapido y claro.
|
||||||
|
|
||||||
|
No debe convertirse en un gestor detallado de tareas ni duplicar informacion que pertenezca a otros documentos.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Lineas de trabajo prioritarias
|
||||||
|
|
||||||
|
### 1. Sistema basico RAG reutilizable
|
||||||
|
|
||||||
|
**Objetivo:**
|
||||||
|
Diseñar una base RAG simple, modular y facil de conectar a otros servicios ya existentes.
|
||||||
|
|
||||||
|
**Enfoque inicial:**
|
||||||
|
- Definir una arquitectura minima reutilizable.
|
||||||
|
- Permitir conexion sencilla desde otros servicios o agentes.
|
||||||
|
- Separar claramente ingesta, almacenamiento, recuperacion y consumo.
|
||||||
|
- Evaluar un formato de configuracion que permita adaptar el RAG a distintos clientes o casos de uso.
|
||||||
|
|
||||||
|
**Pendientes iniciales:**
|
||||||
|
- Definir estructura base del proyecto o modulo.
|
||||||
|
- Elegir estrategia inicial de almacenamiento y vectorizacion.
|
||||||
|
- Definir interfaz de integracion con servicios externos.
|
||||||
|
- Identificar un primer caso real de uso para validacion.
|
||||||
|
|
||||||
|
**Estado:** Pendiente
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. Estructura MCP para integracion de tools
|
||||||
|
|
||||||
|
**Objetivo:**
|
||||||
|
Preparar una estructura MCP que permita ir conectando nuestras tools con otros servicios que vayamos desarrollando y poniendo en funcionamiento.
|
||||||
|
|
||||||
|
**Enfoque inicial:**
|
||||||
|
- Dejar una base de organizacion clara para futuras tools.
|
||||||
|
- Definir criterios para exponer capacidades de forma consistente.
|
||||||
|
- Diseñar una estructura que facilite escalar nuevas integraciones.
|
||||||
|
- Mantener separacion entre tools independientes y tools de soporte para otros servicios.
|
||||||
|
|
||||||
|
**Pendientes iniciales:**
|
||||||
|
- Definir estructura minima MCP del workspace.
|
||||||
|
- Establecer convenciones de organizacion para nuevas tools.
|
||||||
|
- Identificar primeras capacidades a exponer.
|
||||||
|
- Documentar como se conectara con servicios internos y externos.
|
||||||
|
|
||||||
|
**Estado:** Pendiente
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. Potenciar agentes de Retell con herramientas y flujos externos
|
||||||
|
|
||||||
|
**Objetivo:**
|
||||||
|
Preparar una integracion para que Retell pueda conectarse con flujos y servicios externos, como n8n y otros sistemas, de manera que el agente telefonico pueda apoyarse en herramientas adicionales y fuentes externas de informacion.
|
||||||
|
|
||||||
|
**Contexto actual:**
|
||||||
|
- Existe un flujo en n8n para atencion al cliente.
|
||||||
|
- Ese flujo ya dispone de varias herramientas para atender clientes de una empresa concreta.
|
||||||
|
- Actualmente las peticiones suelen llegar desde WhatsApp.
|
||||||
|
- Se quiere que Retell pueda aprovechar ese tipo de flujos externos y tambien futuras integraciones similares.
|
||||||
|
|
||||||
|
**Enfoque inicial:**
|
||||||
|
- Analizar como Retell puede invocar herramientas o endpoints externos.
|
||||||
|
- Diseñar una capa de integracion reutilizable para conectar Retell con n8n u otros servicios.
|
||||||
|
- Reutilizar herramientas ya existentes sin acoplarlas a un unico canal.
|
||||||
|
- Dejar preparada una via estandar para futuras integraciones de voz con fuentes externas.
|
||||||
|
|
||||||
|
**Pendientes iniciales:**
|
||||||
|
- Definir payloads de entrada y salida entre Retell y servicios externos.
|
||||||
|
- Identificar endpoint, adaptador o capa intermedia de integracion.
|
||||||
|
- Mapear como reutilizar el flujo actual de n8n desde Retell.
|
||||||
|
- Detectar necesidades de contexto, autenticacion, trazabilidad y control de errores.
|
||||||
|
|
||||||
|
**Estado:** Pendiente
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. Explorar posible potenciacion del RAG con Obsidian
|
||||||
|
|
||||||
|
**Objetivo:**
|
||||||
|
Evaluar si Obsidian puede aportar valor como capa de organizacion, fuente de conocimiento o apoyo a futuras capacidades del sistema RAG.
|
||||||
|
|
||||||
|
**Pendientes iniciales:**
|
||||||
|
- Revisar en que puntos podria integrarse con el RAG.
|
||||||
|
- Valorar si aporta ventajas reales para documentacion, enlaces entre conceptos o gestion de conocimiento.
|
||||||
|
- Dejarlo como linea futura, sin afectar a la salida de la v1.
|
||||||
|
|
||||||
|
**Estado:** Pendiente
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Criterios generales de desarrollo
|
||||||
|
|
||||||
|
- Priorizar soluciones simples y reutilizables.
|
||||||
|
- Favorecer integraciones desacopladas de un unico canal o proveedor.
|
||||||
|
- Diseñar cada tool pensando en reutilizacion por otros servicios.
|
||||||
|
- Documentar decisiones tecnicas y aprendizajes a medida que avancemos.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Resumen rapido
|
||||||
|
|
||||||
|
| Linea | Estado |
|
||||||
|
|-------|--------|
|
||||||
|
| Sistema basico RAG reutilizable | Pendiente |
|
||||||
|
| Estructura MCP para integracion de tools | Pendiente |
|
||||||
|
| Retell conectado con herramientas externas | Pendiente |
|
||||||
|
| Posible potenciacion del RAG con Obsidian | Pendiente |
|
||||||
75
docs/README.md
Normal file
75
docs/README.md
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
# README para agentes del workspace
|
||||||
|
|
||||||
|
## Proyecto: Workspace de tools IA para empresas
|
||||||
|
|
||||||
|
Lee este archivo antes de crear documentos, editar documentacion o registrar sesiones.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Objetivo del workspace
|
||||||
|
|
||||||
|
En este workspace desarrollamos tools, herramientas, skills, integraciones y componentes reutilizables para potenciar implementaciones de IA en empresas.
|
||||||
|
|
||||||
|
Esto incluye, entre otros:
|
||||||
|
- sistemas RAG
|
||||||
|
- servidores o estructuras MCP
|
||||||
|
- integraciones con servicios externos
|
||||||
|
- herramientas reutilizables para otros servicios de IA
|
||||||
|
- pruebas tecnicas, experimentos e investigaciones aplicadas
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Documentos base
|
||||||
|
|
||||||
|
Consulta siempre estos documentos:
|
||||||
|
|
||||||
|
1. `docs/INDICE_DOCUMENTACION.md`
|
||||||
|
2. `docs/README.md`
|
||||||
|
3. `docs/HISTORIAL_SESIONES.md`
|
||||||
|
4. `docs/PENDIENTES_GENERALES.md`
|
||||||
|
5. `docs/sesion_actual_opencode.md`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Reglas de documentacion
|
||||||
|
|
||||||
|
1. Toda la documentacion del workspace va en `docs/`.
|
||||||
|
2. Antes de crear un documento nuevo, revisa `docs/INDICE_DOCUMENTACION.md` para evitar duplicados.
|
||||||
|
3. Si creas un documento nuevo, indexalo en `docs/INDICE_DOCUMENTACION.md` en la misma sesion.
|
||||||
|
4. Si haces un cambio relevante en el workspace, revisa si hay que actualizar `docs/PENDIENTES_GENERALES.md` o `docs/HISTORIAL_SESIONES.md`.
|
||||||
|
5. `docs/sesion_actual_opencode.md` es una instruccion universal del workspace y no debe modificarse salvo peticion explicita del usuario.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Identificacion del agente
|
||||||
|
|
||||||
|
Si el usuario pide identificacion, revisa `docs/HISTORIAL_SESIONES.md` y responde con:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Soy el [Nombre del Agente].
|
||||||
|
Trabajo en el proyecto: [Nombre del Proyecto].
|
||||||
|
Mi Conversation ID es: [valor disponible o N/D].
|
||||||
|
Me encargo de: [responsabilidad].
|
||||||
|
```
|
||||||
|
|
||||||
|
En este entorno OpenCode puede no exponer `Conversation ID`, por lo que puede usarse `session_id` del workspace como referencia operativa cuando aplique.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Registro de sesiones
|
||||||
|
|
||||||
|
Al finalizar una sesion de trabajo relevante:
|
||||||
|
|
||||||
|
1. Actualiza `docs/HISTORIAL_SESIONES.md`.
|
||||||
|
2. Registra el agente, modelo y trabajo realizado.
|
||||||
|
3. Incluye `session_id` de OpenCode cuando sea util para identificar la sesion del workspace.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Criterios de trabajo
|
||||||
|
|
||||||
|
1. Priorizar soluciones simples, reutilizables y desacopladas.
|
||||||
|
2. Pensar cada tool como pieza aprovechable por otros servicios.
|
||||||
|
3. Mantener foco en integraciones utiles para IA empresarial.
|
||||||
|
4. Evitar arrastrar documentacion de otros proyectos a este workspace.
|
||||||
|
5. Mantener la documentacion alineada con el estado real del workspace.
|
||||||
73
docs/TASK.md
Normal file
73
docs/TASK.md
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
# TASK
|
||||||
|
|
||||||
|
**Proyecto:** Workspace de tools IA para empresas
|
||||||
|
**Ultima actualizacion:** 2026-04-02
|
||||||
|
**Ultima modificacion por:** Agente tools IA para potenciar servicios empresariales
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Proposito
|
||||||
|
|
||||||
|
Este documento sirve para partir lineas de trabajo amplias en bloques de analisis y acuerdos, de forma que podamos avanzar sin dejarnos puntos importantes por el camino.
|
||||||
|
|
||||||
|
No sustituye a `docs/PENDIENTES_GENERALES.md`.
|
||||||
|
|
||||||
|
- `PENDIENTES_GENERALES.md` mantiene el panorama rapido de ideas y lineas pendientes.
|
||||||
|
- `TASK.md` descompone una linea concreta cuando requiere varias conversaciones, decisiones o pasos.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task principal
|
||||||
|
|
||||||
|
### Diseño y planeacion del tipo de RAG a crear
|
||||||
|
|
||||||
|
**Objetivo de esta task:**
|
||||||
|
Definir con claridad que tipo de sistema RAG queremos construir para que sea util, reutilizable, robusto desde una primera version sencilla y preparado para crecer hacia proyectos de clientes.
|
||||||
|
|
||||||
|
**Puntos a analizar y convenir:**
|
||||||
|
|
||||||
|
1. Diseño de fuentes
|
||||||
|
- Que tipos de fuentes debe soportar el sistema.
|
||||||
|
- Si empezamos solo con documentos o dejamos preparada entrada para APIs, bases de datos u otras fuentes.
|
||||||
|
- Como desacoplar conectores para que el sistema se adapte a distintos clientes.
|
||||||
|
|
||||||
|
2. Modelo de conocimiento
|
||||||
|
- Como representar la informacion de forma util para recuperacion.
|
||||||
|
- Que metadatos minimos conviene guardar desde el inicio.
|
||||||
|
- Como preparar el sistema para distintas clases de contenido.
|
||||||
|
|
||||||
|
3. Estrategia de chunking
|
||||||
|
- Como dividir la informacion en fragmentos utiles.
|
||||||
|
- Que nivel de contexto debe conservar cada fragmento.
|
||||||
|
- Como evitar fragmentos demasiado pequenos, demasiado grandes o sin sentido aislado.
|
||||||
|
|
||||||
|
4. Recuperacion
|
||||||
|
- Como encontrar el contexto mas relevante para una consulta.
|
||||||
|
- Que enfoque minimo usar en la primera version.
|
||||||
|
- Como dejar abierta la puerta a mejoras posteriores.
|
||||||
|
|
||||||
|
5. Multi-tenant y reutilizacion por cliente
|
||||||
|
- Como dar servicio a distintos clientes o proyectos con una misma base RAG.
|
||||||
|
- Como aislar informacion por cliente, app o entorno.
|
||||||
|
- Como preparar el sistema para que mejoras internas beneficien a todos los consumidores del servicio.
|
||||||
|
|
||||||
|
6. Interfaz de integracion
|
||||||
|
- Como consumiran el RAG otros agentes, tools o servicios.
|
||||||
|
- Si la primera interfaz sera API, libreria, CLI, MCP o una combinacion.
|
||||||
|
- Como evitar acoplar el RAG a un unico tipo de consumidor.
|
||||||
|
|
||||||
|
7. Observabilidad
|
||||||
|
- Que informacion necesitaremos para entender que esta recuperando el sistema.
|
||||||
|
- Como sabremos de donde sale el contexto devuelto.
|
||||||
|
- Que datos son utiles para depurar y mejorar el RAG.
|
||||||
|
|
||||||
|
8. Evaluacion
|
||||||
|
- Como validar si recupera bien.
|
||||||
|
- Como medir si la respuesta mejora con el contexto recuperado.
|
||||||
|
- Como detectar errores, ruido o falta de cobertura.
|
||||||
|
|
||||||
|
**Criterio de trabajo para esta task:**
|
||||||
|
- Empezar simple.
|
||||||
|
- Diseñar con vision de crecimiento.
|
||||||
|
- Evitar complejidad innecesaria en la primera version.
|
||||||
|
- Dejar preparadas las decisiones que condicionan una evolucion futura sana.
|
||||||
41
docs/sesion_actual_opencode.md
Normal file
41
docs/sesion_actual_opencode.md
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
# Sesion actual de OpenCode (instruccion universal para cualquier agente)
|
||||||
|
|
||||||
|
Objetivo:
|
||||||
|
- Que cualquier agente, incluso uno simple, pueda identificar su sesion del workspace actual sin ambiguedad.
|
||||||
|
|
||||||
|
## Mensaje exacto para dar al agente
|
||||||
|
|
||||||
|
Copiar y pegar tal cual:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Lee `docs/sesion_actual_opencode.md` y ejecuta exactamente este comando para identificar tu sesion del workspace actual:
|
||||||
|
|
||||||
|
opencode db "SELECT id,title,directory,datetime(time_updated/1000,'unixepoch','localtime') AS updated FROM session WHERE directory='$(pwd)' ORDER BY time_updated DESC LIMIT 1" --format tsv
|
||||||
|
|
||||||
|
Luego responde SOLO con:
|
||||||
|
1) `session_id`
|
||||||
|
2) `title`
|
||||||
|
3) `directory`
|
||||||
|
4) `updated`
|
||||||
|
|
||||||
|
Si no hay filas, responde exactamente: `SIN_SESION_EN_ESTE_WORKSPACE`.
|
||||||
|
No inventes datos.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Comando canonico (fuente de verdad)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
opencode db "SELECT id,title,directory,datetime(time_updated/1000,'unixepoch','localtime') AS updated FROM session WHERE directory='$(pwd)' ORDER BY time_updated DESC LIMIT 1" --format tsv
|
||||||
|
```
|
||||||
|
|
||||||
|
## Continuar automaticamente la sesion detectada
|
||||||
|
|
||||||
|
```bash
|
||||||
|
opencode -s "$(opencode db \"SELECT id FROM session WHERE directory='$(pwd)' ORDER BY time_updated DESC LIMIT 1\" --format tsv | awk 'NR==2{print $1}')"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ver historial corto del workspace actual (ultimas 10)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
opencode db "SELECT id,title,datetime(time_updated/1000,'unixepoch','localtime') AS updated FROM session WHERE directory='$(pwd)' ORDER BY time_updated DESC LIMIT 10" --format tsv
|
||||||
|
```
|
||||||
Loading…
Add table
Reference in a new issue