Volver a la wiki

Servicio de sentence-trimming en archivo-core.ts

Resumen

Módulo de procesamiento de texto (archivo-core.ts) que proporciona sentence-splitting resiliente y trimming by relevance para reducir ruido y tokens en el contexto del LLM. Usado por bib_ask (Workers) y ask.ts (Oraculo) cuando RETRIEVAL_TRIM=1.

Componentes

1. splitSentences(text: string): string[]

Proposito: Trocea markdown en “frases” (unidades mínimas de significado) respetando estructuras que NO deben partirse.

Estrategia conservadora:

Entrada/Salida:

input: "El sistema soporta multi-tenancy (v1.2.3). Véase RFC 3986..."
output: [
  "El sistema soporta multi-tenancy (v1.2.3).",
  "Véase RFC 3986..."
]

Implementación:

  1. Extraer code blocks → placeholders.
  2. Separar por párrafos (línea en blanco).
  3. Para cada párrafo:
    • Si es estructural (tabla/lista/quote) → partir por línea.
    • Si es prosa → partir por límite de frase con protección de abreviaturas.
  4. Restaurar code blocks.

Fuente: functions/api/mcp/handlers/archivo-core.ts:440–510.

Helpers privados:

2. trimChunksBySentence(...): Promise<ChunkMatch[]>

Propósito: Recorta chunks al contenido relevante para la query.

Entrada:

ai: Ai                              // Workers AI binding
queryVec: number[]                  // embedding de la query
chunks: ChunkMatch[]                // chunks enteros del retrieval base
opts: {
  threshold: number                 // coseno mínimo (ej 0.38)
  minSentences: number              // garantía mínima por chunk (ej 1)
  window: number                    // contexto ±N frases (ej 0)
}

Proceso:

  1. Troceado: splitSentences en cada chunk (respetando prefijo [Doc:...]).
  2. Embedding batch: todas las frases de todos los chunks en un único batch (eficiente).
  3. Scoring: coseno queryVec vs cada frase.
  4. Selección:
    • Frases con score ≥ threshold.
    • Si insuficientes: añadir las mejores (top K) hasta minSentences.
    • Si window > 0: incluir ±N frases alrededor de las seleccionadas.
  5. Reconstrucción:
    • Ordenar índices conservados.
    • Insertar [...] entre tramos no contiguos.
    • Recomponer con prefijo original.

Salida:

ChunkMatch[] con content recortado y metadatos intactos (id, score original, source)

Ejemplo:

Entrada (3 frases):
"La multi-tenancy aislaba datos. Cada tenant veía solo su esquema. Las queries explícitas mantenían la RLS."

Si solo se conserva frase 0 y 2 (relevantes para "aislamiento de datos"):
"La multi-tenancy aislaba datos. [...] Las queries explícitas mantenían la RLS."

Fuente: functions/api/mcp/handlers/archivo-core.ts:550–610.

3. retrieve(env, queryVec, topK, opts, filters): Promise<RetrievalResult>

Propósito: Wrapper unificado que encadena búsqueda vectorial → trimming opcional → estimación de tokens.

Interfaz:

interface RetrieveOpts {
  trim?: boolean;
  trimThreshold?: number;        // default 0.38
  trimMinSentences?: number;     // default 1
  trimWindow?: number;           // default 0
}

interface RetrievalResult {
  chunks: ChunkMatch[];
  tokensInContext: number;       // estimación post-trimming
  tokensBefore: number;          // estimación del retrieval base
}

Lógica:

  1. Búsqueda vectorial base: searchChunks(env, queryVec, topK, filters) → chunks enteros.
  2. Estimar tokens antes: estimateTokens(raw) (~1.3 tokens/palabra).
  3. Si opts.trim && env.AI && chunks.length > 0:
    • Aplicar trimChunksBySentence con parámetros de opts.
  4. Estimar tokens después: estimateTokens(trimmed).
  5. Retornar tanto chunks como métricas.

Consumo:

const result = await retrieve(env, queryVec, 5, {
  trim: true,
  trimThreshold: 0.38,
  trimWindow: 1
}, { app: 'core' });

console.log(`Tokens: ${result.tokensBefore} → ${result.tokensInContext}`);
// Chunks recortados en result.chunks

Fuente: functions/api/mcp/handlers/archivo-core.ts:620–650.

4. Estimación de tokens

estimateTokens(chunks): number:

Usado pre- y post-trimming para medir reducción exacta.

Integración en bib_ask

Ubicación: functions/api/mcp/handlers/archivo.ts:191–202.

const trimEnabled = args.trim === true || env.RETRIEVAL_TRIM === '1';
if (trimEnabled && env.AI && matches.length > 0) {
  matches = await trimChunksBySentence(env.AI, queryVec, matches, {
    threshold: Number(env.RETRIEVAL_TRIM_THRESHOLD) || 0.38,
    minSentences: 1,
    window: 1,
  });
}

Activación:

OFF por defecto → help widget (proxy Django sin arg trim) no ve cambio.

Integración en Oraculo

Ubicación: functions/api/oraculo/ask.ts:138–149.

if (env.RETRIEVAL_TRIM === '1' && env.AI && matches.length > 0) {
  matches = await trimChunksBySentence(env.AI, queryVec, matches, {
    threshold: Number(env.RETRIEVAL_TRIM_THRESHOLD) || 0.38,
    minSentences: 1,
    window: 1,
  });
}

Solo activo si env RETRIEVAL_TRIM=1. Parámetros idénticos a Workers.

Optimización A/B y calibración

La tool bib_eval_retrieval permite validar el trimming sin redeploy:

bib_eval_retrieval(
  questions: ["¿Multi-tenancy?", ...],
  trim_threshold: 0.40,
  trim_window: 2
)

Métricas producidas:

Ajustar RETRIEVAL_TRIM_THRESHOLD hasta hallar el punto óptimo (máx reducción, mín overlap loss).

Véase también

Subir