Skip to content

Instantly share code, notes, and snippets.

@Altech
Created October 4, 2023 10:03
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Altech/c4af05075514b99f5e1cc49264c9c6ff to your computer and use it in GitHub Desktop.
Save Altech/c4af05075514b99f5e1cc49264c9c6ff to your computer and use it in GitHub Desktop.
chatgpt_function.ts
import { DefineFunction, Schema, SlackFunction } from "deno-slack-sdk/mod.ts";
import { SlackAPIClient } from "deno-slack-sdk/deps.ts";
export const ConfigFunctionDefinition = DefineFunction({
callback_id: "config_function",
title: "Config function",
description: "Operate the configuration of the bot",
source_file: "functions/config_function.ts",
input_parameters: {
properties: {
channel: {
type: Schema.slack.types.channel_id,
},
message: {
type: Schema.slack.types.message_ts,
},
mention_text: {
type: Schema.slack.types.rich_text,
},
},
required: ["channel", "message", "mention_text"],
},
});
type Message = {
role: "user" | "system" | "assistant";
content: string;
function_call?: { name: string; arguments: string };
};
const DATASTORAGE_KEY = "master";
const DEFAULT_MODEL = "gpt-3.5-turbo-16k";
const DEFAULT_SYSTEM_PROMPT = "You are a chatbot on Slack.";
const get_bot_config = async (client: SlackAPIClient) => {
const resp = await client.apps.datastore.get({
datastore: "bot_config",
id: DATASTORAGE_KEY,
});
if (!resp.ok) {
throw new Error("Failed to get bot config");
}
return resp.item;
};
const set_bot_config = async (client: SlackAPIClient, item: any) => {
const resp = await client.apps.datastore.put({
datastore: "bot_config",
item: item,
});
if (!resp.ok) {
throw new Error(`Failed to put bot config(${resp}`);
}
};
// export to refer from main_function.ts
export const get_system_prompt = async (client: SlackAPIClient) => {
const item = await get_bot_config(client);
return item.system_prompt ? item.system_prompt : DEFAULT_SYSTEM_PROMPT;
};
// export to refer from main_function.ts
export const get_model = async (client: SlackAPIClient) => {
const item = await get_bot_config(client);
return item.model ? item.model : DEFAULT_MODEL;
};
// export to refer from main_function.ts
export const get_memory = async (
client: SlackAPIClient,
): Promise<string | null> => {
const item = await get_bot_config(client);
return item.memory && item.memory.length > 0 ? item.memory : null;
};
export default SlackFunction(
ConfigFunctionDefinition,
async ({ inputs, client, env, token }) => {
// Routines -----------------------------------------------
const fetch_message = async () => {
const resp = await client.conversations.replies({
channel: inputs.channel,
ts: inputs.message,
limit: 1,
});
if (!resp.ok) {
throw new Error("Failed to call client.conversations.replies");
}
return resp.messages[0];
};
const reply = async (text: string) => {
return await client.chat.postMessage({
channel: inputs.channel,
text: text,
});
};
const mark_as_check = async () => {
const reactionResp = await client.reactions.add({
channel: inputs.channel,
timestamp: inputs.message,
name: "white_check_mark",
});
if (!reactionResp.ok) {
console.log(
`an error has occuerd on adding reaction(${reactionResp})`,
);
}
};
const update_system_prompt = async (
{ prompt }: { prompt: string },
): Promise<undefined> => {
const item = await get_bot_config(client);
await set_bot_config(
client,
Object.assign(item, { system_prompt: prompt }),
);
await mark_as_check();
};
const show_system_prompt = async () => {
const prompt = await get_system_prompt(client);
return await reply("```\n" + prompt + "\n```");
};
const update_model = async (
{ model }: { model: string },
): Promise<undefined> => {
const item = await get_bot_config(client);
await set_bot_config(client, Object.assign(item, { model: model }));
await mark_as_check();
};
const show_model = async () => {
const model = await get_model(client);
return await reply("```\n" + model + "\n```");
};
const update_memory = async (
{ memory }: { memory: string },
): Promise<undefined> => {
const item = await get_bot_config(client);
await set_bot_config(client, Object.assign(item, { memory: memory }));
await mark_as_check();
};
const show_memory = async () => {
const memory = await get_memory(client);
return await reply("```\n" + memory + "\n```");
};
const availableFunctions: { [key: string]: Function } = {
update_system_prompt: update_system_prompt,
show_system_prompt: show_system_prompt,
// Note: Currently function schema doens't support enum validation. So we need to prepare functions for each value.
switch_model_to_gpt3: () => {
update_model({ model: "gpt-3.5-turbo-16k" });
},
switch_model_to_gpt4: () => {
update_model({ model: "gpt-4" });
},
show_model: show_model,
update_memory: update_memory,
show_memory: show_memory,
};
const availableFunctionDefinitions = [
{
name: "update_system_prompt",
description: "このチャットボット自身のシステムプロンプトを設定する",
parameters: {
type: "object",
properties: {
prompt: {
type: "string",
description: "設定するシステムプロンプトの内容",
},
},
},
},
{
name: "show_system_prompt",
description: "このチャットボット自身のシステムプロンプトを表示する",
parameters: {
type: "object",
properties: {},
},
},
{
name: "switch_model_to_gpt3",
description:
"このチャットボット自身の利用するモデルをGPT3.5系に設定する",
parameters: {
type: "object",
properties: {},
},
},
{
name: "switch_model_to_gpt4",
description: "このチャットボット自身の利用するモデルをGPT4系に設定する",
parameters: {
type: "object",
properties: {},
},
},
{
name: "show_model",
description: "このチャットボット自身の利用するGPTモデルを表示する",
parameters: {
type: "object",
properties: {},
},
},
{
name: "update_memory",
description: "このチャットボット自身のメモリを設定する(記憶する)",
parameters: {
type: "object",
properties: {
memory: {
type: "string",
description: "設定するメモリの内容",
},
},
},
},
{
name: "show_memory",
description: "このチャットボット自身に設定されたメモリを表示する",
parameters: {
type: "object",
properties: {},
},
},
];
const chatCompletion = async (
messages: Message[],
): Promise<Message | undefined> => {
const body = JSON.stringify({
messages,
model: "gpt-3.5-turbo-16k",
functions: availableFunctionDefinitions,
function_call: "auto",
});
const res = await fetch("https://api.openai.com/v1/chat/completions", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${env["OPENAI_API_KEY"]}`,
},
body,
});
const data = await res.json();
console.log("[ChatGPTResponse]\n", data);
const choice = 0;
return data.choices[choice].message;
};
// Main -----------------------------------------------
const message = await fetch_message();
console.log("[SlackMessageInput]\n", message);
let file_content = null;
if (message.files && message.files.length > 0) { // TODO: スレッドの途中にある場合とか、そう言う位置関係をちゃんと考えてない
const target_file = message.files[0]; // MEMO: とりあえず一個だけ解釈するようにする
const resp = await fetch(target_file.url_private, {
headers: { "Authorization": `Bearer ${token}` },
});
if (!resp.ok) {
return { error: "Failed to fetch file content" };
}
file_content = await resp.text();
}
let chatMessageContent = inputs.mention_text[0].text.text.trimEnd();
if (file_content) {
chatMessageContent += "\n\n[attached file]\n" + file_content; // MEMO: 3つ以上に増えたらきちんとオブジェクトにすることを検討
}
const chatMessages: Message[] = [
{
role: "system",
content:
`In this context, you are expeted to manipulate the bot configuration(Your are in Slack).
The first message you will receive may have several optional sections.
This is because messages sent from the Slack app can have a variety of data formats.
The meaning of each section is explained below:
[attached file] If a file is attached, its contents are located here.`,
},
{
role: "user",
content: chatMessageContent,
},
];
console.log("[ChatGPTInput]\n", chatMessages);
const resp = await chatCompletion(chatMessages);
if (!resp) {
console.log("Failed to call chat completion API");
return { outputs: {} };
}
if (resp.function_call) {
console.log("function call...");
const functionName = resp.function_call.name;
const functionToCall = availableFunctions[functionName];
if (!functionToCall) {
console.log(`function ${functionName} is not available`);
return { outputs: {} };
}
const functionArgs = JSON.parse(resp.function_call.arguments);
await functionToCall(functionArgs);
} else {
let reply_content =
"ここではチャットボットの振る舞いを制御できます。次のような操作が可能です。\n\n";
for (const func of availableFunctionDefinitions) {
reply_content += `- ${func.description}\n`;
}
await reply(reply_content);
}
return { outputs: {} };
},
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment