오늘의 실습
참고
원래는 OpenAI의 api를 발급받아서 실습을 진행해야 하는데, 나는 따로 결제를 하지 않기 위해서 Ollama를 이용하여 실습을 진행하였다. 다행히도 실습에서 사용되는 `langchain_openai`의 `ChatOpenAI`와 유사하게 `langchain_ollama`의 `ChatOllama`가 있었다. 아래에 다른 여러 패키지들도 있으니 꼭 OpenAI api를 사용하지 않고 다양하게 해 볼 수 있을 것 같다.(다만 지원되지 않는 것들이 있으니 참고)
모든 코드는 랭체인 실습과 ChatOllama의 docs를 보면서 먼저 구현해보고 `langchain-teddynote` 패키지를 이용하여 한 번 더 작성해 보았다.
ChatOllama
나는 ChatOllama를 사용할 것이기에, 테디 님의 노트와 ChatOllama langchain docs를 참고하며 실습을 진행하였다.
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)
'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 |