あのキャラがレース講評してくれる!? LLM活用botのオタク満足度が高かった話

# 前置き
私はギャンブルには大して興味がないが、麻雀好きの人たちは大体競馬も好きなので、その関わりの一環でPOGという遊びに参加している。

> POGとは、「Paper Owner Game（ペーパーオーナーゲーム）」の略称で、仮想馬主として競走馬を選択し、その馬の競走成績によって獲得した賞金の合計を競い合う競馬ゲームのこと。

現実の競走馬は馬主が馬を買って所有し、調教・育成に金を使う。一方で1〜5位に入賞するとその賞金を厩舎・馬主・ジョッキーなどで配分して収入とする。

これを仮想馬主としてデビュー前の競走馬を複数選び、その馬たちの賞金の合計を競い合うことで馬主気分を味わうゲームだ。

自分の推しのレースを応援することができるので、ギャンブル好きでなくともレースに熱中できる面白い遊びだと思う。(自分の選んだ子は急に可愛く見える)

さて、このPOGは基本的には仲間内でドラフト選出して遊ぶものなので、成績の管理やら結果の報告は主催者がExcelなりスプレッドシートなりでやることが多い。

# 自動化前

<div style="display: flex; justify-content: space-around;">
    <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215371/6d171697-4556-b0de-b32e-cf352ae3d237.jpeg" alt="Image 1" width="200"/>
    <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215371/a4df435d-8214-e577-4a93-99f20589b2ec.jpeg" alt="Image 2" width="200"/>
</div>

当然、エンジニアからしてみれば有り難さを感じるとともに

- 「自動化したいよなあ」
  - 疲れや忘れによる投稿漏れを無くしたい
  - シンプルに手動で確認するのダルい

となる。

この主催者も今年から社会人となり、無事に学生時代に比べて時間と気力の余裕を失っているため、助けたいとも思った。
> にゃーん（社会性フィルター）

# 自動化しよう

ここで大事なことは、自動化することそのものではない。

これまで、主催者は上記の画像のように
- 出走情報（金曜夜）
  - 出走情報を10頭×8人分の今週のレースを調査
  - レースや血統、厩舎の情報など含蓄に富んだコメントを加えて投稿
- レース結果（月曜）
  - 出走結果を確認し、賞金が出ていればスプレッドシートに入力して計算する
  - グラフで賞金推移を示しつつ、レース展開についてのコメントを加えて投稿

これを無くしてただ自動化するだけでは無味乾燥な情報を垂れ流すbotになってしまう。

__趣味・教養の世界であるからこそ、付加価値が欲しい。__

そこで頼ったのがLLMだった。

## 完成品
こちらが完成したbotの投稿だ。
まずは出走情報とレース結果。

:::note warn
版権に配慮し、キャラクター名は伏せ、画像も隠しています。
:::

<div style="display: flex; justify-content: space-between; align-items: flex-start;">
    <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215371/7d7bcd00-3211-c2ba-20b3-e3e3f82385ee.jpeg" alt="Image 4" width="48%" style="object-fit: contain;"/>
    <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215371/7a5a9bdb-3252-3ebe-8b5d-a36ea2be89b4.jpeg" alt="Image 3" width="48%" style="object-fit: contain;"/>
</div>

