Created
June 1, 2025 23:58
-
-
Save celsowm/7d216d54008e3d8ba303813e35f5d321 to your computer and use it in GitHub Desktop.
stream_mode_artefato.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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