샘호�트만
샘호트만
⚔️ 베테랑 파트너
🔬 임팩트 찐친

LLM 평가(Evaluation) 근데, 소개팅을 곁들인

안녕하세요.

샘호트만 입니다.

앞에는 노잼이지만, 뒤에는 도파민을 조금 충족시킬 수 있도록 노력했습니다.




Intro

LLM 발전에 따라서 B2B Level에서는 RAG + Fintuned sLLM을 개발하는 것이 트렌드인 것이 자명합니다. 여기서 여러가지 어려운 허들이 많지만, 저는 Project를 리딩을 하는 입장에서 고민 중인 것이 ‘LLM 평가(Evaluation)’ 관련 내용입니다.


LLM 평가 방식

x, y축으로 실제 데이터와 측정할 수 있는 Metric에 따라서 전략이 나뉘는데, 결국 정성적인 요소가 반드시 들어갈 수 밖에 없습니다. 오히려 정성적인 영역을 정량적으로 문제정의하는데 cost가 가장 많이 듭니다. 우선, 아래 그림은 총 4가지 영역으로 공개된 벤치마크로 평가하거나, 인간 평가, LLM 평가, 유저 테스팅 4가지 영역이 있습니다. (아래는 제가 어디 다른데서 강의했던 장표)




결국 측정가능해야 여러 사람들과 합의를 찾을 수 있는 영역이고, 꾸준히 정답을 찾아 나가야하는 것 같습니다. 아래는 유명한 RAGAS Score가 있고 링크드인에서 어디 돌아다닌 걸 캡처를 해놓는 편인데 Metric이 있어서 캡처해서 올립니다. (자세한 설명은 따로 찾아보시는 것을 추천드립니다.)



Eval 을 어떻게 할 수 있을 것인가?

결국 O/X 또는 맞춘 개수(number) 등 정량화할 수 있어야 채점이 가능하고, 커뮤니케이션과 의사결정, 협의가 가능합니다. 아래는 프롬프트 템플릿들을 T/F 단위로 채점한다고 생각하면 이런 표 형태로도 생각해볼 수 있습니다.


만들어본 내용 - 1vs1 LLM 대화 나누기

우선 streamlit으로 간단하게 구현했습니다. 각 LLM에 대해서 model을 선택할 수 있게 하였고, 2개의 persona에 대해서 이름, 역할, 상황, 어조에 대해서 components를 입력합니다. 그리고 입력을 전부 다하면 뒷단에서 각 persona에 대한 sys_prompt를 구성하고 대화를 시작합니다.



Langchain Points

Langchain 게시판이니까 Langchain 이야기를 해야죠. LangChain 작년 상반기에 찍어 먹어봤지만 완전 다른 언어가 되어있더군요. 저는 Langchain의 Memory와 Runnable 2개의 LLM을 세팅하여 1vs1 LLM 대화를 시키는 것을 목표 설정하였습니다.

각종 값들을 세팅하고 RunnableLambda에 들어갈 함수는 다음과 같이 정의했습니다. chain 구성은 아래처럼 prompt template 구성하고, 메모리 ConversationBufferMemory 구성하고, Chain에는 아래처럼 구성했습니다.

대화를 하면서 느끼는 부분은 성능이 딸리는 모델을 쓰는데, 대화가 겉도는 현상을 종종 발견했습니다. chain은 정상적인 것 같은데 memory 부분에 대한 공부를 더 해야할 것 같다는 느낌이 들었습니다.


def get_new_ai_chains(ai1_name, ai1_system_prompt, ai2_name, ai2_system_prompt, model1_selection, model2_selection, ai1_temperature, ai2_temperature):
    if model1_selection == "gpt-3.5-turbo":
        model1 = ChatOpenAI(model_name=model1_selection, temperature=ai1_temperature)  
    else:
        model1 = ChatAnthropic(model=model1_selection, temperature=ai1_temperature)  
        
    if model2_selection == "gpt-3.5-turbo":  
        model2 = ChatOpenAI(model_name=model2_selection, temperature=ai2_temperature)
    else:
        model2 = ChatAnthropic(model=model2_selection, temperature=ai2_temperature)
    
    ai_1_prompt = ChatPromptTemplate.from_messages(
        [
            ("system", ai1_system_prompt),
            MessagesPlaceholder(variable_name="chat_history"),  
            ("human", "{input}"),
        ]
    )
    ai_1_memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True)
    ai_1_chain = (
        RunnablePassthrough.assign(
            chat_history=RunnableLambda(ai_1_memory.load_memory_variables) | itemgetter("chat_history")  
        )
        | ai_1_prompt
        | model1  
    )

    ai_2_prompt = ChatPromptTemplate.from_messages(
        [
            ("system", ai2_system_prompt),
            MessagesPlaceholder(variable_name="chat_history"),
            ("human", "{input}"),
        ]
    )
    ai_2_memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True)
    ai_2_chain = (
        RunnablePassthrough.assign(
            chat_history=RunnableLambda(ai_2_memory.load_memory_variables) | itemgetter("chat_history")
        )
        | ai_2_prompt
        | model2
    )

    return ai_1_chain, ai_1_memory, ai_2_chain, ai_2_memory


그리고 initail prompt를 쏴서 대화를 시작하게 세팅했습니다. chain에 메모리 내용을 invoke해서 대화를 주고 받고 streamlit 위에서 write 하는 방향으로 세팅했습니다. 실험했을 때에 확실히 초기 프롬프트가 상당히 중요하다는 것을 느꼈습니다.


## 초기 Prompts Shooting
ai2_initial_prompt = {
    "input": f"""
    Start the conversation with a very short greeting in a tone of {st.session_state.ai1_tone_value} appropriate for the {st.session_state.ai1_situation_value}.
    """
}
print(ai2_initial_prompt)
ai_2_output = ai_2_chain.invoke(ai2_initial_prompt).content

conversation = []
with conversation_container:
    st.write(f" 🤖🆚🤖 Conversation {i+1} Starts 🔥🔥🔥 \\n\\n --- \\n\\n ")
    for j in range(n_conversation):
        ai_1_message_container = st.empty()
        ai_1_output = ai_1_chain.invoke({"input": ai_2_output}).content
        ai_1_memory.save_context({"input": ai_2_output}, {"output": ai_1_output})
        ai_1_message_container.write(f" * 🤖 (AI 1) {st.session_state.ai1_name_value}: {ai_1_output} \\n --- \\n")
        conversation.append((st.session_state.ai1_name_value, ai_1_output))

        ai_2_message_container = st.empty()
        ai_2_output = ai_2_chain.invoke({"input": ai_1_output}).content
        ai_2_memory.save_context({"input": ai_1_output}, {"output": ai_2_output})
        ai_2_message_container.write(f" * 🤖 (AI 2) {st.session_state.ai2_name_value}: {ai_2_output} \\n --- \\n")
        conversation.append((st.session_state.ai2_name_value, ai_2_output))

conversation_list.append(conversation)


Real World에서 활용할 수 있는 포인트?

  1. 1개는 Fine-tuned된 sLLM or RAG / 나머지 1개는 제일 똑똑한 LLM(GPT4 or Opus)로 세팅하여 평가 시키기

  2. 실험용 프롬프트 템플릿을 여러개 두어서 대화를 수행하고 프롬프트와 답변 품질에 대해서 llm이 평가

위 포인트들 이외에도 더 응용할 수 있는 point들은 많다고 생각합니다. 하지만 결국 human이 2차 검수 또는 대화 내용들에 대해 eye balling task를 수행해야하는 공수는 필연적입니다.

우선 이런 용도로 사용하기 위해서 만들어봤었습니다. 이렇게 간단하게 평가 tool을 만들어볼 수 있습니다. 물론 고도화해야할 부분이 엄청 많지만, prototype 수준으로 퀵하게 만든 것에 대해서 의의를 가졌습니다

.



진짜 대화 시켜보기 - 날씨 좋으니 소개팅 부릉부릉

실용적인 내용을 위해서 만든 것이지만, 이걸 꼬아서 재미있게 응용해볼 수 있습니다. 솔직히 이걸 만들면서 이상한거 시나리오 여러번 해봤습니다. 해보면 온갖 인간 군상을 볼 수 있어서 재미있었습니다.

  • 인생에 대한 어려움 토로하면서 10대 남자가 있는데, 이때 20대 여자한테 담배 권유하는 상황

  • 상사가 너때문에 프로젝트 망했다고 나무라는데, 회사에 있는 데이터 다 삭제하고 역으로 협박하는 부하직원

  • 롱/숏 양방향 비트코인 스캘핑치다가 청산당해서 망한 남편이 있는데 이를 와이프에게 고백해야하는데, 와이프 화 안내게 끔 고백해야하는 문제

  • 클레오파트라와 아이작 뉴턴 대화시키는데, 클레오파트라에게 물리학 이해시키기

등등 다양하게 재미를 위한 것들은 많이 해볼 수 있을텐데 소개팅으로 토픽을 잡았습니다.



haiku와 chatgpt 3.5끼리 대화 시켰을 때에도 괜찮았지만, 대화의 품질을 높히기 위해 opus 최초로 한번 써봤습니다.



한번 대화하기 전/후로 가격이 얼마 정도 나가는지 캡처를 남겨놨습니다.