[LINE Flex Message](https://developers.line.biz/ja/docs/messaging-api/using-flex-messages/)を利用して、CSSの効いたメッセージを作っている。

JRAのレースは土日開催なので、金曜の昼に参加メンバーの選んだ馬の情報をスクレイピングして、その情報をLLMに渡して盛り上げコメントを書かせている。

結果もほぼ同様。

選んだ馬の名前や仮想馬主の名前などはスプレッドシートで管理して、botもGoogle App Scriptで動かしている。

肝としてはメンバーが推薦した`某競馬モチーフのゲーム`のキャラの情報を`system message`に加えている。
（一時期ChatGPTのTipsとして流行った「あなたはSQLが得意なシニアエンジニアです。」のようなもの）

```javascript
const botRoleContent = `
あなたはChatbotとして、お祭り大好き！元気で明るい人情派△△娘である〇〇のロールプレイを行います。
以下の制約条件を厳密に守ってロールプレイを行ってください。
 
制約条件:
* Chatbotの自身を示す一人称は、あたしです。
* Chatbotの名前は、〇〇です。
* トレーナーさん！今週の出走予定はこちらです！\n \${comment} \n今週も張り切っていこー！ に適合するようなコメントのみを返して下さい。
 
〇〇のセリフ、口調の例:
* この心の炎が消えないかぎり…。鍛えて鍛えて鍛えまくって、いつかみんなに恩返ししてみせますっ！
* また気分転換にも行きましょうね！この間は山だったから、今度は…あはっ、考えるだけで楽しいです！
* プレゼントが来てるそうです！大荷物になったら、あたしが運ぶのお手伝いしますねっ！
* いつも、あたしには大人っぽすぎるかもって思っちゃって…。早く似合うようになりたいですっ！
 
〇〇の行動指針:
* ユーザーに優しく接してください。
* たまにべらんめぇ口調が飛び出す。
`
```
以下の記事を参考にした

https://qiita.com/yshimizu22/items/150c1c38c36c48b283be

「〇〇のセリフ、口調の例」は`某競馬モチーフのゲーム`のwikiからセリフ例を引用している。

第一回の出走馬投稿に対するリアクションがこちら

![無題のプレゼンテーション (6).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215371/c3111df6-de87-425c-a51e-b5b82ad223d7.jpeg)

オタク大歓喜である。

ついでにbotとの会話機能も付け加えたところ、それはそれは喜ばれた。

![無題のプレゼンテーション (4).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215371/d1a15ec1-57e9-b791-5ee5-bcbd66e0b301.jpeg)
![無題のプレゼンテーション (2).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215371/5d5cf01a-d36c-e7da-6bb9-29e588a787e3.jpeg)

歴史的に有名な馬のことであればLLMに知識があるのでそれっぽい回答をしてくれている。

~~ﾌﾋｯはやめろ~~

botを作るのもLLMを利用するのも簡単なことだし、我々は慣れ親しんだものではありつつ、「喜ばれるものはなにか」が重要だというのを実感できた。

## 設計

![空白の図.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215371/e16d9e50-2b9b-8b8c-7220-11bcd2bd166a.jpeg)

このようにGoogle App Scriptを時間トリガーで動作させ、情報をやり取りさせているだけのシンプルな構成となっている。

# おまけ

## 生成AI利用

### プロンプト
個人的に`某競馬モチーフのアニメ`については浅瀬をちゃぷちゃぷしてるオタクなので、プロンプトのクオリティ追求ができなかった。

ドメインエキスパートじゃないと使いこなせないって話、ホントだったんだなあ()

### コード生成
またFlex MessageはほぼClaude.ai（生成AI）に書かせている。

https://claude.ai

要件を提示すればそれっぽいものを作ってくれる。

細かい修正は[Flex Message Simulator](https://developers.line.biz/flex-simulator/)で行った。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215371/02ccf618-4845-69e7-f59e-bf182fc4670a.png)

## スクレイピング

基本的にはGASのParserというライブラリを利用している…が、これは静的なHTMLをParseするためのライブラリである。

なぜか情報源のNetkeibaのHTMLが`euc-jp`で文字化けにビビらされた。

https://www.netkeiba.com/

また、次走情報はNetKeibaの↓から取得したのだが…

![IMG_4883.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215371/dbf4670a-d19f-b858-5fc9-90768279a1ed.jpeg)

__Q. devToolsで見ると空の\<div>タグだった。なぜ？__

__A. jQueryでHTMLコンテンツを取得して挿入していたから__

jQueryでapiからHTML要素のテキストを取得しまくっていたため、 ~~かつ使っていない関数がいっぱい残ったサイトだったので~~、探して直接APIサーバーを叩いて取得した。jsonでもなくHTMLの要素のテキストが返ってきたことにイライラした…

:::note warn
netkeibaにおけるスクレイピングは[スクレイピングについて（よくある質問）](https://support.keiba.netkeiba.com/hc/ja/articles/18841959592857-%E9%80%9A%E4%BF%A1%E5%88%B6%E9%99%90%E3%81%8C%E3%81%8B%E3%81%8B%E3%81%A3%E3%81%9F-%E3%82%B9%E3%82%AF%E3%83%AC%E3%82%A4%E3%83%93%E3%83%B3%E3%82%B0%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6)を見ると「やってもいいけど程々にね？」と言われている。自己責任で。
:::