👋

[AIアプリ]チャットボットでオリジナル野球選手を作成

2024/07/06に公開

パワプロ、エディット機能欲しいですよね?

野球ゲームで有名なパワフルプロ野球、楽しいですよね。⚾️

野球ゲームをプレイすること自体も楽しいですが、
サクセス、マイライフ、マイライフで、自分の思い描く選手を作成するのは、楽しいです!

ただ、少し面倒さもあります。(そこがいいところでもありますが)

サクセスに一時間ほどかけて、思い描く選手が作れなかったり、、

  • パワーがSまでいかなかった
  • 変化球、3種類で総変化量15とか作るの難しい

右打ち、出身とかの設定に、地味に時間を取られたり、、
パワプロ

そこで、、

streamlitで選手作成フォームを作る👩‍💻

以下のように、選手を作成し、作成した選手を見てニヤニヤするWEBアプリを作りました。
streamlitというpythonライブラリを使用いたしました。

※私以外の需要がなかったらすみません🙇

選手作成フォーム

↓のような選手作成フォームに、作りたい選手の情報を入力します!
form

選手閲覧画面

作成した選手の名前をプルダウンから選択すると、、?
このように、作成した選手の情報を閲覧することができます⚾️
listplayers

今回、この選手作成アプリに、
ChatGPTのようなRAGシステムを実装してみました。⚽️⚾️

チャットボットで野球選手を作る

以下のようなフォームに、作りたい選手の情報を入力すると、、、?

  • 岩手出身なのあってる。バランスも良い。ただプロフィールキャッチャーじゃない。
    rag1

  • 思ったよりも野球が上手。外野手でキャッチャーAなので、肩力をつけてキャッチャーやってもいいのに、てなる。
    rag2

  • ちゃんとアメリカ出身の33歳一塁手。能力値もパワーゴリゴリであってる。
    rag3

このように、入力した選手イメージを考慮した(本当に?)選手が作成されます!

実装方法

上記のアプリの実現方法(一部)について、解説いたします。📨
なお、本アプリケーションは、streamlitというpythonライブラリを用いて作成しております。

選手の入力と表示

まず、フォームを使った選手作成については以下のように実装しております。
st.form の中に、フォームに配置するウィジェット(部品)を定義して、最後にpl.write_csvで選手データベース(csv)に保存します。🐣

#%%
import streamlit as st
import polars as pl
import os

import glob

from streamlit_image_select import image_select
import streamlit_authenticator as stauth
import yaml
from yaml.loader import SafeLoader
import sys
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'components')))
from Player import Player

#%%

path_images = glob.glob("input/images/*.png")
disp_cols = ["last_name","first_name","age","position","hand","bbox","character"]


# option_goods = ["チャンスB","アベレージヒッター","パワーヒッター","盗塁B","走塁B","流し打ち","キャッチャーB"]
# option_bads = ["三振","併殺"]



#%%

# CSVファイルのパス
csv_file_path = 'input/players.csv'

# CSVファイルのデータを読み込み
def load_data():
    return pl.read_csv(csv_file_path)

# データをCSVファイルに保存
def save_data(data):
    data.write_csv(csv_file_path)

# メインアプリケーション
def main():
    st.title('選手情報フォーム')

    authenticator.login(location='main')
    #ログイン認証
    if st.session_state["authentication_status"]:
        authenticator.logout()
        # 入力フォーム
        with st.form(key='player_form'):
            
            #一般情報
            st.subheader("共通")
            last_name = st.text_input('氏',max_chars=8)
            first_name = st.text_input('名',max_chars=8)
            # position = st.text_input('ポジション')

            age = st.slider('年齢', 18, 50)

            position = st.selectbox("ポジション",Player.pos_list)
            
            hand = st.radio("利き腕",["右","左"])
            bbox = st.radio("打席",["右","左"])
            
            birthPlace = st.selectbox("出身",Player.birthPlace_list)
            career = st.radio("経歴",Player.career_list)
            character = st.selectbox("性格",Player.character_list)
            buttingForm = st.selectbox("打法",Player.buttingForm_list)
            
            image = image_select("見た目",path_images)
            
            profile = st.text_area('プロフィール', placeholder="力強いスイングが光る、若手外野手。", height=100)
            
            #野手能力(基本)
            st.subheader("基本能力")
            st.text("入力範囲1~100")
            contact = st.number_input('ミート', min_value=1, max_value=100)
            power = st.number_input('パワー', min_value=1, max_value=100)
            speed = st.number_input('走力', min_value=1, max_value=100)
            shoulder = st.number_input('肩力', min_value=1, max_value=100)
            throw = st.number_input('送球', min_value=1, max_value=100)
            catch = st.number_input('捕球', min_value=1, max_value=100)
            
            ##野手能力(特殊)
            st.subheader("特殊能力")
            special_goods = st.multiselect("GOOD",Player.list_optionGoods)
            special_goods = str(special_goods)
            
            special_bads =st.multiselect("BAD",Player.list_optionBoods)
            special_bads = str(special_bads)
            
            submit_button = st.form_submit_button(label='追加')

        # フォームが送信された場合の処理
        if submit_button:
        # 新しい選手情報をデータlast_フレームに追加
            new_player = \
            pl.DataFrame(\
                {'last_name': [last_name],
                'first_name':[first_name],
                'age':[age],
                'hand':[hand],
                'bbox':[bbox],
                'buttingForm':[buttingForm],
                'birthPlace':[birthPlace],
                "career":[career],
                'character':[character],
                'position': [position],
                'profile':[profile],
                'contact':[contact],
                'power':[power],
                'speed':[speed],
                'shoulder':[shoulder],
                'throw':[throw],
                'catch':[catch],
                'image':[image],
                'special_goods':[special_goods],
                'special_bads':[special_bads],
                'cnt_like':[0],
                })
            data = load_data()
            data = pl.concat([data, new_player],how="vertical_relaxed")

            # データを保存
            save_data(data)

            st.success('選手情報が追加されました!')

        # 現在の選手情報を表示
        data = load_data()
        st.write('現在の選手情報')

    #   st.dataframe(data.select(pl.col(disp_cols)))
    elif st.session_state["authentication_status"] is False or st.session_state["authentication_status"] is None:
        st.warning("ログインが必要です。")


