Spaces:
Running
Running
Download src/console/consoleEngine.ts from PYTHAI/bankML-UIF: direct link, hf CLI and curl.
- Browser
- Download file 3.11 kB
-
https://huggingface.co/spaces/PYTHAI/bankML-UIF/resolve/main/src/console/consoleEngine.ts
- Command line
-
hf download hf://spaces/PYTHAI/bankML-UIF/src/console/consoleEngine.ts
-
curl -L -o consoleEngine.ts https://huggingface.co/spaces/PYTHAI/bankML-UIF/resolve/main/src/console/consoleEngine.ts
3.11 kB
| // SPDX-License-Identifier: MIT | |
| // The bankML console's engine for the input field: the console's own same-origin POST /api/ask, which composes bankML's | |
| // persona and a SELF block measured at that moment, streams the answer as NDJSON ({"piece"} … then {"done", "receipt", | |
| // "answer_sha256_ok", "error"}) and logs the exchange. The receipt's response_sha256 is checked again here, in the | |
| // browser, over the text this page received. | |
| export type Turn = { role: 'user' | 'assistant'; content: string }; | |
| export interface Verdict { ok: boolean; line: string; receipt: Record<string, unknown> | null } | |
| async function sha256hex(text: string): Promise<string> { | |
| const h = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(text)); | |
| return [...new Uint8Array(h)].map(b => b.toString(16).padStart(2, '0')).join(''); | |
| } | |
| /** `window` is the output field's title: the console keeps a .memory per window under that name (titles survive a reload). */ | |
| export async function* askConsole(history: Turn[], message: string, done: (v: Verdict) => void, maxTokens = 256, | |
| window?: string): AsyncGenerator<string> { | |
| const r = await fetch('/api/ask', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| // up to 40 messages: the console trims them four exchanges at a time (console.py `window`), so the engine's cached | |
| // prefix holds from turn to turn; a window cut here would slide on every turn and defeat it | |
| body: JSON.stringify({ message, history: history.slice(-40), max_tokens: maxTokens, ...(window ? { window } : {}) }), | |
| }); | |
| if (!r.ok || !r.body) throw new Error(`HTTP ${r.status}: ${(await r.text()).slice(0, 300)}`); | |
| const reader = r.body.getReader(); | |
| const dec = new TextDecoder(); | |
| let buf = '', text = ''; | |
| let end: { receipt?: Record<string, unknown> | null; error?: string | null; metrics?: Record<string, unknown> } = {}; | |
| for (;;) { | |
| const { done: fin, value } = await reader.read(); | |
| if (fin) break; | |
| buf += dec.decode(value, { stream: true }); | |
| let i: number; | |
| while ((i = buf.indexOf('\n')) >= 0) { | |
| const line = buf.slice(0, i).trim(); | |
| buf = buf.slice(i + 1); | |
| if (!line) continue; | |
| const d = JSON.parse(line); | |
| if (d.piece) { text += d.piece; yield d.piece; } | |
| if (d.done) end = d; | |
| } | |
| } | |
| if (end.error) throw new Error(String(end.error)); | |
| const rc = end.receipt || null; | |
| const ok = !!rc && (await sha256hex(text)) === rc.response_sha256; | |
| const m = end.metrics || {}; | |
| const num = (v: unknown, d: number, unit: string) => (typeof v === 'number' ? v.toFixed(d) + unit : 'not measured'); | |
| done({ | |
| ok, | |
| receipt: rc, | |
| line: rc | |
| ? `${ok ? '✓' : '✗'} receipt · bankML ${rc.bankml} · model sha256 ${String(rc.model_sha256 || '').slice(0, 12)}… · answer sha256 ${ok ? 'matches the text received' : 'does NOT match'} · TTFT ${num(m.ttft_ms, 0, ' ms')} · ${num(m.eval_tps, 2, ' tok/s')}` | |
| : '✗ no receipt came with this answer', | |
| }); | |
| history.push({ role: 'user', content: message }, { role: 'assistant', content: text }); | |
| } | |