小さなスタートアップが社内でちまちま育てているチャットボット
こんにちは。FUSSY でエンジニアをしているfunwarioisiiです。
最近 Young Sheldon を U-NEXT で見終えました。続編の公開が待ち遠しいです。
さて、今回は FUSSY が社内で使っているチャットボットについて紹介します。
自分がまだ大学生になったころ、 2013年頃、巷ではチャットボットが流行っていた気がします。
hubot とか、 ruboty とか bolt(これは新しいか)とかいろいろありましたね。
現在は導入が進み、コミュニケーションの円滑化や、業務の効率化に貢献しているのではないでしょうか。
2023年のスタートアップでどのように ChatBot を使っているか、紹介し、似た境遇の方に参考にしていただければと思います。
紹介するコマンド自体はわかりやすい機能ばかりです。
なのでその話をしても仕方ないなという気持ちになったので、これが必要になった背景なども紹介します。
FUSSY とは
現在 FUSSY は「推しの保存と布教」のためのサービスを開発しています。
私含め3人で運営しているスタートアップで、エンジニアは私1人です。
会社概要はこちらをご覧ください。
そんな状況で、コミュニケーションツールには Discord を使っています。
主たる理由としては、他サービスに比べて料金面で優れていることでした。
議事録や Issue などのストック情報は Notion にまとめています。
まずはこの、 Notion の活用と Discord との連携について紹介します。
!issue
コマンド
Notion に Issue を作成するコマンドです。
(画像)
FUSSY はまだまだ伸びしろばかりのスタートアップです。
眼の前の課題、大きな課題、課題しかないです。
どんな小さな課題でもどう解くかをチームで認識を揃えることで、他の仕事が進めやすくなったり、小さく見えていた課題の裏に潜む、大きな課題に気づくことができます。
そのため、 FUSSY では毎週月曜日に、チームで課題をどう解決したかやどういう課題を見つけたかを共有するミーティング、 Issue会をしています。
この Issue の起票について、 Notion に直接起票してもいいのですが、それだと月曜日まで課題に気づきにくいという問題があります。せっかく Discord を使ってるのにもったいないです!
Issue会で初めて Issue に気づくより、先にそういった Issue があることを認識できているほうがより良い時間になると考えています。リアルタイムに悩みが見える方がいいです。
関連した話題として、GitHub での Issue 起票時に Slack に通知される機能が好きです。
Notion はGitHubと違い「公開する」ステップがないので、 Slack で通知を受け取っていた際は、タイミングがドキュメントの更新ごとで書きかけの情報が流れてきてノイジーというのもありました。
を参考に作ったこともありましたが、Issue に関しては Discord から起票するほうが良いコミュニケーションになると考えて運用しています。
さて、話を戻して、 !issue
コマンドの実装です。
FUSSY では Issue をデータベースの中のページとして管理しています。
タグ付けによる運用が簡単になり、リスト表示や進捗でのカンバンのような使い方ができるからです。
この運用に乗せるために、 Notion の API を使って Issue を作成しています。
notion-ruby-client という gem を使っています。
require 'notion-ruby-client'
# create issue on notion
def create_issue(title:)
client = Notion::Client.new(token: ENV['NOTION_API_TOKEN'])
database_id = ENV['NOTION_DATABASE_ID']
properties = {
Name: {
title: [
{
text: {
content: title
}
}
]
},
Tags: {
multi_select: [
{
name: 'Issue'
}
]
},
Status: {
select: {
name: 'Not Started'
}
}
}
client.create_page(
parent: { database_id: },
properties:
)
end
title = 'なんかいい感じにならない'
result = create_issue(title:)
"check it! #{result.url}"
!summary
コマンド
みんな大好き LLM です。
FUSSY ではチーム内でのコンテキストの共有を大事にしています。
そのため、どんなニュースを読んだかなどを共有したりするのですが、記事が長いと読まれにくいです。
そこで、 !summary <URL>
というコマンドを作りました。
リンク先を読みに行って、 LLM に要約させています。
コードはこんな感じです。
require 'open-uri'
require 'nokogiri'
require 'net/http'
OPENAI_API_KEY = ENV['OPENAI_API_KEY']
def summary(args, _meta)
url = args.first
simple_text = fetch_simple_text(url)
response = Net::HTTP.post(
URI('https://api.openai.com/v1/chat/completions'),
{
"model": 'gpt-3.5-turbo-16k',
"messages": [
{ "role": 'system', "content": '次の文章を要約してください。返答は日本語でお願いします。返答は「要約しました。」からはじめてください。' },
{ "role": 'user', "content": simple_text }
]
}.to_json,
'Content-Type' => 'application/json',
'Authorization' => "Bearer #{OPENAI_API_KEY}"
)
response_body = JSON.parse(response.body)
response_body['choices'].first['message']['content']
end
def fetch_simple_text(url)
html = Net::HTTP.get(URI(url))
doc = Nokogiri::HTML.parse(html)
elements = doc.search('//p|//h1|//h2|//h3|//h4|//h5|//h6|//article|//section|//span')
text = elements.map { |element| element.text.strip }.join(' ')
text.gsub(/\s+/, ' ').strip
end
summary('https://google.com')
非常に情けないのですが、gpt-3.5-turbo-16k で扱えるトークン数を超える場合はエラーになります。
長すぎると要約できないので、改善したいなと思っています。
また、 GPT-4 系を選択すると、時間がかかりすぎて体験が悪かったため選択していません。
!revalidate
コマンド
ここはちょっとスタートアップっぽい話です。サービス全体においてまだコンテンツ数が少ないため、温かみのある手オペをしています。
FUSSY はサーバサイドをRailsで、フロントエンドをNext.jsで書いています。
そして Next.js は Vercel でデプロイしています。
SSR に時間がかかるなどの色々な問題で、SSG+ISR でコンテンツを管理しています。
ここで問題になったのが修正したデータの反映です。
あるとうれしいのは管理画面でデータを修正したら、そのデータがすぐに反映されることです。
少ない個別対応のために管理画面を作るのはコスパが悪そうです。
現状は直接データベースにアクセスして修正しています。
しかし、それでは SSG の成果物に変更が反映されないので、 !revalidate
というコマンドを作り Discord から revalidate できるようにしました。
revalidate に成功するとそのURLが返ってくるようになっているので、開発メンバー以外もリアルタイムに変更作業が見られて安心できます。
対応する量が増えたら管理画面を作るかもしれません。
!ruby
コマンド
ボツになったコマンドです。
https://zenn.dev/funwarioisii/scraps/4c6d429e63f901 に書いたので、そちらをご覧ください。
理由はいくつかあり
- 使わない
- 必要とするメモリが多く、EC2インスタンスサイズを上げないと動かない
といったところです。
掃除当番決めるのに %w[watasi omae].sample
などは便利なのですが、それなら個別に !omikuji
などを作ると良さそうです。
!bot
コマンド
Slack でよくある、 bot が反応するコマンドです。
https://nadeko.bot がこの辺をしっかりやっていそうです。
FUSSY でも同等のものを実装していて、!bot register こんにちは hello
というコマンドを実行すると、 !bot こんにちは
というメッセージに対して hello
と返してくれます。
現状は !bot
を先につける必要があり、やや不便なので、登録されているワードが来たら自動で返すようにしたいです。
チームで共有している Google Drive の位置や会社の住所などを登録しています。
割と使われない機能の一つです。
require 'sqlite3'
DATABASE_NAME = 'data/bot.sqlite'
def register(service_id, wake_word, word)
db = SQLite3::Database.new(DATABASE_NAME)
db.execute('INSERT INTO wake_word_responses (server_id, wake_word, response) VALUES (?, ?, ?)',
[service_id, wake_word, word])
<<~TEXT
#{wake_word} に #{word} を登録しました
TEXT
ensure
db.close
'エラーが発生しました。もう一度やり直してください。'
end
def respond(server_id, wake_word)
db = SQLite3::Database.new(DATABASE_NAME)
begin
responses = db.execute('SELECT response FROM wake_word_responses WHERE server_id = ? AND wake_word = ?',
[server_id, wake_word])
responses.sample&.first || "「#{wake_word}」 に対する応答が見つかりませんでした"
rescue StandardError
db.close
end
end
def list(server_id)
db = SQLite3::Database.new(DATABASE_NAME)
begin
responses = db.execute('SELECT wake_word, response FROM wake_word_responses WHERE server_id = ?', [server_id])
responses.map { |wake_word, response| "#{wake_word} => #{response}" }.join("\n")
rescue StandardError
db.close
end
end
一応別のサーバーでも使えるように server_id
でわけています。
fixupx
ハンドラー
ここまではコマンドの紹介でしたが、最後にハンドラーについて紹介します。
良い命名が思いつかなかったので、ハンドラーと呼んでいますが、送られたメッセージ全て(コマンド以外)に対してなんらかの処理を試みる仕組みです。
先ほど紹介した !bot
が自動で反応しない不便を解消するために、コマンドではなく、ハンドラーの仕組みに乗せたいと考えています。
ここで紹介するのは実装済みの fixup
ハンドラーです。
Discord では x.com や twitter.com のOGPがうまく展開されません。
https://github.com/FixTweet/FixTweet の実装が使われている fxtwitter.com というサービスを使っています
たとえば、 Discord で https://twitter.com/FUSSY_OFFICIAL/status/1713901972150157417
はOGPが展開されませんが、 https://fxtwitter.com/FUSSY_OFFICIAL/status/1713901972150157417
とすれば表示されます。
Discord でのコミュニケーションで Twitter の URL が貼られることが多いので、これを変換し、展開されるようにしています。
def replace(message)
unless message.match?(%r{https?://twitter\.com/.*?/status/}) || message.match?(%r{https?://x\.com/.*?/status/})
return nil
end
message.gsub(%r{https?://(x|twitter)\.com/}, 'https://fxtwitter.com/')
end
まとめ
FUSSY のチャットボットでできることやその背景について紹介しました。
片手間で機能を追加し、憂さ晴らしできて、メンテコストも低い。やっぱりチャットボットは最高です。
語り尽くされていそうな話題ですが、2023年っぽい LLM や fixupx などの話もできたのではないかと思います。
読んでくださった方の最近のチャットボット事情を共有いただけると幸いです。
最後に少々宣伝を。
チャットボットではなく、サービスの方は現在事前登録受付中です。
お時間あればご覧ください。
Discussion