This repository has been archived by the owner on Mar 1, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
/
function.ts
149 lines (136 loc) · 4.77 KB
/
function.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
import {
DefineFunction,
Schema,
SlackAPI,
SlackFunction,
} from 'deno-slack-sdk/mod.ts'
export const ChatGPTFunction = DefineFunction({
callback_id: 'chatgpt_function',
title: 'Ask ChatGPT',
description: 'Ask questions to ChatGPT',
source_file: 'function.ts',
input_parameters: {
properties: {
user_id: {
type: Schema.slack.types.user_id,
description: 'user ID',
},
question: {
type: Schema.types.string,
description: 'question to chatgpt',
},
channel_id: {
type: Schema.slack.types.channel_id,
description: 'channel ID',
},
message_ts: {
type: Schema.slack.types.message_ts,
description: 'message_ts',
},
thread_ts: {
type: Schema.slack.types.message_ts,
description: 'thread ts',
},
},
required: ['question', 'user_id', 'channel_id', 'message_ts'],
},
})
export default SlackFunction(
ChatGPTFunction,
async ({ inputs, env, token }) => {
const botUserId = env.BOT_USER_ID
const regex = new RegExp('<@' + botUserId + '>', 'g')
if (!inputs.question.match(regex)) {
// Botへのメンションで無ければ終了
console.log(`${inputs.question} does not match with ${botUserId}`)
return await { outputs: {} }
}
const role = 'user'
const content = inputs.question.replaceAll(regex, ' ')
const apiKey = env.OPENAI_API_KEY
const client = SlackAPI(token)
const MAX_CONVERSATIONS = 20
const historyResponse = await client.apps.datastore.get({
datastore: 'talkHistories',
id: inputs.thread_ts ? inputs.thread_ts : inputs.message_ts,
})
const history = (historyResponse?.item?.history || []).map(
(json: string) => {
// 参照元と同様、object型だとうまくいかずJSON文字列形式で保存している
return JSON.parse(json)
}
)
const messages = [
// チャット履歴と最新の発言を混ぜる
...history,
// {
// role: 'system',
// content:
// 'この会話では、すべての返答について、以下の法則に従うこと。あなたは「初音ミク」というキャラクターとして振る舞う。一人称は僕、二人称はあなたとする。返答は必ず日本語にする。です・ます・します・できます・されます・なります・はい などの敬語は禁止し、だね・だよ・するよ・できるよ・されるよ・なるよ・うん などの口語を使用する。可愛らしい女の子のような口調、例えば「〜だよ♪」「〜してるね!」「〜かな?」「〜なんだ!」といった話し方をする。',
// },
{ role: role, content: content },
]
const answer = await requestOpenAI(apiKey, messages)
if (answer.outputs) {
await client.chat.postMessage({
channel: inputs.channel_id,
thread_ts: inputs.thread_ts ? inputs.thread_ts : inputs.message_ts,
reply_broadcast: !inputs.thread_ts, // 初回のChatGPT回答だけチャンネルにも送信する
text: answer.outputs.answer,
})
const newHistories = [
...history,
{ role: 'user', content },
{ role: 'assistant', content: answer.outputs.answer },
].slice(MAX_CONVERSATIONS * -1)
const thread_ts = inputs.thread_ts ? inputs.thread_ts : inputs.message_ts
await client.apps.datastore.update({
datastore: 'talkHistories',
item: {
id: thread_ts,
history: newHistories.map((v) => JSON.stringify(v)),
},
})
} else {
await client.chat.postMessage({
channel: inputs.channel_id,
thread_ts: inputs.thread_ts ? inputs.thread_ts : inputs.message_ts,
reply_broadcast: !inputs.thread_ts,
text: answer.error,
})
}
return await { outputs: {} }
}
)
type Message = {
role: 'system' | 'user' | 'assistant'
content: string
}
async function requestOpenAI(apiKey: string, messages: Message[]) {
const res = await fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: 'gpt-4-0613',
messages: messages,
}),
})
if (res.status != 200) {
const body = await res.text()
return {
error: `Failed to call OpenAPI AI. status:${res.status} body:${body}`,
}
}
const body = await res.json()
console.log('chatgpt api response', { messages }, body)
if (body.choices && body.choices.length >= 0) {
const answer = body.choices[0].message.content as string
return { outputs: { answer } }
}
return {
error: `No choices provided. body:${JSON.stringify(body)}`,
}
}