File size: 12,946 Bytes
c624d50
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1d54aec
c624d50
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
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/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()