Skip to content

[2024/08/05/월 - 2024/08/09/금] 모델 응답이 혼자서 시나리오를 짜는 걸 막기 #23

Description

@SeoMiYoung

🤔 현재 상황

사용 모델: sangthree/eeve_gguf/eeve_model_from_HF.gguf (21.61GB)

현재 유정이가 최근에 야놀자의 EEVE 모델을 기반으로 상쓰리의 커스텀 데이터셋을 파인튜닝한 모델을 사용하고 있다. 그러나, 기존 fastAPI 서버코드로 프로그램을 작동시켰을때는, 결과에 문제가 있었다.

[ 기존 fastAPI 서버코드 ]

from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from langchain_openai import ChatOpenAI
from langchain_core.callbacks.streaming_stdout import StreamingStdOutCallbackHandler
from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser

app = FastAPI()

# CORS 설정
origins = [
    "http://localhost",         # 개발 중인 클라이언트 주소
    "http://localhost:3000",    # React 개발 서버 주소
]

app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=["POST"],
    allow_headers=["Content-Type"],
)

llm = ChatOpenAI(
    base_url="http://localhost:5000/v1",  # LM Studio의 URL
    api_key="lm-studio",
    model="sangthree/eeve_gguf",
    streaming=True,
    callbacks=[StreamingStdOutCallbackHandler()],
)

class Message(BaseModel):
    question: str  # 클라이언트에서 question 필드로 전송될 것으로 기대

@app.post("/chat")
async def chat_with_bot(message: Message):
    try:
        prompt = PromptTemplate.from_template(
            """You are an assistant, and your task is to answer only in Korean as if you were a visiting patient. The doctor is asking questions to better understand your symptoms. You answer the doctor's questions with simple and easy expressions.
            
            #Question:
            {question}

            #Answer: """
        )

        chain = prompt | llm | StrOutputParser()

        response = chain.invoke({"question": message.question})  # 클라이언트로부터 받은 질문 사용
        return {"response": response}  # JSON 형태로 응답

    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

image

위의 결과를 보면 알겠지만, 기존 서버 코드로 응답을 도출했을때는, 혼자서.. 마치 시나리오를 짜는 것 같았다. 나는 모델이 혼자 시나리오를 작성하는 듯한 응답을 생성하는 이유로 프롬프트의 명확성 부족을 뽑았다.

💫 프롬프트의 명확성 부족은 다음과 같이 구분할 수 있다.

(1) 설명 부족: 프롬프트가 명확하게 정의되지 않으면, 모델이 추가적인 맥락이나 시나리오를 생성하려고 시도할 수 있습니다.
(2) 역할 구분 부족: 프롬프트에서 역할 (예: 사용자와 어시스턴트)을 명확히 구분하지 않으면, 모델이 혼란스러워할 수 있습니다.

(1)번을 처음에 시도하고 있었는데, 보영이로부터 연락이 왔다.

그래서 ChatPromptTemplate을 알아보기 시작했다ㅎㅎ

✏️ PromptTemplate VS. ChatPromptTemplate

일단 기존에 사용했던 PromptTemplateChatPromptTemplate의 차이에 대해 살펴보자.

[ PromptTemplate ]

  • 정의: PromptTemplate은 일반적인 프롬프트 템플릿을 생성하는 데 사용됩니다. 이는 대화형 요소가 필요 없는 표준 언어 모델 작업에 적합합니다.
  • 용도: 변수 자리 표시자를 포함한 템플릿을 정의할 수 있으며, 특정 값으로 채워져 프롬프트가 사용됩니다. 주로 간단한 질문이나 요청을 생성할 때 유용합니다.

