Skip to content

Commit

Permalink
Add LangChainBot and AzureOpenAIAPIBot
Browse files Browse the repository at this point in the history
close #268
  • Loading branch information
qfxiongbin authored and sunner committed Jun 24, 2023
1 parent af2bcc3 commit c1c9386
Show file tree
Hide file tree
Showing 13 changed files with 738 additions and 85 deletions.
464 changes: 458 additions & 6 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"electron-builder": "^24.4.0",
"github-markdown-css": "^5.2.0",
"highlight.js": "^11.8.0",
"langchain": "0.0.96",
"material-design-icons": "^3.0.1",
"update-electron-app": "^2.0.1",
"uuid": "^9.0.0",
Expand Down
Binary file added public/bots/openai-35-azure-logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
68 changes: 68 additions & 0 deletions src/bots/LangChainBot.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import Bot from "@/bots/Bot";
import {
HumanChatMessage,
AIChatMessage,
SystemChatMessage,
} from "langchain/schema";

export default class LangChainBot extends Bot {
static _brandId = "langChainBot";
static _chatModel = undefined; // ChatModel instance

constructor() {
super();
}

async _sendPrompt(prompt, onUpdateResponse, callbackParam) {
let messages = await this.getChatContext();
// Remove old messages if exceeding the pastRounds limit
while (messages.length > this.getPastRounds() * 2) {
messages.shift();
}

// Convert the messages to the correct format
messages = messages.map((item) => {
if (item.type === "human") {
return new HumanChatMessage(item.data.content);
} else if (item.type === "ai") {
return new AIChatMessage(item.data.content);
} else if (item.type === "system") {
return new SystemChatMessage(item.data.content);
} else {
return item;
}
});

// Add the prompt to the messages
messages.push(new HumanChatMessage(prompt));

let res = "";
const model = this.constructor._chatModel;
const callbacks = [
{
handleLLMNewToken(token) {
if (token) {
res += token;
onUpdateResponse(callbackParam, { content: res, done: false });
} else {
onUpdateResponse(callbackParam, { done: true });
}
},
},
];
model.callbacks = callbacks;
await model.call(messages);
messages.push(new AIChatMessage(res));
this.setChatContext(messages);
}

async createChatContext() {
return [];
}

getPastRounds() {
throw new Error(
"Abstract property 'pastRounds' must be implemented in the subclass.",
);
}
}
2 changes: 2 additions & 0 deletions src/bots/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import ClaudePlusPoeBot from "./poe/ClaudePlusPoeBot";
import SkyWorkBot from "./SkyWorkBot";
import ChatGPT4MobileBot from "./openai/ChatGPT4MobileBot";
import OpenAIAPI3516KBot from "./openai/OpenAIAPI3516KBot";
import AzureOpenAIAPI35Bot from "./microsoft/AzureOpenAIAPI35Bot";

