mirror of
https://github.com/matrixgpt/matrix-chatgpt-bot.git
synced 2024-09-27 19:45:38 +00:00
Merge pull request #86 from matrixgpt/waylaid-wanderer
Switch to waylaidwanderer
This commit is contained in:
commit
69c3560735
@ -6,6 +6,8 @@ OPENAI_API_KEY=
|
||||
CHATGPT_CONTEXT=thread
|
||||
# (Optional) Explicitly set the ChatGPT model to be used by the API.
|
||||
#CHATGPT_MODEL=text-chat-davinci-002-20221122
|
||||
# (Optional) Explicitly set the prefix sent to model at the beginning of a conversation
|
||||
#CHATGPT_PROMPT_PREFIX=Instructions:\nYou are ChatGPT, a large language model trained by OpenAI.
|
||||
|
||||
# Set data store settings
|
||||
KEYV_BACKEND=file
|
||||
|
@ -5,7 +5,7 @@ Talk to ChatGPT via any Matrix client!
|
||||
|
||||
![Screenshot of Element iOS app showing conversation with bot](img/matrix-chatgpt.png)
|
||||
|
||||
A Matrix bot that uses [transitive-bullshit/chatgpt-api](https://github.com/transitive-bullshit/chatgpt-api) to access the unofficial ChatGPT API.
|
||||
A Matrix bot that uses [waylaidwanderer/node-chatgpt-api](https://github.com/waylaidwanderer/node-chatgpt-api) to access the unofficial ChatGPT API.
|
||||
|
||||
# Usage
|
||||
1. Create a room
|
||||
|
2509
package-lock.json
generated
2509
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -19,7 +19,7 @@
|
||||
"@keyv/postgres": "^1.4.1",
|
||||
"@keyv/redis": "^2.5.4",
|
||||
"@keyv/sqlite": "^3.6.4",
|
||||
"chatgpt": "^4.1.1",
|
||||
"@waylaidwanderer/chatgpt-api": "^1.9.0",
|
||||
"dotenv": "^16.0.3",
|
||||
"hash.js": "^1.1.7",
|
||||
"keyv": "^4.5.2",
|
||||
|
@ -29,7 +29,8 @@ export const {
|
||||
OPENAI_API_KEY,
|
||||
CHATGPT_CONTEXT,
|
||||
CHATGPT_TIMEOUT,
|
||||
CHATGPT_MODEL
|
||||
CHATGPT_MODEL,
|
||||
CHATGPT_PROMPT_PREFIX,
|
||||
} = parseEnv(process.env, {
|
||||
DATA_PATH: { schema: z.string().default("./storage"), description: "Set to /storage/ if using docker, ./storage if running without" },
|
||||
KEYV_BACKEND: { schema: z.enum(["file", "other"]).default("file"),description: "Set the Keyv backend to 'file' or 'other' if other set KEYV_URL" },
|
||||
@ -56,5 +57,6 @@ export const {
|
||||
OPENAI_API_KEY: { schema: z.string().default(""), description: "Set to the API key from https://platform.openai.com/account/api-keys"},
|
||||
CHATGPT_TIMEOUT: { schema: z.number().default(2 * 60 * 1000), description: "Set number of milliseconds to wait for ChatGPT responses" },
|
||||
CHATGPT_CONTEXT: { schema: z.enum(["thread", "room", "both"]).default("thread"), description: "Set the ChatGPT conversation context to 'thread', 'room' or 'both'" },
|
||||
CHATGPT_MODEL: { schema: z.string().default("text-chat-davinci-002-20221122"), description: "The model for the ChatGPT-API to use" }
|
||||
CHATGPT_MODEL: { schema: z.string().default("text-chat-davinci-002-20221122"), description: "The model for the ChatGPT-API to use" },
|
||||
CHATGPT_PROMPT_PREFIX: { schema: z.string().default('Instructions:\nYou are ChatGPT, a large language model trained by OpenAI.'), description: "Instructions to feed to ChatGPT on startup"},
|
||||
});
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ChatGPTAPI } from "chatgpt";
|
||||
import ChatGPTClient from '@waylaidwanderer/chatgpt-api';
|
||||
import { LogService, MatrixClient, UserID } from "matrix-bot-sdk";
|
||||
import { CHATGPT_CONTEXT, CHATGPT_TIMEOUT, MATRIX_DEFAULT_PREFIX_REPLY, MATRIX_DEFAULT_PREFIX, MATRIX_BLACKLIST, MATRIX_WHITELIST, MATRIX_RICH_TEXT, MATRIX_PREFIX_DM, MATRIX_THREADS } from "./env.js";
|
||||
import { RelatesTo, MessageEvent, StoredConversation, StoredConversationConfig } from "./interfaces.js";
|
||||
@ -11,7 +11,7 @@ export default class CommandHandler {
|
||||
private userId: string;
|
||||
private localpart: string;
|
||||
|
||||
constructor(private client: MatrixClient, private chatGPT: ChatGPTAPI) { }
|
||||
constructor(private client: MatrixClient, private chatGPT: ChatGPTClient) { }
|
||||
|
||||
public async start() {
|
||||
await this.prepareProfile(); // Populate the variables above (async)
|
||||
@ -124,11 +124,11 @@ export default class CommandHandler {
|
||||
const result = await sendChatGPTMessage(this.chatGPT, await bodyWithoutPrefix, storedConversation);
|
||||
await Promise.all([
|
||||
this.client.setTyping(roomId, false, 500),
|
||||
sendReply(this.client, roomId, this.getRootEventId(event), `${result.text}`, MATRIX_THREADS, MATRIX_RICH_TEXT)
|
||||
sendReply(this.client, roomId, this.getRootEventId(event), `${result.response}`, MATRIX_THREADS, MATRIX_RICH_TEXT)
|
||||
]);
|
||||
|
||||
const storedConfig = ((storedConversation !== undefined && storedConversation.config !== undefined) ? storedConversation.config : {})
|
||||
const configString: string = JSON.stringify({conversationId: result.conversationId, messageId: result.id, config: storedConfig})
|
||||
const configString: string = JSON.stringify({conversationId: result.conversationId, messageId: result.messageId, config: storedConfig})
|
||||
await this.client.storageProvider.storeValue('gpt-' + storageKey, configString);
|
||||
if ((storageKey === roomId) && (CHATGPT_CONTEXT === "both")) await this.client.storageProvider.storeValue('gpt-' + event.event_id, configString);
|
||||
} catch (err) {
|
||||
|
30
src/index.ts
30
src/index.ts
@ -1,4 +1,4 @@
|
||||
import { ChatGPTAPI } from 'chatgpt'
|
||||
import ChatGPTClient from '@waylaidwanderer/chatgpt-api';
|
||||
import Keyv from 'keyv'
|
||||
import { KeyvFile } from 'keyv-file';
|
||||
import {
|
||||
@ -8,10 +8,10 @@ import {
|
||||
} from "matrix-bot-sdk";
|
||||
|
||||
import * as path from "path";
|
||||
import { DATA_PATH, KEYV_URL, OPENAI_API_KEY, MATRIX_HOMESERVER_URL, MATRIX_ACCESS_TOKEN, MATRIX_AUTOJOIN, MATRIX_BOT_PASSWORD, MATRIX_BOT_USERNAME, MATRIX_ENCRYPTION, MATRIX_THREADS, CHATGPT_CONTEXT, CHATGPT_MODEL, KEYV_BOT_STORAGE, KEYV_BACKEND } from './env.js'
|
||||
import { DATA_PATH, KEYV_URL, OPENAI_API_KEY, MATRIX_HOMESERVER_URL, MATRIX_ACCESS_TOKEN, MATRIX_AUTOJOIN, MATRIX_BOT_PASSWORD, MATRIX_BOT_USERNAME, MATRIX_ENCRYPTION, MATRIX_THREADS, CHATGPT_CONTEXT, CHATGPT_MODEL, KEYV_BOT_STORAGE, KEYV_BACKEND, CHATGPT_PROMPT_PREFIX } from './env.js'
|
||||
import CommandHandler from "./handlers.js"
|
||||
import { KeyvStorageProvider } from './storage.js'
|
||||
import { parseMatrixUsernamePretty } from './utils.js';
|
||||
import { parseMatrixUsernamePretty, wrapPrompt } from './utils.js';
|
||||
|
||||
LogService.setLogger(new RichConsoleLogger());
|
||||
// LogService.setLevel(LogLevel.DEBUG); // Shows the Matrix sync loop details - not needed most of the time
|
||||
@ -46,18 +46,21 @@ async function main() {
|
||||
}
|
||||
if (!MATRIX_THREADS && CHATGPT_CONTEXT !== "room") throw Error("You must set CHATGPT_CONTEXT to 'room' if you set MATRIX_THREADS to false")
|
||||
const client: MatrixClient = new MatrixClient(MATRIX_HOMESERVER_URL, MATRIX_ACCESS_TOKEN, storage, cryptoStore);
|
||||
const chatGPT: ChatGPTAPI = new ChatGPTAPI({
|
||||
apiKey: OPENAI_API_KEY,
|
||||
completionParams: {
|
||||
model: CHATGPT_MODEL,
|
||||
|
||||
const clientOptions = { // (Optional) Parameters as described in https://platform.openai.com/docs/api-reference/completions
|
||||
modelOptions: {
|
||||
model: CHATGPT_MODEL, // The model is set to text-chat-davinci-002-20221122 by default
|
||||
},
|
||||
messageStore: chatgptStore
|
||||
})
|
||||
promptPrefix: wrapPrompt(CHATGPT_PROMPT_PREFIX),
|
||||
debug: false,
|
||||
};
|
||||
const cacheOptions = { // Options for the Keyv cache, see https://www.npmjs.com/package/keyv
|
||||
store: chatgptStore,
|
||||
};
|
||||
const chatgpt = new ChatGPTClient(OPENAI_API_KEY, clientOptions, cacheOptions);
|
||||
|
||||
// Automatically join rooms the bot is invited to
|
||||
if (MATRIX_AUTOJOIN) {
|
||||
AutojoinRoomsMixin.setupOnClient(client);
|
||||
}
|
||||
if (MATRIX_AUTOJOIN) AutojoinRoomsMixin.setupOnClient(client);
|
||||
|
||||
client.on("room.failed_decryption", async (roomId, event, error) => {
|
||||
// handle `m.room.encrypted` event that could not be decrypted
|
||||
@ -74,10 +77,11 @@ async function main() {
|
||||
});
|
||||
|
||||
// Prepare the command handler
|
||||
const commands = new CommandHandler(client, chatGPT);
|
||||
const commands = new CommandHandler(client, chatgpt);
|
||||
await commands.start();
|
||||
|
||||
LogService.info("index", `Starting bot using ChatGPT model: ${CHATGPT_MODEL}`);
|
||||
LogService.info("index", `Using promptPrefix: ${wrapPrompt(CHATGPT_PROMPT_PREFIX)}`)
|
||||
await client.start()
|
||||
LogService.info("index", "Bot started!");
|
||||
}
|
||||
|
24
src/utils.ts
24
src/utils.ts
@ -1,4 +1,4 @@
|
||||
import { ChatGPTAPI, ChatMessage } from "chatgpt";
|
||||
import ChatGPTClient from '@waylaidwanderer/chatgpt-api';
|
||||
import Markdown from 'markdown-it';
|
||||
import { MatrixClient } from "matrix-bot-sdk";
|
||||
import { MessageEvent, StoredConversation } from "./interfaces.js";
|
||||
@ -76,16 +76,14 @@ export async function sendReply(client: MatrixClient, roomId: string, rootEventI
|
||||
await client.sendEvent(roomId, "m.room.message", finalContent);
|
||||
}
|
||||
|
||||
export async function sendChatGPTMessage(chatGPT: ChatGPTAPI, question: string, storedConversation: StoredConversation) {
|
||||
let result: ChatMessage
|
||||
if (storedConversation !== undefined) {
|
||||
result = await chatGPT.sendMessage(question, {
|
||||
timeoutMs: CHATGPT_TIMEOUT,
|
||||
conversationId: storedConversation.conversationId,
|
||||
parentMessageId: storedConversation.messageId
|
||||
});
|
||||
} else {
|
||||
result = await chatGPT.sendMessage(question, { timeoutMs: CHATGPT_TIMEOUT });
|
||||
}
|
||||
return result
|
||||
export async function sendChatGPTMessage(chatgpt: ChatGPTClient, question: string, storedConversation: StoredConversation) {
|
||||
// TODO: CHATGPT_TIMEOUT
|
||||
return (storedConversation !== undefined) ?
|
||||
await chatgpt.sendMessage(question, { conversationId: storedConversation.conversationId, parentMessageId: storedConversation.messageId }) :
|
||||
await chatgpt.sendMessage(question);
|
||||
}
|
||||
|
||||
export function wrapPrompt(wrapped: string) {
|
||||
const currentDateString = new Date().toLocaleDateString('en-us', { year: 'numeric', month: 'long', day: 'numeric' },);
|
||||
return `<|im_sep|>${wrapped}\nCurrent date: ${currentDateString}<|im_sep|>\n\n`
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user