import json import os import random import time import pandas as pd import requests import streamlit as st # 環境変数 with open("models_info.json", "r") as json_file: MODELS_INFO = json.load(json_file) with open("test.csv", "r") as file: QUESTION_DF = pd.read_csv(file) MODELS = list(MODELS_INFO.keys()) NUM_QUESTION = 100 # ランキングを取得 def get_leaderboard(): try: response = requests.get(os.environ['DARABASE_URL']) response_data = response.json() return response_data except Exception as e: print(f"An unexpected error occurred: {e}") return "Error" # リーダーボードを作成 def create_leaderboard_df(): # リーダーボードを取得 ranking = get_leaderboard() # エラー処理 if ranking == "Error": st.error("リーダーボードを取得できませんでした。") print("リーダーボードを取得できませんでした。") # ログを表示 return pd.DataFrame() else: # データの初期化 ranks, model_names, ratings, organizations, licenses = [], [], [], [], [] # リーダーボードの作成 for i in range(len(ranking)): ranks.append(i + 1) model_names.append(MODELS_INFO[ranking[i]["model"]][0]) ratings.append(ranking[i]["rating"]) organizations.append(MODELS_INFO[ranking[i]["model"]][2]) licenses.append(MODELS_INFO[ranking[i]["model"]][1]) # データフレームを返す return pd.DataFrame({ "ランク" : ranks, "🤖 モデル" : model_names, "⭐️ Eloレーティング" : ratings, "🏢 組織" : organizations, "📃 ライセンス" : licenses }) # サーバーから回答を取得 @st.cache_data def get_answer(model_name, question_id): try: params = {'modelName': model_name, 'questionId': question_id} response = requests.get(os.environ['ANSWER_URL'], params=params) response_data = response.json() return response_data["answer"] except Exception as e: print(f"An unexpected error occurred: {e}") return "Error" # サーバーに回答を送信 def send_choice(question_id, model_a, model_b, winner, language): # エラー処理 (データが入力されていない場合) if not question_id or not model_a or not model_b or not winner or not language: st.error("データが入力されていないため、回答を送信できませんでした。") print("質問と回答を取得してください。") # ログを表示 return "Error" try: data = { "question_id": question_id, "model_a": model_a, "model_b": model_b, "winner": winner, "language": language, "tstamp": time.time(), } headers = { 'Content-Type': 'application/json' } response = requests.post(os.environ['DARABASE_URL'], headers=headers, data=json.dumps(data)) response_data = response.text return response_data except Exception as e: print(f"An unexpected error occurred: {e}") return "Error" ### Callback Functions ### # ステートの初期化を行う def handle_init_state(): if "chat_history_a" not in st.session_state: st.session_state["chat_history_a"] = [] if "chat_history_b" not in st.session_state: st.session_state["chat_history_b"] = [] if "question_id" not in st.session_state: st.session_state["question_id"] = None if "model_a" not in st.session_state: st.session_state["model_a"] = None if "model_b" not in st.session_state: st.session_state["model_b"] = None if "question" not in st.session_state: st.session_state["question"] = None # ボタンの状態を初期化 if "question_loaded" not in st.session_state: st.session_state["question_loaded"] = False # 送信を状態を初期化 if "answer_sent" not in st.session_state: st.session_state["answer_sent"] = False # 質問と回答を取得する def handle_init_question(): # エラー処理 if st.session_state.question_loaded: st.session_state.question_loaded = False st.session_state.chat_history_a = [] st.session_state.chat_history_b = [] st.error("ボタンを連打しないでください。") print("既に質問と回答を取得しています。") # ログを表示 else: # ボタンの状態を更新 st.session_state.question_loaded = True st.success("質問と回答を取得しています。しばらくお待ちください。") # 質問を取得 st.session_state.question_id = random.randint(1, NUM_QUESTION) st.session_state.question = QUESTION_DF["input"][st.session_state.question_id - 1] st.session_state.chat_history_a.append({"role": "user", "content": st.session_state.question}) st.session_state.chat_history_b.append({"role": "user", "content": st.session_state.question}) # 回答を取得 random.shuffle(MODELS) st.session_state.model_a = MODELS[0] st.session_state.model_b = MODELS[1] answer_a = get_answer(st.session_state.model_a, st.session_state.question_id) answer_b = get_answer(st.session_state.model_b, st.session_state.question_id) # チャット履歴を更新 st.session_state.chat_history_a.append({"role": "assistant", "content": answer_a}) st.session_state.chat_history_b.append({"role": "assistant", "content": answer_b}) st.success("質問と回答を取得しました。回答を選択してください。") print("質問と回答を取得しました。") # ログを表示 # ユーザーの回答を送信する def handle_send_choice(winner): # エラー処理 if st.session_state.answer_sent: st.error("既に回答を送信しています。") print("既に回答を送信しています。") # ログを表示 else: # ボタンの状態を更新 st.session_state.answer_sent = True # ユーザーの回答を送信 response = send_choice( question_id=st.session_state.question_id, model_a=st.session_state.model_a, model_b=st.session_state.model_b, winner=winner, language="Japanese" ) # エラーが発生した場合 if response == "Error": st.error("予期せぬエラーが発生しました。") else: st.success("選択肢は正常に送信されました。") # 初期化 st.session_state.question_loaded = False # 表示部分 def main(): # page config st.set_page_config( page_title="日本語チャットボットアリーナ", page_icon="🏆", layout="wide", ) # ステートの初期化 handle_init_state() # 説明を表示 st.markdown("# 🏆 日本語チャットボットアリーナ") st.markdown("## 📖 説明") st.markdown("| [Twitter](https://twitter.com/yutohub) | [GitHub](https://github.com/yutohub) | [ブログ](https://zenn.dev/yutohub) |") st.markdown("日本語チャットボットアリーナは、日本語に対応しているLLMの評価のためのクラウドソーシングプラットフォームです。[LMSYS Chatbot Arena](https://huggingface.co/spaces/lmsys/chatbot-arena-leaderboard) を参考に、日本語に対応しているLLMのリーダーボードを作成することを目的としています。また、一部の質問と回答は、 [ELYZA-tasks-100](https://huggingface.co/datasets/elyza/ELYZA-tasks-100) を利用しています。") st.markdown(""" > **注意事項:** > > 日本語チャットボットアリーナが提供する情報によって生じたいかなる損害についても、サービス提供者は一切の責任を負いません。 > 日本語チャットボットアリーナは開発中であり、予告なく停止または終了する可能性があります。 > また、ユーザーの回答を収集し、Creative Commons Attribution (CC-BY) または同様のライセンスの下で配布する権利を留保しています。 """) # チャット履歴の表示部分 st.markdown("## ⚔️ チャットボットアリーナ ⚔️") st.markdown(" 2つの匿名モデル (ChatGPT、Llama など) の回答を見て、より良いモデルに投票してください。") with st.expander(f"🔍 展開するとアリーナに参加している {len(MODELS)} 個のモデルの一覧が表示されます。"): st.write(MODELS) model_a, model_b = st.columns([1, 1]) with model_a: st.markdown("### モデル A") if not st.session_state.chat_history_a: st.markdown("質問を取得してください。") else: for message in st.session_state.chat_history_a: with st.chat_message(message["role"]): st.write(message["content"]) # 送信後に正解のモデルを表示する if st.session_state.answer_sent: with st.chat_message("assistant"): st.markdown(f"`{st.session_state.model_a}` が回答しました、") with model_b: st.markdown("### モデル B") if not st.session_state.chat_history_b: st.markdown("質問を取得してください。") else: for message in st.session_state.chat_history_b: with st.chat_message(message["role"]): st.write(message["content"]) # 送信後に正解のモデルを表示する if st.session_state.answer_sent: with st.chat_message("assistant"): st.markdown(f"`{st.session_state.model_b}` が回答しました。") # 質問を取得する load_question = st.button( label="質問を取得", on_click=handle_init_question, # 回答済みの場合 or 質問を取得済の場合はボタンを無効化 disabled=st.session_state.answer_sent or st.session_state.question_loaded, type="primary", use_container_width=True ) # 回答を送信する choice_1, choice_2, choice_3, choice_4 = st.columns([1, 1, 1, 1]) with choice_1: choice_1 = st.button( label="👈 Aの方が良い", on_click=handle_send_choice, args=("model_a",), disabled=not st.session_state.question_loaded, use_container_width=True ) with choice_2: choice_2 = st.button( label="👉 Bの方が良い", on_click=handle_send_choice, args=("model_b",), disabled=not st.session_state.question_loaded, use_container_width=True ) with choice_3: choice_3 = st.button( label="🤝 どちらも良い", on_click=handle_send_choice, args=("tie",), disabled=not st.session_state.question_loaded, use_container_width=True ) with choice_4: choice_4 = st.button( label="👎 どちらも悪い", on_click=handle_send_choice, args=("tie (bothbad)",), disabled=not st.session_state.question_loaded, use_container_width=True ) # リーダーボードを表示する st.markdown("## 🏆 リーダーボード") st.markdown(f"合計で {len(MODELS)} 個のモデルがアリーナに参加しています。30 分毎にリーダーボードが更新されます。") # 回答を送信した場合のみ表示する if st.session_state.answer_sent: # リーダーボードを取得 leaderboard = create_leaderboard_df() st.dataframe( data=leaderboard, height=(len(MODELS) + 1) * 35 + 3, use_container_width=True, hide_index=True, ) else: st.markdown(""" > まずは、「⚔️ チャットボットアリーナ ⚔️」に回答を送信してください。 > 回答を送信すると、リーダーボードが表示されます。 """) # 引用を表示する st.markdown("## 📚 引用") st.markdown(""" ``` @misc{elyzatasks100, title={ELYZA-tasks-100: 日本語instructionモデル評価データセット}, url={https://huggingface.co/elyza/ELYZA-tasks-100}, author={Akira Sasaki and Masato Hirakawa and Shintaro Horie and Tomoaki Nakamura}, year={2023}, } ``` """) if __name__ == "__main__": main()