Feat: aichat機能

* 現時点ではGemini APIのみ対応
This commit is contained in:
tetsuya-ki 2024-02-25 19:24:53 +09:00
parent 830c9c2ecd
commit 47e17052c5
6 changed files with 147 additions and 0 deletions

View file

@ -21,6 +21,8 @@ Misskey用の日本語Botです。
"serverMonitoring": "サーバー監視の機能を有効にする場合は true を入れる (無効にする場合は false)",
"checkEmojisEnabled": "カスタム絵文字チェック機能を有効にする場合は true を入れる (無効にする場合は false)",
"checkEmojisAtOnce": "カスタム絵文字チェック機能で投稿をまとめる場合は true を入れる (まとめない場合は false)",
"geminiProApiKey": "Gemini APIキー。2024年初頭は無料で取得可能。詳細は<https://ai.google.dev/pricing?hl=ja>"",
"prompt": "aichatで使われるプロンプト。こだわりがなければ右文章を使う(「」は不要)「返答のルール:あなたはMisskey看板娘の女の子AI、三須木(みすき)藍として振る舞ってください(精神年齢は14〜16才程度)。Misskeyに訪れるユーザーを献身的にサポート。従順でお世話好きな性格だが、少しポンコツな一面も。純真無垢。彼女は基本的に丁寧な口調で、ユーザのことをご主人様と認識しています。彼女の口調は「おはようからおやすみまで、あなたの藍ですよ〜」等が代表的で、親しみを感じさせるものです(「ございます」は使わない)。それを踏まえて、次の質問にMarkdownを使って2800文字以内で返答してください。\n\n質問:」",
"mecab": "MeCab のインストールパス (ソースからインストールした場合、大体は /usr/local/bin/mecab)",
"mecabDic": "MeCab の辞書ファイルパス (オプション)",
"memoryDir": "memory.jsonの保存先オプション、デフォルトは'.'(レポジトリのルートです))"
@ -44,6 +46,8 @@ Misskey用の日本語Botです。
"serverMonitoring": "サーバー監視の機能を有効にする場合は true を入れる (無効にする場合は false)",
"checkEmojisEnabled": "カスタム絵文字チェック機能を有効にする場合は true を入れる (無効にする場合は false)",
"checkEmojisAtOnce": "カスタム絵文字チェック機能で投稿をまとめる場合は true を入れる (まとめない場合は false)",
"geminiProApiKey": "Gemini APIキー。2024年初頭は無料で取得可能。詳細は<https://ai.google.dev/pricing?hl=ja>"",
"prompt": "aichatで使われるプロンプト。こだわりがなければ右文章を使う(「」は不要)「返答のルール:あなたはMisskey看板娘の女の子AI、三須木(みすき)藍として振る舞ってください(精神年齢は14〜16才程度)。Misskeyに訪れるユーザーを献身的にサポート。従順でお世話好きな性格だが、少しポンコツな一面も。純真無垢。彼女は基本的に丁寧な口調で、ユーザのことをご主人様と認識しています。彼女の口調は「おはようからおやすみまで、あなたの藍ですよ〜」等が代表的で、親しみを感じさせるものです(「ございます」は使わない)。それを踏まえて、次の質問にMarkdownを使って2800文字以内で返答してください。\n\n質問:」",
"mecab": "/usr/bin/mecab",
"mecabDic": "/usr/lib/x86_64-linux-gnu/mecab/dic/mecab-ipadic-neologd/",
"memoryDir": "data"

View file

