한 권으로 끝내는 랭체인 노트 따라하기 Day 1

2024. 12. 23. 10:13·ML & DL/NLP

오늘의 실습

 

04. OpenAI API 사용(GPT-4o 멀티모달)

.custom { background-color: #008d8d; color: white; padding: 0.25em 0.5…

wikidocs.net

참고

원래는 OpenAI의 api를 발급받아서 실습을 진행해야 하는데, 나는 따로 결제를 하지 않기 위해서 Ollama를 이용하여 실습을 진행하였다. 다행히도 실습에서 사용되는 `langchain_openai`의 `ChatOpenAI`와 유사하게 `langchain_ollama`의 `ChatOllama`가 있었다. 아래에 다른 여러 패키지들도 있으니 꼭 OpenAI api를 사용하지 않고 다양하게 해 볼 수 있을 것 같다.(다만 지원되지 않는 것들이 있으니 참고)

모든 코드는 랭체인 실습과 ChatOllama의 docs를 보면서 먼저 구현해보고 `langchain-teddynote` 패키지를 이용하여 한 번 더 작성해 보았다.

ChatOllama

나는 ChatOllama를 사용할 것이기에, 테디 님의 노트와 ChatOllama langchain docs를 참고하며 실습을 진행하였다.

 

ChatOllama | 🦜️🔗 LangChain

Ollama allows you to run open-source large language models, such as Llama 2, locally.

python.langchain.com

from langchain_ollama import ChatOllama 

llama = ChatOllama(
    model="llama3.2:latest",
    temperature=0,
    base_url="http://localhost:7869",
)

위와 같이 객체를 생성할 수 있으며, 파라미터는 다양하게 있는데 그중 중요한 것만 살펴보자면 아래와 같다.

- model : 모델명(Ollama에서 다운로드,== ChatOpenAI의 model_name)

- temperature(default 0.8) : 샘플링 온도(0~1)

- base_url: ollama가 실행되고 있는 ip

- num_predict(default 128): 생성할 토큰의 최대개수(=ChatOpenAI의 max_tokens)

Q&A

질문을 할 때는 동일하게 string형식의 질문을 넣어주고 `invoke()`함수를 사용하면 된다.

question = "대한민국의 수도는 어디인가요?"

response = llama.invoke(question)

print(response)
>> content='대한민국의 수도는 서울입니다.' additional_kwargs={} response_metadata={'model': 'llama3.2:latest', 'created_at': '2024-12-21T01:25:10.70289894Z', 'done': True, 'done_reason': 'stop', 'total_duration': 154653998, 'load_duration': 35949594, 'prompt_eval_count': 34, 'prompt_eval_duration': 25000000, 'eval_count': 9, 'eval_duration': 91000000, 'message': Message(role='assistant', content='', images=None, tool_calls=None)} id='run-9ae4c481-ed53-4c86-8002-900c3bfd1017-0' usage_metadata={'input_tokens': 34, 'output_tokens': 9, 'total_tokens': 43}

print(f"[answer]: {response.content}")
>> [answer]: 대한민국의 수도는 서울입니다.

 

LogProb 활성화

아쉽게도 `ChatOllama`에서는 `LogProb`를 사용할 수 없었다. `LogProb`는 주어진 텍스트에 대한 모델의 토큰 확률의 로그값을 출력하는 파라미터라고 한다. 

# https://wikidocs.net/233343

llm_with_logprob = ChatOpenAI(
    temperature=0.1, 
    max_tokens=2048,  
    model_name="gpt-3.5-turbo",  
).bind(logprobs=True)

# 질의내용
question = "대한민국의 수도는 어디인가요?"

# 질의
response = llm_with_logprob.invoke(question)

# 결과 출력
response.response_metadata

>>{'token_usage': {'completion_tokens': 15,  'prompt_tokens': 24,  'total_tokens': 39}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': {'content': [{'token': '대',    'bytes': [235, 140, 128],    'logprob': -0.03859115,    'top_logprobs': []},   {'token': '한',    'bytes': [237, 149, 156],    'logprob': -5.5122365e-07,    'top_logprobs': []},   {'token': '\\xeb\\xaf',    'bytes': [235, 175],    'logprob': -2.8160932e-06,    'top_logprobs': []},   {'token': '\\xbc', 'bytes': [188], 'logprob': 0.0, 'top_logprobs': []},   {'token': '\\xea\\xb5',    'bytes': [234, 181],    'logprob': -6.704273e-07,    'top_logprobs': []},   {'token': '\\xad', 'bytes': [173], 'logprob': 0.0, 'top_logprobs': []},   {'token': '의',    'bytes': [236, 157, 152],    'logprob': -6.2729996e-06,    'top_logprobs': []},   {'token': ' 수',    'bytes': [32, 236, 136, 152],    'logprob': -5.5122365e-07,    'top_logprobs': []},   {'token': '도',    'bytes': [235, 143, 132],    'logprob': -5.5122365e-07,    'top_logprobs': []},   {'token': '는',    'bytes': [235, 138, 148],    'logprob': -1.9361265e-07,    'top_logprobs': []},   {'token': ' 서',    'bytes': [32, 236, 132, 156],    'logprob': -5.080963e-06,    'top_logprobs': []},   {'token': '\\xec\\x9a',    'bytes': [236, 154],    'logprob': 0.0,    'top_logprobs': []},   {'token': '\\xb8', 'bytes': [184], 'logprob': 0.0, 'top_logprobs': []},   {'token': '입니다',    'bytes': [236, 158, 133, 235, 139, 136, 235, 139, 164],    'logprob': -0.13815464,    'top_logprobs': []},   {'token': '.',    'bytes': [46],    'logprob': -9.0883464e-07,    'top_logprobs': []}]}}

Streaming

스트리밍옵션은 질문에 대한 답변을 실시간으로 출력되듯 받을 수 있다.

answer = llama.stream("서울의 데이트장소 5곳과 주소를 알려주세요!")
for token in answer:
    print(token.content, end="", flush=True)

테니노트님의 package를 이용하면 매번 반복문을 쓰지 않고 간편하게 출력할 수도 있다.

# langchain-teddynote
from langchain_teddynote.messages import stream_response

answer = llama.stream("서울의 데이트장소 5곳과 주소를 알려주세요!")
stream_response(answer)

Multimodal

다음은 여러 가지 형태의 입력을 받을 수 있는 Multomodal이다. docs에서 ChatOllama는 image/audio/video input은 따로 지원하지 않는다고 나와있는데, 또 밑에는 Multimodal 가이드가 나와있었다. 따로 함수를 정의하여 이미지를 base64 형태로 바꿔주고 input으로 넣어주는 방식이었다.

우선 나는 Ollama에서 multimodal 중 이미지를 지원하는 모델을 가지고 실습을 진행하였다.

llama_vision = ChatOllama(
    model="llama3.2-vision",
    temperature=0,
    base_url="http://localhost:7869",  # Set the remote server URL
)

우선 아래의 함수 2개를 따로 정의해 준다. 하나는 PIL이미지를 Base64 type으로 인코딩해주는 함수이며 다른 하나는 image와 text를 prompt로 형식으로 만들어주는 함수였다. (둘 다 docs에 있던 함수)

def convert_to_base64(pil_image):
    """
    Convert PIL images to Base64 encoded strings
    :param pil_image: PIL image
    :return: Base64 string
    """
    buffered = BytesIO()
    pil_image.save(buffered, format="JPEG")  # You can change the format if needed
    img_str = base64.b64encode(buffered.getvalue()).decode("utf-8")
    
    return img_str
    
 def prompt_func(data):
    text = data["text"]
    image = data["image"]

    image_part = {
        "type": "image_url",
        "image_url": f"data:image/jpeg;base64,{image}",
    }

    content_parts = []

    text_part = {"type": "text", "text": text}

    content_parts.append(image_part)
    content_parts.append(text_part)

    return [HumanMessage(content=content_parts)]

이후, 이미지를 불러온 뒤 인코딩해주고 이를 chain으로 묶어준다. 아직 chain이 뭔지도 SrtOutputParser이 뭔지 잘 모르겠지만 실습을 그대로 따라 하였다. 

from PIL import Image

image_path = 'dog.jpeg'
pil_image = Image.open(image_path)

# Base64로 변환
image = convert_to_base64(pil_image)

chain = prompt_func | llama_vision | StrOutputParser()

query_chain = chain.invoke(
    {"text": "이게 어떤 사진인지 설명해줘.", "image": image}
)
query_chain
>> "이 사진은 포메라니안 개의 사진입니다. \n\n포메라니안은 미국에서 유래한 개 품종으로, 작은 크기의 개로 알려져 있습니다. 이 사진 속 개는 포메라니안의 한 종류인 툴레그리 포메라니안이며, 일반적으로 '툴레그리'라고 불립니다.\n\n포메라니안은 미국에서 유래한 개 품종으로, 20세기 초에 처음 개발되었습니다. 이들은 작은 크기의 개로 알려져 있으며, 보통 몸무게가 3-7kg 정도입니다. 포메라니안의 특징 중 하나는 긴 털과 다양한 색깔이 있습니다.\n\n포메라니안은 일반적으로 가족을 위한 애완동물로 사용되며, 친절하고 순수한 성격으로 알려져 있습니다. 이들은 또한 경주와 다른 종류의 동물 활동에 적합합니다.\n\n이 사진 속 개는 툴레그리 포메라니안이며, 보통 몸무게가 3-7kg 정도입니다. 이들은 작은 크기의 개로 알려져 있으며, 긴 털과 다양한 색깔을 가지고 있습니다. 포메라니안은 일반적으로 가족을 위한 애완동물로 사용되며, 친절하고 순수한 성격으로 알려져 있습니다."

위의 Multimodal부분도 langchain-teddynote 패키지에 multimodal class를 이용하여 구현하면 이미지 인코딩 부분이랑 prompt를 만들어주는 부분이 포함되어 있어 간편하게 확인할 수 있었다.

langchain-teddynote
from langchain_teddynote.models import MultiModal

multimodal_llm = MultiModal(llama_vision)

answer = multimodal_llm.stream(image_path)
stream_response(answer)

 

Prompt 수정

마지막으로 prompt를 주고, 답변을 받는 방식이었다. 이것 또한 prompt에 내 input 인자에 위에서 정의한 `prompt_func`를 이용해서 multimodal형식을 입력받게 해 주고, chain을 엮어서 실행해 줬다. 

from langchain_core.prompts import ChatPromptTemplate

prompt = ChatPromptTemplate.from_messages(
    [
        ("system", "You are a senior veterinarian."),
        ("human", prompt_func({"text": '{text}', "image": '{image}'}, return_list=True)),
    ]
)

chain = prompt | llama_vision | StrOutputParser()

query_chain = chain.invoke(
    {"text": "이 강아지의 상태를 묘사해줘. 한글로 대답해", "image": image}
)

query_chain
>> '강아지는 매우 작은 크기의 포메라니안 품종의 개입니다. \n\n*   **체형:** 포메라니안은 일반적으로 중간 크기인 반면, 이 강아지의 경우 매우 작고 가늘며, 몸무게는 1.5kg 정도로 추정됩니다.\n*   **털:** 털이 짧고, 흰색과 오렌지색을 띠며, 머리와 귀에 더 많은 오렌지색이 있습니다.\n*   **눈:** 눈은 어두운 색이며, 주변의 오렌지색 털이 눈썹을 형성합니다. \n*   **입: 입술이 짧고, 코가 작으며, 뾰족한 모양입니다.\n*   **귀:** 귀는 작은데도 불구하고, 포메라니안 특유의 귀 모양으로 보입니다.\n*   **코: 코는 작고, 뾰족하며, 털이 짧습니다. \n*   **몸: 몸은 가늘며, 긴 다리와 꼬리가 있습니다. \n\n이 강아지는 포메라니안 품종의 특징을 잘 나타내고 있으며, 매우 귀여운 외모를 가지고 있습니다.'
# langchain-teddynote 
system_prompt = "You are a senior veterinarian."
user_prompt = "이 강아지의 상태를 묘사해줘 한글로 대답해"

multimodal_llm_with_prompt = MultiModal(
    llama_vision, system_prompt=system_prompt, user_prompt=user_prompt
)
answer = multimodal_llm_with_prompt.stream(image_path)

stream_response(answer)

 

끝! 

저작자: 테디노트 / <랭체인 LangChain 노트> - LangChain 한국어 튜토리얼 / (CC BY-NC-ND 2.0 KR)

 

728x90

'ML & DL > NLP' 카테고리의 다른 글

한 권으로 끝내는 랭체인 노트 따라하기 Day 3 - prompt  (0) 2024.12.23
한 권으로 끝내는 랭체인 노트 따라하기 Day 2 - LCEL  (0) 2024.12.23
한 권으로 끝내는 랭체인 노트 따라하기 Day 0  (0) 2024.12.20
[패캠/NLP] BERT  (0) 2023.12.28
[패캠/NLP] GPT  (0) 2023.12.27
'ML & DL/NLP' 카테고리의 다른 글
  • 한 권으로 끝내는 랭체인 노트 따라하기 Day 3 - prompt
  • 한 권으로 끝내는 랭체인 노트 따라하기 Day 2 - LCEL
  • 한 권으로 끝내는 랭체인 노트 따라하기 Day 0
  • [패캠/NLP] BERT
창빵맨
창빵맨
  • 창빵맨
    Let's be Developers
    창빵맨
    로그인/로그아웃
  • 전체
    오늘
    어제
    • 분류 전체보기 (481)
      • 알쓸신잡 (88)
      • ML & DL (85)
        • Computer v.. (22)
        • NLP (22)
        • 파이썬 머신러닝 완.. (3)
        • 개념정리 (38)
      • 리눅스 (21)
      • 프로젝트 (29)
        • 산불 발생 예측 (6)
        • 음성비서 (12)
        • pdf 병합 프로그.. (0)
        • 수위 예측 (5)
        • 가짜 뉴스 분류 (5)
        • 전력사용량 예측 (1)
      • 코딩테스트 (217)
        • 프로그래머스[Pyt.. (17)
        • 프로그래머스[Fai.. (3)
        • 백준[Python] (160)
        • 이것이취업을위한코딩.. (18)
        • 파이썬 알고리즘 (19)
      • 데이터분석실습 (25)
        • 데이터 과학 기반의.. (18)
        • 헬로 데이터 과학 (7)
      • 메모장 (0)
      • 잡담 (4)
  • Blog

    • 🏠 Home

    ✏️글쓰기
    💻 관리

    Personal

    GITHUB
    Instagram
  • 공지사항

  • 인기 글

  • 태그

    DFS
    그리디
    나동빈
    이코테
    이것이취업을위한코딩테스트다
    파이썬
    백준
    BFS
    dp
    이분탐색
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
상단으로

티스토리툴바