흩어진 힘을 하나로!
어릴 때 유희왕이라는 카드 게임 만화를 많이 봤는데
이 만화에는 카드 5장을 모으면 무조건 이기는 엑조디아라는 괴물 같은 카드가 있었습니다 ☠️
[ 체인 악세사리의 개수를 보니 이 친구, 랭체인 좀 칠 것 같다! ]
벌써 그로부터 20년의 세월이 흘렀고,
저는 어느덧 유희왕 대신 LangChain Docs를 볼 만큼 나이를 먹었지만
제 곁에는 아직 도 흩어진 힘을 하나로 모으면 묻지지도 따지지도 않고 뭐든 이겨버릴 것만 같은 엑조디아
아니 랭조디아가 함께하고 있습니다 ✊
넣고 싶은 거 있으면 다 쓰까(?)~!
쓰까-먹다
동사 섞어 먹다라는 뜻의 경상도 방언
예시 “와 이래 고민해 쌋노? 랭체인에는 넣고 싶은 거 있으면 그냥 다~ 쓰까 묵는기다!“
우리의 친구 랭조디아와 함께라면 그냥 고민할 필요가 없습니다.
챗GPT요? 허깅페이스요? Unsplash요? Tavily Search API요?
그냥 넣고 싶은 재료 다 넣어서 비빔밥처럼 쓰까-먹으면 됩니다.
그래서 4개 다 넣어봤습니다.
Colab 위에서, 랭체인 안에서 하나되는 우리 ✊
1단계 : 모듈 설치
랭조디아의 힘을 하나로 모으기 위한 제물을 바칠 시간입니다
입맛대로 골라담으세요
# 잡다구리한 모듈
!pip install python-dotenv streamlit torch
# 근본 랭체인 & 허깅페이스 모듈
!pip install langchain langchain-openai langchain-community langchain-huggingface huggingface_hub
# 랭조디아의 한 조각 같은 모듈
!pip install requests tavily-python
2단계 : .env 파일 생성
API 키를 환경변수로 불러오는 비밀스러운 의식도 거쳐야 합니다.
OPENAI_API_KEY=안알랴줌
UNSPLASH_API_KEY=말할수없는비밀
TAVILY_API_KEY=시끄릿또
3단계 : app.py 파일 생성
그리고 app.py에서 모두의 힘을 하나로 모아줍니다.
%%writefile app.py
########## 환경 변수 불러오기 ##########
from dotenv import load_dotenv
load_dotenv()
########## 이미지 분류 모델 가져오기 ##########
import torch
from transformers import pipeline
from langchain_huggingface import HuggingFacePipeline
model_id = "MDZN/fruit-classifier"
fruit_classifier_pipe = pipeline("image-classification", model=model_id)
def classify_fruit(image_input):
"""
GPTers 11기 3주차 실습 세션에서 성준님의 기가 막힌 강의를 따라가며 만든 허깅페이스 모델로,
과일 이미지를 보고 어떤 과일인지 분류하는 함수입니다
"""
# 이미지 분류
results = fruit_classifier_pipe(image_input)
# 스트림릿으로 출력
st.write(results)
return results
########## Unsplash API ##########
import os
import requests
from PIL import Image
from io import BytesIO
import streamlit as st
UNSPLASH_API_KEY = os.getenv("UNSPLASH_API_KEY")
def search_unsplash_images(fruit_name):
"""Unsplash API로 이미지를 검색해 가져오는 함수입니다"""
# (상수) 페이징 파라미터
PER_PAGE = 5
PAGE = 1
# Unsplash API로 HTTP 요청 보내기
url = "https://api.unsplash.com/search/photos"
headers = {
"Accept-Version": "v1",
"Authorization": f"Client-ID {UNSPLASH_API_KEY}"
}
params = {
"query": fruit_name,
"per_page": PER_PAGE,
"page": PAGE,
}
response = requests.get(url, headers=headers, params=params)
# HTTP 응답 결과 처리
if response.status_code == 200:
return [item["urls"]["raw"] for item in response.json()["results"]]
else:
response.raise_for_status()
def draw_unsplash_images(unsplash_images):
"""Unsplash에서 가져온 이미지를 화면에 그리는 함수입니다"""
# (상수) 화면에 그릴 이미지 개수
COLS_COUNT = 5
# 스트림릿 컬럼 생성
cols = st.columns(COLS_COUNT)
# COLS_COUNT개 만큼 마크다운으로 이미지 출력
for i, image_url in enumerate(unsplash_images):
cols[i % COLS_COUNT].markdown(f"")
########## Tavily Search API ##########
import os
from langchain_community.retrievers import TavilySearchAPIRetriever
from langchain.agents import tool
TAVILY_API_KEY = os.getenv("TAVILY_API_KEY")
tavily_retriever = TavilySearchAPIRetriever() # 기본 옵션 그대로 적용
def flat_tavily_documents(docs):
return "\n\n".join(doc.page_content for doc in docs)
########## LangChain 쿼리 결과 출력을 위한 Pydantic 파서 정의 ##########
from langchain_core.pydantic_v1 import BaseModel, Field
from langchain_core.output_parsers import JsonOutputParser
class FruitInfo(BaseModel):
fruit_name: str = Field(description="name of a fruit")
fruit_info: str = Field(description="information about the fruit")
url: str = Field(description="url of the information source")
thumbnail: str = Field(description="thumbnail image of the fruit")
output_parser = JsonOutputParser(pydantic_object=FruitInfo)
format_instructions = output_parser.get_format_instructions()
########## LangChain Agent 코드 ##########
import os
from langchain_core.prompts import PromptTemplate
from langchain_core.runnables import RunnablePassthrough, RunnableLambda
from langchain_openai import ChatOpenAI
from langchain.agents import create_tool_calling_agent, AgentExecutor, create_react_agent
from langchain_core.output_parsers import StrOutputParser
import streamlit as st
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
########## 실행 : 모두의 힘을 하나로, 나와라 랭조디아! ##########
def main(form):
# 왼팔 : fruit-classifier! 과일 이미지에 맞는 과일 이름을 분류해줘!
fruit_classification = classify_fruit(form["image_input"])
fruit_name = fruit_classification[0]["label"]
# 오른팔 : Unsplash! 과일 이름에 맞는 무료 라이선스 이미지를 가져와서 그려줘!
unsplash_images = search_unsplash_images(fruit_name)
draw_unsplash_images(unsplash_images)
# 왼다리 : Tavily Search API Retriever! 이 과일에 대한 최신 정보를 인터넷에서 찾아와줘!
runnable = RunnablePassthrough.assign(
context=(lambda x: x["fruit_name"])
| tavily_retriever
| RunnableLambda(flat_tavily_documents),
fruit_image=(lambda x: x["fruit_image"])
)
# 오른다리 : 프롬프트! 이 모두의 힘을 하나로 합치고, JSON으로 마무리해줘!
prompt = PromptTemplate(
template="""
Answer the user query.
{format_instructions}
{context}
Get fruit information of {fruit_name}
And the thumbnail image of the fruit is: {fruit_image}
And You must answer this query in Korean language.
""",
input_variables={"fruit_name"},
partial_variables={"format_instructions": format_instructions},
)
# 본체 : GPT 3.5 터보! 이제 준비가 끝났어! 넌 알아서 적당히해! (나머진 팔다리가 서포트 해줄거야!)
llm = ChatOpenAI(model="gpt-3.5-turbo-0125")
# 모두의 힘을 체인으로 모아
chain = (
runnable
| prompt
| llm
| output_parser
)
# 체인 인보크!
response = chain.invoke({"fruit_name": fruit_name, "fruit_image": unsplash_images[0]})
# 결과물, 출력!!
st.write(response)
st.markdown(f"## {response['fruit_name']}")
st.markdown(f"")
st.write(response["fruit_info"])
st.markdown(f"[👉 정보 출처]({response['url']})")
########## Streamlit 화면 코드 ##########
from PIL import Image
# 여기서부턴 대-충 그리기..
st.header("Streamlit Application")
st.write("Hello, *World!* :sunglasses:")
with st.form('form', clear_on_submit=True):
uploaded_image = st.file_uploader("이미지 파일을 업로드하세요.", type=["png", "jpg", "jpeg"])
submitted = st.form_submit_button("Generate")
if submitted and uploaded_image:
image_input = Image.open(uploaded_image)
st.image(image_input, caption="업로드된 이미지", use_column_width=True)
st.write("이미지 처리 완료")
main({"image_input": image_input})
4단계 : 코랩에서 다 끝내자! Streamlit 띄우기
이제 모든 API의 힘을 모으면 랭조디아가 얼마나 강력해지는지
두 눈으로 확인할 시간입니다
4-1. localtunnel로 Streamlit을 띄우기 위한 IP 주소 출력!
import urllib
print("localtunnel에 입력할 비밀번호/IP: ",urllib.request.urlopen('https://ipv4.icanhazip.com').read().decode('utf8').strip("\n"))
실행 시 출력 : localtunnel에 입력할 비밀번호/IP: 12.345.678.90
4-2. 터미널에서 localtunnel 실행!
!npm install localtunnel
!streamlit run app.py &>/content/logs.txt &
!npx localtunnel --port 8501
실행 시 출력 : 어쩌구~ 저쩌구~
your url is: https://random-string-url.loca.lt
위 URL을 클릭하면 아래의 localtunnel 페이지가 열립니다
그러면 영어가 나와도 당황하지 말고 침착하게
바로 앞에서 얻은 IP 주소를 Tunnel Password에 입력하면!
이렇게 방금 만든 Streamlit 화면이 BAAAAAAM!
결과
성준님 실습 세션을 따라가며 만든
제 생애 첫 허깅페이스 모델인 Fruit-Classifier로 (https://huggingface.co/MDZN/fruit-classifier)
랭조디아의 힘이 얼마나 강력한지 느껴보겠습니다
먼저 잘~ 익은 샤인 머스캣 두 송이를 마지막 제물로 쓰까 넣고
결과를 확인하면?
왼팔 : Fruit-Classifier
오른팔 : Unsplash API
왼다리 + 오른다리 : Tavily Search API + ChatGPT
보시다시피 랭조디아의 팔다리들이
휘적휘적~ 손발이 안 보일만큼 열심히 일해줍니다
최종 결과물
그 결과
“포도” : 첨부한 과일 사진을 보고 과일의 이름을 맞춘 뒤
Unsplash 무료 이미지 썸네일 : Unsplash에서 해당 과일의 무료 라이선스 이미지를 가져와 출력하고
챗GPT와 Tavily API가 합작한 과일 정보 : LLM이 Tavily API가 가져온 최신 정보로 과일에 대한 상세 설명을 작성해주고
정보 출처 : Tavily API가 정보를 가져온 실제 출처를 링크로 달아주는
어디에 써먹어야 될지는 모르겠지만, 나름 있어보이는 랭체인 뭐시기가 하나 탄생했습니다!
#11기랭체인