빅데이타 & 머신러닝/생성형 AI (ChatGPT etc)

Langchain을 이용한 LLM 애플리케이션의 구현 #14 - Chain을 이용한 워크 플로우 구현 #2

Terry Cho 2024. 1. 25. 15:03

Chain을 이용한 복잡한 워크 플로우의 구현

조대협 (http://bcho.tistory.com)

 

Advanced Sequential Chain 

앞의 예제는 순차적으로 LLMChain을 간단한 워크 플로우를 구현해봤다. SequentialChain은 순차적인 실행뿐만 아니라, 병렬로 LLM 호출을 하는 흐름등을 구현이 가능하다. 이번 예제에서는 조금 더 발전된 Chain의 구조를 살펴보자. 

아래 예제는 도시명{city}과 교통편{transport}를 입력하면, 유명 관광지를 추천해서 그곳까지 도착하기 위한 교통편과 식당에 대한 정보를 출력하는 Chain의 구조이다. 

 

<그림 관광지 교통 정보와, 식당 정보를 출력하는 LLMChain 흐름>

 

예제 코드를 살펴보기전에, 먼저 흐름을 보자.

애플리케이션에서 도시명{city}와 교통편{transport)를 입력받는다. 

  • chain1에서는 도시에서 유명한 관광지를 추천 받아 {place}로 리턴한다.
  • chain2에서는 chain1의 출력값인 관광지{place}를 기반으로 근처에 레스토랑 5개를 추천받는다.
  • chain3에서는 chain2에서 추천받은 5개의 레스토랑 중에서 패밀리 디너로 좋은 음식을 추천받는다.
  • chain4에서는 chain1의 관광지 장소로 가기 위한 경로를 애플리케이션에서 입력받은 교통편{transport} 기반으로 추천 받는다.
  • 마지막으로 final_prompt에서는 chain3과 chain4의 출력값을 합쳐서 관광지 주변의 레스토랑의 추천 음식과 교통편 정보를 함께 출력한다. 

아래 예제코드를 보자. 

from langchain.llms import OpenAI

from langchain.prompts import PromptTemplate

from langchain.chains import LLMChain

from langchain.chains import SequentialChain

 

OPEN_AI_APIKEY="{YOUR_OPENAI_KEY}"

model = OpenAI(openai_api_key=OPEN_AI_APIKEY)

 

prompt1 = PromptTemplate.from_template("what is the famous tour place in {city}? Tell me the name of the place only without additional comments.")

prompt2 = PromptTemplate.from_template("What is the top 5 restaurant in the {place} in city {city} without additional comments?") #output : restaurants

prompt3 = PromptTemplate.from_template("What is the best one restaurant and food for family dinner among {restaurants} ?") #output : restaurant_information

prompt4 = PromptTemplate.from_template("How can I get the {place} by using {transport}?") #output : transport_information

final_prompt = PromptTemplate.from_template("""

Please summarize the tour information with reastaurant information and transportation by using the this information.

Restaurant informations : {restaurant_information}

Transport information : {transport_information}

""")

            

chain1 = LLMChain(llm=model,prompt=prompt1,output_key="place",verbose=True)    

chain2 = LLMChain(llm=model,prompt=prompt2,output_key="restaurants",verbose=True)

chain3 = LLMChain(llm=model,prompt=prompt3,output_key="restaurant_information",verbose=True)

chain4 = LLMChain(llm=model,prompt=prompt4,output_key="transport_information",verbose=True)

final_chain = LLMChain(llm=model,prompt=final_prompt,output_key="tour_summary",verbose=True)

chain = SequentialChain(chains=[chain1,chain2,chain3,chain4,final_chain]

                        ,input_variables=["city","transport"],verbose=True)

chain.run({'city':'Seoul','transport':'subway'})

 

앞의 예제에 비하면 LLMChain의 수만 늘어난것을 확인할 수 있다. 그런데 chains에서 입력값은 chains=[chain1,chain2,chain3,chain4,final_chain] 와 같은데, chain1에서 chain2,4로 분기하고  chain3,4의 출력값을 final_chain으로 모으는 흐름은 어떻게 표현했을까? 답은 output_key와 템플릿에 있다. 

chain1의 출력값 키는 chain1 = LLMChain(llm=model,prompt=prompt1,output_key="place",verbose=True) 와 같이 {place}가 된다. 

이 {place}는 chain2와4의 프롬프트에서 다음과 같이 입력값으로 사용된다. 

 

prompt2 = PromptTemplate.from_template("What is the top 5 restaurant in the {place} in city {city} without additional comments?") 

 

prompt4 = PromptTemplate.from_template("How can I get the {place} by using {transport}?")

마찬가지로,  chain3와4의 출력이 final_chain으로 합쳐지는 것도 아래 코드와 같이 chain3의 출력키가 “restaurant_information”, chain4의 출력키가 “transport_information” 인데, 

 

chain3 = LLMChain(llm=model,prompt=prompt3,output_key="restaurant_information",verbose=True)

chain4 = LLMChain(llm=model,prompt=prompt4,output_key="transport_information",verbose=True)

아래 final_chain의 프롬프트에서 입력변수로 사용되었다.

 

final_prompt = PromptTemplate.from_template("""

Please summarize the tour information with reastaurant information and transportation by using the this information.

Restaurant informations : {restaurant_information}

Transport information : {transport_information}

""")

 

아래는 최종 출력 결과이다. Chain의 실행 과정을 모니터링 하기 위해서 SequentialChain과 LLMChain에 모두 verbose=True 옵션을 설정하였다. 




> Entering new SequentialChain chain... # SequentialChain 진입



> Entering new LLMChain chain... #Chain1 진입

Prompt after formatting:

what is the famous tour place in Seoul? Tell me the name of the place only without additional comments.

 

> Finished chain.



> Entering new LLMChain chain... #Chain2 진입

Prompt after formatting:

What is the top 5 restaurant in the  

 

Gyeongbokgung Palace in city Seoul without additional comments?

 

> Finished chain.



> Entering new LLMChain chain... #Chain3 진입

Prompt after formatting:

What is the best one restaurant and food for family dinner among 

 

1. HwaDongJang (화동장)

2. Tosokchon Samgyetang (토속촌 삼계탕)

3. Gyeongbokgung Palace Korean Restaurant (경복궁 한정식당)

4. Jaha Son Mandu (자하손만두)

5. Gaehwaok (개화옥) ?

 

> Finished chain.



> Entering new LLMChain chain... #Chain4 진입

Prompt after formatting:

How can I get the  

 

Gyeongbokgung Palace by using subway?

 

> Finished chain.



> Entering new LLMChain chain... #Final_Chain 진입

Prompt after formatting:

Human: 

Please summarize the tour information with reastaurant information and transportation by using the this information.

Restaurant informations :  

 

It ultimately depends on personal preferences, but Gyeongbokgung Palace Korean Restaurant (경복궁 한정식당) would be a great option for a family dinner. It offers traditional Korean cuisine in a beautiful setting, perfect for a special occasion or family gathering. The menu is also diverse, catering to different tastes and dietary restrictions. Additionally, the restaurant is located near the famous Gyeongbokgung Palace, providing a unique cultural experience for the whole family.

Transport information : To get to Gyeongbokgung Palace by subway, you can take the Seoul Subway Line 3 to Gyeongbokgung Station (Exit 5). From there, it is a short walk to the palace entrance. Alternatively, you can take the Seoul Subway Line 5 to Gwanghwamun Station (Exit 2) and walk to the palace. Both stations are within walking distance to the palace and have clear signs in English to guide you. You can also use a navigation app on your phone to find the best route from your current location to Gyeongbokgung Palace.



> Finished chain.

 

> Finished chain.

# 최종 결과

 

' \nThe recommended restaurant for a family dinner is Gyeongbokgung Palace Korean Restaurant, which offers traditional Korean cuisine and is located near the famous Gyeongbokgung Palace. For transportation, you can take the Seoul Subway Line 3 or Line 5 to either Gyeongbokgung Station (Exit 5) or Gwanghwamun Station (Exit 2) and walk to the palace. Navigation apps are also available to help you find the best route.'

 

Router Chain

지금까지 순차적 LLMChain을 실행하는 방법에 대해서 알아보았다. 다른 방법으로는 입력값에 따라서 Chain을 선택해서 분기 하는 RouterChain에 대해서 알아보겠다. 여행 챗봇에서 레스토랑 정보, 교통편정보, 여행지 정보에 대한 LLM 모델 3개를 LLMChain으로 만들어놓고, 질문의 종류에 따라서 적절한 LLMChain으로 라우팅 하는 시나리오이다.  



만약에 적절한 LLMChain이 없는 경우, 예를 들어 여행정보가 아니라 전혀 관계 없는 질문이 들어올 경우에는 앞에 언급한 3가지 LLMChain이 아니라 Default Chain을 사용하도록 구현한다. 

 

이제 예제 코드를 보자. 

from langchain.chains.router import MultiPromptChain

from langchain.chains.router.llm_router import LLMRouterChain,RouterOutputParser

from langchain.prompts import PromptTemplate

from langchain.chains.router.multi_prompt_prompt import MULTI_PROMPT_ROUTER_TEMPLATE

from langchain.chains import LLMChain

from langchain.llms import OpenAI



OPEN_AI_APIKEY="{YOUR_OPENAI_KEY}"

model = OpenAI(openai_api_key=OPEN_AI_APIKEY)

 

restaurant_template = """

You are a tourist guide. You know many good restaurants around the tourist destination.

You can recommend good foods and restaurants.

Here is a question:

{input}

"""

 

transport_template = """

You are a tourist guide. You have a lot of knowledge in public transportation.

You can provide information about public transportation to help tourists get to the tourist destination.

Here is a question:

{input}

"""

 

destination_template = """

You are a tourist guide. You know many good tourist places.

You can recommend good tourist places to the tourists.

Here is a question:

{input}

"""

 

prompt_infos = [

    {

        "name":"restaurants",

        "description":"Good for recommending restaurants around the tourist destinations",

        "prompt_template": restaurant_template

    },

    {

        "name":"transport",

        "description":"Good for guiding the transport to get the place",

        "prompt_template": transport_template

    },

    {

        "name":"destination",

        "description":"Good for recommending place to tour",

        "prompt_template": destination_template

    }

]

 

destination_chains = {}

for prompt_info in prompt_infos:

    name = prompt_info["name"]

    prompt = PromptTemplate.from_template(prompt_info["prompt_template"])

    chain = LLMChain(llm = model, prompt=prompt,verbose=True)

    destination_chains[name] = chain

default_prompt = PromptTemplate.from_template("{input}")

default_chain = LLMChain(llm=model, prompt=default_prompt)

   

destinations = [f"{p['name']}: {p['description']}" for p in prompt_infos]

destinations_str = "\n".join(destinations)

 

router_template = MULTI_PROMPT_ROUTER_TEMPLATE.format(

    destinations=destinations_str

)

#print(router_template)

router_prompt = PromptTemplate(

    template=router_template,

    input_variables=["input"],

    output_parser=RouterOutputParser(),

)

 

router_chain = LLMRouterChain.from_llm(model, router_prompt)



chain = MultiPromptChain(router_chain=router_chain, 

                         destination_chains=destination_chains, 

                         default_chain=default_chain, verbose=True

                        )

 

chain.run("What is best restaurant in Seoul?")



먼저 앞의 시나리오에서 이야기 한 restaurant chain, transport chain, destination chain 3개를 생성한다. 

생성된 chain은 destination_chains 라는 이름의 딕셔너리에 저장된다. 그리고 default_chain을 생성한다. default_chain은 별도의 프롬프트가 없이 입력된 input을 그대로 LLM에 저장하도록 한다. 

for prompt_info in prompt_infos:

    name = prompt_info["name"]

    prompt = PromptTemplate.from_template(prompt_info["prompt_template"])

    chain = LLMChain(llm = model, prompt=prompt,verbose=True)

    destination_chains[name] = chain

default_prompt = PromptTemplate.from_template("{input}")

default_chain = LLMChain(llm=model, prompt=default_prompt)

 

default_chain을 포함하여 총 4개의 LLMChain을 생성하였다. 

이 4개의 LLMChain중 어느 Chain을 사용할것인지를 판단하는 RouterChain을 만들어야한다.  RouterChain은 미리 Langchain에서 정의되어 있는 MULTI_PROMPT_ROUTER_TEMPLATE 프롬프트를 이용하여 생성한다.  이때 포맷팅으로 destination_str을 전달한다. 

 

router_template = MULTI_PROMPT_ROUTER_TEMPLATE.format(

    destinations=destinations_str

)

destination_str의 내용은 아래와  같다.

restaurants: Good for recommending restaurants around the tourist destinations

transport: Good for guiding the transport to get the place

destination: Good for recommending place to tour

앞에서 restaurant,transport,destination LLMChain을 만들기 위한 정보를 p_info 리스트에 저장했는데, p_info 리스트에는 각 LLMChain을 만들기 위한 LLMChain의 이름과 PromptTemplate 객체 정보가 있고, LLMChain에 대한 설명을 description에 저장해놓았다. destination_str은 “LLMChain이름 : LLMChain의 description”으로된 문자열로, RouterChain은 들어온 질문에 대해서 어떤 LLMChain을 사용할지를 이 description을 보고 결정한다. (LLM 모델이 질문과 description을 보고 적절한 LLMChain을 찾도록 MULTI_PROMPT_ROUTER_TEMPLATE에 프롬프트로 정의 되어 있다.)

 

정리하자면 RouterChain은 각 LLMChain에 대한 설명(description)을 기반으로, 입력된 질문에 적합한 LLMChain을 선택하는 역할을 한다. 

 

RouterChain을 생성하였으면, 이 RouterChain을 다른 LLMChain들과 연결해야 하는데, 이 역할을 하는 것이 MultiPromptChain이다. 아래 코드와 같이 MultiPromptChain은 분기에 대한 의사 결정을 할 Routerchain과, 분기의 목적지가 되는 destinaiton_chains를 인자로 받고, 적절한 LLMChain이 없을 경우 선택이 되는 default_chain을 인자로 받는다. 

 

chain = MultiPromptChain(router_chain=router_chain, 

                         destination_chains=destination_chains, 

                         default_chain=default_chain, verbose=True

                        )

 

완성된 코드를 실행하면 다음과 같은 결과를 얻을 수 있다. 질문이 “'What is best restaurant in Seoul?'”이기 때문에 RouterChain은 식당 관련 LLMChain인 restaurant chain을 선택하게 되고, 

You are a tourist guide. You know many good restaurants around the tourist destination.

You can recommend good foods and restaurants.

Here is a question:

What is best restaurant in Seoul?

Restaurant chain의 프롬프트가 위와 같이 선택되어 실행되는 것을 확인할 수 있다. 

 

> Entering new MultiPromptChain chain...

 

/Users/terrycho/anaconda3/lib/python3.11/site-packages/langchain/chains/llm.py:316: UserWarning: The predict_and_parse method is deprecated, instead pass an output parser directly to LLMChain.

  warnings.warn(

 

restaurants: {'input': 'What is best restaurant in Seoul?'}

 

> Entering new LLMChain chain...

Prompt after formatting:

 

You are a tourist guide. You know many good restaurants around the tourist destination.

You can recommend good foods and restaurants.

Here is a question:

What is best restaurant in Seoul?



> Finished chain.

 

> Finished chain.

 

'\nThere are many great restaurants in Seoul, but one that consistently stands out is Jungsik. This Michelin-starred restaurant offers a modern take on traditional Korean cuisine, with dishes that are both visually stunning and incredibly delicious. Their tasting menu is a must-try, featuring a variety of unique and creative dishes that showcase the best of Korean flavors. The service and atmosphere at Jungsik are also top-notch, making it a memorable dining experience for any tourist visiting Seoul. Be sure to make a reservation in advance, as this restaurant is very popular among locals and tourists alike.