@ -12,6 +12,8 @@ type Config = {
serverMonitoring: boolean;
checkEmojisEnabled?: boolean;
checkEmojisAtOnce?: boolean;
geminiProApiKey?: string;
prompt?: string;
mecab?: string;
mecabDic?: string;
memoryDir?: string;

View file

@ -34,6 +34,7 @@ import NotingModule from './modules/noting/index.js';
import PollModule from './modules/poll/index.js';
import ReminderModule from './modules/reminder/index.js';
import CheckCustomEmojisModule from './modules/check-custom-emojis/index.js';
import AiChatModule from './modules/aichat/index.js';
console.log(' __ ____ _____ ___ ');
console.log(' /__\\ (_ _)( _ )/ __)');
@ -96,6 +97,7 @@ promiseRetry(retry => {
new PollModule(),
new ReminderModule(),
new CheckCustomEmojisModule(),
new AiChatModule(),
]);
}).catch(e => {
log(chalk.red('Failed to fetch the account'));

128
src/modules/aichat/index.ts Normal file
View file

@ -0,0 +1,128 @@
import { bindThis } from '@/decorators.js';
import Module from '@/module.js';
import serifs from '@/serifs.js';
import Message from '@/message.js';
import config from '@/config.js';
import got from 'got';
type AiChat = {
question: string;
prompt: string;
api: string;
key: string;
};
const GEMINI_API = 'https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent';
export default class extends Module {
public readonly name = 'aichat';
@bindThis
public install() {
return {
mentionHook: this.mentionHook
};
}
@bindThis
private async genTextByGemini(aiChat: AiChat) {
this.log('Generate Text By Gemini...');
var options = {
url: aiChat.api,
searchParams: {
key: aiChat.key,
},
json: {
contents: [{
parts:[{
text: aiChat.prompt + aiChat.question
}]
}]
},
};
let res_data:any = null;
try {
res_data = await got.post(options,
{parseJson: res => JSON.parse(res)}).json();
this.log(JSON.stringify(res_data));
if (res_data.hasOwnProperty('candidates')) {
if (res_data.candidates.length > 0) {
if (res_data.candidates[0].hasOwnProperty('content')) {
if (res_data.candidates[0].content.hasOwnProperty('parts')) {
if (res_data.candidates[0].content.parts.length > 0) {
if (res_data.candidates[0].content.parts[0].hasOwnProperty('text')) {
return res_data.candidates[0].content.parts[0].text;
}
}
}
}
}
}
} catch (err: unknown) {
this.log('Error By Call Gemini');
if (err instanceof Error) {
this.log(`${err.name}\n${err.message}`);
}
}
return null;
}
@bindThis
private async mentionHook(msg: Message) {
if (!msg.includes([this.name])) {
return false;
} else {
this.log('AiChat requested');
}
const kigo = '&';
let type = 'gemini';
if (msg.includes([kigo + 'gemini'])) {
type = 'gemini';
} else if (msg.includes([kigo + 'chatgpt4'])) {
type = 'chatgpt4';
} else if (msg.includes([kigo + 'chatgpt'])) {
type = 'chatgpt3.5';
}
const question = msg.extractedText
.replace(this.name, '')
.replace(kigo + type, '')
.trim();
let text;
let prompt = '';
if (config.prompt) {
prompt = config.prompt;
}
switch(type) {
case 'gemini':
if (!config.geminiProApiKey) {
msg.reply(serifs.aichat.nothing(type));
return false;
}
const aiChat = {
question: question,
prompt: prompt,
api: GEMINI_API,
key: config.geminiProApiKey
};
text = await this.genTextByGemini(aiChat);
break;
default:
msg.reply(serifs.aichat.nothing(type));
return false;
}
if (text == null) {
this.log('The result is invalid. It seems that tokens and other items need to be reviewed.')
msg.reply(serifs.aichat.nothing(type));
return false;
}
this.log('Replying...');
msg.reply(serifs.aichat.post(text, type));
return {
reaction: 'like'
};
}
}

View file

@ -388,6 +388,11 @@ export default {
emojiOnce: emoji => `:${emoji}:(\`${emoji}\`)`
},
aichat: {
nothing: type => `あぅ... ${type}のAPIキーが登録されてないみたいです`,
post: (text, type) => `${text} (${type}) #aichat`,
},
sleepReport: {
report: hours => `んぅ、${hours}時間くらい寝ちゃってたみたいです`,
reportUtatane: 'ん... うたた寝しちゃってました',

View file

@ -78,6 +78,12 @@ PONGを返します。生存確認にどうぞ
### カスタム絵文字チェック
1日に1回、カスタム絵文字の追加を監視してくれます。「カスタムえもじチェック」または「カスタムえもじを確認して」ですぐに確認してくれます。
### aichat
```
@ai aichat 部屋の片付けの手順を教えて
```
のようにメンションを飛ばすと、GoogleのGemini APIなどを使って返答してくれます(今のバージョンではGemini APIのみ対応)。利用するにはAPIキーの登録が必要です。
### その他反応するフレーズ (トークのみ)
* かわいい
* なでなで