Implement cleanup endpoint and playground tab for scope deletion
This commit is contained in:
parent
6cdf82e80e
commit
93a5aee6cb
9 changed files with 211 additions and 10 deletions
|
|
@ -409,7 +409,32 @@ Si se usa `sourceId`, el archivo subido no se mezcla con otros scopes salvo que
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 8. `GET /logs/recent`
|
### 8. `POST /cleanup`
|
||||||
|
|
||||||
|
Sirve para eliminar vectores (chunks) ya ingeridos en el RAG segun un scope determinado, permitiendo limpiar contexto viejo antes de una reingesta.
|
||||||
|
|
||||||
|
Payload base (requiere `sourceId` o `sourceRef` por seguridad):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"scope": {
|
||||||
|
"sourceId": "src:default:folder:src_a9df8105"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Respuesta esperada:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ok": true,
|
||||||
|
"deleted": 124
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 9. `GET /logs/recent`
|
||||||
|
|
||||||
Devuelve los logs recientes de evaluacion guardados por el sistema.
|
Devuelve los logs recientes de evaluacion guardados por el sistema.
|
||||||
|
|
||||||
|
|
@ -428,7 +453,7 @@ Tambien admite actualizacion posterior del estado de revision mediante `PATCH /l
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 9. `POST /logs/manual`
|
### 10. `POST /logs/manual`
|
||||||
|
|
||||||
Permite registrar manualmente una consulta o respuesta que quieras revisar despues.
|
Permite registrar manualmente una consulta o respuesta que quieras revisar despues.
|
||||||
|
|
||||||
|
|
@ -456,7 +481,7 @@ Payload base:
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 10. `PATCH /logs/:id`
|
### 11. `PATCH /logs/:id`
|
||||||
|
|
||||||
Permite actualizar el seguimiento de un log ya creado.
|
Permite actualizar el seguimiento de un log ya creado.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,3 +43,7 @@ Dar continuidad al RAG en `RAG/` a partir del estado actual documentado.
|
||||||
- Implementacion de ayuda visual en la zona de `Bootstrap` del playground.
|
- Implementacion de ayuda visual en la zona de `Bootstrap` del playground.
|
||||||
- Añadidos tooltip y `aria-label` en `Cargar bootstrap`, `Reemplazar contexto`, `Vaciar contexto`, `Preset docs`, `Preset RAG docs` y `Preset codigo`.
|
- Añadidos tooltip y `aria-label` en `Cargar bootstrap`, `Reemplazar contexto`, `Vaciar contexto`, `Preset docs`, `Preset RAG docs` y `Preset codigo`.
|
||||||
- Actualizacion de `RAG/docs/PLAYGROUND.md` y `RAG/docs/TEXTOS_AYUDA_PLAYGROUND.md` para reflejar la mejora.
|
- Actualizacion de `RAG/docs/PLAYGROUND.md` y `RAG/docs/TEXTOS_AYUDA_PLAYGROUND.md` para reflejar la mejora.
|
||||||
|
- Implementacion de la pestaña Limpieza en el playground y soporte en el backend (`POST /cleanup`) para borrado seguro de contextos ya ingeridos.
|
||||||
|
- Limpieza ejecutada exitosamente sobre el `scope` del código fuente antiguo (`RAG/src`).
|
||||||
|
- Reingesta del directorio `RAG/src` con el código actualizado.
|
||||||
|
- Documento de seguimiento `RAG/docs/TASK_LIMPIEZA.md` y documentacion API `RAG/docs/API_RAG.md` actualizados.
|
||||||
|
|
|
||||||
|
|
@ -69,14 +69,19 @@ Tambien permite:
|
||||||
|
|
||||||
## Mecanica actual del playground
|
## Mecanica actual del playground
|
||||||
|
|
||||||
El playground ya no funciona como una sola caja de consulta tecnica. Ahora se organiza en tres pestañas:
|
El playground ya no funciona como una sola caja de consulta tecnica. Ahora se organiza en cuatro pestañas:
|
||||||
|
|
||||||
1. `Ingesta`
|
1. `Ingesta`
|
||||||
- lanzar ingesta documental o de codigo
|
- lanzar ingesta documental o de codigo
|
||||||
- subir archivos directamente desde el navegador
|
- subir archivos directamente desde el navegador
|
||||||
- definir un `sourceId` propio para aislar una carga concreta
|
- definir un `sourceId` propio para aislar una carga concreta
|
||||||
|
|
||||||
2. `Bootstrap`
|
2. `Limpieza`
|
||||||
|
- eliminar todo el contexto asociado a un scope especifico
|
||||||
|
- util para poder reingestar una fuente actualizada sin duplicar chunks
|
||||||
|
- dialogos nativos de confirmacion para evitar borrados accidentales
|
||||||
|
|
||||||
|
3. `Bootstrap`
|
||||||
- elegir scope
|
- elegir scope
|
||||||
- elegir modo
|
- elegir modo
|
||||||
- cargar un mapa inicial del dominio
|
- cargar un mapa inicial del dominio
|
||||||
|
|
@ -84,7 +89,7 @@ El playground ya no funciona como una sola caja de consulta tecnica. Ahora se or
|
||||||
- reemplazar o vaciar el contexto de sesion
|
- reemplazar o vaciar el contexto de sesion
|
||||||
- mostrar ayuda visible y tooltip en los botones principales para reducir confusiones de uso
|
- mostrar ayuda visible y tooltip en los botones principales para reducir confusiones de uso
|
||||||
|
|
||||||
3. `Chat`
|
4. `Chat`
|
||||||
- conversar con el modelo
|
- conversar con el modelo
|
||||||
- ver visualmente si hay contexto cargado o no
|
- ver visualmente si hay contexto cargado o no
|
||||||
- reutilizar el ultimo bootstrap como contexto base
|
- reutilizar el ultimo bootstrap como contexto base
|
||||||
|
|
|
||||||
31
RAG/docs/TASK_LIMPIEZA.md
Normal file
31
RAG/docs/TASK_LIMPIEZA.md
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
# Task: Limpieza y reingesta controlada del RAG
|
||||||
|
|
||||||
|
**Proyecto:** Workspace de tools IA para empresas
|
||||||
|
**Modulo:** RAG
|
||||||
|
**Ultima actualizacion:** 2026-04-06
|
||||||
|
**Estado:** En implementacion
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Proposito
|
||||||
|
Permitir borrar contenido ya ingerido del RAG de forma controlada antes de reingestar una fuente actualizada, evitando que convivan chunks viejos y nuevos del mismo `scope`.
|
||||||
|
|
||||||
|
## Alcance de la implementacion
|
||||||
|
|
||||||
|
### 1. Backend (API y Qdrant)
|
||||||
|
- **VectorStoreClient:** Añadir metodo `delete(scope: RetrieveScope): Promise<number>` (devuelve cantidad de puntos borrados).
|
||||||
|
- **Endpoint:** Exponer `POST /cleanup` que reciba el `scope` (ej. `sourceId` o `sourceRef`) y orqueste el borrado.
|
||||||
|
|
||||||
|
### 2. Frontend (Playground)
|
||||||
|
- **UI:** Nueva pestaña "Limpieza".
|
||||||
|
- **Comportamiento:**
|
||||||
|
- Selector de scopes disponibles (se reutiliza el endpoint `/sources`).
|
||||||
|
- Resumen visual de lo que se va a borrar (`sourceId`, `sourceRef`).
|
||||||
|
- Boton de borrado.
|
||||||
|
- Dialogo de confirmacion nativo (`window.confirm`).
|
||||||
|
- Log del resultado de la peticion.
|
||||||
|
|
||||||
|
### 3. Validacion
|
||||||
|
- Borrar el codigo de `RAG/src` previamente ingerido.
|
||||||
|
- Reingestar `RAG/src`.
|
||||||
|
- Confirmar que se mantiene la integridad sin duplicar fragmentos.
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
const healthButton = document.getElementById("healthButton");
|
const healthButton = document.getElementById("healthButton");
|
||||||
const ingestButton = document.getElementById("ingestButton");
|
const ingestButton = document.getElementById("ingestButton");
|
||||||
|
const cleanupButton = document.getElementById("cleanupButton");
|
||||||
const bootstrapButton = document.getElementById("bootstrapButton");
|
const bootstrapButton = document.getElementById("bootstrapButton");
|
||||||
const replaceBootstrapButton = document.getElementById("replaceBootstrapButton");
|
const replaceBootstrapButton = document.getElementById("replaceBootstrapButton");
|
||||||
const clearBootstrapButton = document.getElementById("clearBootstrapButton");
|
const clearBootstrapButton = document.getElementById("clearBootstrapButton");
|
||||||
|
|
@ -12,6 +13,7 @@ const presetCode = document.getElementById("presetCode");
|
||||||
|
|
||||||
const healthResult = document.getElementById("healthResult");
|
const healthResult = document.getElementById("healthResult");
|
||||||
const ingestResult = document.getElementById("ingestResult");
|
const ingestResult = document.getElementById("ingestResult");
|
||||||
|
const cleanupResult = document.getElementById("cleanupResult");
|
||||||
const bootstrapResult = document.getElementById("bootstrapResult");
|
const bootstrapResult = document.getElementById("bootstrapResult");
|
||||||
const mainResult = document.getElementById("mainResult");
|
const mainResult = document.getElementById("mainResult");
|
||||||
const compareResult = document.getElementById("compareResult");
|
const compareResult = document.getElementById("compareResult");
|
||||||
|
|
@ -33,6 +35,11 @@ const ingestMode = document.getElementById("ingestMode");
|
||||||
const ingestTags = document.getElementById("ingestTags");
|
const ingestTags = document.getElementById("ingestTags");
|
||||||
const ingestModeHint = document.getElementById("ingestModeHint");
|
const ingestModeHint = document.getElementById("ingestModeHint");
|
||||||
|
|
||||||
|
const cleanupScopeSelect = document.getElementById("cleanupScopeSelect");
|
||||||
|
const cleanupSourceId = document.getElementById("cleanupSourceId");
|
||||||
|
const cleanupSourceRef = document.getElementById("cleanupSourceRef");
|
||||||
|
const cleanupTags = document.getElementById("cleanupTags");
|
||||||
|
|
||||||
const bootstrapQuery = document.getElementById("bootstrapQuery");
|
const bootstrapQuery = document.getElementById("bootstrapQuery");
|
||||||
const bootstrapMode = document.getElementById("bootstrapMode");
|
const bootstrapMode = document.getElementById("bootstrapMode");
|
||||||
const answerModel = document.getElementById("answerModel");
|
const answerModel = document.getElementById("answerModel");
|
||||||
|
|
@ -100,6 +107,12 @@ function applySelectedScope(scope) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function applySelectedCleanupScope(scope) {
|
||||||
|
cleanupSourceId.value = scope?.sourceId || "";
|
||||||
|
cleanupSourceRef.value = scope?.sourceRef || "";
|
||||||
|
cleanupTags.value = (scope?.tags || []).join(", ");
|
||||||
|
}
|
||||||
|
|
||||||
function updateScopeEditState() {
|
function updateScopeEditState() {
|
||||||
const locked = scopeEditMode.value !== "manual";
|
const locked = scopeEditMode.value !== "manual";
|
||||||
scopeSourceRef.readOnly = locked;
|
scopeSourceRef.readOnly = locked;
|
||||||
|
|
@ -185,24 +198,31 @@ async function loadScopes() {
|
||||||
const scopes = await fetch("/sources").then((response) => response.json());
|
const scopes = await fetch("/sources").then((response) => response.json());
|
||||||
availableScopes = scopes;
|
availableScopes = scopes;
|
||||||
scopePresetSelect.innerHTML = "";
|
scopePresetSelect.innerHTML = "";
|
||||||
|
cleanupScopeSelect.innerHTML = "";
|
||||||
|
|
||||||
const placeholder = document.createElement("option");
|
const placeholder = document.createElement("option");
|
||||||
placeholder.value = "";
|
placeholder.value = "";
|
||||||
placeholder.textContent = "Selecciona un scope disponible";
|
placeholder.textContent = "Selecciona un scope disponible";
|
||||||
scopePresetSelect.appendChild(placeholder);
|
scopePresetSelect.appendChild(placeholder.cloneNode(true));
|
||||||
|
cleanupScopeSelect.appendChild(placeholder.cloneNode(true));
|
||||||
|
|
||||||
for (const scope of scopes) {
|
for (const scope of scopes) {
|
||||||
const option = document.createElement("option");
|
const option = document.createElement("option");
|
||||||
option.value = JSON.stringify(scope);
|
option.value = JSON.stringify(scope);
|
||||||
option.textContent = buildScopeLabel(scope);
|
option.textContent = buildScopeLabel(scope);
|
||||||
scopePresetSelect.appendChild(option);
|
scopePresetSelect.appendChild(option.cloneNode(true));
|
||||||
|
cleanupScopeSelect.appendChild(option.cloneNode(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (scopes.length === 0) {
|
if (scopes.length === 0) {
|
||||||
placeholder.textContent = "No hay scopes detectados";
|
const emptyMsg = "No hay scopes detectados";
|
||||||
|
scopePresetSelect.options[0].textContent = emptyMsg;
|
||||||
|
cleanupScopeSelect.options[0].textContent = emptyMsg;
|
||||||
} else if (!scopePresetSelect.value) {
|
} else if (!scopePresetSelect.value) {
|
||||||
scopePresetSelect.value = JSON.stringify(scopes[0]);
|
scopePresetSelect.value = JSON.stringify(scopes[0]);
|
||||||
|
cleanupScopeSelect.value = JSON.stringify(scopes[0]);
|
||||||
applySelectedScope(scopes[0]);
|
applySelectedScope(scopes[0]);
|
||||||
|
applySelectedCleanupScope(scopes[0]);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
scopePresetSelect.innerHTML = `<option value="">Error cargando scopes: ${error}</option>`;
|
scopePresetSelect.innerHTML = `<option value="">Error cargando scopes: ${error}</option>`;
|
||||||
|
|
@ -256,6 +276,15 @@ scopePresetSelect.addEventListener("change", () => {
|
||||||
applySelectedScope(scope);
|
applySelectedScope(scope);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
cleanupScopeSelect.addEventListener("change", () => {
|
||||||
|
if (!cleanupScopeSelect.value) {
|
||||||
|
applySelectedCleanupScope(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const scope = JSON.parse(cleanupScopeSelect.value);
|
||||||
|
applySelectedCleanupScope(scope);
|
||||||
|
});
|
||||||
|
|
||||||
ingestUploadFile.addEventListener("change", updateIngestUiState);
|
ingestUploadFile.addEventListener("change", updateIngestUiState);
|
||||||
ingestScopeMode.addEventListener("change", updateIngestUiState);
|
ingestScopeMode.addEventListener("change", updateIngestUiState);
|
||||||
scopeEditMode.addEventListener("change", updateScopeEditState);
|
scopeEditMode.addEventListener("change", updateScopeEditState);
|
||||||
|
|
@ -311,6 +340,29 @@ ingestButton.addEventListener("click", async () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
cleanupButton.addEventListener("click", async () => {
|
||||||
|
if (!cleanupScopeSelect.value) {
|
||||||
|
cleanupResult.textContent = "Error: Debes seleccionar un scope primero.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const scope = JSON.parse(cleanupScopeSelect.value);
|
||||||
|
const identifier = scope.sourceId || scope.sourceRef || "scope desconocido";
|
||||||
|
|
||||||
|
if (!window.confirm(`¿Seguro que quieres eliminar TODO el contexto de:\n\n${identifier}\n\nEsto borrara todos los vectores de Qdrant asociados y no se puede deshacer.`)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanupResult.textContent = "Eliminando contexto...";
|
||||||
|
try {
|
||||||
|
const data = await request("/cleanup", { scope });
|
||||||
|
cleanupResult.textContent = format(data);
|
||||||
|
await loadScopes();
|
||||||
|
} catch (error) {
|
||||||
|
cleanupResult.textContent = String(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
async function executeBootstrap() {
|
async function executeBootstrap() {
|
||||||
bootstrapResult.textContent = "Cargando bootstrap...";
|
bootstrapResult.textContent = "Cargando bootstrap...";
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@
|
||||||
|
|
||||||
<section class="tabs">
|
<section class="tabs">
|
||||||
<button class="tab-button active" data-tab="ingest">Ingesta</button>
|
<button class="tab-button active" data-tab="ingest">Ingesta</button>
|
||||||
|
<button class="tab-button" data-tab="cleanup">Limpieza</button>
|
||||||
<button class="tab-button" data-tab="bootstrap">Bootstrap</button>
|
<button class="tab-button" data-tab="bootstrap">Bootstrap</button>
|
||||||
<button class="tab-button" data-tab="chat">Chat</button>
|
<button class="tab-button" data-tab="chat">Chat</button>
|
||||||
</section>
|
</section>
|
||||||
|
|
@ -75,6 +76,33 @@
|
||||||
</article>
|
</article>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section id="tab-cleanup" class="tab-panel">
|
||||||
|
<article class="panel">
|
||||||
|
<h2>Limpieza controlada</h2>
|
||||||
|
<p class="helper">Permite eliminar contexto ya ingerido del RAG antes de reingestar una fuente, para evitar duplicados y versiones mezcladas.</p>
|
||||||
|
<div class="grid single-grid">
|
||||||
|
<label>Scope disponible a limpiar
|
||||||
|
<select id="cleanupScopeSelect">
|
||||||
|
<option value="">Cargando scopes...</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label>Source ID (obligatorio si no hay Source Ref)
|
||||||
|
<input id="cleanupSourceId" readonly />
|
||||||
|
</label>
|
||||||
|
<label>Source Ref (obligatorio si no hay Source ID)
|
||||||
|
<input id="cleanupSourceRef" readonly />
|
||||||
|
</label>
|
||||||
|
<label>Tags asociados (informativo)
|
||||||
|
<input id="cleanupTags" readonly />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="actions">
|
||||||
|
<button id="cleanupButton" style="background: var(--danger); color: white;" title="Borrar de Qdrant los vectores del scope seleccionado" aria-label="Eliminar scope de Qdrant">Eliminar contexto seleccionado</button>
|
||||||
|
</div>
|
||||||
|
<pre id="cleanupResult">Selecciona un scope y pulsa el boton para eliminarlo.</pre>
|
||||||
|
</article>
|
||||||
|
</section>
|
||||||
|
|
||||||
<section id="tab-bootstrap" class="tab-panel">
|
<section id="tab-bootstrap" class="tab-panel">
|
||||||
<article class="panel">
|
<article class="panel">
|
||||||
<h2>Bootstrap de sesion</h2>
|
<h2>Bootstrap de sesion</h2>
|
||||||
|
|
|
||||||
|
|
@ -124,6 +124,31 @@ export function createApp() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.post("/cleanup", async (req, res) => {
|
||||||
|
try {
|
||||||
|
if (!req.body.scope) {
|
||||||
|
res.status(400).json({ ok: false, error: "Missing scope for cleanup" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const scope: RetrieveScope = {
|
||||||
|
sourceId: req.body.scope.sourceId ? String(req.body.scope.sourceId) : undefined,
|
||||||
|
sourceRef: req.body.scope.sourceRef ? String(req.body.scope.sourceRef) : undefined,
|
||||||
|
tags: Array.isArray(req.body.scope.tags) ? req.body.scope.tags.map(String) : undefined
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!scope.sourceId && !scope.sourceRef) {
|
||||||
|
res.status(400).json({ ok: false, error: "Cleanup requires at least sourceId or sourceRef to prevent accidental mass deletion" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await ingestService.cleanup(scope);
|
||||||
|
res.status(200).json({ ok: true, ...result });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ ok: false, error: error instanceof Error ? error.message : "Unknown cleanup error" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
app.post("/ingest/upload", upload.single("file"), async (req: UploadRequest, res) => {
|
app.post("/ingest/upload", upload.single("file"), async (req: UploadRequest, res) => {
|
||||||
let tempPath: string | undefined;
|
let tempPath: string | undefined;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { parseDocument, isSupportedDocument } from "../parsers/parser-registry.j
|
||||||
import { chunkDocument, codeChunkingPolicy, documentalChunkingPolicy } from "../process/chunking.js";
|
import { chunkDocument, codeChunkingPolicy, documentalChunkingPolicy } from "../process/chunking.js";
|
||||||
import type { EmbeddingProvider } from "../embeddings/provider.js";
|
import type { EmbeddingProvider } from "../embeddings/provider.js";
|
||||||
import type { VectorStoreClient } from "../vectorstore/client.js";
|
import type { VectorStoreClient } from "../vectorstore/client.js";
|
||||||
import type { IngestResult, IngestSourceInput, IngestedChunk } from "../../shared/types/rag.js";
|
import type { IngestResult, IngestSourceInput, IngestedChunk, RetrieveScope } from "../../shared/types/rag.js";
|
||||||
import { buildChunkId, buildDocumentId, buildQdrantPointId, buildSourceId, normalizeDocumentKey } from "../../shared/utils/ids.js";
|
import { buildChunkId, buildDocumentId, buildQdrantPointId, buildSourceId, normalizeDocumentKey } from "../../shared/utils/ids.js";
|
||||||
import { listFilesRecursively } from "../../shared/utils/files.js";
|
import { listFilesRecursively } from "../../shared/utils/files.js";
|
||||||
import { env } from "../../config/env.js";
|
import { env } from "../../config/env.js";
|
||||||
|
|
@ -14,6 +14,11 @@ export class IngestService {
|
||||||
private readonly vectorStore: VectorStoreClient
|
private readonly vectorStore: VectorStoreClient
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
async cleanup(scope: RetrieveScope): Promise<{ deleted: number }> {
|
||||||
|
const count = await this.vectorStore.deleteScope(scope);
|
||||||
|
return { deleted: count };
|
||||||
|
}
|
||||||
|
|
||||||
async ingest(source: IngestSourceInput): Promise<IngestResult> {
|
async ingest(source: IngestSourceInput): Promise<IngestResult> {
|
||||||
const sourceId = source.sourceId ?? buildSourceId(source.sourceType, source.sourceRef);
|
const sourceId = source.sourceId ?? buildSourceId(source.sourceType, source.sourceRef);
|
||||||
const discoveredFiles = await this.resolveInputFiles(source);
|
const discoveredFiles = await this.resolveInputFiles(source);
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ export interface VectorStoreClient {
|
||||||
search(queryVector: number[], limit: number, mode?: string, scope?: RetrieveScope): Promise<RetrievedItem[]>;
|
search(queryVector: number[], limit: number, mode?: string, scope?: RetrieveScope): Promise<RetrievedItem[]>;
|
||||||
listScopes(): Promise<AvailableScope[]>;
|
listScopes(): Promise<AvailableScope[]>;
|
||||||
browseScope(limit: number, mode?: string, scope?: RetrieveScope): Promise<RetrievedItem[]>;
|
browseScope(limit: number, mode?: string, scope?: RetrieveScope): Promise<RetrievedItem[]>;
|
||||||
|
deleteScope(scope: RetrieveScope): Promise<number>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildSearchFilter(mode?: string, scope?: RetrieveScope) {
|
function buildSearchFilter(mode?: string, scope?: RetrieveScope) {
|
||||||
|
|
@ -218,4 +219,29 @@ export class QdrantVectorStoreClient implements VectorStoreClient {
|
||||||
endLine: point.payload?.end_line ? Number(point.payload.end_line) : undefined
|
endLine: point.payload?.end_line ? Number(point.payload.end_line) : undefined
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async deleteScope(scope: RetrieveScope): Promise<number> {
|
||||||
|
const collections = await this.client.getCollections();
|
||||||
|
const exists = collections.collections.some((collection) => collection.name === env.qdrantCollection);
|
||||||
|
|
||||||
|
if (!exists || !scope || (!scope.sourceId && !scope.sourceRef)) {
|
||||||
|
return 0; // Se requiere al menos un identificador fuerte para no borrar a lo loco
|
||||||
|
}
|
||||||
|
|
||||||
|
const filter = buildSearchFilter(undefined, scope);
|
||||||
|
if (!filter) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contamos antes de borrar para devolver la cantidad
|
||||||
|
const countResult = await this.client.count(env.qdrantCollection, {
|
||||||
|
filter
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.client.delete(env.qdrantCollection, {
|
||||||
|
filter
|
||||||
|
});
|
||||||
|
|
||||||
|
return countResult.count;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue