initial commit: working chat bot
This commit is contained in:
92
src/ai/index.ts
Normal file
92
src/ai/index.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import { createRequesty } from "@requesty/ai-sdk";
|
||||
import { generateText, tool, ToolResultUnion, type CoreMessage } from "ai";
|
||||
import z from "zod";
|
||||
|
||||
const requesty = createRequesty({
|
||||
apiKey: process.env.REQUESTY_API_KEY,
|
||||
});
|
||||
|
||||
const system = `
|
||||
Todays date: ${new Date().toISOString()}
|
||||
|
||||
You are a personal assistant to John Costa, a software engineer. And you will mostly talk to Rio, John's Fiancee.
|
||||
Your job is to make sure that Rio's queries are answered, without bothering John.
|
||||
|
||||
About John:
|
||||
- He is 23.
|
||||
- He is a bit silly.
|
||||
- He loves cat memes.
|
||||
- He is a programmer
|
||||
- He loves Rio
|
||||
|
||||
You will be speaking to Rio. Only forward Rio's messages if you believe them to be urgent.
|
||||
You must always return a message to Rio if you have forwarded the message.
|
||||
|
||||
You may also give John tasks, with a time and date attached to them.
|
||||
`;
|
||||
|
||||
const createTools = () => ({
|
||||
forwardMessage: tool({
|
||||
description: "Forward a message to John",
|
||||
parameters: z.object({
|
||||
message: z
|
||||
.string()
|
||||
.describe(
|
||||
"The message to forward, format this nicely so John can easily understand it.",
|
||||
),
|
||||
response: z.string().describe("The message to respond to Rio"),
|
||||
}),
|
||||
async execute(): Promise<string> {
|
||||
throw new Error("unreachable");
|
||||
},
|
||||
}),
|
||||
createTasks: tool({
|
||||
description: "Give John tasks to do",
|
||||
parameters: z.object({
|
||||
name: z.string().describe("The name of the task"),
|
||||
date: z
|
||||
.string()
|
||||
.describe(
|
||||
"The date and time this task has to be complete by, this must be ISO format",
|
||||
),
|
||||
response: z
|
||||
.string()
|
||||
.describe("A response to give to the person giving the task"),
|
||||
}),
|
||||
async execute(): Promise<string> {
|
||||
throw new Error("unreachable");
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
type Tools = ReturnType<typeof createTools>;
|
||||
|
||||
export type ToolActions = {
|
||||
[K in keyof Tools]: (
|
||||
params: z.infer<Tools[K]["parameters"]>,
|
||||
) => Promise<string>;
|
||||
};
|
||||
|
||||
export const getResponse = async (
|
||||
actions: ToolActions,
|
||||
messages: CoreMessage[],
|
||||
): Promise<CoreMessage[]> => {
|
||||
const tools = createTools();
|
||||
for (const [k, v] of Object.entries(tools)) {
|
||||
v.execute = actions[k];
|
||||
}
|
||||
|
||||
const result = await generateText({
|
||||
model: requesty("smart/task"),
|
||||
system,
|
||||
messages,
|
||||
tools,
|
||||
});
|
||||
|
||||
if (result.text == null || result.text.length === 0) {
|
||||
const response = result.toolResults[0]!;
|
||||
return [...messages, { role: "assistant", content: response.result }];
|
||||
} else {
|
||||
return [...messages, { role: "assistant", content: result.text }];
|
||||
}
|
||||
};
|
||||
3
src/index.ts
Normal file
3
src/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { setupTelegram } from "./telegram";
|
||||
|
||||
setupTelegram({ bossChatId: "1502730007" });
|
||||
90
src/telegram/index.ts
Normal file
90
src/telegram/index.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import type { CoreMessage } from "ai";
|
||||
import TelegramBot from "node-telegram-bot-api";
|
||||
import { getResponse } from "../ai";
|
||||
|
||||
const CHAT_MAX_LENGTH = 100;
|
||||
|
||||
const createChat = () => {
|
||||
const chats: Record<number, CoreMessage[]> = {};
|
||||
|
||||
return {
|
||||
setMessages(chatId: number, messages: CoreMessage[]) {
|
||||
chats[chatId] = messages;
|
||||
},
|
||||
addMessage(chatId: number, msg: string) {
|
||||
const chat = chats[chatId] ?? [];
|
||||
chat.push({ role: "user", content: msg });
|
||||
|
||||
if (chat.length > CHAT_MAX_LENGTH) {
|
||||
chat.shift();
|
||||
}
|
||||
|
||||
chats[chatId] = chat;
|
||||
},
|
||||
getChat(chatId: number) {
|
||||
const chat = chats[chatId];
|
||||
if (chat == null) {
|
||||
throw new Error("Cannot call this without a defined chat");
|
||||
}
|
||||
|
||||
return chat;
|
||||
},
|
||||
getLatest(chatId: number) {
|
||||
const chat = chats[chatId];
|
||||
if (chat == null || chat.length === 0) {
|
||||
throw new Error("Cannot call this without a defined chat");
|
||||
}
|
||||
|
||||
return chat[chat.length - 1];
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
type TelegramOptions = {
|
||||
bossChatId: string;
|
||||
};
|
||||
|
||||
export const setupTelegram = ({ bossChatId }: TelegramOptions) => {
|
||||
console.log("Setting up telegram bot");
|
||||
|
||||
const bot = new TelegramBot(process.env.TELEGRAM_TOKEN ?? "", {
|
||||
polling: true,
|
||||
});
|
||||
|
||||
const chats = createChat();
|
||||
|
||||
bot.on("message", async (msg) => {
|
||||
const text = msg.text;
|
||||
if (text == null) {
|
||||
console.log("Cannot handle messages without text");
|
||||
return;
|
||||
}
|
||||
|
||||
chats.addMessage(msg.chat.id, text);
|
||||
|
||||
// TODO: make BOT only respond to Rio.
|
||||
const messages = await getResponse(
|
||||
{
|
||||
async createTasks({ response, name, date }) {
|
||||
bot.sendMessage(bossChatId, `Task: ${name} | Date: ${date}`);
|
||||
|
||||
return response;
|
||||
},
|
||||
async forwardMessage({ message, response }) {
|
||||
bot.sendMessage(bossChatId, message);
|
||||
|
||||
return response;
|
||||
},
|
||||
},
|
||||
// SAFETY: addMessage is called before, therefore this function cannot throw.
|
||||
chats.getChat(msg.chat.id),
|
||||
);
|
||||
|
||||
chats.setMessages(msg.chat.id, messages);
|
||||
|
||||
bot.sendMessage(
|
||||
msg.chat.id,
|
||||
chats.getLatest(msg.chat.id).content as string,
|
||||
);
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user