Mostrar un run en vivo en tu UI
Pasar el stream del Met desde tu backend hasta el navegador, palabra por palabra, sin exponer la key y sin perder el resultado si el stream se corta.
Esperar diez segundos mirando un spinner se siente roto aunque no lo esté. Meteor entrega el run como Server-Sent Events, y esta receta lo lleva hasta el navegador.
El punto que no se puede saltar: la key es un secreto de servidor. El navegador nunca habla con la API de Meteor; habla con tu backend, y tu backend retransmite.
Antes de empezar
Tu key necesita runs:execute.
1. El relay en tu backend
import express from 'express';
import Met from '@meteor.ia/sdk';
const met = new Met(process.env.MET_API_KEY!, {
workspaceId: Number(process.env.MET_WORKSPACE_ID),
});
const app = express();
app.get('/api/preguntar', async (req, res) => {
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
res.flushHeaders();
try {
for await (const ev of met.runs.stream({ input: String(req.query.q) })) {
res.write(`event: ${ev.type}\ndata: ${JSON.stringify(ev.data)}\n\n`);
}
} catch (err) {
res.write(`event: error\ndata: ${JSON.stringify({ message: 'se cortó' })}\n\n`);
} finally {
res.end();
}
});
app.listen(3000);
El SDK te entrega objetos { type, data } ya parseados. El stream emite un conjunto cerrado de eventos:
| evento | cuándo |
|---|---|
run.started | el run arrancó |
run.step | un paso del Met, en versión curada |
run.output | un fragmento de la respuesta |
run.completed | terminó bien |
run.failed | terminó con error |
2. El navegador
<p id="respuesta"></p>
<script>
const es = new EventSource('/api/preguntar?q=' + encodeURIComponent(pregunta));
const destino = document.getElementById('respuesta');
es.addEventListener('run.output', (e) => {
destino.textContent += JSON.parse(e.data); // llega por fragmentos
});
es.addEventListener('run.completed', () => es.close());
es.addEventListener('run.failed', () => { destino.textContent = 'No pude responder.'; es.close(); });
</script>
EventSource reconecta solo cuando se cae la conexión, y ese comportamiento aquí juega en contra: reconectar a /api/preguntar vuelve a ejecutar el Met, con su costo. Por eso se cierra explícitamente al terminar.
3. No pierdas el resultado si el stream se corta
Un stream cortado a la mitad no se reanuda de forma transparente. Si lo que estás mostrando también tiene que quedar guardado, no dependas del stream para eso: el run existe en la API con o sin stream.
const run = await met.runs.retrieve(runId); // la fuente de verdad, sin apuro
El patrón para lógica que importa es usar el stream solo para pintar y confirmar contra retrieve() cuando el usuario ya se fue. Si nunca guardas el resultado, un usuario que cierra la pestaña a los ocho segundos deja un run que te cobraron y nadie leyó.
Verlo desde la terminal
curl -N https://api.met.meteor.com.co/api/v1/workspaces/7/runs \
-H "Authorization: Bearer $MET_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: text/event-stream" \
-d '{"input":"Hola","stream":true}'
-N desactiva el buffering: sin él ves todo junto al final y parece que el streaming no funciona.
Y después
- El esquema de eventos y qué expone
run.step: Streaming (SSE). - Para eventos del workspace en vez de los de un run: Eventos en vivo.