openai = {
const $url = Symbol('url');
const $model = Symbol('model');
const $maxTokens = Symbol('maxTokens');
const $overlap = Symbol('tokenOverlap');
const $temperature = Symbol('temperature');
const $role = Symbol('role');
const $which = Symbol('which');
const $chat = Symbol('chat');
const $text = Symbol('text');
const tokenizer = new GPT3Tokenizer({ type: 'gpt3' });
const self = {};
Object.assign(self, {
request,
split,
complete,
chat,
tokenizer,
url: (x) => ({ [$url]: x }),
model: (x) => ({ [$model]: x }),
maxTokens: (x) => ({ [$maxTokens]: x }),
overlap: (x) => ({ [$overlap]: x }),
temperature: (x) => ({ [$temperature]: x }),
role: (x) => ({ [$role]: x }),
$url,
$model,
$maxTokens,
$overlap,
$temperature,
$role,
});
return self;
async function request(parameters) {
if (Array.isArray(parameters)) {
return await Promise.all(parameters.map(request));
}
const { [$which]: which } = parameters;
if (which === $text) {
const { prompt, model, maxTokens, url, temperature } = parameters;
const key = JSON.stringify([prompt, model, maxTokens, url, temperature]);
const hash = md5(key);
if (localStorage.getItem(hash) !== null) {
const { value } = JSON.parse(localStorage.getItem(hash));
return Promise.resolve(value);
}
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${API_KEY}`,
},
mode: 'cors',
body: JSON.stringify({
model,
max_tokens: maxTokens,
temperature,
prompt,
}),
});
const json = await response.json();
const value = json.choices[0].text;
localStorage.setItem(hash, JSON.stringify({ key, value }));
return value;
} else if (which === $chat) {
const { messages, model, maxTokens, url } = parameters;
const key = JSON.stringify([messages.map(({ role, content }) => ([role, content])), model, maxTokens, url]);
const hash = md5(key);
if (localStorage.getItem(hash) !== null) {
const { value } = JSON.parse(localStorage.getItem(hash));
return Promise.resolve(value);
}
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${API_KEY}`,
},
mode: 'cors',
body: JSON.stringify({
model,
max_tokens: maxTokens,
messages,
}),
});
const json = await response.json();
const value = json.choices[0].content;
localStorage.setItem(hash, JSON.stringify({ key, value }));
return value;
}
}
function chat(strings, ...expressions) {
strings = NORMALIZE(strings);
const options = {
[$maxTokens]: 1024,
[$role]: 'user',
[$model]: 'gpt-3.5-turbo',
};
const parts = [];
for (let i=0, n=strings.length+expressions.length; i<n; ++i) {
const part = [strings, expressions][ (i%2)|0 ][ (i/2)|0 ];
switch (TYPE(part)) {
case 'object':
Object.assign(options, part);
break;
case 'array':
parts.push(part.map((text) => ({ text, ...options })));
break;
case 'string':
parts.push([{ text: part, ...options }]);
break;
} // switch
} // for
const { [$maxTokens]: maxTokens } = options;
const { [$model]: model } = options;
const ret = [];
const messages = [];
for (let parts of d3.cross(...parts)) {
prompt = prompt.join('');
prompt = prompt.trim();
messages.push({ role, content: prompt });
}
return { [$which]: $chat, model, max_tokens: maxTokens, messages };
}
function split(strings, ...expressions) {
strings = NORMALIZE(strings);
const options = {
[$maxTokens]: 256,
[$overlap]: 32,
};
const parts = [];
for (let i=0, n=strings.length+expressions.length; i<n; ++i) {
const part = [strings, expressions][ (i%2)|0 ][ (i/2)|0 ];
switch (TYPE(part)) {
case 'object':
Object.assign(options, part);
break;
case 'array':
parts.push(part);
break;
case 'string':
parts.push([part]);
break;
} // switch
} // for
const { [$maxTokens]: maxTokens } = options;
const { [$overlap]: overlap } = options;
if (maxTokens - overlap <= 0) {
throw `bad config: maxTokens (${maxTokens}) - overlap (${overlap}) <= 0`;
}
const ret = [];
for (let prompt of d3.cross(...parts)) {
prompt = prompt.join('');
prompt = prompt.trim();
const { text } = tokenizer.encode(prompt);
for (let i=0, n=text.length; i<n; i += maxTokens - overlap) {
ret.push(text.slice(i, i + maxTokens).join(''));
}
}
return ret;
}
function complete(strings, ...expressions) {
strings = NORMALIZE(strings);
const options = {
[$url]: `https://api.openai.com/v1/completions`,
[$model]: 'text-curie-001',
[$maxTokens]: 256,
[$temperature]: 0.2,
};
const parts = [];
for (let i=0, n=strings.length+expressions.length; i<n; ++i) {
const part = [strings, expressions][ (i%2)|0 ][ (i/2)|0 ];
switch (TYPE(part)) {
case 'object':
Object.assign(options, part);
break;
case 'array':
parts.push(part);
break;
case 'string':
parts.push([part]);
break;
} // switch
} // for
const { [$url]: url } = options;
const { [$model]: model } = options;
const { [$maxTokens]: maxTokens } = options;
const { [$temperature]: temperature } = options;
const ret = [];
for (let prompt of d3.cross(...parts)) {
prompt = prompt.join('');
prompt = prompt.trim();
ret.push({ [$which]: $text, prompt, model, maxTokens, url, temperature });
}
return ret;
}
function TYPE(x) {
return Array.isArray(x) ? 'array' : typeof x;
}
function NORMALIZE(strings) {
let prefix = Infinity;
const getPrefix = /^( +)[^ ]/;
for (const string of strings) {
for (const line of string.split('\n')) {
const match = getPrefix.exec(line);
if (match) {
prefix = Math.min(prefix, match[1].length);
}
}
}
if (prefix === Infinity) {
return strings;
}
const matchPrefix = new RegExp(String.raw`^ {${prefix}}`);
const ret = [];
for (const string of strings) {
const parts = [];
for (let line of string.split('\n')) {
line = line.replace(matchPrefix, '');
parts.push(line);
}
ret.push(parts.join('\n'));
}
return ret;
} // function NORMALIZE
};