UNPKG

basic-ai

Version:

Super basic AI API powered by gpt-4o-mini

196 lines (190 loc) 8.43 kB
export const Tool = (name = '', desc = '', params, strict = false) => { name = name.replace(/[^a-zA-Z0-9-_]/g,'').slice(0, 64) return `{"name":"${name}","description":${JSON.stringify(desc)}${params?',"parameters":'+JSON.stringify(params):''},"strict":${!!strict}}` } export const Response = (name = '', desc = '', params, strict = false) => { name = name.replace(/[^a-zA-Z0-9-_]/g,'').slice(0, 64) return `{"name":"${name}","description":${JSON.stringify(desc)},"schema":${JSON.stringify(params)},"strict":${!!strict}}` } const req = {method: 'POST', headers: {'Content-Type': 'application/json', Authorization: ''}, body: ''} let m = `{"model":"gpt-4o-mini","messages":[`, _m = 'gpt-4o-mini', dp = '' export const config = { pricingFor: u => u.prompt_tokens*.00000015 - u.prompt_tokens_details.cached_tokens*.000000075 + u.completion_tokens*.0000006, endpoint: 'https://api.openai.com/v1/chat/completions', get model(){return _m}, set model(a){ m = `{"model":${JSON.stringify(_m = a)},"messages":[` }, get token(){ return req.headers.Authorization.slice(7) }, set token(a){ req.headers.Authorization = 'Bearer '+a }, } class SimpleStream{ [Symbol.asyncIterator](){ return this } next(){ return this } #v = ''; #e = undefined; done = false #r = null; #c = null then(r, c){ if(this.e !== undefined){ try{c(this.e)}catch(e){Promise.reject(e)} }else if(this.#v){ try{r({value: this.#v, done: false})}catch(e){Promise.reject(e)} this.#v = '' }else if(this.done){ try{r({value: null, done: true})}catch(e){Promise.reject(e)} }else this.#r = r, this.#c = c } _append(v){ const r=this.#r; if(r){ this.#r = this.#c = null; try{r({value: v, done: false})}catch(e){Promise.reject(e)} }else this.#v += v } _reject(e = null){ this.#e = e; const c=this.#c; if(c){ this.#r = this.#c = null; try{c(e)}catch(e){Promise.reject(e)} } } _end(){ this.done = true; if(this.#r){ try{ this.#r({value: null, done: true}) }catch(e){Promise.reject(e)}; this.#r = this.#c = null } } } export class Chat extends Array{ constructor(ctx=10){ super(); this.context = ctx } _sticky = 0 usage = 0 temperature = 0.5 addUserMessage(txt='', name=''){ name = name.replace(/[^a-zA-Z0-9-_]/g,'').slice(0, 64) this.push(`{"role":"user"${name?`,"name":"${name}"`:''},"content":${JSON.stringify(txt)}}`) const i = this.length-this.context-this._sticky if(i>=0) this.splice(this._sticky,i) } addSystemMessage(txt='', name=''){ name = name.replace(/[^a-zA-Z0-9-_]/g,'').slice(0, 64) this.push(`{"role":"system"${name?`,"name":"${name}"`:''},"content":${JSON.stringify(txt)}}`) const i = this.length-this.context-this._sticky if(i>=0) this.splice(this._sticky,i) } addAssistantMessage(txt='',name=''){ name = name.replace(/[^a-zA-Z0-9-_]/g,'').slice(0, 64) this.push(`{"role":"assistant"${name?`,"name":"${name}"`:''},"content":${JSON.stringify(txt)}}`) const i = this.length-this.context-this._sticky if(i>=0) this.splice(this._sticky,i) } clear(sticky = false){ this.length = sticky ? (this._sticky = 0) : this._sticky } setSticky(clearOld = true){ if(clearOld && this._sticky) this.splice(0, this._sticky) this._sticky = this.length } get(tools = null, responseJSON = false, insert = true, maxTokens = 128, name = '', userData = null){ if(!this.length) throw 'No messages!' let t = ']' if(tools){ t = '],"tools":[{"type":"function","function":' for(const k in tools) (t.length>41&&(t+='},{"type":"function","function":'), t+=k) if(t.length<=40) t='' else t += '}]' } req.body = m+super.join(',')+t+`${!responseJSON?'':typeof responseJSON!='string'?',"response_format":{"type":"json_object"}':`,"response_format":{"type":"json_schema","json_schema":${responseJSON}}`},"temperature":${+this.temperature},"top_p":1,"frequency_penalty":0.5,"presence_penalty":1.5,"max_tokens":${+maxTokens}}` return fetch(config.endpoint, req).then(a => a.json()).then(res => { if(res.error || res.code) throw res.error || res.message this.usage += config.pricingFor(res.usage) const msg = res.choices[0].message if(msg.refusal) throw msg.refusal if(name) msg.name = name if(msg.tool_calls === null) delete msg.tool_calls let t = insert ? JSON.stringify(msg) : '', w = 0 const done = () => { if(insert){ this.push(t) const i = this.length - this.context - this._sticky if(i >= 0) this.splice(this._sticky, i) } } if(msg.tool_calls){ msg.content = ''; for(const {function:f,id} of msg.tool_calls){ for(const k in tools) if(k.slice(9,k.indexOf('"',9)) == f.name){ try{ const v = tools[k]?.(JSON.parse(f.arguments), userData) if(!t) break if(typeof v?.then == 'function') w++, v.then(v => (t += `,{"role":"tool","tool_call_id":"${id}","content":${JSON.stringify(''+(v??''))}}`, --w||done()), e => { t += `{"role":"tool","tool_call_id":"${id}","content":null}` --w||done(); throw e }) else t += `,{"role":"tool","tool_call_id":"${id}","content":${JSON.stringify(''+(v??''))}}` }catch(e){Promise.reject(e); if(t) t += `{"role":"tool","tool_call_id":"${id}","content":null}`} break } } } if(!w) done() return responseJSON ? JSON.parse(msg.content) : msg.content || '' }) } stream(tools = null, responseJSON = false, insert = true, maxTokens = 128, name = '', userData = null){ if(!this.length) throw 'No messages!' let t = ']' if(tools){ t = '],"tools":[{"type":"function","function":' for(const k in tools) (t.length>41&&(t+='},{"type":"function","function":'), t+=k) if(t.length<=40) t='' else t += '}]' } req.body = m+super.join(',')+t+`${!responseJSON?'':typeof responseJSON!='string'?',"response_format":{"type":"json_object"}':`,"response_format":{"type":"json_schema","json_schema":${responseJSON}}`},"temperature":${+this.temperature},"top_p":1,"frequency_penalty":0.5,"presence_penalty":1.5,"max_tokens":${+maxTokens},"stream":true,"stream_options":{"include_usage":true}}` let resStr = new SimpleStream() fetch(config.endpoint, req).then(async res => { let v = '', msg = null for await(const chunk of res.body.pipeThrough(new TextDecoderStream())){ v += chunk let i = v.indexOf('\n\n') while(i >= 0){ const line = v.slice(0, i) const j = line.indexOf(':'), dat = line.slice(j+1).trim() if(j < 0 || line.slice(0,j).trim() != 'data'){ // err r.cancel() resStr._reject(dat) throw dat } v = v.slice(i+2) i = v.indexOf('\n\n') if(dat[0] == '[') continue const ch = JSON.parse(dat) if(ch.usage) this.usage += config.pricingFor(ch.usage) if(!ch.choices.length) continue if(!msg){ msg = ch.choices[0].delta; continue } const d = ch.choices[0].delta if(d.content) msg.content += d.content, resStr._append(d.content) if(d.tool_calls){ msg.tool_calls ??= [] for(const t of d.tool_calls){ const i = t.index>>>0 if(i>=msg.tool_calls.length) delete t.index, msg.tool_calls.push(t) else msg.tool_calls[Math.min(i,msg.tool_calls.length)].function.arguments += t.function.arguments } } } } if(v){ msg = JSON.parse(v) if(msg.error || msg.code) throw msg.error || msg.message } if(msg.refusal) throw msg.refusal if(name) msg.name = name if(msg.tool_calls === null) delete msg.tool_calls let t = insert ? JSON.stringify(msg) : '', w = 0 const done = () => { if(insert){ this.push(t) const i = this.length - this.context - this._sticky if(i >= 0) this.splice(this._sticky, i) } resStr._end() } if(msg.tool_calls){ msg.content = ''; for(const {function:f,id} of msg.tool_calls){ for(const k in tools) if(k.slice(9,k.indexOf('"',9)) == f.name){ try{ const v = tools[k]?.(JSON.parse(f.arguments), userData) if(!t) break if(typeof v?.then == 'function') w++, v.then(v => (t += `,{"role":"tool","tool_call_id":"${id}","content":${JSON.stringify(''+(v??''))}}`, --w||done()), e => { t += `{"role":"tool","tool_call_id":"${id}","content":null}` --w||done(); throw e }) else t += `,{"role":"tool","tool_call_id":"${id}","content":${JSON.stringify(''+(v??''))}}` }catch(e){Promise.reject(e); if(t) t += `{"role":"tool","tool_call_id":"${id}","content":null}`} break } } } if(!w) done() }).catch(e => {resStr._reject(e)}) return resStr } }