Langchain | Few-Shot Prompt & json파싱

Ruah·2024년 11월 26일

프롬프트 설계에서 종종 “Few-Shot”이라는 개념을 들어봤을 거다. 이는 모델에게 학습 데이터를 직접 제공하지 않고도, 몇 가지 예제를 통해 문제를 이해하고 응답하도록 하는 방식이다. 오늘은 이 Few-Shot 프롬프트를 구현한 경험과 원리를 간단히 정리해보려고 한다

Few-Shot 프롬프트의 기본 개념

Few-Shot 프롬프트란, 모델이 특정 입력에 대해 올바른 응답을 생성할 수 있도록 몇 가지 예제를 제공하는 방식이다. 모델은 주어진 예제를 기반으로 패턴을 파악하고, 사용자가 입력한 질문에 맞춰 응답을 생성한다. 이 방식은 대규모 데이터셋 없이도 고성능을 낼 수 있는 게 장점이다.

예제 기반의 접근 방식

Few-shot 에서는 다음과 같은 형식으로 프롬프트를 작성한다.
1. 몇가지 예제를 "시스템"역할로 입력
2. 사용자의 질문을 "유저"역할로 입력.
3. 모델의 응답을 "ai"역할로 입력.

Few-Shot프롬프트

example_prompt

먼저, 사용자가 어떤 질문을 할지 예상하고 이에 적합한 응답을 작성한다. 이 예제를 모델에게 제공하면, 모델은 예제에서 학습한 맥락을 기반으로 새로운 입력에 응답하게 된다. 예를 들어:

from langchain_core.prompts import ChatPromptTemplate

example_prompt = ChatPromptTemplate.from_messages(
    [
        ("user", "{question}"),
        ("ai", "{answer}"),
    ]
)

여기서 "{question}"과 "{answer}"는 사용자가 입력할 질문과 모델이 생성할 응답을 나타낸다.

few_prompt 실제예제

few_examples = [
    {
        "question":"타지키스탄 국제협력업무 회장은 누구야?",
        "answer" : """
        ```json
        {
            "valid": true,
            "year_importance": 0,
            "person_importance": 8,
            "past_importance": 0,
            "other_importance": 8,
            "which_year": 0,
            "related_list_of_people": false,
            "other_q": ["타지키스탄 국제협력의 주요 인물은 누구인가요?", "타지키스탄 국제협력의 성과는 무엇인가요?"]
        }
        ```
        """
    },
    ...
]

실제예제들이 담긴 few_examples

SemanticSimilarityExampleSelector

  • SemanticSimilarityExampleSelector는 질문과 예제 사이의 유사도를 계산하여 가장 적합한 예제를 선택하는 역할을 한다.
  • 이 때, 유사도를 계산하기 위해 사용되는 것이 get_embeddings() 함수이다.
    get_embeddings() 함수는 사전 학습된 모델(BGE 모델 등)을 활용해 질문과 예제 텍스트를 벡터로 변환한다.
    이 벡터는 고차원 공간에서 의미적으로 유사한 항목끼리 가까워지도록 만들어지며, 이를 통해 예제 선택의 효율성과 정확도를 높인다.
from langchain_core.example_selectors import SemanticSimilarityExampleSelector

example_selector = SemanticSimilarityExampleSelector.from_examples(
    examples=few_examples, 
    embeddings=get_embeddings(), # 임베딩 
    vectorstore_cls=get_fewshot_vectorstore(), # 예제를 저장하고 검색할 벡터 스토어
    k=2, # 유사한 예제 2개를 선택
)

주요 매개변수 설명:

  • examples: 미리 정의한 실제 예제들. few_examples 리스트에서 가져오며, 질문과 응답 구조를 가진다.
  • embeddings: 텍스트를 벡터화해 유사도를 계산하는 함수.
  • vectorstore_cls: 벡터를 저장하고 검색하는 벡터 데이터베이스. get_fewshot_vectorstore() 함수로 생성한다.
  • k=2: 입력 질문과 가장 유사한 예제 2개를 선택하도록 설정한다.

작동 원리:

  • 사용자가 질문을 입력하면, SemanticSimilarityExampleSelector는 입력된 질문을 벡터화한다.
  • 예제 리스트에 있는 모든 질문도 벡터화한 뒤, 입력 질문과의 유사도를 계산한다.
  • 계산된 유사도 점수를 기준으로 가장 유사한 k개의 예제를 선택한다.

FewShotchatMessagePromptTemplate 생성

  • FewShotChatMessagePromptTemplate는 선택된 예제를 example_prompt 템플릿 형식에 맞춰 최종 프롬프트에 포함하는 역할을 한다.
  • 즉, example_selector가 고른 예제를 example_prompt 형식으로 변환한 뒤 최종적으로 모델에 입력하는 구조다.
from langchain_core.prompts import FewShotChatMessagePromptTemplate

few_shot_prompt = FewShotChatMessagePromptTemplate(
    example_selector=example_selector,  # 질문과 유사한 예제를 선택하는 로직
    example_prompt=example_prompt       # 예제의 형식을 정의한 템플릿
)

