jvcgpt / src /utils /oobabooga.ts
Greums's picture
first alpha version
1982de5
raw
history blame
6.3 kB
import {Post, Topic} from "./topics";
import {Settings} from "./settings";
import {generateUUID} from "./uuids";
import {tokenizeTopic, tokensToPosts, tokensToTopic} from "./model";
// import {replaceSmileysInText} from "./smileys";
//
// try {
// console.log(replaceSmileysInText("lol"))
// }catch(e) {}
// @see https://github.com/openai/openai-node/blob/14784f95797d4d525dafecfd4ec9c7a133540da0/src/resources/chat/completions.ts
type OobaboogaStreamChunk = {
id: string; // Unique identifier for the chunk
object: string; // The type of the chunk, e.g., "text_completion.chunk"
created: number; // Unix timestamp of when the chunk was created
model: string; // Name or identifier of the model generating the completion
choices: {
index: number; // The index of the choice in the completion
finish_reason: string | null; // Reason why the completion stopped, or null if still in progress
text: string; // The generated text for this chunk
logprobs: {
top_logprobs: Record<string, number>[]; // Log probabilities for the top tokens, as an array of key-value pairs
};
}[];
usage?: {
prompt_tokens: number; // Number of tokens in the prompt
completion_tokens: number; // Number of tokens generated in the completion
total_tokens: number; // Total tokens used
};
};
export async function generateTopic(settings: Settings, nPosts: number): Promise<Topic> {
console.log(settings);
const rawOutput = await fetApiWithStream(settings, "<|start_header_id|>", nPosts);
// const rawOutput = await fetApi(settings);
// console.log(rawOutput);
// let rawOutput = "rawOutput";
return tokensToTopic(rawOutput);
}
export async function generatePosts(settings: Settings, nPosts: number, topic: Topic): Promise<Post[]> {
// console.log(settings);
const rawOutput = await fetApiWithStream(settings, tokenizeTopic(topic), nPosts);
// const rawOutput = await fetApi(settings);
// console.log(rawOutput);
// let rawOutput = "rawOutput";
console.log("rawOutput");
console.log(rawOutput);
return tokensToPosts(rawOutput);
}
async function fetApi(settings: Settings): Promise<string> {
const response = await fetch(new URL("/v1/completions", settings.apiURL), {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
prompt: "<|start_header_id|>",
temperature: settings.temperature,
max_tokens: 1000,
stream: false,
skip_special_tokens: false,
stop: "<|end_of_post|>"
// top_p: 1,
// frequency_penalty: 0,
// presence_penalty: 0,
}),
});
if (response.status !== 200) {
throw new Error(`Failed to fetch API (${response.status}): ${response.statusText}`);
}
const json = await response.json();
console.log(json)
return json.choices[0].text;
}
const postEndToken = "<|end_of_post|>";
// @see https://github.com/openai/openai-node/issues/18
// nPosts: number of post before stop
async function fetApiWithStream(settings: Settings, prompt: string, nPosts: number): Promise<string> {
const controller = new AbortController()
const response = await fetch(new URL("/v1/completions", settings.apiURL), {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
prompt,
temperature: settings.temperature,
max_tokens: 2000,
stream: true,
skip_special_tokens: false,
// stop: "<|end_of_post|>"
// top_p: 1,
// frequency_penalty: 0,
// presence_penalty: 0,
}),
signal: controller.signal,
});
if (!response.ok) {
throw new Error(`Failed to fetch API (${response.status} ${response.statusText}): ${await response.text()}`);
}
// console.log("Streaming !!!!");
//
// const decoderStream = new TextDecoderStream("utf-8");
// const writer = new WritableStream({
// write(rawChunk: string) {
// // output.innerHTML += chunk;
// const chunk = JSON.parse(rawChunk.trimStart().slice(6)) as OobaboogaStreamChunk; // remove "data: " and parse
// console.log(chunk)
// }
// });
console.log(`Fetching topic with ${nPosts} posts...`);
let endTokenCount = 0;
let tokens = ""; // Dont know why but the first token is skipped
let finishReason: string | null = null;
try {
await response.body.pipeThrough(new TextDecoderStream("utf-8")).pipeTo(new WritableStream({
write(rawChunk: string) {
// chunk can contains multiple lines, one chunk data per line
for (const rawChunkLine of rawChunk.split("\n")) {
if (!rawChunkLine.startsWith("data:")) continue;
const chunk = JSON.parse(rawChunkLine.slice(6)) as OobaboogaStreamChunk; // remove "data: " and parse
const text = chunk.choices[0].text;
console.log(text)
tokens += chunk.choices[0].text;
if (text.includes(postEndToken)) {
endTokenCount++;
if(endTokenCount >= nPosts) {
finishReason = "custom_stop";
controller.abort();
break;
}
} else {
finishReason = chunk.choices[0].finish_reason;
}
}
// output.innerHTML += chunk;
// console.log("----")
// console.log(rawChunk)
// console.log(rawChunk.slice(6).trimEnd())
// console.log(chunk.choices[0].text)
// tokens += chunk.choices[0].text;
}
}));
} catch (e) {
if (e.name !== 'AbortError') {
throw e;
}
}
console.log("Done fetching data")
console.log(`Finish reason: ${finishReason}`)
console.log(`Tokens: ${tokens}`)
return tokens;
}