[ ChatPromptTemplate ]

  • 정의: ChatPromptTemplate은 대화형 언어 모델을 위해 특별히 설계된 템플릿입니다. 시스템, 인간, AI등 다양한 역할 간의 상호작용을 포함하는 프롬프트를 구성하는 데 사용됩니다.
  • 용도: 역할별 메시지를 포함할 수 있으며, 대화의 구조를 관리하는 데 유리합니다. from_template()from_messages()와 같은 메서드를 사용하여 대화 상호작용에 맞춘 프롬프트를 쉽게 생성할 수 있습니다.

위의 내용에 따르면, 우리는 역할놀이 서비스이므로 대화형 모델을 사용하는데, ChatPromptTemplate은 대화형 환경에서의 역할과 대화 흐름을 관리하는 데 특화되어있기 때문에, 우리의 프로젝트에는 ChatPromptTemplate이 적합함을 알 수 있었다!!

🥲 시행착오 (명시적 방법 --> from_messages()사용)

처음에는 보영이가 말해준대로, SystemMessagePromptTemplate, AIMessagePromptTemplate, HumanMessagePromptTemplate을 명시적으로 사용하려고 했으나ㅠㅠㅠ 정말 많은 시도를 해도 실패했다 (자꾸 시도만 하면 500번 에러가 발생하였다.)

물론, 해결할 수 있는 방법이 있을 수 있지만, 계속 시도끝에 안되서 명시적으로 말고, 찾아보니 from_messages()와 같은 메서드를 사용하여 프롬프트를 쉽게 생성할 수 있다고 하여, 그 방법을 시도해보기로 하였다.

=> 아하 그런데 지금보니깐, 보영이가 참고하라고 보내준 colab코드에 from_messages()를 사용한 걸 확인할 수 있었다.

✏️ from_messages()의 작동 방식 설명

  1. 역할과 내용 짝짓기: from_messages() 메서드에 전달되는 각 튜플은 역할**(예: "system", "human", "ai")**과 해당 역할에 대한 메시지 내용을 포함합니다. 예를 들어, ("system", "You are a helpful AI.")는 AI에게 주어진 시스템 메시지를 정의합니다.

  2. 자동 역할 처리: 메서드는 각 튜플의 역할을 인식하고, 프롬프트를 적절하게 구성합니다. 이를 통해 대화의 각 메시지가 어떤 역할에 속하는지를 자동으로 이해합니다. 예를 들어, 시스템 명령, 사용자 질문, AI 응답 등을 구분합니다.

  3. 동적 메시지 삽입: 메시지 내용에 자리 표시자를 사용할 수 있으며, 이는 프롬프트가 호출될 때 실제 데이터로 채워질 수 있습니다. 이를 통해 유연하고 상황에 맞는 프롬프트 생성을 지원합니다.

    • 자리표시자 사용 예시
      • (step1) 프롬프트 템플릿에서 {}를 사용하여 자리 표시자를 정의합니다. 예를 들어, ("human", "안녕하세요, {name}님. 어떻게 도와드릴까요?")처럼 사용할 수 있습니다.
      • (step2) 프롬프트가 호출될 때, 자리 표시자에 실제 값을 전달하여 채웁니다. 예를 들어, {"name": "홍길동"}이라는 데이터를 전달하면, 최종 프롬프트는 "안녕하세요, 홍길동님. 어떻게 도와드릴까요?"가 됩니다.
      • (step3) 유연한 프롬프트 생성: 이렇게 하면, 동일한 템플릿을 다양한 상황에 맞게 재사용할 수 있습니다. 예를 들어, 사용자 이름이나 특정 상황에 따라 프롬프트 내용을 동적으로 변경할 수 있습니다.
  4. AI 모델과의 통합: 이렇게 생성된 구조화된 프롬프트는 대화형 AI 모델과 직접 사용될 수 있습니다. 모델은 명확하게 정의된 입력을 받아, 기대하는 대화 맥락에 맞는 적절한 응답을 생성할 수 있습니다.

🤔 코드 변경 시행착오

어쨌든 ChatPromptTemplate방식으로 바꿨는데, 시행착오가 있었다.

