Volver a la wiki

Endpoint POST /api/correo/ask · Chat de la Secretaria (F1)

Descripción

Endpoint multiturno que implementa el chat de la secretaria del buzón de Zoho. Usa Gemma 4 con decodificación restringida (patrón Auto-Plan/Oráculo): el modelo decide tres acciones (responder|buscar|leer) en JSON, el Worker las ejecuta contra la API real de Zoho, y re-pregunta hasta que responde o se agotan 3 rondas.

Firma

POST /api/correo/ask
Content-Type: application/json

{
  "question": string,      // ≤2000 chars, obligatorio
  "history": Array<{       // historial de turnos previos (efímeros, no persistidos)
    role: "user" | "assistant",
    content: string        // ≤2000 chars por mensaje
  }>                       // máx 12 últimos mensajes
}

Respuesta (200 OK):

{
  "answer": string,        // respuesta final del modelo
  "tool_rounds": number,   // cuántas rondas de búsqueda/lectura hizo
  "duration_ms": number,   // latencia total
  "model": "gemma-4-26b-a4b-it"
}

Errores:

Identidad

Obligatorio: solo navegador (CF Access).

La autenticación se resuelve con resolveTeamMember(request) del módulo _lib/staff. Tokens de servicio no juegan — no es posible llamar a este endpoint desde un script o herramienta MCP.

Contexto Principal

Carga el informe más reciente del miembro (correo_digests, tabla de F0):

SELECT generated_at, resumen, ruido_count, payload
  FROM correo_digests
  WHERE owner = ?
  ORDER BY created_at DESC, id DESC
  LIMIT 1

Si existe, el prompt incluye:

Última pasada de {generated_at} · {resumen} · ruido filtrado: {ruido_count}
Ítems: {payload}

Si no existe (F0 nunca ha corrido), dice: (sin informe todavía — la primera pasada de /correo aún no ha corrido).

Decodificación Restringida

El modelo recibe un prompt con 3 acciones posibles definidas en el responseSchema de Gemma 4:

const requestBody = JSON.stringify({
  contents: [{ role: 'user', parts: [{ text: prompt }] }],
  generationConfig: {
    temperature: 1.0,
    topP: 0.95,
    topK: 64,
    maxOutputTokens: 1024,
    responseMimeType: 'application/json',
    responseSchema: {
      type: 'OBJECT',
      properties: {
        action: { type: 'STRING', enum: ['responder', 'buscar', 'leer'] },
        answer: { type: 'STRING' },
        query: { type: 'STRING' },
        from: { type: 'STRING' },
        subject: { type: 'STRING' },
        messageId: { type: 'STRING' },
        folderId: { type: 'STRING' },
      },
      required: ['action'],
    },
  },
});

El modelo siempre devuelve un JSON válido con action y los parámetros que apliquen. Si JSON se rompe pese al constrained decoding, el Worker lo trata como una respuesta plana (responder).

Herramientas (Tool Rounds)

toolBuscar: Búsqueda en el Buzón

Llama a /api/accounts/{accountId}/messages/search de Zoho:

toolLeer: Lectura de Cuerpo

Llama a /api/accounts/{accountId}/folders/{folderId}/messages/{messageId}/content:

Rate-Limit y Guardarraíles

Telemetría

Tras cada pregunta (éxito o fracaso), registra una fila en correo_chat_queries:

INSERT INTO correo_chat_queries (owner, question, tool_rounds, duration_ms)
  VALUES (?, ?, ?, ?)

Best-effort: no interrumpe la respuesta si el insert falla. Es el dato real de la apuesta #9 (WAGERS.md).

Conversación Efímera

Componente React (CorreoChat.tsx)

UI para el chat (panel en /correo):

Prompt Canónico

El Worker construye un prompt multilingüe (adapta al idioma del usuario) que:

  1. Define la identidad: “eres la secretaria del buzón de [miembro]”.
  2. Especifica 3 acciones y reglas (máx 3 rondas, sin inventar datos).
  3. Incluye el contexto del informe F0.
  4. Incluye el historial previo de turnos (si existe).
  5. Incluye los resultados de búsquedas/lecturas de rondas anteriores en esta pregunta.
  6. Formula la pregunta nueva.

Ejemplo (simplificado):

Eres la secretaria del buzón de correo de Edu (equipo CreaRack / Esferic Labs).
Hablas llano y al grano, como una compañera de confianza.

Tienes tres acciones posibles — responde SIEMPRE con un único JSON:
- {"action":"responder","answer":"..."}
- {"action":"buscar","query":"..."} | {"action":"buscar","from":"..."}
- {"action":"leer","messageId":"...","folderId":"..."}

Máximo 3 consultas al buzón. Si con lo que hay no basta, responde con lo que sepas.

Último informe (contexto principal):
Pasada de 2026-08-12 14:00:00 · 12 correos nuevos desde ayer, ruido filtrado: 3
Ítems: ...

Pregunta de Edu: ¿qué me decía el último aviso de Hetzner?

Véase también

Subir