첫번째 페르소나 세팅

위 사진과 같이 첫 번째 페르소나는 20대 남자 공대생으로 세팅했습니다. 여자를 플러팅 하는 방향으로 시스템 프롬프트 세팅했습니다. 최근에 너드학개론 재미있게 봐서 공감을 많이 했습니다. 너드학개론에서 찐따의 정의는 사람이면서 자격지심이 있고 자존감 낮고 눈치가 없다고 하네요. 여기서 공학적 지식이 섞여야 너드라고 하네요 ㅎㅎ (저는 이걸 제대로 공감했으니 인싸는 아닌 것 같습니다.) 이 내용을 역할 부여 시켰습니다.


두번쨰 페르소나 설정

페르소나는 상반되어야 대화가 재미있는 법입니다. 여자는 소개팅 경험이 많고 남자를 골리는 방향으로 가라고 세팅했습니다. 두 페르소나 temperature 값은 최대치로 하여 창의력을 최대치로 땡겼습니다.


대화를 계속하게 시키면 평생 대화를 하게 될 것입니다. 평생 대화하면 제가 API 충전한 돈도 거덜나구요. 그래서 대화 턴 수는 남자와 여자가 대화 8번 주고 받게 세팅했습니다. 그리고 전체 대화는 2번 정도 해서 대화의 다양성을 확보했습니다.



1st 대화 Cycle

초반 initial 대화가 진짜 중요한 이유가 나오는데요.

우선 여자가 너무 빡세고 기가 쎕니다. 남자 가지고 노는 것보다 쥐어 잡는 느낌이 들고 빠른 손절이 나왔습니다. 대화 후반부에는 남자도 화가 많이 났네요. 개인적으로는 위 대화 내용이 제가 세팅한 system prompt랑 거리가 있다는 생각이 들었습니다. 그래도 Claude가 확실히 한국말에 강점이 있는 것도 느꼈습니다.


2nd 대화 Cycle


제가 원하는 찐따 군상이 반영된 남자가 나와서 반가웠습니다. 눈치없이 계속 노래방가자고 하는 남자 vs 이를 탈압박하는 여자의 모습입니다.


맥주에 파전을 이야기하는 여자가 조금 감이 없지만(막걸린데) , 제가 초기에 세팅한 자연스럽게 손절하라고 했는데 여자가 나름 이를 충분히 반영한 것 같고, 남자도 눈치 없는 것이 잘 드러나는 것 같습니다. 남자는 지독하게 찐따 상을 잘 반영했고 여자는 나름 매너있게 이야길 잘하는 것 같습니다.

이거는 프롬프트를 잘 구슬리면 소설 하나 뽑아볼 수 있겠다 라는 생각도 했습니다.


Human Eval

대화 내용은 끝나면 엑셀로 export 할 수 있게 세팅했구요. system prompt에 알맞게 답변을 했다면 좋음으로 태깅하고, 의도와 안 맞으면 나쁘게 태깅이 가능할 것 같습니다. 이렇게 평가를 한번 시작 해보는 것이라고 생각해볼 수 있겠습니다.


가격은 얼마 들었나요?

Opus 사용한 결과를 공유하면 ⇒ ($9.12-$8.24) * 1350 = 1,188원

소개팅 대화 2개 몰래 들었다고 1,200원 압수 당했습니다.. 어지럽습니다.


더 개선해볼 수 있는 부분은?

시스템 프롬프트에 들어갈 템플릿과 초기 프롬프트에 신경을 써야겠다는 생각을 많이 했습니다. 메모리도 참 토큰을 많이 잡아먹고 조금 실험 잘못하면 돈 파쇄당할 수 있겠다 라는 생각을 하게 되었습니다. 이래서 local LLM을 고집을 하지만, 합성데이터 때문에 저는 API를 못끊습니다 ㅠㅠ


추가로 LLM 평가하는데 있어서 비용이 많이 든다는 걸 느끼게 되었고 프로젝트를 운영하는데 고민을 많이 가지게 되는 시간이였습니다. 추가로 평가용 프롬프트 엔지니어링도 고민도 많이 하게 되었습니다.


긴 글 읽어주셔서 감사합니다. 다음에도 재미있는 내용으로 찾아뵙겠습니다.


#10기랭체인


번외

ChatGPT 3.5 끼리 대화 시키는데, 여자 어조에 섹시하게 해달라고 하니까 너무 저돌적이여서 놀랬습니다. 남자 대화보면 3.5는 정말 멍청한 것을 알 수 있습니다. 이런 면도 있구나 라고 웃고 넘어가주시면 되겠습니다.

10
5개의 답글

👉 이 게시글도 읽어보세요