메인 콘텐츠로 건너뛰기
이 문서는 대화형 노트북입니다. 로컬에서 실행하거나 아래 링크를 사용할 수 있습니다.
생성된 LLM 응답을 자동으로 평가하는 것은 어려울 수 있습니다. 응답을 더 잘 평가하고 개선하려면 문제 지점을 파악할 수 있도록 직접적인 사용자 피드백을 추가로 수집할 수 있습니다. 이 노트북에서는 맞춤형 챗봇의 응답에 대한 사용자 피드백을 수집하는 방법을 보여줍니다. Streamlit을 사용해 인터페이스를 구축하고, LLM 상호작용과 피드백을 W&B Weave에 캡처합니다. 최종적으로는 각 응답을 Weave에 기록하고, 사용자들의 좋아요 또는 싫어요 반응과 자유 텍스트 피드백도 함께 남기는 작동하는 챗봇이 준비되므로, 프로덕션 상호작용을 검토하고 개선이 필요한 영역을 파악할 수 있습니다.

설정

먼저 챗봇이 OpenAI API를 호출하고 Weave에 Call을 기록할 수 있도록 필요한 패키지를 설치한 다음, OpenAI 및 W&B API 키를 설정하세요.
!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 호출을 Weave op으로 감싸며, 반응과 notes를 해당 Weave call에 다시 연결하는 피드백 컨트롤을 렌더링합니다.
# 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():
    """아직 존재하지 않는 session_state 키를 설정합니다."""
    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):
    """
    지금까지의 전체 대화 이력을 기반으로 스트리밍 모드에서 OpenAI API를 호출합니다.
    full_history는 딕셔너리 목록입니다: [{"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):
    """해당 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():
    """st.session_state.messages에 저장된 대화를 피드백 버튼과 함께 표시합니다."""
    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():
    """채팅 프롬프트 입력창을 표시합니다."""
    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로 이를 실행할 수 있습니다. 이제 이 애플리케이션을 사용해 보고, 각 응답 뒤에 있는 피드백 버튼을 클릭할 수 있습니다. 첨부된 피드백을 보려면 Weave UI로 이동하세요. 각 채팅 상호작용은 Weave call로 기록되며, 제출한 모든 반응이나 메모는 해당 응답을 생성한 call에 연결됩니다.

설명

다음 섹션에서는 챗봇에서 사용되는 주요 Weave API를 살펴보고, 동일한 패턴을 자체 애플리케이션에 적용하는 방법을 설명합니다. 다음과 같이 데코레이터를 적용한 prediction 함수를 살펴보겠습니다:
import weave

weave.init("feedback-example")

@weave.op
def predict(input_data):
    # 예측 로직을 여기에 작성하세요
    some_result = "hello world"
    return some_result
평소처럼 이를 사용해 사용자에게 모델 응답을 전달할 수 있습니다:
with weave.attributes(
    {"session": "123abc", "env": "prod"}
):  # call에 inputs & outputs과 함께 임의의 속성을 첨부합니다
    result = predict(input_data="your data here")  # App UI를 통한 사용자 질문
피드백을 연결하려면 call 객체가 필요하며, 이 객체는 평소처럼 함수를 직접 호출하는 대신 .call() 방법을 사용해 획득합니다:
result, call = predict.call(input_data="your data here")
특정 응답에 피드백을 연결하려면 이 call 객체가 필요합니다. 호출을 실행하면 오퍼레이션의 출력이 result에 저장됩니다. call 객체가 있으면 해당 응답에 대한 사용자 피드백을 기록할 수 있습니다:
call.feedback.add_reaction("👍")  # App UI를 통한 사용자 반응

결론

이 튜토리얼에서는 Streamlit으로 채팅 UI를 구축하고, Weave에서 입력과 출력을 캡처하며, 좋아요 및 싫어요 버튼으로 사용자 피드백도 캡처하도록 구성했습니다.