Skip to content

Instantly share code, notes, and snippets.

@celsowm
Created June 1, 2025 23:58
Show Gist options
  • Save celsowm/7d216d54008e3d8ba303813e35f5d321 to your computer and use it in GitHub Desktop.
Save celsowm/7d216d54008e3d8ba303813e35f5d321 to your computer and use it in GitHub Desktop.
stream_mode_artefato.js
(async function() {
// 1) URL do seu servidor
const ENDPOINT = "http://localhost:8081/v1/chat/completions";
// 2) Nome exato do modelo (case‐sensitive)
const MODEL = "seu-modelo-aqui";
// 3) Texto que o usuário pediu
const PROMPT_DO_USUARIO = "escreva uma redação sobre a vida";
// 4) System prompt que força um JSON com "artefato" + "reflexao"
const systemPrompt = `
Você é um gerador de objetos JSON. Responda **exatamente** com este objeto:
{
"artefato": "<conteúdo da redação completa>",
"reflexao": "<breve frase sobre a tarefa concluída>"
}
Regras de streaming:
1) Comece imediatamente escrevendo '{"artefato":"'.
2) Escreva toda a redação (artefato) e, ao terminar, feche com aspas e vírgula: '",'.
3) Então abra '"reflexao":"' e escreva apenas uma frase curta (ex.: "aqui está uma redação sobre a vida"), depois feche '}'.
4) Não inclua nada antes de '{' nem depois de '}'.`.trim();
// 5) Monta o body sem response_format
const body = {
model: MODEL,
messages: [
{ role: "system", content: systemPrompt },
{ role: "user", content: PROMPT_DO_USUARIO }
],
stream: true,
temperature: 0.7
};
console.log("→ Enviando requisição “artefato/reflexao” ao servidor");
console.log("Request body:", JSON.stringify(body, null, 2));
try {
// 6) Executa o fetch
const resp = await fetch(ENDPOINT, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body)
});
console.log("→ Resposta HTTP recebida. Status:", resp.status);
if (!resp.ok) {
console.error("Erro HTTP:", resp.status, resp.statusText);
return;
}
// 7) Lê o corpo como ReadableStream
const reader = resp.body.getReader();
const decoder = new TextDecoder("utf-8");
let current = null; // null | "artefato" | "reflexao"
let escapeNext = false; // para lidar com barras invertidas (\")
let prefix = ""; // acumula últimos chars para detectar chave JSON
const buf = { artefato: "", reflexao: "" };
console.log("→ Iniciando leitura do stream. Aguarde…");
while (true) {
const { done, value } = await reader.read();
if (done) {
console.log("→ Stream finalizado");
break;
}
// Decodifica o chunk recebido
const chunkTexto = decoder.decode(value, { stream: true });
console.log("[STREAM] Fragmento bruto recebido:\n", chunkTexto);
// Cada linha "data:" pode conter JSON ou "[DONE]"
for (const linha of chunkTexto.split("\n")) {
if (!linha.trimStart().startsWith("data:")) continue;
const payload = linha.replace(/^data:\s*/, "").trim();
if (payload === "[DONE]") {
console.log("[STREAM] Recebido [DONE], encerrando leitura.");
break;
}
// Tenta parsear o payload como JSON
let delta;
try {
const obj = JSON.parse(payload);
delta = obj.choices?.[0]?.delta;
} catch (err) {
console.warn("[STREAM] Payload inválido (não JSON); pulando:", err);
continue;
}
if (!delta || typeof delta.content !== "string") {
console.warn("[STREAM] Delta sem conteúdo válido; pulando.");
continue;
}
// 8) Máquina de estados char-a-char
for (const ch of delta.content) {
if (current) {
// Se estivermos dentro de "artefato" ou "reflexao"
if (ch === '"' && !escapeNext) {
console.log(`[state] Fechou campo "${current}"`);
current = null;
prefix = "";
} else {
buf[current] += ch;
console.log(`[update] ${current} →`, buf[current]);
}
escapeNext = (ch === "\\" && !escapeNext);
continue;
}
// Se current == null, acumulamos prefix para detectar "artefato":" ou "reflexao":"
prefix += ch;
if (prefix.length > 50) prefix = prefix.slice(-50);
console.log("[state] Prefix atual:", JSON.stringify(prefix.slice(-20)));
// Regex para detectar término de '"artefato":"'
if (/\"artefato\"\s*:\s*\"$/.test(prefix)) {
current = "artefato";
escapeNext = false;
console.log('[state] Entrou no campo "artefato"');
continue;
}
// Regex para detectar término de '"reflexao":"'
if (/\"reflexao\"\s*:\s*\"$/.test(prefix)) {
current = "reflexao";
escapeNext = false;
console.log('[state] Entrou no campo "reflexao"');
continue;
}
}
}
}
// 9) Quando o stream terminar, exibimos o conteúdo final de cada campo
console.log("\n→ ARTEFATO FINAL:\n", buf.artefato);
console.log("\n→ REFLEXAO FINAL:\n", buf.reflexao);
// 10) Monta o JSON e tenta parsear para validar
const jsonCompleto = `{"artefato":"${buf.artefato}","reflexao":"${buf.reflexao}"}`;
console.log("\n→ JSON completo montado:\n", jsonCompleto);
try {
const parsed = JSON.parse(jsonCompleto);
console.log("→ Objeto JavaScript resultante:", parsed);
} catch (err) {
console.error("❌ Falha ao JSON.parse final:", err);
console.log("Texto bruto para debug:", JSON.stringify(jsonCompleto));
}
} catch (err) {
console.error("❌ Erro ao fazer fetch/stream:", err);
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment