> ## 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 API를 호출하고 Call을 Weave에 로깅할 수 있도록 OpenAI 및 W\&B API 키를 설정하세요.

```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"])

            # assistant 메시지이면 피드백 양식을 표시합니다
            if message["role"] == "assistant":
                # st.session_state.calls에서 이 assistant 메시지의 인덱스를 구합니다
                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)

                # assistant 메시지를 저장합니다
                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으로 채팅 UI를 구축하고, 입력과 출력을 Weave에 캡처했습니다. 또한 좋아요 및 싫어요 버튼을 추가해 사용자 피드백도 캡처했습니다.