작동 원리:

  • example_selector에서 선택된 예제 2개를 가져온다.
  • 각 예제를 example_prompt 템플릿("user": "{question}", "ai": "{answer}") 형식으로 변환한다.
  • 변환된 템플릿은 최종 프롬프트에 추가되어, 모델에게 질문-응답 패턴을 학습시킨다.

final_prompt에서 few_shot_prompt 사용의 의의

final_prompt에서 few_shot_prompt를 포함하면, 모델이 정해진 패턴에 따라 질문에 응답하는 방식을 학습할 수 있다.
Few-Shot 방식은 전체 데이터셋을 미리 학습시키는 대신, 프롬프트 자체에 중요한 예제를 포함시키는 방법이다.

final_prompt = ChatPromptTemplate.from_messages(
    [
        ("system", "여기 시스템 메시지가 들어간다."),
        few_shot_prompt,
        ("user", "{question}")
    ]
)

주요 구성:

  • system 메시지: 모델이 대답할 규칙을 명확히 정의.
    예: “JSON 형식으로만 답변해라.”
  • few_shot_prompt: SemanticSimilarityExampleSelector에서 선택된 예제를 포함.
  • user 메시지: 사용자가 입력한 실제 질문.

작동 흐름:

  • 사용자가 질문을 입력하면, example_selector가 유사한 예제를 선택하고 이를 example_prompt 형식으로 변환한다.
  • 최종 프롬프트는 시스템 메시지, 예제, 사용자 질문을 포함해 모델로 전달된다.
  • 모델은 이를 기반으로 패턴을 학습하고 응답을 생성한다.

ollama_request 함수의 작동 설명

API 요청을 처리하는 함수로, 주로 모델과의 상호작용에 사용된다.

def ollama_request(messages):
    """
    Example API 요청 함수
    """
    data = {
        "model": "gemma2:2b",  # LLM 모델 설정
        "messages": messages,
        "device": "cuda",
        "temperature": 0,
        "top_p": 0.5,
    }
    response = requests.post("http://localhost:11434/api/chat", json=data)
    if response.status_code != 200:
        print(f"Error: API returned status code {response.status_code}")
        print(f"Response text: {response.text}")
        return None  # 오류 발생 시 None 반환
    return response.json()

작동 원리:

  • messages는 시스템 메시지와 사용자 질문으로 구성된다.
  • 지정된 LLM(여기선 “gemma2:2b”)에 요청을 보낸다.
  • 모델의 응답을 JSON 형식으로 반환받는다.
  • 오류가 발생하면 상태 코드와 응답 내용을 출력하고 None을 반환한다.

JSON 파싱 함수 (extract_json_from_text)

extract_json_from_text 함수는 모델이 반환한 JSON 형식의 텍스트에서 데이터를 추출한다.

def extract_json_from_text(text):
    """
    텍스트에서 JSON 데이터를 추출하는 함수.
    """
    try:
        print(f"Extracting JSON from text: {text}")  # 원본 텍스트 출력 (디버깅용)
        
        # JSON 형태의 텍스트가 있다면, 중괄호를 기준으로 데이터 추출
        if "{{" in text and "}}" in text:
            text = text.replace("{{", "{").replace("}}", "}")  # 중복 중괄호 제거
        start = text.find('{')
        end = text.rfind('}') + 1
        if start != -1 and end != -1:
            json_str = text[start:end]  # JSON 텍스트 추출
            return json.loads(json_str)  # JSON 디코딩
        else:
            print("No JSON format found in text.")
            return None  # JSON 형식이 없을 경우
    except json.JSONDecodeError as e:
        print(f"JSON decoding error: {e}")
        return None  # JSON 디코딩 실패 시

개선할 점:

  • 모델 응답이 반드시 JSON 형식이 아니어도 예외 처리가 필요하다.
  • 예외 처리가 부족하면 예상치 못한 입력으로 프로그램이 중단될 수 있다.

호출

def get_response_data(question):
    extract_json_from_text

    # 'final_prompt'를 포맷팅
    formatted_prompt = final_prompt.format(question=question)

    # 메시지를 API로 전달
    response = ollama_request([
        {"role": "system", "content": formatted_prompt},
        {"role": "user", "content": question}
    ])

    # 응답 텍스트에서 JSON 데이터 추출
    parsed_data = extract_json_from_text(response['message']['content'])

    return parsed_data

결과

{
    "valid": true,
    "year_importance": 0,
    "person_importance": 0,
    "past_importance": 5,
    "other_importance": 10,
    "which_year": 2024,
    "related_list_of_people": false,
    "other_q": [
        "타지키스탄 협력 사업의 주요 성과는 무엇인가요?",
        "2024년 타지키스탄 협력 계획은 어떻게 진행되고 있나요?"
    ]
}
profile
집요한 주니어 개발자의 호되게 당했던 기록

1개의 댓글

comment-user-thumbnail
2024년 12월 12일

너무 유익하네요 추가적인 포스팅을 기다립니다 : )

답글 달기