const all = [
AlpacaBot.getInstance(),
Expand All @@ -47,6 +48,7 @@ const all = [
HuggingChatBot.getInstance(),
MOSSBot.getInstance(),
OpenAIAPI35Bot.getInstance(),
AzureOpenAIAPI35Bot.getInstance(),
OpenAIAPI3516KBot.getInstance(),
OpenAIAPI4Bot.getInstance(),
QianWenBot.getInstance(),
Expand Down
11 changes: 11 additions & 0 deletions src/bots/microsoft/AzureOpenAIAPI35Bot.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import AzureOpenAIAPIBot from "./AzureOpenAIAPIBot";

export default class AzureOpenAIAPI35Bot extends AzureOpenAIAPIBot {
static _className = "AzureOpenAIAPI35Bot";
static _logoFilename = "openai-35-azure-logo.png";
static _model = "gpt-3.5-turbo";

constructor() {
super();
}
}
41 changes: 41 additions & 0 deletions src/bots/microsoft/AzureOpenAIAPIBot.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import LangChainBot from "@/bots/LangChainBot";
import store from "@/store";
import { ChatOpenAI } from "langchain/chat_models/openai";

export default class AzureOpenAIAPIBot extends LangChainBot {
static _brandId = "azureOpenaiApi";
static _className = "AzureOpenAIAPIBot";

constructor() {
super();
}

async checkAvailability() {
if (
!store.state.azureOpenaiApi.azureApiKey ||
!store.state.azureOpenaiApi.azureApiInstanceName ||
!store.state.azureOpenaiApi.azureOpenAIApiDeploymentName ||
!store.state.azureOpenaiApi.azureOpenAIApiVersion
) {
this.constructor._isAvailable = false;
} else {
const chatModel = new ChatOpenAI({
azureOpenAIApiKey: store.state.azureOpenaiApi.azureApiKey,
azureOpenAIApiInstanceName:
store.state.azureOpenaiApi.azureApiInstanceName,
azureOpenAIApiDeploymentName:
store.state.azureOpenaiApi.azureOpenAIApiDeploymentName,
azureOpenAIApiVersion: store.state.azureOpenaiApi.azureOpenAIApiVersion,
temperature: store.state.azureOpenaiApi.temperature,
streaming: true,
});
this.constructor._chatModel = chatModel;
this.constructor._isAvailable = true;
}
return this.isAvailable();
}

getPastRounds() {
return store.state.azureOpenaiApi.pastRounds;
}
}
87 changes: 12 additions & 75 deletions src/bots/openai/OpenAIAPIBot.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import Bot from "@/bots/Bot";
import { SSE } from "sse.js";
import LangChainBot from "@/bots/LangChainBot";
import store from "@/store";
import { ChatOpenAI } from "langchain/chat_models/openai";

export default class OpenAIAPIBot extends Bot {
export default class OpenAIAPIBot extends LangChainBot {
static _brandId = "openaiApi";
static _className = "OpenAIAPIBot";
static _logoFilename = "";
static _loginUrl = ""; // URL for the login button on the bots page
static _model = "";

constructor() {
super();
Expand All @@ -17,79 +14,19 @@ export default class OpenAIAPIBot extends Bot {
if (!store.state.openaiApi.apiKey) {
this.constructor._isAvailable = false;
} else {
this.constructor._isAvailable = true;
}
return this.isAvailable();
}

async _sendPrompt(prompt, onUpdateResponse, callbackParam) {
let messages = await this.getChatContext();
// Remove old messages if exceeding the pastRounds limit
while (messages.length > store.state.openaiApi.pastRounds * 2) {
messages.shift();
}

// Send the prompt to the OpenAI API
try {
const headers = {
"Content-Type": "application/json",
Authorization: `Bearer ${store.state.openaiApi.apiKey}`,
};

messages.push({ role: "user", content: `‘${prompt}’` });
const payload = JSON.stringify({
model: this.constructor._model,
messages,
const chatModel = new ChatOpenAI({
openAIApiKey: store.state.openaiApi.apiKey,
modelName: this.constructor._model ? this.constructor._model : "",
temperature: store.state.openaiApi.temperature,
stream: true,
});

const requestConfig = {
headers,
method: "POST",
payload,
};

let res = "";
return new Promise((resolve, reject) => {
// call OpenAI API
const apiUrl =
store.state.openaiApi.alterUrl ||
"https://api.openai.com/v1/chat/completions";
const source = new SSE(apiUrl, requestConfig);
source.addEventListener("message", (event) => {
const regex = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{6}$/;
if (event.data === "[DONE]") {
onUpdateResponse(callbackParam, { done: true });
messages.push({ role: "assistant", content: res });
this.setChatContext(messages);
source.close();
resolve();
} else if (regex.test(event.data)) {
// Ignore the timestamp
return;
} else {
const data = JSON.parse(event.data);
const partialText = data.choices?.[0]?.delta?.content;
if (partialText) {
res += partialText;
onUpdateResponse(callbackParam, { content: res, done: false });
}
}
});
source.addEventListener("error", (error) => {
const data = JSON.parse(error.data);
source.close();
reject(data.error.message);
});
source.stream();
streaming: true,
});
} catch (error) {
console.error("Error sending prompt to OpenAIAPI:", error);
this.constructor._chatModel = chatModel;
this.constructor._isAvailable = true;
}
return this.isAvailable();
}

async createChatContext() {
return [];
getPastRounds() {
return store.state.openaiApi.pastRounds;
}
}
98 changes: 98 additions & 0 deletions src/components/BotSettings/AzureOpenAIAPIBotSettings.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
<template>
<v-list-subheader>{{ bot.getBrandName() }}</v-list-subheader>
<v-list-item>
<v-list-item-title>{{ $t("azureOpenaiApi.azureOpenAIApiKey") }}</v-list-item-title>
<v-list-item-subtitle>{{
$t("settings.secretPrompt")
}}</v-list-item-subtitle>
<v-text-field
v-model="azureOpenaiApi.azureApiKey"
outlined
dense
placeholder="b40..."
@update:model-value="setAzureOpenaiApi({ azureApiKey: $event })"
></v-text-field>
<v-list-item-title>{{ $t("azureOpenaiApi.azureApiInstanceName") }}</v-list-item-title>
<v-list-item-subtitle>{{
$t("azureOpenaiApi.azureApiInstanceNamePrompt")
}}</v-list-item-subtitle>
<v-text-field
v-model="azureOpenaiApi.azureApiInstanceName"
outlined
dense
@update:model-value="setAzureOpenaiApi({ azureApiInstanceName: $event })"
></v-text-field>
<v-list-item-title>{{ $t("azureOpenaiApi.azureOpenAIApiDeploymentName") }}</v-list-item-title>
<v-list-item-subtitle>{{
$t("azureOpenaiApi.azureOpenAIApiDeploymentNamePrompt")
}}</v-list-item-subtitle>
<v-text-field
v-model="azureOpenaiApi.azureOpenAIApiDeploymentName"
outlined
dense
@update:model-value="setAzureOpenaiApi({ azureOpenAIApiDeploymentName: $event })"
></v-text-field>
<v-list-item-title>{{ $t("azureOpenaiApi.azureOpenAIApiVersion") }}</v-list-item-title>
<v-text-field
v-model="azureOpenaiApi.azureOpenAIApiVersion"
outlined
dense
placeholder="2023-03-15-preview"
@update:model-value="setAzureOpenaiApi({ azureOpenAIApiVersion: $event })"
></v-text-field>

<v-list-item-title>{{ $t("azureOpenaiApi.temperature") }}</v-list-item-title>
<v-list-item-subtitle>{{
$t("azureOpenaiApi.temperaturePrompt")
}}</v-list-item-subtitle>
<v-slider
v-model="azureOpenaiApi.temperature"
color="primary"
:min="0"
:max="2"
:step="0.1"
thumb-label
show-ticks="always"
:ticks="temperatureLabels"
@update:model-value="setAzureOpenaiApi({ temperature: $event })"
></v-slider>

<v-list-item-title>{{ $t("bot.pastRounds") }}</v-list-item-title>
<v-list-item-subtitle>{{
$t("bot.pastRoundsPrompt")
}}</v-list-item-subtitle>
<v-slider
v-model="azureOpenaiApi.pastRounds"
color="primary"
:min="0"
:max="10"
:step="1"
thumb-label
show-ticks
@update:model-value="setAzureOpenaiApi({ pastRounds: $event })"
></v-slider>
</v-list-item>
</template>

<script>
import { mapState, mapMutations } from "vuex";
import Bot from "@/bots/microsoft/AzureOpenAIAPIBot";
import i18n from "@/i18n";
export default {
data() {
return {
bot: Bot.getInstance(),
temperatureLabels: {
0: i18n.global.t("azureOpenaiApi.temperature0"),
2: i18n.global.t("azureOpenaiApi.temperature2"),
},
};
},
methods: {
...mapMutations(["setAzureOpenaiApi"]),
},
computed: {
...mapState(["azureOpenaiApi"]),
},
};
</script>
2 changes: 2 additions & 0 deletions src/components/SettingsModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ import { useTheme } from "vuetify";
import ChatGPTBotSettings from "@/components/BotSettings/ChatGPTBotSettings.vue";
import OpenAIAPIBotSettings from "@/components/BotSettings/OpenAIAPIBotSettings.vue";
import AzureOpenAIAPIBotSettings from "./BotSettings/AzureOpenAIAPIBotSettings.vue";
import BingChatBotSettings from "@/components/BotSettings/BingChatBotSettings.vue";
import SparkBotSettings from "./BotSettings/SparkBotSettings.vue";
import BardBotSettings from "@/components/BotSettings/BardBotSettings.vue";
Expand All @@ -85,6 +86,7 @@ const emit = defineEmits(["update:open", "done"]);
const settings = [
ChatGPTBotSettings,
OpenAIAPIBotSettings,
AzureOpenAIAPIBotSettings,
WenxinQianfanBotSettings,
GradioAppBotSettings,
BardBotSettings,
Expand Down
Loading

1 comment on commit c1c9386

@vercel
Copy link

@vercel vercel bot commented on c1c9386 Jun 24, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

chatall – ./

chatall-llm.vercel.app
chatall-sunner.vercel.app
chatall-git-main-sunner.vercel.app

Please sign in to comment.