Servicio buildQueryVec — Orquestador de Embedding con Fallback
Resumen
buildQueryVec(env, question, opts): Promise<{vec, hyde?}>
Orquesta la construcción del vector de query para retrieval. Si HyDE está activo, genera un pasaje hipotético y embebe pregunta + pasaje concatenados. Si HyDE falla, cae automáticamente al embedding normal de la pregunta. Retorna el vector y (opcionalmente) el pasaje generado para instrumentación.
Firma y ubicación
Archivo: functions/api/mcp/handlers/archivo-core.ts
Línea: 694
Tipo: async function
export async function buildQueryVec(
env: Env,
question: string,
opts: { hyde?: boolean; apiKey?: string },
): Promise<{ vec: number[]; hyde?: string }>
Parámetros
| Nombre | Tipo | Propósito |
|---|---|---|
env | Env | Entorno CloudFlare (contiene env.AI, env.GOOGLE_AI_API_KEY) |
question | string | Pregunta del usuario |
opts.hyde | boolean? | Si true, intenta activar HyDE (default: false) |
opts.apiKey | string? | API key para Gemma (p.ej. env.GOOGLE_AI_API_KEY) |
Flujo de ejecución
┌─ buildQueryVec(env, question, { hyde: true, apiKey })
│
├─ if opts.hyde && opts.apiKey && env.AI
│ │
│ ├─ try:
│ │ ├─ hyp = await generateHyDE(apiKey, question)
│ │ ├─ if hyp (no vacío):
│ │ │ ├─ text = "${question}\n\n${hyp}"
│ │ │ ├─ [vec] = await embedTexts(env.AI, [text])
│ │ │ └─ return { vec, hyde: hyp }
│ │ │
│ │ └─ (HyDE vacío: continúa a fallback)
│ │
│ └─ catch:
│ └─ (fallback silencioso a paso siguiente)
│
└─ // Fallback: embedding normal
├─ [vec] = await embedTexts(env.AI, [question])
└─ return { vec }
Paso 1: Tentativa de HyDE
-
Verifica condiciones para activar HyDE:
opts.hyde === true(explícitamente activado)opts.apiKeyproporcionado (necesario para Gemma)env.AIdisponible (binding CloudFlare Workers AI)
-
Si todas se cumplen, entra en bloque
try:- Llama
generateHyDE(opts.apiKey, question)→ retorna string (puede ser vacío) - Si pasaje no vacío:
- Concatena:
text = "${question}\n\n${hyp}" - Embebe
text→embedTexts(env.AI, [text])retorna array de vectores - Extrae primer vector:
[vec] - Retorna
{ vec, hyde: hyp }con evidencia del pasaje
- Concatena:
- Llama
-
Si
generateHyDElanza excepción (timeout, HTTP error, etc.):- Catch silencioso (no lanza)
- Continúa a fallback
-
Si pasaje está vacío (p.ej. JSON parse falló):
- Fallback silencioso (no retorna)
- Continúa a fallback
Paso 2: Fallback a embedding normal
- Si no se tomó HyDE, o HyDE falló:
- Embebe solo la pregunta:
[vec] = await embedTexts(env.AI, [question]) - Retorna
{ vec }(sin propiedadhyde)
- Embebe solo la pregunta:
Salida
Tipo: Promise<{ vec: number[]; hyde?: string }>
| Campo | Tipo | Presente si | Ejemplo |
|---|---|---|---|
vec | number[] | Siempre | [0.001, -0.023, ..., 0.456] (dim BGE-M3) |
hyde | string? | HyDE exitoso | "Para configurar multi-tenancy..." |
Integración en handlers
archivo.ts (línea 172)
const hydeEnabled = args.hyde === true || env.RETRIEVAL_HYDE === '1';
const { vec: queryVec } = await buildQueryVec(env, question, {
hyde: hydeEnabled,
apiKey: env.GOOGLE_AI_API_KEY,
});
Lógica:
- MCP arg
hyde=trueO env varRETRIEVAL_HYDE=1→ activar HyDE - Extrae vector con destructuring
- Descarta pasaje (no lo usa; opcional en harness)
oraculo/ask.ts (línea 99)
const { vec: queryVec } = await buildQueryVec(env, question, {
hyde: env.RETRIEVAL_HYDE === '1',
apiKey: env.GOOGLE_AI_API_KEY,
});
Lógica:
- Solo respeta env var (no acepta arg MCP)
- Oráculo es backend, menos crítico en latencia que Help Widget
bib_eval_retrieval (harness, línea 530)
let qv = baseQv;
if ((v === 'hyde' || v === 'hyde+trim') && env.GOOGLE_AI_API_KEY) {
const bv = await buildQueryVec(env, q, { hyde: true, apiKey: env.GOOGLE_AI_API_KEY });
qv = bv.vec;
}
Lógica:
- Para variante
hydeohyde+trim: usabuildQueryVeccon HyDE forzado - Descarta pasaje (pero
buildQueryVeclo genera; útil si instrumentación futura) - Compara vector resultante vs
baseQven retrieval
Decisiones de diseño
¿Por qué concatenación pregunta + pasaje?
Alternativas:
- Solo pasaje: vector más cercano a docs reales, pero pierde anclaje si alucina
- Pregunta + pasaje (elegido): pregunta ancla el vector; si pasaje es falso, vector sigue cercano al tema
- Promedio: complejidad innecesaria
Justificación: robustez a alucinaciones sin perder recall.
¿Por qué try-catch silencioso?
- No lanza en fallback: si HyDE falla (API down, timeout), el query sigue funcionando normalmente.
- Degradation path implícito: usuario no ve degradation, pero query es más lento si HyDE estaba activo.
- Alternativa rechazada: lanzar excepción bloqueaba todo
/ask.
¿Cómo se instrumenta HyDE?
- Retorna
hyde?: stringpara que el caller pueda loguear/metrificar si necesita. archivo.tsyoraculo/ask.tsno lo usan (por ahora).bib_eval_retrievalpodría usarlo para logging post-hoc.
¿Por qué dos puntos de entrada (args vs env)?
archivo.ts(MCP handler): acepta arghyde=truepara granularidad per-call.- Útil para testing:
ask({q: "...", hyde: true})sin cambiar env.
- Útil para testing:
oraculo/ask.ts: solo env varRETRIEVAL_HYDE=1.- Oráculo es backend, menos necesidad de control per-call.
- Si necesitara flexibilidad, se añade arg fácilmente.
Casos de uso
| Scenario | Flujo | Resultado |
|---|---|---|
hyde=true, API válida, Gemma rápido | HyDE → pasaje → concat → embed | ✅ {vec (de concat), hyde: "..."} |
hyde=true, API válida, Gemma timeout | HyDE lanza → catch → fallback | ✅ {vec (pregunta)} |
hyde=true, API inválida | HyDE lanza → catch → fallback | ✅ {vec (pregunta)} |
hyde=false | Salta HyDE → fallback directo | ✅ {vec (pregunta)} |
hyde=true pero opts.apiKey undefined | Salta condición → fallback | ✅ {vec (pregunta)} |
Performance
- HyDE activo, éxito: ~1.5s (Gemma) + ~100ms (embed) = ~1.6s total
- HyDE activo, fallo: ~15s timeout + ~100ms embed = ~15.1s (degradation visible)
- Fallback (HyDE off o fallo): ~100-200ms (embed solo)
Implicación: +1-1.5s sensible en Help Widget; aceptable en Oráculo backend.
Testing
Con HyDE activo (harness)
bib_eval_retrieval({variants: ["baseline", "hyde", "hyde+trim"]})
Compara:
baseline:buildQueryVec(..., { hyde: false })hyde:buildQueryVec(..., { hyde: true })
Métrica: overlap_vs_baseline (% chunks en común) → si ↑, HyDE aporta recall.
Unitario (mock)
const mock_vec = await buildQueryVec({AI, GOOGLE_AI_API_KEY}, "¿Qué es X?", {
hyde: false,
});
expect(mock_vec.vec.length).toBe(1024); // dim BGE-M3
expect(mock_vec.hyde).toBeUndefined();
Véase también
- [[feature—biblioteca—hyde-optional-retrieval]]
- [[entity—functions—service—generate-hyde]]
- [[concept—biblioteca—embedding-vectors]]
- [[concept—biblioteca—retrieval-augmented-generation]]
- [[runbook—biblioteca—measure-retrieval-quality]]