if __name__ == "__main__":
    main()

RAGを使った選手生成🐣🐴

RAGを使った選手作成の手順は、以下です。
rag process

たとえば基本能力が欲しい場合、以下のようにプロンプトを定義し、

        template_text = """
        #タスク
        あなたのタスクは以下です。
        入力されたprofileを基に、出力形式に沿って選手のプロフィールを作成してください。

        #出力形式
        ```json
        {{
            "position":"外野手",
            "age":25,
            "birthPlace":"東京",
            "hand":"右",
            "bbox":"左",
            "buttingForm":"スクエア",
            "character":"熱血",
            "career":大卒,
            "image":"input/images/003.png"
        }}

        #野球選手(バッター)のプロフィール
        {profile}

        #制約
        プロフィールの入力値は、{common_dict}の要素から決定してください。
        
        """

プロフィールを生成する場合は、以下です。
※ユーザーの入力が、選手紹介のプロフィールとして使用するのに適しているとは限らないためこの処理を設けています。

        template_text = """
        #タスク
        あなたのタスクは以下です。
        入力された{query}を基に、出力形式に沿って野球選手のプロフィールを作成してください。

        #出力形式
        ```json
        {{
        "profile":パワーが魅力のスラッガー。ベテランになっても、撃ち続ける。
        }}
        
        ##制約
        プロフィールは、50文字以内で作成してください。

⇧で定義したプロンプトをLLMchain(chatgpt)の入力として渡すことで、
選手の能力やプロフフィール の作成を自動化することができます。

ユーザーの入力(progile)を元に、野手能力の基本情報を作成する場合は、以下のような処理を実行します。

from langchain.chains import LLMChain

def create_BaseAbility(self,profile:str)->str:

    client = self.rag_setting()
    
    template_text = """
    #タスク
    あなたのタスクは以下です。
    入力された野球選手(バッター)のプロフィールを基に、出力形式に沿って能力値をつけてください。

    #出力形式
    ```json
    {{
    "contact":60,
    "power":80,
    "speed":70,
    "shoulder":80,
    "throw":70,
    "catch":75
    }}

    #野球選手(バッター)のプロフィール
    {profile}

    #制約
    能力値は、1~100の整数としてください。

    #注意
    * 値が大きいほど、その能力値が高いことを意味します。
    * 能力の平均値は50とします。
    """
    chat_prompt = ChatPromptTemplate(
    messages=[
        SystemMessage(content="あなたはプロフィールから、野球選手の能力値を割り当てる担当です。"),
        HumanMessagePromptTemplate.from_template(template_text),
    ],
    input_variables=["profile"],
    )
        
    chain = LLMChain(llm=client, prompt=chat_prompt)
    start = time.time()
    response = chain({"profile": profile})

    response_json = response["text"].strip().strip('```json\n').strip('\n```')
    
    st.write("base_ability:\n",response_json)
    base_ability = json.loads(response_json)
    end = time.time()
    print("elapsed_time: ", end-start)
    
    return base_ability


課題

実際に使ってもらえるアプリになることを想定した課題です。

  • 見た目が地味。
  • RAG生成、選手の顔画像だけ固定値になる。。
  • 投手作成の実装。
  • streamlitサーバーなどで公開

memo

  • 時間があれば、クラウドetcへアップロードし、利用できるようにしてみます⚽️
  • 小学校のとき、最初に思いついた最強の一塁手が、真野誠也(左打)です。⛹️

Discussion