今回は、Discord に RAG の機能を持った bot を導入します。
1. botの導入
Discord Developer Portal にアクセスし、botを作成します。
まず、"Applications" タブから "New Application" ボタンを押し、アプリケーションの名前を入力します。
次に、"Bot" タブから "Reset Token" ボタンを押し、トークンを生成します。
このトークンは後で利用しますので、控えておいてください。
さらに、同じく "Bot" タブから "Message Content Intent" をオンにしておきます。
最後に、"OAuth2" タブから "OAuth2 URL Generator" で "bot" と "applications.commands" にチェックを入れ、"Bot Permissions" で "Send Message" と "Read Message History" にチェックを入れます。
こうすることで最下部に出現する "GENERATED URL" をコピーし、別タブでアクセスします。
これで bot の導入は完了です。
2. 環境構築
まず、必要なパッケージをインストールします。
python-dotenv
# ChromaDB
chromadb==0.6.2
# Discord
discord.py==2.4.0
# LangChain
langchain==0.3.14
langchain-community==0.3.14
langchain-openai==0.3.0
tiktoken==0.8.0
unstructured==0.15.5
# JupyterLab
jupyterlab==4.3.4
# OpenAI
openai==1.59.7
また、env ファイルに OpenAI の API キーと、先ほど控えた bot の Token を書いておきます。
3. RAG の実装
肝心の RAG ですが、ここは自由に実装していただいて構いません。
今回は、以下を満たす関数 get_answer()
として実装してあるとします。
- 入力: 質問内容
query
- 出力: 回答
answer
- RAG の元データとして、 実行ファイルと同じディレクトリに存在する
messages.csv
を用いる
このもとで、 get_answer()
を呼び出す bot 部分を以下のように実装します。
import csv
import discord
import openai
import os
from datetime import datetime, timedelta, timezone
from discord.ext import commands
from io import StringIO
from rag import get_answer
intents = discord.Intents.default()
intents.message_content = True
bot = commands.Bot(
command_prefix="!",
case_insensitive=True,
intents=intents
)
token = os.environ['DISCORD_TOKEN']
openai.api_key = os.environ['OPENAI_API_KEY']
@bot.command(
name="message",
aliases=["msg", "m"],
)
async def get_message(ctx: commands.Context, channel: discord.TextChannel) -> None:
stream = StringIO()
data = []
async for message in channel.history(
after=datetime.now(timezone.utc) - timedelta(hours=1),
oldest_first=True,
):
if message.author.bot == True:
continue
else:
jst = message.created_at + timedelta(hours=9)
msg = [message.author.name, jst.strftime('%Y/%m/%d %H:%M:%S'), message.content]
data.append(msg)
writer = csv.writer(stream)
writer.writerow(["User", "Timestamp", "Message"])
writer.writerows(data)
stream.seek(0)
await ctx.send(file=discord.File(stream, filename="messages.csv"))
stream.close()
@bot.command(
name="query",
aliases=["q"],
)
async def set_query(ctx: commands.Context, channel: discord.TextChannel, query: str) -> None:
results = await get_answer(query)
for result in results:
await channel.send(result)
bot.run(token)
4. 動作サンプル
今回、messages.csv
の中身は以下のようにしました。
一般的な知識では答えられないものや、RAG の性能を試せるようなものを用意したつもりです。
最後の文章は、みなさんご存じ太宰治の『走れメロス』から引用しています。
User,Timestamp,Message
****,2025/01/14 23:44:50,私の好物はオレンジジュースです。
****,2025/01/14 23:45:06,明日の天気は霰です。
****,2025/01/14 23:46:26,私の現在位置はハワイです。
****,2025/01/14 23:49:55,明日は早起きする必要があります。
****,2025/01/14 23:50:27,私は今日4食食べました。
****,2025/01/14 23:56:56,"メロスは、単純な男であった。買い物を、背負ったままで、のそのそ王城にはいって行った。たちまち彼は、巡邏じゅんらの警吏に捕縛された。調べられて、メロスの懐中からは短剣が出て来たので、騒ぎが大きくなってしまった。メロスは、王の前に引き出された。
「この短刀で何をするつもりであったか。言え!」暴君ディオニスは静かに、けれども威厳を以もって問いつめた。その王の顔は蒼白そうはくで、眉間みけんの皺しわは、刻み込まれたように深かった。
「市を暴君の手から救うのだ。」とメロスは悪びれずに答えた。
「おまえがか?」王は、憫笑びんしょうした。「仕方の無いやつじゃ。おまえには、わしの孤独がわからぬ。」
「言うな!」とメロスは、いきり立って反駁はんばくした。「人の心を疑うのは、最も恥ずべき悪徳だ。王は、民の忠誠をさえ疑って居られる。」
「疑うのが、正当の心構えなのだと、わしに教えてくれたのは、おまえたちだ。人の心は、あてにならない。人間は、もともと私慾のかたまりさ。信じては、ならぬ。」暴君は落着いて呟つぶやき、ほっと溜息ためいきをついた。「わしだって、平和を望んでいるのだが。」
「なんの為の平和だ。自分の地位を守る為か。」こんどはメロスが嘲笑した。「罪の無い人を殺して、何が平和だ。」
「だまれ、下賤げせんの者。」王は、さっと顔を挙げて報いた。「口では、どんな清らかな事でも言える。わしには、人の腹綿の奥底が見え透いてならぬ。おまえだって、いまに、磔はりつけになってから、泣いて詫わびたって聞かぬぞ。」
「ああ、王は悧巧りこうだ。自惚うぬぼれているがよい。私は、ちゃんと死ぬる覚悟で居るのに。命乞いなど決してしない。ただ、――」と言いかけて、メロスは足もとに視線を落し瞬時ためらい、「ただ、私に情をかけたいつもりなら、処刑までに三日間の日限を与えて下さい。たった一人の妹に、亭主を持たせてやりたいのです。三日のうちに、私は村で結婚式を挙げさせ、必ず、ここへ帰って来ます。」
「ばかな。」と暴君は、嗄しわがれた声で低く笑った。「とんでもない嘘うそを言うわい。逃がした小鳥が帰って来るというのか。」
「そうです。帰って来るのです。」メロスは必死で言い張った。「私は約束を守ります。私を、三日間だけ許して下さい。妹が、私の帰りを待っているのだ。そんなに私を信じられないならば、よろしい、この市にセリヌンティウスという石工がいます。私の無二の友人だ。あれを、人質としてここに置いて行こう。私が逃げてしまって、三日目の日暮まで、ここに帰って来なかったら、あの友人を絞め殺して下さい。たのむ、そうして下さい。」
それを聞いて王は、残虐な気持で、そっと北叟笑ほくそえんだ。生意気なことを言うわい。どうせ帰って来ないにきまっている。この嘘つきに騙だまされた振りして、放してやるのも面白い。そうして身代りの男を、三日目に殺してやるのも気味がいい。人は、これだから信じられぬと、わしは悲しい顔して、その身代りの男を磔刑に処してやるのだ。世の中の、正直者とかいう奴輩やつばらにうんと見せつけてやりたいものさ。
「願いを、聞いた。その身代りを呼ぶがよい。三日目には日没までに帰って来い。おくれたら、その身代りを、きっと殺すぞ。ちょっとおくれて来るがいい。おまえの罪は、永遠にゆるしてやろうぞ。」
「なに、何をおっしゃる。」
「はは。いのちが大事だったら、おくれて来い。おまえの心は、わかっているぞ。」"
このデータを元に動作させた結果、次のようになりました。
いずれも、与えられたデータに基づいて回答できています。
もちろん RAG 部分の実装によって性能が異なってきますが、ここまで実装できれば後は何とでもなるかと思います。
例えば、pdf などのドキュメントを元にしてみたり、複数のチャンネルのログを元にしてみたり、元データの部分だけでも様々に変更することができそうです。
5. まとめ
今回は、Discord に RAG の機能を持った bot を導入しました。
実際には、チャットボットとしての機能など RAG 以外にも様々な機能を持たせることができます。
皆さんも自分だけの bot を作ってみてはいかがでしょうか。