UNPKG

grix-connector

Version:

Connect local AI coding agents (Claude, Codex, Gemini, Qwen, DeepSeek, Cursor, OpenCode, Pi, OpenHuman, Reasonix) to the Grix scheduling platform. Also serves as an OpenClaw plugin for Grix channel transport.

11 lines (10 loc) 5.26 kB
#!/usr/bin/env node import{performance as f}from"node:perf_hooks";import{parseArgs as x}from"node:util";const _="Generate a stable semantic embedding for this short bilingual memory sample. It should be suitable for retrieval, recall, and near-duplicate detection.";function w(){return["Usage:"," node scripts/bench_ollama_embeddings.js [options] <model...>","","Options:"," --host <url> Ollama host (default http://127.0.0.1:11434)"," --rounds <n> Measured rounds per test after warmup (default 1)"," --batch-size <n> Inputs per batch test (default 4)"," --timeout <seconds> Request timeout (default 300)"," --single-text <text> Input used for the single-input benchmark"," --skip-warmup Skip warmup request per model"," --json Emit JSON results"].join(` `)}function p(e,n){const s=Number(e);if(!Number.isFinite(s))throw new Error(`Invalid --${n}: ${String(e)}`);return s}function g(e){if(!e.length)throw new Error("median() requires at least one value");const n=[...e].sort((t,r)=>t-r),s=Math.floor(n.length/2);return n.length%2===0?(n[s-1]+n[s])/2:n[s]}function k(e){const n=e.filter(s=>typeof s=="number"&&Number.isFinite(s));return n.length?g(n):null}function v(e,n){return Array.from({length:n},(s,t)=>`${e} [sample ${t+1}]`)}async function $(e,n,s){const t=new AbortController,r=setTimeout(()=>t.abort(),s);try{return await fetch(e,{...n,signal:t.signal})}finally{clearTimeout(r)}}async function O(e){const n=e.host.replace(/\/+$/,"")+"/api/embed",s={model:e.model,input:e.inputs.length>1?e.inputs:e.inputs[0]},t=await $(n,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s)},Math.max(1,e.timeoutSeconds)*1e3),r=await t.text();if(!t.ok)throw new Error(`HTTP ${t.status}: ${r}`);let u;try{u=JSON.parse(r)}catch{throw new Error(`Unexpected JSON from Ollama: ${r.slice(0,256)}`)}return u}async function b(e){const n=f.now(),s=await O(e),t=(f.now()-n)/1e3,r=s.total_duration,u=typeof r=="number"&&Number.isFinite(r)?r/1e9:null,a=s.embeddings;if(!Array.isArray(a))throw new Error(`Unexpected Ollama response for ${e.model}: ${JSON.stringify(s)}`);const o=a[0],i=Array.isArray(o)?o.length:0;return{wall_seconds:t,api_seconds:u,items:a.length,vector_dims:i}}async function E(e){e.skipWarmup||await b({host:e.host,model:e.model,inputs:[e.singleText],timeoutSeconds:e.timeoutSeconds});const n=[],s=[];for(let t=0;t<e.rounds;t+=1)n.push(await b({host:e.host,model:e.model,inputs:[e.singleText],timeoutSeconds:e.timeoutSeconds}));for(let t=0;t<e.rounds;t+=1)s.push(await b({host:e.host,model:e.model,inputs:e.batchInputs,timeoutSeconds:e.timeoutSeconds}));return{model:e.model,ok:!0,single_wall_seconds:g(n.map(t=>t.wall_seconds)),single_api_seconds:k(n.map(t=>t.api_seconds)),batch_wall_seconds:g(s.map(t=>t.wall_seconds)),batch_api_seconds:k(s.map(t=>t.api_seconds)),batch_size:e.batchInputs.length,vector_dims:n.at(-1)?.vector_dims??0,rounds:e.rounds}}function m(e){return e===null?"-":e.toFixed(2)}function T(e,n){const s=["Model","Single(s)",`Batch${n}(s)`,"API Single(s)",`API Batch${n}(s)`,"Dims","Status"],t=e.map(o=>o.ok?[o.model,m(o.single_wall_seconds),m(o.batch_wall_seconds),m(o.single_api_seconds),m(o.batch_api_seconds),String(o.vector_dims),"ok"]:[o.model,"-","-","-","-","-",o.error]),r=s.map((o,i)=>Math.max(o.length,...t.map(l=>(l[i]??"").length))),u=o=>{process.stdout.write(`${o.map((i,l)=>i.padEnd(r[l]??i.length)).join(" ")} `)};u(s),u(r.map(o=>"-".repeat(o)));for(const o of t)u(o);const a=e.filter(o=>o.ok);if(a.length){const o=[...a].sort((i,l)=>i.batch_wall_seconds!==l.batch_wall_seconds?i.batch_wall_seconds-l.batch_wall_seconds:i.single_wall_seconds!==l.single_wall_seconds?i.single_wall_seconds-l.single_wall_seconds:i.model.localeCompare(l.model));process.stdout.write(` Recommended by speed: ${o[0].model} `)}}async function B(){const e=x({args:process.argv.slice(2),allowPositionals:!0,options:{host:{type:"string",default:"http://127.0.0.1:11434"},rounds:{type:"string",default:"1"},"batch-size":{type:"string",default:"4"},timeout:{type:"string",default:"300"},"single-text":{type:"string",default:_},"skip-warmup":{type:"boolean",default:!1},json:{type:"boolean",default:!1},help:{type:"boolean",default:!1}}});if(e.values.help)return process.stdout.write(`${w()} `),0;const n=e.positionals.map(c=>String(c).trim()).filter(Boolean);if(!n.length)return process.stderr.write(`${w()} `),1;const s=String(e.values.host??"").trim()||"http://127.0.0.1:11434",t=p(e.values.rounds,"rounds"),r=p(e.values["batch-size"],"batch-size"),u=p(e.values.timeout,"timeout"),a=String(e.values["single-text"]??_),o=!!e.values["skip-warmup"],i=!!e.values.json;if(t<1)throw new Error("--rounds must be at least 1");if(r<1)throw new Error("--batch-size must be at least 1");const l=v(a,r),d=[];for(const c of n)try{d.push(await E({host:s,model:c,singleText:a,batchInputs:l,rounds:t,timeoutSeconds:u,skipWarmup:o}))}catch(h){const S=h instanceof Error?h.message:String(h);d.push({model:c,ok:!1,error:S})}const y=d.some(c=>c.ok);return i?process.stdout.write(`${JSON.stringify({results:d},null,2)} `):T(d,r),y?0:1}B().then(e=>{process.exitCode=e}).catch(e=>{const n=e instanceof Error?e.message:String(e);process.stderr.write(`${n} `),process.exitCode=1});