export const ENDPOINT = 'https://api.valen-team.workers.dev/v1/decisions'; export const MODELS = ['valen-2b', 'valen-4b', 'valen-0-8b', 'valen-preview-0923']; export function buildPayload({model, mode, question, context = '', options = '', image}) { if (!MODELS.includes(model) || !['predicate', 'choice', 'score'].includes(mode)) throw new Error('model'); question = question.trim(); context = context.trim(); if (!question || question.length > 1000) throw new Error('question'); if (context.length > 4000) throw new Error('context'); const q = {name: 'q0', type: mode, instructions: question}; if (mode !== 'predicate') { const lines = options.split('\n').map(s => s.trim()).filter(Boolean); if (lines.length < 2 || lines.length > 8 || lines.some(s => s.length > 300)) throw new Error('options'); if (mode === 'choice') q.choices = lines.map((description, i) => ({value: `option_${i+1}`, description})); else q.levels = lines.map((description, i) => ({label: String(i), description})); } const evidence = context || (image ? 'Answer using only the visible image.' : question); if (image && !/^data:image\/jpeg;base64,[A-Za-z0-9+/]+={0,2}$/.test(image)) throw new Error('image'); const input = image ? [{role: 'user', content: [{type: 'input_text', text: evidence}, {type: 'input_image', image_url: image}]}] : evidence; const payload = {model, input, questions: [q], temperature: 1.0}; if (new TextEncoder().encode(JSON.stringify(payload)).length > 4500000) throw new Error('size'); return payload; } export function previewPayload(payload) { const value = structuredClone(payload); if (Array.isArray(value.input)) value.input[0].content[1].image_url = ''; return value; } export function pythonExample(payload, attachment) { const value = structuredClone(payload); const hasImage = Array.isArray(value.input); if (hasImage) value.input[0].content[1].image_url = 'IMAGE_DATA_URL'; const isVideo=hasImage&&attachment?.kind==='video'; const filename=JSON.stringify(attachment?.name||(isVideo?'video.mp4':'image.jpg')); const setup = isVideo ? `import base64 import io import av # Extract the middle frame, as in the Valen cookbook. video_path = ${filename} with av.open(video_path) as video: frame_count = sum(1 for _ in video.decode(video=0)) if frame_count == 0: raise ValueError("The video has no decodable frames") with av.open(video_path) as video: for index, frame in enumerate(video.decode(video=0)): if index == frame_count // 2: image = frame.to_image().convert("RGB") break image.thumbnail((1536, 1536)) buffer = io.BytesIO() image.save(buffer, format="JPEG", quality=85) image_url = "data:image/jpeg;base64," + base64.b64encode(buffer.getvalue()).decode("ascii") ` : hasImage ? `import base64 import io from PIL import Image, ImageOps # Use your selected image in the current directory. with Image.open(${filename}) as image: image = ImageOps.exif_transpose(image).convert("RGB") image.thumbnail((1536, 1536)) buffer = io.BytesIO() image.save(buffer, format="JPEG", quality=85) image_url = "data:image/jpeg;base64," + base64.b64encode(buffer.getvalue()).decode("ascii") ` : ''; // json.loads safely preserves quotes, Unicode and literal user text in Python. const literal = JSON.stringify(JSON.stringify(value)).replaceAll('\u2028', '\\u2028').replaceAll('\u2029', '\\u2029'); return `# pip install requests${hasImage ? ' Pillow' : ''}${isVideo ? ' av==16.1.0' : ''} import json import requests ${setup} payload = json.loads(${literal}) ${hasImage ? 'payload["input"][0]["content"][1]["image_url"] = image_url\n' : ''} response = requests.post( "${ENDPOINT}", headers={"Origin": "https://valen-team.github.io"}, json=payload, timeout=120, ) response.raise_for_status() result = response.json() print(result["answers"][0]) print("Model ID:", result.get("model_id")) `; } export function parseAnswer(data, payload) { const fail = () => {throw new Error('response');}; const q = payload.questions[0]; if (!Array.isArray(data?.answers) || data.answers.length !== 1) fail(); const a = data.answers[0]; const probability = value => typeof value === 'number' && Number.isFinite(value) && value >= 0 && value <= 1; if (!a || a.name !== 'q0' || a.type !== q.type || (a.confidence !== undefined && !probability(a.confidence))) fail(); if (q.type === 'predicate') { if (!probability(a.probability)) fail(); return {title: a.probability >= .5 ? 'yes' : 'no', confidence: a.confidence, rows: [{label: 'yes', probability: a.probability}, {label: 'no', probability: 1 - a.probability}]}; } const labels = q.type === 'choice' ? new Map(q.choices.map(c => [c.value, c.description])) : new Map(q.levels.map((l, i) => [i, l.description])); if (!Array.isArray(a.probabilities) || a.probabilities.length !== labels.size) fail(); const seen = new Set(); const rows = a.probabilities.map(p => { if (!p) fail(); const key = q.type === 'score' && typeof p.value === 'string' && /^\d+$/.test(p.value) ? Number(p.value) : p.value; if (!labels.has(key) || seen.has(key) || !probability(p.probability)) fail(); seen.add(key); return {label: labels.get(key), probability: p.probability}; }); if (Math.abs(rows.reduce((sum, row) => sum + row.probability, 0) - 1) > .02) fail(); let title; if (q.type === 'choice') {if (!labels.has(a.choice)) fail(); title = labels.get(a.choice);} else { if (typeof a.score !== 'number' || !Number.isFinite(a.score) || a.score < 0 || a.score > labels.size-1) fail(); title = `${a.score.toFixed(2)} / ${labels.size-1}`; } return {title, rows, confidence: a.confidence}; } export async function requestDecision(payload, signal, fetcher = fetch) { // The browser supplies the Space's real Origin. Never try to spoof it in JS. const response = await fetcher(ENDPOINT, {method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(payload), signal, credentials: 'omit', redirect: 'error'}); if (!response.ok) throw new Error(`http_${response.status}`); let data; try {data = await response.json();} catch {throw new Error('response');} return {data, answer: parseAnswer(data, payload)}; }