Files
chatgpt-api-web/src/chatgpt.ts

205 lines
5.2 KiB
TypeScript

export interface Message {
role: "system" | "user" | "assistant" | "function";
content: string;
name?: "example_user" | "example_assistant";
}
export interface ChunkMessage {
model: string;
choices: {
delta: { role: "assitant" | undefined; content: string | undefined };
}[];
}
export interface FetchResponse {
error?: any;
id: string;
object: string;
created: number;
model: string;
usage: {
prompt_tokens: number | undefined;
completion_tokens: number | undefined;
total_tokens: number | undefined;
};
choices: {
message: Message | undefined;
finish_reason: "stop" | "length";
index: number | undefined;
}[];
}
// https://help.openai.com/en/articles/4936856-what-are-tokens-and-how-to-count-them
export function calculate_token_length(content: string): number {
const totalCount = content.length;
const chineseCount = content.match(/[\u00ff-\uffff]|\S+/g)?.length ?? 0;
const englishCount = totalCount - chineseCount;
const tokenLength = englishCount / 4 + (chineseCount * 4) / 3;
return ~~tokenLength;
}
class Chat {
OPENAI_API_KEY: string;
messages: Message[];
sysMessageContent: string;
total_tokens: number;
max_tokens: number;
tokens_margin: number;
apiEndpoint: string;
model: string;
temperature: number;
top_p: number;
presence_penalty: number;
frequency_penalty: number;
constructor(
OPENAI_API_KEY: string | undefined,
{
systemMessage = "你是一个有用的人工智能助理",
max_tokens = 4096,
tokens_margin = 1024,
apiEndPoint = "https://api.openai.com/v1/chat/completions",
model = "gpt-3.5-turbo",
temperature = 1.0,
top_p = 1,
presence_penalty = 0,
frequency_penalty = 0,
} = {}
) {
if (OPENAI_API_KEY === undefined) {
throw "OPENAI_API_KEY is undefined";
}
this.OPENAI_API_KEY = OPENAI_API_KEY;
this.messages = [];
this.total_tokens = 0;
this.max_tokens = max_tokens;
this.tokens_margin = tokens_margin;
this.sysMessageContent = systemMessage;
this.apiEndpoint = apiEndPoint;
this.model = model;
this.temperature = temperature;
this.top_p = top_p;
this.presence_penalty = presence_penalty;
this.frequency_penalty = frequency_penalty;
}
_fetch(stream = false) {
return fetch(this.apiEndpoint, {
method: "POST",
headers: {
Authorization: `Bearer ${this.OPENAI_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: this.model,
messages: [
{ role: "system", content: this.sysMessageContent },
...this.messages,
],
stream,
temperature: this.temperature,
top_p: this.top_p,
presence_penalty: this.presence_penalty,
frequency_penalty: this.frequency_penalty,
}),
});
}
async fetch(): Promise<FetchResponse> {
const resp = await this._fetch();
const j = await resp.json();
if (j.error !== undefined) {
throw JSON.stringify(j.error);
}
return j;
}
async say(content: string): Promise<string> {
this.messages.push({ role: "user", content });
await this.complete();
return this.messages.slice(-1)[0].content;
}
processFetchResponse(resp: FetchResponse): string {
if (resp.error !== undefined) {
throw JSON.stringify(resp.error);
}
this.total_tokens = resp?.usage?.total_tokens ?? 0;
if (resp?.choices[0]?.message) {
this.messages.push(resp?.choices[0]?.message);
}
if (resp.choices[0]?.finish_reason === "length") {
this.forceForgetSomeMessages();
} else {
this.forgetSomeMessages();
}
return (
resp?.choices[0]?.message?.content ?? `Error: ${JSON.stringify(resp)}`
);
}
async complete(): Promise<string> {
const resp = await this.fetch();
return this.processFetchResponse(resp);
}
completeWithSteam() {
this.total_tokens = this.messages
.map((msg) => this.calculate_token_length(msg.content) + 20)
.reduce((a, v) => a + v);
return this._fetch(true);
}
calculate_token_length(content: string): number {
return calculate_token_length(content);
}
user(...messages: string[]) {
for (const msg of messages) {
this.messages.push({ role: "user", content: msg });
this.total_tokens += this.calculate_token_length(msg);
this.forgetSomeMessages();
}
}
assistant(...messages: string[]) {
for (const msg of messages) {
this.messages.push({ role: "assistant", content: msg });
this.total_tokens += this.calculate_token_length(msg);
this.forgetSomeMessages();
}
}
forgetSomeMessages() {
// forget occur condition
if (this.total_tokens + this.tokens_margin >= this.max_tokens) {
this.forceForgetSomeMessages();
}
}
forceForgetSomeMessages() {
this.messages = [
...this.messages.slice(Math.max(~~(this.messages.length / 4), 2)),
];
}
forgetAllMessage() {
this.messages = [];
}
stats(): string {
return (
`total_tokens: ${this.total_tokens}` +
"\n" +
`max_tokens: ${this.max_tokens}` +
"\n" +
`tokens_margin: ${this.tokens_margin}` +
"\n" +
`messages.length: ${this.messages.length}`
);
}
}
export default Chat;