[ 초반 프롬프트 템플릿 ]

prompt = ChatPromptTemplate.from_messages([
            ("system", "You are an assistant, and your task is to answer only in Korean as if you were a visiting patient. The doctor is asking questions to better understand your symptoms. You answer the doctor's questions with simple and easy expressions."),
            ("human", message.question),
            ("assistant", "")
        ])

그러나, LM Studio의 화면에서 다음과 같은 에러가 발생했다.
image

에러 메시지

{"title":"'messages' array must only contain objects with a 'content' field that is not empty"}

LM Studio에서 발생한 오류메시지를 보면, messages배열에 포함된 객체 중 하나가 빈 content필드를 가지고 있어 문제가 발생한 것으로 보였습니다. ChatPromptTemplate에서 assistant역할의 메시지를 빈 문자열로 설정했기 때문에 발생한 문제입니다.

이를 해결하기 위해서는 assistant 역할의 메시지를 빈 문자열로 설정하지 않고, 대신 해당 메시지를 생략하거나 적절한 초기 메시지를 설정해야 합니다.

일단은 당장은 생략했는데, 내 생각에 나중에 AI가 먼저 "안녕~ 우리 지금부터 병원놀이를 시작해보자~"이런식으로 먼저 말을 걸 때 이 방법을 쓰면 구현할 수 있을 것 같다는 생각이 든다.

[ assistant 역할의 메시지에 문자열을 직접 넣는 경우 ]

  1. 초기 응답 제공
    예시: AI가 대화의 시작 부분에서 특정한 초기 응답을 제공해야 할 때, assistant 역할에 문자열을 넣을 수 있습니다. 이는 사용자가 대화를 시작하기 전에 AI가 먼저 정보를 제공하거나, 특정한 안내를 해야 하는 경우에 유용합니다.

  2. 고정된 응답
    예시: 특정 질문에 대해 항상 동일한 응답을 제공해야 하는 경우, assistant 역할에 고정된 응답을 미리 설정할 수 있습니다. 예를 들어, "안녕하세요, 무엇을 도와드릴까요?"와 같은 인사말을 설정할 수 있습니다.

    • 환영 메시지: AI가 대화의 시작 부분에서 "안녕하세요! 무엇을 도와드릴까요?"와 같은 인사말을 제공할 수 있습니다.
    • 지침 제공: AI가 대화의 목적이나 사용 방법에 대해 설명할 수 있습니다. 예를 들어, "저는 건강 관련 질문에 답변할 수 있는 AI입니다. 어떤 증상이 있으신가요?"와 같은 메시지를 통해 사용자가 어떤 질문을 할 수 있는지 안내할 수 있습니다.
  3. 대화 흐름 제어
    예시: 대화의 흐름을 특정 방향으로 유도하기 위해 AI가 특정 메시지를 미리 설정할 수 있습니다. 이는 대화의 맥락을 설정하거나, 사용자가 특정 작업을 수행하도록 유도하는 데 사용될 수 있습니다.

어쨌든, 일단은 assistant를 생략했습니다.

👋 최종적으로 변경된 fastAPI 서버 코드

from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from langchain_openai import ChatOpenAI
from langchain_core.callbacks.streaming_stdout import StreamingStdOutCallbackHandler
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser

app = FastAPI()

# CORS 설정
origins = [
    "http://localhost",
    "http://localhost:3000",
]

app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=["POST"],
    allow_headers=["Content-Type"],
)

llm = ChatOpenAI(
    base_url="http://localhost:5000/v1",
    api_key="lm-studio",
    model="sangthree/eeve_gguf",
    streaming=True,
    callbacks=[StreamingStdOutCallbackHandler()],
)

class Message(BaseModel):
    question: str

@app.post("/chat")
async def chat_with_bot(message: Message):
    try:
        # 'assistant' 역할의 빈 메시지를 제거
        prompt = ChatPromptTemplate.from_messages([
            ("system", "You are an assistant, and your task is to answer only in Korean as if you were a visiting patient. The doctor is asking questions to better understand your symptoms. You answer the doctor's questions with simple and easy expressions."),
            ("human", message.question)
        ])

        chain = prompt | llm | StrOutputParser()

        response = chain.invoke({"question": message.question})
        return {"response": response}

    except Exception as e:
        print(f"Error: {e}")
        raise HTTPException(status_code=500, detail="Internal Server Error")

위의 코드대로 작동시켰을때 결과이다.
Animation
문제가 해결된 것을 확인할 수 있었다.

[ 참고 ] 위 움짤 돌릴때, LM Studio 의 Server logs에 찍힌 코드

[2024-08-09 03:15:47.930] [INFO] Received POST request to /v1/chat/completions with body: {
  "messages": [
    {
      "content": "You are an assistant, and your task is to answer only in Korean as if you were a visiting patient. The doctor is asking questions to better understand your symptoms. You answer the doctor's questions with simple and easy expressions.",
      "role": "system"
    },
    {
      "content": "안녕하세요",
      "role": "user"
    }
  ],
  "model": "sangthree/eeve_gguf",
  "n": 1,
  "stream": true,
  "temperature": 0.7
}
[2024-08-09 03:15:47.931] [INFO] [LM STUDIO SERVER] Context Overflow Policy is: Rolling Window
[2024-08-09 03:15:47.931] [INFO] [LM STUDIO SERVER] Streaming response...
[2024-08-09 03:15:49.602] [INFO] [LM STUDIO SERVER] First token generated. Continuing to stream response..
[2024-08-09 03:15:54.318] [INFO] Finished streaming response
[2024-08-09 03:16:01.178] [INFO] [LM STUDIO SERVER] Processing queued request...
[2024-08-09 03:16:01.179] [INFO] Received POST request to /v1/chat/completions with body: {
  "messages": [
    {
      "content": "You are an assistant, and your task is to answer only in Korean as if you were a visiting patient. The doctor is asking questions to better understand your symptoms. You answer the doctor's questions with simple and easy expressions.",
      "role": "system"
    },
    {
      "content": "어디가 아파서 왔어요?",
      "role": "user"
    }
  ],
  "model": "sangthree/eeve_gguf",
  "n": 1,
  "stream": true,
  "temperature": 0.7
}
[2024-08-09 03:16:01.179] [INFO] [LM STUDIO SERVER] Context Overflow Policy is: Rolling Window
[2024-08-09 03:16:01.180] [INFO] [LM STUDIO SERVER] Streaming response...
[2024-08-09 03:16:03.741] [INFO] [LM STUDIO SERVER] First token generated. Continuing to stream response..
[2024-08-09 03:16:10.315] [INFO] Finished streaming response
[2024-08-09 03:16:18.536] [INFO] [LM STUDIO SERVER] Processing queued request...
[2024-08-09 03:16:18.537] [INFO] Received POST request to /v1/chat/completions with body: {
  "messages": [
    {
      "content": "You are an assistant, and your task is to answer only in Korean as if you were a visiting patient. The doctor is asking questions to better understand your symptoms. You answer the doctor's questions with simple and easy expressions.",
      "role": "system"
    },
    {
      "content": "언제부터 아팠어요?",
      "role": "user"
    }
  ],
  "model": "sangthree/eeve_gguf",
  "n": 1,
  "stream": true,
  "temperature": 0.7
}
[2024-08-09 03:16:18.538] [INFO] [LM STUDIO SERVER] Context Overflow Policy is: Rolling Window
[2024-08-09 03:16:18.538] [INFO] [LM STUDIO SERVER] Streaming response...
[2024-08-09 03:16:20.961] [INFO] [LM STUDIO SERVER] First token generated. Continuing to stream response..
[2024-08-09 03:16:30.648] [INFO] Finished streaming response

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions