> ## Documentation Index
> Fetch the complete documentation index at: https://docs.coreweave.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 本番環境でのフィードバック

> W&B Weave のフィードバックトラッキング機能を使用して、本番環境における LLM の出力に対するユーザーフィードバックを収集・分析します。

<Note>
  これはインタラクティブなノートブックです。ローカルで実行するか、以下のリンクを使用してください。

  * [Google Colab で開く](https://colab.research.google.com/github/wandb/docs/blob/main/weave/cookbooks/source/feedback_prod.ipynb)
  * [GitHub でソースを表示](https://github.com/wandb/docs/blob/main/weave/cookbooks/source/feedback_prod.ipynb)
</Note>

LLM が生成した応答を自動で評価するのは難しい場合があります。応答の評価と改善の精度を高めるには、ユーザーから直接フィードバックを収集し、問題点を特定する方法も併用できます。

このノートブックでは、カスタムチャットボットの応答に対するユーザーフィードバックを収集する方法を紹介します。Streamlit でインターフェースを構築し、LLM とのやり取りとフィードバックを W\&B Weave で取得します。最終的には、各応答をユーザーによる高評価・低評価のリアクションや自由記述のフィードバックとあわせて Weave にログするチャットボットが完成し、本番環境でのやり取りを確認して改善点を特定できるようになります。

<h2 id="setup">
  セットアップ
</h2>

まず、必要なパッケージをインストールし、OpenAI と W\&B の APIキーを設定します。これにより、チャットボットが OpenAI API を呼び出し、Call を Weave にログできるようになります。

```python lines theme={"system"}
!pip install weave openai streamlit wandb
!pip install set-env-colab-kaggle-dotenv -q # 環境変数の設定用
python
# OpenAI と WandB の APIキーを記述した .env ファイルを追加します
from set_env import set_env

_ = set_env("OPENAI_API_KEY")
_ = set_env("WANDB_API_KEY")
```

次に、以下の内容で `chatbot.py` というファイルを作成します。このファイルでは、Streamlit のチャットインターフェースを定義し、各応答が追跡されるように OpenAI の Call を Weave op でラップします。さらに、リアクションやメモを対応する Weave の Call に紐付けるためのフィードバックコントロールもレンダリングします。

```python lines {13,15,27} theme={"system"}
# chatbot.py

import openai
import streamlit as st
import wandb
from set_env import set_env

import weave

_ = set_env("OPENAI_API_KEY")
_ = set_env("WANDB_API_KEY")

wandb.login()

weave_client = weave.init("feedback-example")
oai_client = openai.OpenAI()

def init_states():
    """Set up session_state keys if they don't exist yet."""
    if "messages" not in st.session_state:
        st.session_state["messages"] = []
    if "calls" not in st.session_state:
        st.session_state["calls"] = []
    if "session_id" not in st.session_state:
        st.session_state["session_id"] = "123abc"

@weave.op
def chat_response(full_history):
    """
    Calls the OpenAI API in streaming mode given the entire conversation history so far.
    full_history is a list of dicts: [{"role":"user"|"assistant","content":...}, ...]
    """
    stream = oai_client.chat.completions.create(
        model="gpt-4", messages=full_history, stream=True
    )
    response_text = st.write_stream(stream)
    return {"response": response_text}

def render_feedback_buttons(call_idx):
    """Renders thumbs up/down and text feedback for the call."""
    col1, col2, col3 = st.columns([1, 1, 4])

    # 高評価ボタン
    with col1:
        if st.button("👍", key=f"thumbs_up_{call_idx}"):
            st.session_state.calls[call_idx].feedback.add_reaction("👍")
            st.success("Thanks for the feedback!")

    # 低評価ボタン
    with col2:
        if st.button("👎", key=f"thumbs_down_{call_idx}"):
            st.session_state.calls[call_idx].feedback.add_reaction("👎")
            st.success("Thanks for the feedback!")

    # テキストによるフィードバック
    with col3:
        feedback_text = st.text_input("Feedback", key=f"feedback_input_{call_idx}")
        if (
            st.button("Submit Feedback", key=f"submit_feedback_{call_idx}")
            and feedback_text
        ):
            st.session_state.calls[call_idx].feedback.add_note(feedback_text)
            st.success("Feedback submitted!")

def display_old_messages():
    """Displays the conversation stored in st.session_state.messages with feedback buttons"""
    for idx, message in enumerate(st.session_state.messages):
        with st.chat_message(message["role"]):
            st.markdown(message["content"])

            # アシスタント メッセージの場合は、フィードバック フォームを表示する
            if message["role"] == "assistant":
                # st.session_state.calls 内で、このアシスタント メッセージに対応するインデックスを特定する
                assistant_idx = (
                    len(
                        [
                            m
                            for m in st.session_state.messages[: idx + 1]
                            if m["role"] == "assistant"
                        ]
                    )
                    - 1
                )
                # 高評価・低評価ボタンとテキスト入力欄を表示する
                if assistant_idx < len(st.session_state.calls):
                    render_feedback_buttons(assistant_idx)

def display_chat_prompt():
    """Displays the chat prompt input box."""
    if prompt := st.chat_input("Ask me anything!"):
        # 新しいユーザー メッセージをすぐに表示する
        with st.chat_message("user"):
            st.markdown(prompt)

        # ユーザー メッセージをセッションに保存する
        st.session_state.messages.append({"role": "user", "content": prompt})

        # API に渡すチャット履歴を準備する
        full_history = [
            {"role": msg["role"], "content": msg["content"]}
            for msg in st.session_state.messages
        ]

        with st.chat_message("assistant"):
            # 会話ごとにトラッキングできるよう、Weave の属性を付与する
            with weave.attributes(
                {"session": st.session_state["session_id"], "env": "prod"}
            ):
                # OpenAI API をストリーミング モードで呼び出す
                result, call = chat_response.call(full_history)

                # アシスタント メッセージを保存する
                st.session_state.messages.append(
                    {"role": "assistant", "content": result["response"]}
                )

                # フィードバックを該当する応答に関連付けるため、Weave の Call オブジェクトを保存する
                st.session_state.calls.append(call)

                # 新しいメッセージのフィードバック ボタンを表示する
                new_assistant_idx = (
                    len(
                        [
                            m
                            for m in st.session_state.messages
                            if m["role"] == "assistant"
                        ]
                    )
                    - 1
                )

                # フィードバック ボタンを表示する
                if new_assistant_idx < len(st.session_state.calls):
                    render_feedback_buttons(new_assistant_idx)

def main():
    st.title("Chatbot with immediate feedback forms")
    init_states()
    display_old_messages()
    display_chat_prompt()

if __name__ == "__main__":
    main()
```

`streamlit run chatbot.py` で実行できます。

これで、このアプリケーションを操作し、各応答の後にフィードバックボタンをクリックできます。関連付けられたフィードバックは Weights & Biases UI で確認できます。チャットの各やり取りは Weave の Call として記録され、送信したリアクションやメモは、その応答を生成した Call に紐付けられます。

<h2 id="explanation">
  解説
</h2>

このセクションでは、チャットボットで使用している主要な Weave API について順を追って説明します。同じパターンをご自身のアプリケーションにも適用できます。

次の、デコレーターを付けた予測関数を見てみましょう。

```python lines {5} theme={"system"}
import weave

weave.init("feedback-example")

@weave.op
def predict(input_data):
    # ここに予測ロジックを記述
    some_result = "hello world"
    return some_result
```

通常どおり使用して、モデルの応答をユーザーに返すことができます。

```python lines theme={"system"}
with weave.attributes(
    {"session": "123abc", "env": "prod"}
):  # 入力と出力に加えて、任意の属性を Call に付与します
    result = predict(input_data="your data here")  # App UI から入力されたユーザーの質問
```

フィードバックを追加するには `call` オブジェクトが必要です。このオブジェクトは、関数を通常どおり呼び出すのではなく、`.call()` メソッドを使用して取得します。

```python lines theme={"system"}
result, call = predict.call(input_data="your data here")
```

特定の応答にフィードバックを付与するには、この `call` オブジェクトが必要です。Call を実行すると、オペレーションの出力を `result` として参照できます。

`call` オブジェクトを取得したら、その応答に対するユーザーフィードバックを記録できます。

```python lines theme={"system"}
call.feedback.add_reaction("👍")  # App UI 経由のユーザーのリアクション
```

<h2 id="conclusion">
  まとめ
</h2>

このチュートリアルでは、Streamlit を使用して、Weave で入力と出力を取得し、ユーザーのフィードバックを取得するためのサムズアップボタンとサムズダウンボタンを備えたチャット UI を構築しました。
