CreaRack-SL

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:

  • Code blocks (``` / ~~~) → unidades atómicas. No parte un comando docker run --rm -it ... en mitad.
  • Líneas estructurales (tabla, bullets, listas numeradas, blockquotes) → una unidad por línea.
  • Abreviaturas + números → protegidas contra split erróneo:
    • . en abreviaturas (Sr., e.g., vol., approx.).
    • . en versiones (v1.2.3), decimales (3.14), IPs (192.168.1.1).
  • Prosa normal → parte por . ! ? + espacio + mayúscula/apertura (", ().

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:

  • protectDots(s: string) — sustituye . en abreviaturas/números por placeholder Z9DOTZ9.
  • restoreCode(s: string, codeBlocks) — restaura code blocks desde placeholders.
  • splitChunkPrefix(content) — separa encabezado [Doc: ... ] del cuerpo (no trocea el prefijo).

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:

  • Cuenta palabras en chunks (split por whitespace).
  • Multiplica por 1.3 (heurística tokens/palabra para BGE/Gemma).
  • Redondea al entero.

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:

  • Argumento MCP trim: true, O
  • Variable de entorno RETRIEVAL_TRIM=1.

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:

  • avg_tokens_baseline / avg_tokens_trim — ahorro medible.
  • token_reduction_pct — % reducción (ej 43%).
  • avg_overlap_vs_baseline — loss de chunks (ej 0.92 = 8% chunks perdidos).

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

Véase también

  • [[feature—biblioteca—arcrift-cherry-pick-1]]
  • [[entity—functions—tool—bib-eval-retrieval]]
  • [[entity—functions—handler—archivo-ask]]
  • [[concept—biblioteca—retrieval-pipeline]]
  • [[concept—nlp—sentence-boundaries]]