Valen-API / api.mjs
yuhangzang's picture
Use website text, image and video cookbook examples
4f01dd5 verified
Raw History Blame Contribute Delete
6.3 kB
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 = '<JPEG data URL omitted from preview>';
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)};
}