NamH 6e2210cb22
feat: adding create bot functionality (#368)
* feat: adding create bot functionality

Signed-off-by: James <james@jan.ai>

* update the temperature progress bar

Signed-off-by: James <james@jan.ai>

* chore: remove tgz

Signed-off-by: James <james@jan.ai>

* update core dependency

Signed-off-by: James <james@jan.ai>

* fix e2e test

Signed-off-by: James <james@jan.ai>

---------

Signed-off-by: James <james@jan.ai>
Co-authored-by: James <james@jan.ai>
2023-10-23 15:57:56 +07:00

171 lines
5.4 KiB
TypeScript

import {
EventName,
InferenceService,
NewMessageRequest,
PluginService,
events,
store,
invokePluginFunc,
} from "@janhq/core";
import { Observable } from "rxjs";
const initModel = async (product) => invokePluginFunc(MODULE_PATH, "initModel", product);
const stopModel = () => {
invokePluginFunc(MODULE_PATH, "killSubprocess");
};
function requestInference(recentMessages: any[], bot?: any): Observable<string> {
return new Observable((subscriber) => {
const requestBody = JSON.stringify({
messages: recentMessages,
stream: true,
model: "gpt-3.5-turbo",
max_tokens: bot?.maxTokens ?? 2048,
frequency_penalty: bot?.frequencyPenalty ?? 0,
presence_penalty: bot?.presencePenalty ?? 0,
temperature: bot?.customTemperature ?? 0,
});
console.debug(`Request body: ${requestBody}`);
fetch(INFERENCE_URL, {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "text/event-stream",
"Access-Control-Allow-Origin": "*",
},
body: requestBody,
})
.then(async (response) => {
const stream = response.body;
const decoder = new TextDecoder("utf-8");
const reader = stream?.getReader();
let content = "";
while (true && reader) {
const { done, value } = await reader.read();
if (done) {
console.log("SSE stream closed");
break;
}
const text = decoder.decode(value);
const lines = text.trim().split("\n");
for (const line of lines) {
if (line.startsWith("data: ") && !line.includes("data: [DONE]")) {
const data = JSON.parse(line.replace("data: ", ""));
content += data.choices[0]?.delta?.content ?? "";
if (content.startsWith("assistant: ")) {
content = content.replace("assistant: ", "");
}
subscriber.next(content);
}
}
}
subscriber.complete();
})
.catch(subscriber.error);
});
}
async function retrieveLastTenMessages(conversationId: string, bot?: any) {
// TODO: Common collections should be able to access via core functions instead of store
const messageHistory = (await store.findMany("messages", { conversationId }, [{ createdAt: "asc" }])) ?? [];
let recentMessages = messageHistory
.filter((e) => e.message !== "" && (e.user === "user" || e.user === "assistant"))
.slice(-9)
.map((message) => ({
content: message.message.trim(),
role: message.user === "user" ? "user" : "assistant",
}));
if (bot && bot.systemPrompt) {
// append bot's system prompt
recentMessages = [{
content: `[INST] ${bot.systemPrompt}`,
role: 'system'
},...recentMessages];
}
console.debug(`Last 10 messages: ${JSON.stringify(recentMessages, null, 2)}`);
return recentMessages;
}
async function handleMessageRequest(data: NewMessageRequest) {
const conversation = await store.findOne("conversations", data.conversationId);
let bot = undefined;
if (conversation.botId != null) {
bot = await store.findOne("bots", conversation.botId);
}
const recentMessages = await retrieveLastTenMessages(data.conversationId, bot);
const message = {
...data,
message: "",
user: "assistant",
createdAt: new Date().toISOString(),
_id: undefined,
};
// TODO: Common collections should be able to access via core functions instead of store
const id = await store.insertOne("messages", message);
message._id = id;
events.emit(EventName.OnNewMessageResponse, message);
requestInference(recentMessages, bot).subscribe({
next: (content) => {
message.message = content;
events.emit(EventName.OnMessageResponseUpdate, message);
},
complete: async () => {
message.message = message.message.trim();
// TODO: Common collections should be able to access via core functions instead of store
await store.updateOne("messages", message._id, message);
},
error: async (err) => {
message.message = message.message.trim() + "\n" + "Error occurred: " + err;
// TODO: Common collections should be able to access via core functions instead of store
await store.updateOne("messages", message._id, message);
},
});
}
async function inferenceRequest(data: NewMessageRequest): Promise<any> {
const message = {
...data,
message: "",
user: "assistant",
createdAt: new Date().toISOString(),
};
return new Promise(async (resolve, reject) => {
const recentMessages = await retrieveLastTenMessages(data.conversationId);
requestInference([...recentMessages, { role: "user", content: data.message }]).subscribe({
next: (content) => {
message.message = content;
},
complete: async () => {
resolve(message);
},
error: async (err) => {
reject(err);
},
});
});
}
const registerListener = () => {
events.on(EventName.OnNewMessageRequest, handleMessageRequest);
};
const onStart = async () => {
registerListener();
};
// Register all the above functions and objects with the relevant extension points
export function init({ register }) {
register(PluginService.OnStart, PLUGIN_NAME, onStart);
register(InferenceService.InitModel, initModel.name, initModel);
register(InferenceService.StopModel, stopModel.name, stopModel);
register(InferenceService.InferenceRequest, inferenceRequest.name, inferenceRequest);
}