Add bootstrap-aware chat playground for RAG
This commit is contained in:
parent
93985976d6
commit
150aa818ad
9 changed files with 634 additions and 188 deletions
|
|
@ -337,6 +337,51 @@ Payload base:
|
|||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 6. `POST /chat`
|
||||
|
||||
Sirve para conversar con un modelo usando:
|
||||
|
||||
- contexto bootstrap precargado
|
||||
- historial reciente de mensajes
|
||||
- y opcionalmente una consulta adicional al RAG durante la propia conversacion
|
||||
|
||||
Payload base:
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "y ahora cuales son los siguientes pasos mas naturales?",
|
||||
"history": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "dame un mapa inicial del workspace"
|
||||
}
|
||||
],
|
||||
"mode": "documental",
|
||||
"model": "openai/gpt-4.1-mini",
|
||||
"preloadedContext": "<resumen del bootstrap>",
|
||||
"allowAdditionalRetrieve": true,
|
||||
"scope": {
|
||||
"sourceRef": "/home/pancho/Documentos/Empresa/Desarrollo/IA/docs"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Respuesta esperada resumida:
|
||||
|
||||
```json
|
||||
{
|
||||
"model": "openai/gpt-4.1-mini",
|
||||
"answer": "...",
|
||||
"usedBootstrapContext": true,
|
||||
"usedAdditionalRetrieve": true,
|
||||
"retrieved": {
|
||||
"summary": "..."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Respuesta esperada:
|
||||
|
||||
```json
|
||||
|
|
|
|||
|
|
@ -47,10 +47,12 @@ El backend lo sirve desde:
|
|||
|
||||
1. `health`
|
||||
2. `ingest`
|
||||
3. `retrieve`
|
||||
4. `answer`
|
||||
5. `answer` sin RAG para comparar impacto del contexto
|
||||
6. seleccion explicita del modelo de `answer`
|
||||
3. `bootstrap`
|
||||
4. `chat` con contexto precargado
|
||||
5. `retrieve`
|
||||
6. `answer`
|
||||
7. `answer` sin RAG para comparar impacto del contexto
|
||||
8. seleccion explicita del modelo de `answer`
|
||||
|
||||
Tambien permite:
|
||||
|
||||
|
|
@ -62,6 +64,46 @@ Tambien permite:
|
|||
|
||||
---
|
||||
|
||||
## Mecanica actual del playground
|
||||
|
||||
El playground ya no funciona como una sola caja de consulta tecnica. Ahora se organiza en tres pestañas:
|
||||
|
||||
1. `Ingesta`
|
||||
- lanzar ingesta documental o de codigo
|
||||
|
||||
2. `Bootstrap`
|
||||
- elegir scope
|
||||
- elegir modo
|
||||
- cargar un mapa inicial del dominio
|
||||
- opcionalmente pedir a un modelo que sintetice ese bootstrap
|
||||
- reemplazar o vaciar el contexto de sesion
|
||||
|
||||
3. `Chat`
|
||||
- conversar con el modelo
|
||||
- ver visualmente si hay contexto cargado o no
|
||||
- reutilizar el ultimo bootstrap como contexto base
|
||||
- permitir que el modelo haga consultas adicionales al RAG durante la conversacion
|
||||
|
||||
### Indicador visual de contexto
|
||||
|
||||
En la pestaña `Chat` hay un indicador visual:
|
||||
|
||||
- rojo: no hay bootstrap cargado
|
||||
- verde: hay contexto bootstrap activo
|
||||
|
||||
Tambien se muestra el `scope` actualmente cargado.
|
||||
|
||||
### Chat con consultas adicionales al RAG
|
||||
|
||||
El chat ya soporta dos niveles:
|
||||
|
||||
1. respuesta usando solo el bootstrap cargado
|
||||
2. respuesta usando bootstrap y, si se activa la opcion correspondiente, una consulta adicional al RAG durante la conversacion
|
||||
|
||||
Esto permite aproximar mejor el comportamiento esperado de una app o agente conectado al servicio.
|
||||
|
||||
---
|
||||
|
||||
## Idea de uso
|
||||
|
||||
Este playground no sustituye a clientes finales ni al futuro MCP.
|
||||
|
|
|
|||
|
|
@ -1,30 +1,46 @@
|
|||
const healthButton = document.getElementById("healthButton");
|
||||
const ingestButton = document.getElementById("ingestButton");
|
||||
const queryButton = document.getElementById("queryButton");
|
||||
const bootstrapButton = document.getElementById("bootstrapButton");
|
||||
const replaceBootstrapButton = document.getElementById("replaceBootstrapButton");
|
||||
const clearBootstrapButton = document.getElementById("clearBootstrapButton");
|
||||
const sendChatButton = document.getElementById("sendChatButton");
|
||||
const clearChatButton = document.getElementById("clearChatButton");
|
||||
const presetDocs = document.getElementById("presetDocs");
|
||||
const presetRagDocs = document.getElementById("presetRagDocs");
|
||||
const presetCode = document.getElementById("presetCode");
|
||||
|
||||
const healthResult = document.getElementById("healthResult");
|
||||
const ingestResult = document.getElementById("ingestResult");
|
||||
const bootstrapResult = document.getElementById("bootstrapResult");
|
||||
const mainResult = document.getElementById("mainResult");
|
||||
const compareResult = document.getElementById("compareResult");
|
||||
|
||||
const queryInput = document.getElementById("queryInput");
|
||||
const queryMode = document.getElementById("queryMode");
|
||||
const queryIntent = document.getElementById("queryIntent");
|
||||
const queryOperation = document.getElementById("queryOperation");
|
||||
const answerModel = document.getElementById("answerModel");
|
||||
const useModelInRetrieve = document.getElementById("useModelInRetrieve");
|
||||
const scopePresetSelect = document.getElementById("scopePresetSelect");
|
||||
const scopeSourceRef = document.getElementById("scopeSourceRef");
|
||||
const scopeTags = document.getElementById("scopeTags");
|
||||
const compareWithoutRag = document.getElementById("compareWithoutRag");
|
||||
const bootstrapContextResult = document.getElementById("bootstrapContextResult");
|
||||
const chatMessages = document.getElementById("chatMessages");
|
||||
const contextIndicator = document.getElementById("contextIndicator");
|
||||
const contextStatusText = document.getElementById("contextStatusText");
|
||||
const contextScopeText = document.getElementById("contextScopeText");
|
||||
|
||||
const ingestSourceType = document.getElementById("ingestSourceType");
|
||||
const ingestSourceRef = document.getElementById("ingestSourceRef");
|
||||
const ingestMode = document.getElementById("ingestMode");
|
||||
const ingestTags = document.getElementById("ingestTags");
|
||||
|
||||
const bootstrapQuery = document.getElementById("bootstrapQuery");
|
||||
const bootstrapMode = document.getElementById("bootstrapMode");
|
||||
const answerModel = document.getElementById("answerModel");
|
||||
const useModelInRetrieve = document.getElementById("useModelInRetrieve");
|
||||
const reuseBootstrapContext = document.getElementById("reuseBootstrapContext");
|
||||
const allowAdditionalRetrieve = document.getElementById("allowAdditionalRetrieve");
|
||||
const scopePresetSelect = document.getElementById("scopePresetSelect");
|
||||
const scopeSourceRef = document.getElementById("scopeSourceRef");
|
||||
const scopeTags = document.getElementById("scopeTags");
|
||||
const compareWithoutRag = document.getElementById("compareWithoutRag");
|
||||
const chatMode = document.getElementById("chatMode");
|
||||
const chatInput = document.getElementById("chatInput");
|
||||
|
||||
let lastBootstrapContext = null;
|
||||
let chatHistory = [];
|
||||
|
||||
function format(value) {
|
||||
return JSON.stringify(value, null, 2);
|
||||
}
|
||||
|
|
@ -38,21 +54,66 @@ function buildScopeLabel(scope) {
|
|||
return `${scope.sourceRef} [${modes}]`;
|
||||
}
|
||||
|
||||
async function request(url, payload, method = "POST") {
|
||||
const response = await fetch(url, {
|
||||
function request(url, payload, method = "POST") {
|
||||
return fetch(url, {
|
||||
method,
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: payload ? JSON.stringify(payload) : undefined
|
||||
});
|
||||
|
||||
}).then(async (response) => {
|
||||
const data = await response.json();
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || `HTTP ${response.status}`);
|
||||
}
|
||||
|
||||
return data;
|
||||
});
|
||||
}
|
||||
|
||||
function renderBootstrapContext() {
|
||||
if (!lastBootstrapContext) {
|
||||
bootstrapContextResult.textContent = "Aun no hay bootstrap cargado.";
|
||||
contextIndicator.className = "indicator indicator-off";
|
||||
contextStatusText.textContent = "Sin contexto cargado";
|
||||
contextScopeText.textContent = "No hay bootstrap activo.";
|
||||
return;
|
||||
}
|
||||
|
||||
bootstrapContextResult.textContent = format(lastBootstrapContext);
|
||||
contextIndicator.className = "indicator indicator-on";
|
||||
contextStatusText.textContent = "Contexto bootstrap activo";
|
||||
contextScopeText.textContent = lastBootstrapContext.scope?.sourceRef || "Scope no especificado";
|
||||
}
|
||||
|
||||
function renderChatHistory() {
|
||||
if (chatHistory.length === 0) {
|
||||
chatMessages.innerHTML = '<p class="empty-chat">Aun no hay conversacion. Carga un bootstrap y empieza a preguntar.</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
chatMessages.innerHTML = chatHistory.map((entry) => `
|
||||
<article class="message ${entry.role}">
|
||||
<header>${entry.role === "user" ? "Usuario" : "Modelo"}</header>
|
||||
<p>${entry.content.replace(/</g, "<")}</p>
|
||||
</article>
|
||||
`).join("");
|
||||
|
||||
chatMessages.scrollTop = chatMessages.scrollHeight;
|
||||
}
|
||||
|
||||
function buildScopeFromInputs() {
|
||||
return {
|
||||
sourceRef: scopeSourceRef.value,
|
||||
tags: splitTags(scopeTags.value)
|
||||
};
|
||||
}
|
||||
|
||||
function applyPreset(mode, query, sourceRef, useRetrieveModel = false) {
|
||||
bootstrapMode.value = mode;
|
||||
chatMode.value = mode;
|
||||
bootstrapQuery.value = query;
|
||||
scopeSourceRef.value = sourceRef;
|
||||
useModelInRetrieve.checked = useRetrieveModel;
|
||||
}
|
||||
|
||||
async function loadScopes() {
|
||||
|
|
@ -76,11 +137,7 @@ async function loadScopes() {
|
|||
placeholder.textContent = "No hay scopes detectados";
|
||||
}
|
||||
} catch (error) {
|
||||
scopePresetSelect.innerHTML = "";
|
||||
const option = document.createElement("option");
|
||||
option.value = "";
|
||||
option.textContent = `Error cargando scopes: ${error}`;
|
||||
scopePresetSelect.appendChild(option);
|
||||
scopePresetSelect.innerHTML = `<option value="">Error cargando scopes: ${error}</option>`;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -88,26 +145,29 @@ async function loadAnswerModels() {
|
|||
try {
|
||||
const payload = await fetch("/models/answer").then((response) => response.json());
|
||||
answerModel.innerHTML = "";
|
||||
|
||||
for (const model of payload.models || []) {
|
||||
const option = document.createElement("option");
|
||||
option.value = model;
|
||||
option.textContent = model;
|
||||
answerModel.appendChild(option);
|
||||
}
|
||||
|
||||
if (payload.defaultModel) {
|
||||
answerModel.value = payload.defaultModel;
|
||||
}
|
||||
} catch {
|
||||
answerModel.innerHTML = "";
|
||||
const option = document.createElement("option");
|
||||
option.value = "openai/gpt-4.1-mini";
|
||||
option.textContent = "openai/gpt-4.1-mini";
|
||||
answerModel.appendChild(option);
|
||||
answerModel.innerHTML = '<option value="openai/gpt-4.1-mini">openai/gpt-4.1-mini</option>';
|
||||
}
|
||||
}
|
||||
|
||||
document.querySelectorAll(".tab-button").forEach((button) => {
|
||||
button.addEventListener("click", () => {
|
||||
document.querySelectorAll(".tab-button").forEach((entry) => entry.classList.remove("active"));
|
||||
document.querySelectorAll(".tab-panel").forEach((panel) => panel.classList.remove("active"));
|
||||
button.classList.add("active");
|
||||
document.getElementById(`tab-${button.dataset.tab}`).classList.add("active");
|
||||
});
|
||||
});
|
||||
|
||||
scopePresetSelect.addEventListener("change", () => {
|
||||
if (!scopePresetSelect.value) {
|
||||
return;
|
||||
|
|
@ -118,17 +178,18 @@ scopePresetSelect.addEventListener("change", () => {
|
|||
scopeTags.value = (scope.tags || []).join(", ");
|
||||
|
||||
if (scope.chunkModes.includes("codigo")) {
|
||||
queryMode.value = "codigo";
|
||||
bootstrapMode.value = "codigo";
|
||||
chatMode.value = "codigo";
|
||||
} else if (scope.chunkModes.includes("documental")) {
|
||||
queryMode.value = "documental";
|
||||
bootstrapMode.value = "documental";
|
||||
chatMode.value = "documental";
|
||||
}
|
||||
});
|
||||
|
||||
healthButton.addEventListener("click", async () => {
|
||||
healthResult.textContent = "Comprobando...";
|
||||
try {
|
||||
const response = await fetch("/health");
|
||||
const data = await response.json();
|
||||
const data = await fetch("/health").then((response) => response.json());
|
||||
healthResult.textContent = format(data);
|
||||
} catch (error) {
|
||||
healthResult.textContent = String(error);
|
||||
|
|
@ -136,7 +197,7 @@ healthButton.addEventListener("click", async () => {
|
|||
});
|
||||
|
||||
ingestButton.addEventListener("click", async () => {
|
||||
mainResult.textContent = "Ejecutando ingesta...";
|
||||
ingestResult.textContent = "Ejecutando ingesta...";
|
||||
try {
|
||||
const data = await request("/ingest", {
|
||||
sourceType: ingestSourceType.value,
|
||||
|
|
@ -144,72 +205,122 @@ ingestButton.addEventListener("click", async () => {
|
|||
mode: ingestMode.value,
|
||||
tags: splitTags(ingestTags.value)
|
||||
});
|
||||
mainResult.textContent = format(data);
|
||||
ingestResult.textContent = format(data);
|
||||
await loadScopes();
|
||||
} catch (error) {
|
||||
mainResult.textContent = String(error);
|
||||
ingestResult.textContent = String(error);
|
||||
}
|
||||
});
|
||||
|
||||
queryButton.addEventListener("click", async () => {
|
||||
async function executeBootstrap() {
|
||||
bootstrapResult.textContent = "Cargando bootstrap...";
|
||||
try {
|
||||
const data = await request("/retrieve", {
|
||||
mode: bootstrapMode.value,
|
||||
intent: "bootstrap",
|
||||
query: bootstrapQuery.value,
|
||||
model: answerModel.value,
|
||||
useModelInRetrieve: useModelInRetrieve.checked,
|
||||
scope: buildScopeFromInputs()
|
||||
});
|
||||
|
||||
lastBootstrapContext = data;
|
||||
bootstrapResult.textContent = format(data);
|
||||
renderBootstrapContext();
|
||||
} catch (error) {
|
||||
bootstrapResult.textContent = String(error);
|
||||
}
|
||||
}
|
||||
|
||||
bootstrapButton.addEventListener("click", executeBootstrap);
|
||||
replaceBootstrapButton.addEventListener("click", executeBootstrap);
|
||||
|
||||
clearBootstrapButton.addEventListener("click", () => {
|
||||
lastBootstrapContext = null;
|
||||
renderBootstrapContext();
|
||||
});
|
||||
|
||||
sendChatButton.addEventListener("click", async () => {
|
||||
const message = chatInput.value.trim();
|
||||
if (!message) {
|
||||
return;
|
||||
}
|
||||
|
||||
chatHistory.push({ role: "user", content: message });
|
||||
renderChatHistory();
|
||||
mainResult.textContent = "Consultando...";
|
||||
compareResult.textContent = compareWithoutRag.checked ? "Comparando..." : "Desactivada.";
|
||||
|
||||
const payload = {
|
||||
mode: queryMode.value,
|
||||
intent: queryIntent.value,
|
||||
query: queryInput.value,
|
||||
model: answerModel.value,
|
||||
useModelInRetrieve: useModelInRetrieve.checked,
|
||||
scope: {
|
||||
sourceRef: scopeSourceRef.value,
|
||||
tags: splitTags(scopeTags.value)
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const endpoint = queryOperation.value === "answer" ? "/answer" : "/retrieve";
|
||||
const data = await request(endpoint, payload);
|
||||
mainResult.textContent = format(data);
|
||||
const response = await request("/chat", {
|
||||
message,
|
||||
history: chatHistory,
|
||||
mode: chatMode.value,
|
||||
model: answerModel.value,
|
||||
preloadedContext: reuseBootstrapContext.checked && lastBootstrapContext
|
||||
? (lastBootstrapContext.modelSummary || lastBootstrapContext.summary || "")
|
||||
: undefined,
|
||||
allowAdditionalRetrieve: allowAdditionalRetrieve.checked,
|
||||
scope: buildScopeFromInputs()
|
||||
});
|
||||
|
||||
if (compareWithoutRag.checked && queryOperation.value === "answer") {
|
||||
chatHistory.push({ role: "assistant", content: response.answer });
|
||||
renderChatHistory();
|
||||
mainResult.textContent = format(response);
|
||||
|
||||
if (compareWithoutRag.checked) {
|
||||
const comparison = await request("/answer/direct", {
|
||||
query: queryInput.value,
|
||||
model: answerModel.value
|
||||
query: message,
|
||||
model: answerModel.value,
|
||||
preloadedContext: reuseBootstrapContext.checked && lastBootstrapContext
|
||||
? (lastBootstrapContext.modelSummary || lastBootstrapContext.summary || "")
|
||||
: undefined
|
||||
});
|
||||
compareResult.textContent = format(comparison);
|
||||
}
|
||||
|
||||
chatInput.value = "";
|
||||
} catch (error) {
|
||||
mainResult.textContent = String(error);
|
||||
compareResult.textContent = compareWithoutRag.checked ? String(error) : "Desactivada.";
|
||||
}
|
||||
});
|
||||
|
||||
clearChatButton.addEventListener("click", () => {
|
||||
chatHistory = [];
|
||||
renderChatHistory();
|
||||
mainResult.textContent = "Sin ejecutar aun.";
|
||||
compareResult.textContent = "Desactivada.";
|
||||
});
|
||||
|
||||
presetDocs.addEventListener("click", () => {
|
||||
queryMode.value = "documental";
|
||||
queryIntent.value = "specific";
|
||||
queryOperation.value = "answer";
|
||||
queryInput.value = "que tenemos pendiente por hacer en este workspace";
|
||||
scopeSourceRef.value = "/home/pancho/Documentos/Empresa/Desarrollo/IA/docs";
|
||||
answerModel.value = "openai/gpt-4.1-mini";
|
||||
applyPreset(
|
||||
"documental",
|
||||
"dame un mapa inicial del workspace, sus lineas de trabajo principales, reglas, documentacion base y puntos importantes a tener presentes",
|
||||
"/home/pancho/Documentos/Empresa/Desarrollo/IA/docs",
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
presetRagDocs.addEventListener("click", () => {
|
||||
queryMode.value = "documental";
|
||||
queryIntent.value = "bootstrap";
|
||||
queryOperation.value = "retrieve";
|
||||
queryInput.value = "dame un mapa inicial del modulo RAG, su arquitectura, decisiones, estado actual y documentos clave";
|
||||
scopeSourceRef.value = "/home/pancho/Documentos/Empresa/Desarrollo/IA/RAG/docs";
|
||||
answerModel.value = "openai/gpt-4.1-mini";
|
||||
applyPreset(
|
||||
"documental",
|
||||
"dame un mapa inicial del modulo RAG, su arquitectura, decisiones, estado actual y documentos clave",
|
||||
"/home/pancho/Documentos/Empresa/Desarrollo/IA/RAG/docs",
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
presetCode.addEventListener("click", () => {
|
||||
queryMode.value = "codigo";
|
||||
queryIntent.value = "specific";
|
||||
queryOperation.value = "answer";
|
||||
queryInput.value = "como se construye source_id en el rag";
|
||||
scopeSourceRef.value = "/home/pancho/Documentos/Empresa/Desarrollo/IA/RAG/src";
|
||||
answerModel.value = "openai/gpt-4.1-mini";
|
||||
applyPreset(
|
||||
"codigo",
|
||||
"dame un mapa inicial del codigo del modulo RAG, sus modulos principales, flujo interno y piezas clave",
|
||||
"/home/pancho/Documentos/Empresa/Desarrollo/IA/RAG/src",
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
loadScopes();
|
||||
loadAnswerModels();
|
||||
renderBootstrapContext();
|
||||
renderChatHistory();
|
||||
|
|
|
|||
|
|
@ -11,15 +11,22 @@
|
|||
<header class="hero">
|
||||
<div>
|
||||
<p class="eyebrow">RAG Playground</p>
|
||||
<h1>Pruebas rapidas del RAG</h1>
|
||||
<p class="lead">Interfaz interna para probar ingesta, retrieve, answer y comparacion sin RAG desde el mismo servicio.</p>
|
||||
<h1>Laboratorio del RAG</h1>
|
||||
<p class="lead">Prueba ingesta, bootstrap y chat con contexto visible para evaluar el RAG como si estuviera integrado en una app o agente real.</p>
|
||||
</div>
|
||||
<button id="healthButton" class="secondary">Comprobar health</button>
|
||||
</header>
|
||||
|
||||
<section class="grid">
|
||||
<section class="tabs">
|
||||
<button class="tab-button active" data-tab="ingest">Ingesta</button>
|
||||
<button class="tab-button" data-tab="bootstrap">Bootstrap</button>
|
||||
<button class="tab-button" data-tab="chat">Chat</button>
|
||||
</section>
|
||||
|
||||
<section id="tab-ingest" class="tab-panel active">
|
||||
<article class="panel">
|
||||
<h2>Ingesta</h2>
|
||||
<div class="grid single-grid">
|
||||
<label>Tipo de fuente
|
||||
<select id="ingestSourceType">
|
||||
<option value="folder">folder</option>
|
||||
|
|
@ -29,7 +36,7 @@
|
|||
<label>Ruta de la fuente
|
||||
<input id="ingestSourceRef" value="/home/pancho/Documentos/Empresa/Desarrollo/IA/docs" />
|
||||
</label>
|
||||
<label>Modo
|
||||
<label>Modo de ingesta
|
||||
<select id="ingestMode">
|
||||
<option value="mechanical">mechanical</option>
|
||||
<option value="interactive">interactive</option>
|
||||
|
|
@ -38,43 +45,18 @@
|
|||
<label>Tags (coma separada)
|
||||
<input id="ingestTags" value="workspace,global-docs" />
|
||||
</label>
|
||||
<button id="ingestButton">Lanzar ingesta</button>
|
||||
</article>
|
||||
|
||||
<article class="panel panel-wide">
|
||||
<h2>Consulta</h2>
|
||||
<label>Pregunta
|
||||
<textarea id="queryInput" rows="4">que tenemos pendiente por hacer en este workspace</textarea>
|
||||
</label>
|
||||
|
||||
<div class="row">
|
||||
<label>Modo
|
||||
<select id="queryMode">
|
||||
<option value="documental">documental</option>
|
||||
<option value="codigo">codigo</option>
|
||||
<option value="auto">auto</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>Intent
|
||||
<select id="queryIntent">
|
||||
<option value="specific">specific</option>
|
||||
<option value="bootstrap">bootstrap</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>Operacion
|
||||
<select id="queryOperation">
|
||||
<option value="retrieve">retrieve</option>
|
||||
<option value="answer">answer</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>Modelo answer
|
||||
<select id="answerModel">
|
||||
<option value="openai/gpt-4.1-mini">openai/gpt-4.1-mini</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button id="ingestButton">Lanzar ingesta</button>
|
||||
</div>
|
||||
<pre id="ingestResult">Sin ejecutar aun.</pre>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<div class="row">
|
||||
<section id="tab-bootstrap" class="tab-panel">
|
||||
<article class="panel">
|
||||
<h2>Bootstrap de sesion</h2>
|
||||
<div class="grid single-grid">
|
||||
<label>Scope disponible
|
||||
<select id="scopePresetSelect">
|
||||
<option value="">Cargando scopes...</option>
|
||||
|
|
@ -86,36 +68,98 @@
|
|||
<label>Tags (coma separada)
|
||||
<input id="scopeTags" value="" />
|
||||
</label>
|
||||
<label>Modo del bootstrap
|
||||
<select id="bootstrapMode">
|
||||
<option value="documental">documental</option>
|
||||
<option value="codigo">codigo</option>
|
||||
<option value="auto">auto</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>Modelo para sintetizar bootstrap
|
||||
<select id="answerModel">
|
||||
<option value="openai/gpt-4.1-mini">openai/gpt-4.1-mini</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>Consulta bootstrap
|
||||
<textarea id="bootstrapQuery" rows="4">dame un mapa inicial del workspace, sus lineas de trabajo principales, reglas, documentacion base y puntos importantes a tener presentes</textarea>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" id="compareWithoutRag" />
|
||||
Comparar tambien con respuesta sin RAG
|
||||
<input type="checkbox" id="useModelInRetrieve" checked />
|
||||
Usar un modelo para sintetizar el bootstrap recuperado
|
||||
</label>
|
||||
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" id="useModelInRetrieve" />
|
||||
Usar tambien un modelo para sintetizar el resultado de retrieve
|
||||
</label>
|
||||
|
||||
<div class="actions">
|
||||
<button id="queryButton">Ejecutar consulta</button>
|
||||
<button id="bootstrapButton">Cargar bootstrap</button>
|
||||
<button id="replaceBootstrapButton" class="secondary">Reemplazar contexto</button>
|
||||
<button id="clearBootstrapButton" class="secondary">Vaciar contexto</button>
|
||||
<button id="presetDocs" class="secondary">Preset docs</button>
|
||||
<button id="presetRagDocs" class="secondary">Preset RAG docs</button>
|
||||
<button id="presetCode" class="secondary">Preset codigo</button>
|
||||
</div>
|
||||
<pre id="bootstrapResult">Sin ejecutar aun.</pre>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="grid results-grid">
|
||||
<article class="panel">
|
||||
<h2>Resultado principal</h2>
|
||||
<pre id="mainResult">Sin ejecutar aun.</pre>
|
||||
<section id="tab-chat" class="tab-panel">
|
||||
<div class="grid chat-grid">
|
||||
<article class="panel context-panel">
|
||||
<h2>Estado del contexto</h2>
|
||||
<div class="context-status" id="contextStatusCard">
|
||||
<span id="contextIndicator" class="indicator indicator-off"></span>
|
||||
<div>
|
||||
<strong id="contextStatusText">Sin contexto cargado</strong>
|
||||
<p id="contextScopeText">No hay bootstrap activo.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" id="reuseBootstrapContext" checked />
|
||||
Reutilizar bootstrap como contexto base
|
||||
</label>
|
||||
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" id="allowAdditionalRetrieve" checked />
|
||||
Permitir que el modelo haga consultas adicionales al RAG durante la conversacion
|
||||
</label>
|
||||
|
||||
<label>Modo de chat
|
||||
<select id="chatMode">
|
||||
<option value="documental">documental</option>
|
||||
<option value="codigo">codigo</option>
|
||||
<option value="auto">auto</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<h3>Contexto bootstrap cargado</h3>
|
||||
<pre id="bootstrapContextResult">Aun no hay bootstrap cargado.</pre>
|
||||
</article>
|
||||
<article class="panel">
|
||||
<h2>Comparacion sin RAG</h2>
|
||||
|
||||
<article class="panel chat-panel">
|
||||
<h2>Chat con el modelo</h2>
|
||||
<div id="chatMessages" class="chat-messages">
|
||||
<p class="empty-chat">Aun no hay conversacion. Carga un bootstrap y empieza a preguntar.</p>
|
||||
</div>
|
||||
|
||||
<label>Tu mensaje
|
||||
<textarea id="chatInput" rows="4">que tenemos pendiente por hacer en este workspace</textarea>
|
||||
</label>
|
||||
|
||||
<div class="actions">
|
||||
<button id="sendChatButton">Enviar mensaje</button>
|
||||
<button id="clearChatButton" class="secondary">Limpiar chat</button>
|
||||
</div>
|
||||
|
||||
<h3>Ultima respuesta estructurada</h3>
|
||||
<pre id="mainResult">Sin ejecutar aun.</pre>
|
||||
|
||||
<h3>Comparacion sin RAG</h3>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" id="compareWithoutRag" />
|
||||
Comparar tambien con respuesta sin RAG
|
||||
</label>
|
||||
<pre id="compareResult">Desactivada.</pre>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="panel">
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
--accent: #72e0a8;
|
||||
--accent-2: #7ca8ff;
|
||||
--border: #2e3a5f;
|
||||
--danger: #ff6b6b;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
|
|
@ -20,12 +21,12 @@ body {
|
|||
}
|
||||
|
||||
.layout {
|
||||
max-width: 1280px;
|
||||
max-width: 1320px;
|
||||
margin: 0 auto;
|
||||
padding: 32px;
|
||||
}
|
||||
|
||||
.hero, .panel {
|
||||
.hero, .panel, .tabs {
|
||||
background: rgba(21, 29, 49, 0.92);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 20px;
|
||||
|
|
@ -38,9 +39,35 @@ body {
|
|||
justify-content: space-between;
|
||||
gap: 20px;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: inline-flex;
|
||||
gap: 10px;
|
||||
padding: 10px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.tab-button {
|
||||
width: auto;
|
||||
padding: 12px 18px;
|
||||
border-radius: 14px;
|
||||
border: 1px solid var(--border);
|
||||
background: transparent;
|
||||
color: var(--text);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tab-button.active {
|
||||
background: linear-gradient(135deg, var(--accent) 0%, #4fceff 100%);
|
||||
color: #0b1020;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.tab-panel { display: none; margin-bottom: 24px; }
|
||||
.tab-panel.active { display: block; }
|
||||
|
||||
.eyebrow {
|
||||
margin: 0 0 8px;
|
||||
color: var(--accent);
|
||||
|
|
@ -49,22 +76,20 @@ body {
|
|||
letter-spacing: 0.14em;
|
||||
}
|
||||
|
||||
h1, h2 { margin: 0 0 12px; }
|
||||
.lead { margin: 0; color: var(--muted); max-width: 720px; }
|
||||
h1, h2, h3 { margin: 0 0 12px; }
|
||||
.lead { margin: 0; color: var(--muted); max-width: 760px; }
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
gap: 24px;
|
||||
grid-template-columns: 1fr 2fr;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.results-grid { grid-template-columns: 1fr 1fr; }
|
||||
.panel-wide { min-width: 0; }
|
||||
.single-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||
.chat-grid { grid-template-columns: 0.9fr 1.3fr; }
|
||||
|
||||
.panel {
|
||||
padding: 24px;
|
||||
}
|
||||
.panel { padding: 24px; }
|
||||
|
||||
label {
|
||||
display: block;
|
||||
|
|
@ -100,25 +125,14 @@ button.secondary {
|
|||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.row, .actions {
|
||||
.actions, .checkbox {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.row > label {
|
||||
flex: 1 1 220px;
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.checkbox input {
|
||||
width: auto;
|
||||
}
|
||||
.checkbox input { width: auto; }
|
||||
|
||||
pre {
|
||||
background: #0b1020;
|
||||
|
|
@ -126,13 +140,65 @@ pre {
|
|||
border-radius: 16px;
|
||||
padding: 16px;
|
||||
overflow: auto;
|
||||
min-height: 180px;
|
||||
min-height: 160px;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.context-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
padding: 14px 16px;
|
||||
border-radius: 16px;
|
||||
border: 1px solid var(--border);
|
||||
background: #0b1020;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.context-status p {
|
||||
margin: 4px 0 0;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.indicator {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 999px;
|
||||
box-shadow: 0 0 14px rgba(255,255,255,0.15);
|
||||
}
|
||||
|
||||
.indicator-off { background: var(--danger); }
|
||||
.indicator-on { background: var(--accent); }
|
||||
|
||||
.chat-messages {
|
||||
min-height: 260px;
|
||||
max-height: 520px;
|
||||
overflow: auto;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 16px;
|
||||
padding: 16px;
|
||||
background: #0b1020;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.empty-chat { color: var(--muted); margin: 0; }
|
||||
|
||||
.message {
|
||||
margin-bottom: 14px;
|
||||
padding: 14px;
|
||||
border-radius: 14px;
|
||||
background: rgba(28, 39, 66, 0.9);
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.message.user { border-left: 4px solid #4fceff; }
|
||||
.message.assistant { border-left: 4px solid var(--accent); }
|
||||
.message header { font-size: 12px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); margin-bottom: 8px; }
|
||||
.message p { margin: 0; white-space: pre-wrap; }
|
||||
|
||||
@media (max-width: 980px) {
|
||||
.grid, .results-grid {
|
||||
.grid, .single-grid, .chat-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ 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";
|
||||
import type { ChatMessage, ChunkMode, RetrieveIntent, RetrieveScope } from "./shared/types/rag.js";
|
||||
|
||||
export function createApp() {
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
|
|
@ -122,7 +122,8 @@ export function createApp() {
|
|||
}
|
||||
: undefined;
|
||||
const model = req.body.model ? String(req.body.model) : undefined;
|
||||
const result = await answerService.answer(mode, intent, query, scope, model);
|
||||
const preloadedContext = req.body.preloadedContext ? String(req.body.preloadedContext) : undefined;
|
||||
const result = await answerService.answer(mode, intent, query, scope, model, preloadedContext);
|
||||
res.json(result);
|
||||
} catch (error) {
|
||||
res.status(500).json({ ok: false, error: error instanceof Error ? error.message : "Unknown answer error" });
|
||||
|
|
@ -133,12 +134,53 @@ export function createApp() {
|
|||
try {
|
||||
const query = String(req.body.query ?? "");
|
||||
const model = req.body.model ? String(req.body.model) : undefined;
|
||||
const result = await answerService.answerWithoutRag(query, model);
|
||||
const preloadedContext = req.body.preloadedContext ? String(req.body.preloadedContext) : undefined;
|
||||
const result = await answerService.answerWithoutRag(query, model, preloadedContext);
|
||||
res.json(result);
|
||||
} catch (error) {
|
||||
res.status(500).json({ ok: false, error: error instanceof Error ? error.message : "Unknown direct answer error" });
|
||||
}
|
||||
});
|
||||
|
||||
app.post("/chat", async (req, res) => {
|
||||
try {
|
||||
const message = String(req.body.message ?? "");
|
||||
const mode = (req.body.mode ?? "documental") as ChunkMode;
|
||||
const model = req.body.model ? String(req.body.model) : undefined;
|
||||
const preloadedContext = req.body.preloadedContext ? String(req.body.preloadedContext) : undefined;
|
||||
const allowAdditionalRetrieve = Boolean(req.body.allowAdditionalRetrieve);
|
||||
const historyEntries = Array.isArray(req.body.history) ? req.body.history as Array<{ role?: string; content?: unknown }> : [];
|
||||
const history: ChatMessage[] = historyEntries.length > 0
|
||||
? historyEntries
|
||||
.map((entry: { role?: string; content?: unknown }): ChatMessage => ({
|
||||
role: entry.role === "assistant" ? "assistant" : "user",
|
||||
content: String(entry.content ?? "")
|
||||
}))
|
||||
.filter((entry) => entry.content.length > 0)
|
||||
: [];
|
||||
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.chat({
|
||||
message,
|
||||
history,
|
||||
mode,
|
||||
scope,
|
||||
modelOverride: model,
|
||||
preloadedContext,
|
||||
allowAdditionalRetrieve
|
||||
});
|
||||
|
||||
res.json(result);
|
||||
} catch (error) {
|
||||
res.status(500).json({ ok: false, error: error instanceof Error ? error.message : "Unknown chat error" });
|
||||
}
|
||||
});
|
||||
|
||||
return app;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import OpenAI from "openai";
|
||||
import { env } from "../../config/env.js";
|
||||
import type { AnswerResponse, ChunkMode, DirectAnswerResponse, RetrieveIntent, RetrieveResponse, RetrieveScope, RetrievedItem } from "../../shared/types/rag.js";
|
||||
import type { AnswerResponse, ChatMessage, ChatResponse, ChunkMode, DirectAnswerResponse, RetrieveIntent, RetrieveResponse, RetrieveScope, RetrievedItem } from "../../shared/types/rag.js";
|
||||
import { RetrieveService } from "../retrieve/service.js";
|
||||
|
||||
const answerModelFallbacks = [
|
||||
|
|
@ -11,7 +11,7 @@ const answerModelFallbacks = [
|
|||
"mistralai/mistral-small-3.1-24b-instruct"
|
||||
];
|
||||
|
||||
function buildPrompt(query: string, summary: string, items: RetrievedItem[]): string {
|
||||
function buildPrompt(query: string, summary: string, items: RetrievedItem[], preloadedContext?: string): string {
|
||||
const context = items
|
||||
.map((item, index) => [
|
||||
`Fuente ${index + 1}: ${item.title}`,
|
||||
|
|
@ -27,11 +27,42 @@ function buildPrompt(query: string, summary: string, items: RetrievedItem[]): st
|
|||
"Responde usando solo el contexto recuperado.",
|
||||
"Si el contexto no es suficiente, dilo claramente.",
|
||||
"Prioriza exactitud y brevedad.",
|
||||
preloadedContext ? `Contexto base precargado de la sesion: ${preloadedContext}` : undefined,
|
||||
`Resumen del contexto: ${summary}`,
|
||||
`Pregunta: ${query}`,
|
||||
"Contexto recuperado:",
|
||||
context
|
||||
].join("\n\n");
|
||||
].filter(Boolean).join("\n\n");
|
||||
}
|
||||
|
||||
function buildChatPrompt(message: string, history: ChatMessage[], preloadedContext?: string, retrieved?: RetrieveResponse): string {
|
||||
const serializedHistory = history
|
||||
.slice(-8)
|
||||
.map((entry) => `${entry.role.toUpperCase()}: ${entry.content}`)
|
||||
.join("\n");
|
||||
|
||||
const retrievedContext = retrieved
|
||||
? [
|
||||
`Resumen retrieve: ${retrieved.summary}`,
|
||||
retrieved.modelSummary ? `Sintesis con modelo: ${retrieved.modelSummary}` : undefined,
|
||||
`Temas: ${retrieved.topics.join(", ")}`,
|
||||
`Puntos criticos: ${retrieved.criticalPoints.join(", ")}`,
|
||||
"Fragmentos recuperados:",
|
||||
retrieved.items.map((item, index) => [
|
||||
`Fuente ${index + 1}: ${item.title}`,
|
||||
item.sectionTitle ? `section_title: ${item.sectionTitle}` : undefined,
|
||||
item.startLine ? `line_range: ${item.startLine}-${item.endLine ?? item.startLine}` : undefined,
|
||||
item.content
|
||||
].filter(Boolean).join("\n")).join("\n\n---\n\n")
|
||||
].filter(Boolean).join("\n\n")
|
||||
: undefined;
|
||||
|
||||
return [
|
||||
preloadedContext ? `Contexto base precargado:\n${preloadedContext}` : undefined,
|
||||
serializedHistory ? `Historial reciente:\n${serializedHistory}` : undefined,
|
||||
retrievedContext ? `Contexto adicional recuperado:\n${retrievedContext}` : undefined,
|
||||
`Mensaje actual del usuario:\n${message}`
|
||||
].filter(Boolean).join("\n\n");
|
||||
}
|
||||
|
||||
export class AnswerService {
|
||||
|
|
@ -44,7 +75,7 @@ export class AnswerService {
|
|||
});
|
||||
}
|
||||
|
||||
async answer(mode: ChunkMode, intent: RetrieveIntent, query: string, scope?: RetrieveScope, modelOverride?: string): Promise<AnswerResponse> {
|
||||
async answer(mode: ChunkMode, intent: RetrieveIntent, query: string, scope?: RetrieveScope, modelOverride?: string, preloadedContext?: string): Promise<AnswerResponse> {
|
||||
if (!env.answerApiKey) {
|
||||
throw new Error("Missing ANSWER_API_KEY for answer provider");
|
||||
}
|
||||
|
|
@ -61,7 +92,7 @@ export class AnswerService {
|
|||
},
|
||||
{
|
||||
role: "user",
|
||||
content: buildPrompt(query, retrieved.summary, retrieved.items)
|
||||
content: buildPrompt(query, retrieved.summary, retrieved.items, preloadedContext)
|
||||
}
|
||||
]
|
||||
});
|
||||
|
|
@ -86,7 +117,7 @@ export class AnswerService {
|
|||
};
|
||||
}
|
||||
|
||||
async answerWithoutRag(query: string, modelOverride?: string): Promise<DirectAnswerResponse> {
|
||||
async answerWithoutRag(query: string, modelOverride?: string, preloadedContext?: string): Promise<DirectAnswerResponse> {
|
||||
if (!env.answerApiKey) {
|
||||
throw new Error("Missing ANSWER_API_KEY for answer provider");
|
||||
}
|
||||
|
|
@ -102,7 +133,9 @@ export class AnswerService {
|
|||
},
|
||||
{
|
||||
role: "user",
|
||||
content: query
|
||||
content: preloadedContext
|
||||
? `Contexto base precargado de la sesion:\n${preloadedContext}\n\nPregunta:\n${query}`
|
||||
: query
|
||||
}
|
||||
]
|
||||
});
|
||||
|
|
@ -178,4 +211,53 @@ export class AnswerService {
|
|||
return answerModelFallbacks;
|
||||
}
|
||||
}
|
||||
|
||||
async chat(options: {
|
||||
message: string;
|
||||
history?: ChatMessage[];
|
||||
mode: ChunkMode;
|
||||
scope?: RetrieveScope;
|
||||
modelOverride?: string;
|
||||
preloadedContext?: string;
|
||||
allowAdditionalRetrieve?: boolean;
|
||||
}): Promise<ChatResponse> {
|
||||
if (!env.answerApiKey) {
|
||||
throw new Error("Missing ANSWER_API_KEY for answer provider");
|
||||
}
|
||||
|
||||
const answerModel = options.modelOverride?.trim() || env.answerModel;
|
||||
let retrieved: RetrieveResponse | undefined;
|
||||
|
||||
if (options.allowAdditionalRetrieve) {
|
||||
retrieved = await this.retrieveService.retrieve(options.mode, "specific", options.message, options.scope);
|
||||
}
|
||||
|
||||
const completion = await this.client.chat.completions.create({
|
||||
model: answerModel,
|
||||
temperature: 0.2,
|
||||
messages: [
|
||||
{
|
||||
role: "system",
|
||||
content: "Eres un asistente conversacional apoyado por un sistema RAG. Usa primero el contexto precargado si existe. Si tambien recibes contexto adicional recuperado, apoyate en el. Si la informacion sigue siendo insuficiente, dilo claramente."
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
content: buildChatPrompt(
|
||||
options.message,
|
||||
options.history ?? [],
|
||||
options.preloadedContext,
|
||||
retrieved
|
||||
)
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
return {
|
||||
model: answerModel,
|
||||
answer: completion.choices[0]?.message?.content?.trim() || "No se pudo generar respuesta.",
|
||||
usedBootstrapContext: Boolean(options.preloadedContext),
|
||||
usedAdditionalRetrieve: Boolean(retrieved),
|
||||
retrieved
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -91,3 +91,16 @@ export interface DirectAnswerResponse {
|
|||
model: string;
|
||||
answer: string;
|
||||
}
|
||||
|
||||
export interface ChatMessage {
|
||||
role: "user" | "assistant";
|
||||
content: string;
|
||||
}
|
||||
|
||||
export interface ChatResponse {
|
||||
model: string;
|
||||
answer: string;
|
||||
usedBootstrapContext: boolean;
|
||||
usedAdditionalRetrieve: boolean;
|
||||
retrieved?: RetrieveResponse;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ Este archivo registra agentes y sesiones de trabajo de este workspace.
|
|||
- Implementacion de un playground web interno servido por el propio backend del RAG para probar `health`, `ingest`, `retrieve`, `answer` y comparacion sin RAG.
|
||||
- Creacion de `RAG/docs/PLAYGROUND.md` para documentar la tecnologia elegida, su ubicacion y su papel dentro del modulo.
|
||||
- Ajuste de la API y del playground para hacer visible y seleccionable el modelo de `answer`, evitando dejarlo oculto como una decision fija del backend.
|
||||
- Evolucion del playground a una mecanica mas completa con pestañas `Ingesta / Bootstrap / Chat`, indicador visual de contexto activo y endpoint `/chat` con bootstrap reutilizable y consultas adicionales al RAG durante la conversacion.
|
||||
- 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.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue