랭체인

강의 Ch.4 11~13 파일 05-Runnable

유방울 2025. 1. 18. 19:59

Runnable 

3가지가 있음

 

데이터 효과적으로 전달하는 방법

key value 값으로 넣어주느 거 보다는

하나만 넣어주고 싶을 때가 있음

 

아래는 기존 코드

invoke시 딕셔너리를 사용함

from langchain_core.prompts import PromptTemplate
from langchain_openai import ChatOpenAI


# prompt 와 llm 을 생성합니다.
prompt = PromptTemplate.from_template("{num} 의 10배는?")
llm = ChatOpenAI(temperature=0)

# chain 을 생성합니다.
chain = prompt | llm

# chain 을 실행합니다.
chain.invoke({"num": 5})

AIMessage(content='50입니다.', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 4, 'prompt_tokens': 16, 'total_tokens': 20, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-3.5-turbo-0125', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, id='run-6b9cef1e-ad02-41d6-9c2b-05c1e0948412-0', usage_metadata={'input_tokens': 16, 'output_tokens': 4, 'total_tokens': 20, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})

Runnablepassthrough (RAG할 때 자주 쓰임)

: 데이터를 받아서 전달한다는 의미

: 받은 것을 전달한 것을 알 수 있음

 

데이터 입력은 딕셔너리로 들어가야 하니까 파이프를 앞에 추가함

-> 사용자가 값만 넣을 수 있음

from langchain_core.runnables import RunnablePassthrough

# runnable
RunnablePassthrough().invoke({"num": 10})

{'num': 10}

 

# prompt는 무조건 dictionary type이어야 함
runnable_chain = {"num": RunnablePassthrough()} | prompt | ChatOpenAI()

# dict 값이 RunnablePassthrough() 로 변경되었습니다.
runnable_chain.invoke(10)

AIMessage(content='10의 10배는 100입니다.', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 12, 'prompt_tokens': 16, 'total_tokens': 28, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-3.5-turbo-0125', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, id='run-00126aa7-18d9-4ea5-9ebf-c8b3cf696b9b-0', usage_metadata={'input_tokens': 16, 'output_tokens': 12, 'total_tokens': 28, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})

 

그니까 딕셔너리를 파이프라인에 넣었으니까 잘 작동함

why 많이 쓰는지?

나중에 key값을 기억을 못함 근데 넣어야하는 건 기억하니까!

그냥 RunnablePassthrough()를 자주 씀

 

RunnablePassthrough.assign()

key값은 x로 들어감 그래서 1로 바꾸게 됨 

이 1 은 new_num으로 들어감

num : 1 원래값이 들어가고 , 할당되어 계산된 값은 new_num에 들어감

# 입력 키: num, 할당(assign) 키: new_num
# num도 출력되고 new_num 도 출력됨
(RunnablePassthrough.assign(new_num=lambda x: x["num"] * 3)).invoke({"num": 1})

{'num': 1, 'new_num': 3}

 


RunnableParallel

from langchain_core.runnables import RunnableParallel

# RunnableParallel 인스턴스를 생성합니다. 이 인스턴스는 여러 Runnable 인스턴스를 병렬로 실행할 수 있습니다.
runnable = RunnableParallel(
    # RunnablePassthrough 인스턴스를 'passed' 키워드 인자로 전달합니다. 이는 입력된 데이터를 그대로 통과시키는 역할을 합니다.
    passed=RunnablePassthrough(),
    # 'extra' 키워드 인자로 RunnablePassthrough.assign을 사용하여, 'mult' 람다 함수를 할당합니다. 이 함수는 입력된 딕셔너리의 'num' 키에 해당하는 값을 3배로 증가시킵니다.
    extra=RunnablePassthrough.assign(mult=lambda x: x["num"] * 3),
    # 'modified' 키워드 인자로 람다 함수를 전달합니다. 이 함수는 입력된 딕셔너리의 'num' 키에 해당하는 값에 1을 더합니다.
    modified=lambda x: x["num"] + 1,
)

# runnable 인스턴스에 {'num': 1} 딕셔너리를 입력으로 전달하여 invoke 메소드를 호출합니다.
runnable.invoke({"num": 1})

{'passed': {'num': 1}, 'extra': {'num': 1, 'mult': 3}, 'modified': 2}

chain 도 가능

chain1 = (
    {"country": RunnablePassthrough()}
    | PromptTemplate.from_template("{country} 의 수도는?")
    | ChatOpenAI()
)
chain2 = (
    {"country": RunnablePassthrough()}
    | PromptTemplate.from_template("{country} 의 면적은?")
    | ChatOpenAI()
)

combined_chain = RunnableParallel(capital=chain1, area=chain2)
combined_chain.invoke("대한민국")

{'capital': AIMessage(content='서울특별시입니다.', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 11, 'prompt_tokens': 19, 'total_tokens': 30, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-3.5-turbo-0125', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, id='run-eaa58c44-d545-4115-a2fe-f91a3e0e6737-0', usage_metadata={'input_tokens': 19, 'output_tokens': 11, 'total_tokens': 30, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}}),
 'area': AIMessage(content='대한민국의 면적은 총 100,363 km² 입니다.', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 24, 'prompt_tokens': 20, 'total_tokens': 44, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-3.5-turbo-0125', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, id='run-3beb161d-747e-45e4-9b7a-1e4ff47be041-0', usage_metadata={'input_tokens': 20, 'output_tokens': 24, 'total_tokens': 44, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})}

RunnableLambda

사용자 정의 함수 맵핑 가능

 

# 오늘 날짜를 출력하고 반환함
from langchain_core.output_parsers import StrOutputParser

from langchain_core.prompts import PromptTemplate

from langchain_openai import ChatOpenAI
from datetime import datetime


# 매개변수를 받는데 a가 어디에도 쓰이지 않아서 a를 지워도 됨
# 없어도 되는 매개변수를 지정한 이유는?
# invoke로 호출해서 값이 들어가면
# runnablelambda를 매개변수가 필요하기 때문임

def get_today(a):

    # 오늘 날짜를 가져오기

    return datetime.today().strftime("%b-%d")

# 오늘 날짜를 출력

get_today(None)

'Jan-22'
from langchain_core.runnables import RunnableLambda, RunnablePassthrough

# prompt 와 llm 을 생성합니다.
# todya, n 둘 다 변수임
# invoke 3 은 runnablepassthrough로 들어감
# 프롬프트에 입력으로 넣어주는 입력에서 get_today가 today로 들어가는 구조임
prompt = PromptTemplate.from_template(
    "{today} 가 생일인 유명인 {n} 명을 나열하세요. 생년월일을 표기해 주세요."
)
llm = ChatOpenAI(temperature=0, model_name="gpt-4o")

# chain 을 생성합니다.
chain = (
    {"today": RunnableLambda(get_today), "n": RunnablePassthrough()}
    | prompt
    | llm
    | StrOutputParser()
)

# 출력
print(chain.invoke(3))

1. 다이앤 레인 (Diane Lane) - 1965년 1월 22일
2. 가브리엘 마흐트 (Gabriel Macht) - 1972년 1월 22일
3. 베벌리 미첼 (Beverley Mitchell) - 1981년 1월 22일

itemgetter method 포함 버전

from langchain_core.runnables import RunnableLambda, RunnablePassthrough
from operator import itemgetter

# prompt 와 llm 을 생성합니다.
# todya, n 둘 다 변수임
# invoke 3 은 runnablepassthrough로 들어감
# 프롬프트에 입력으로 넣어주는 입력에서 get_today가 today로 들어가는 구조임
prompt = PromptTemplate.from_template(
    "{today} 가 생일인 유명인 {n} 명을 나열하세요. 생년월일을 표기해 주세요."
)
llm = ChatOpenAI(temperature=0, model_name="gpt-4o")

# chain 을 생성합니다.
chain = (
    {
        "today": RunnableLambda(get_today),
        "n": itemgetter("n"),
    }  # n의 딕셔너리가 invoke되지면 itemgetter로 n의 value값만 가지고 와서 3이 가지고옴
    | prompt
    | llm
    | StrOutputParser()
)

# 출력
print(chain.invoke({"n": 3}))

1월 22일에 태어난 유명인 중 세 명을 소개하겠습니다.

1. **다이앤 레인 (Diane Lane)** - 1965년 1월 22일 출생. 미국의 배우로, 여러 영화와 TV 시리즈에서 활약했습니다.

2. **가브리엘 마흐트 (Gabriel Macht)** - 1972년 1월 22일 출생. 미국의 배우로, TV 시리즈 "Suits"에서 하비 스펙터 역으로 잘 알려져 있습니다.

3. **비비안 리 (Vivien Leigh)** - 1913년 1월 22일 출생. 영국의 배우로, 영화 "바람과 함께 사라지다"에서 스칼렛 오하라 역으로 유명합니다.

이들은 각자의 분야에서 큰 영향을 미친 인물들입니다.

Itemgetter()

dictionary에서 key의 value값을 가지고 오는 것임 

 

아래거 해석하면 word1의 value는 hello임!!

"a": itemgetter("word1")
chain.invoke({"word1": "hello", "word2": "world"})

 

from operator import itemgetter

from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnableLambda
from langchain_openai import ChatOpenAI


# 문장의 길이를 반환하는 함수입니다.
def length_function(text):
    return len(text)


# 두 문장의 길이를 곱한 값을 반환하는 함수입니다.


def _multiple_length_function(text1, text2):
    return len(text1) * len(text2)


# _multiple_length_function 함수를 사용하여 두 문장의 길이를 곱한 값을 반환하는 함수입니다.
def multiple_length_function(_dict):
    return _multiple_length_function(_dict["text1"], _dict["text2"])


prompt = ChatPromptTemplate.from_template("{a} + {b} 는 무엇인가요?")
model = ChatOpenAI()

chain1 = prompt | model

chain = (
    {
        "a": itemgetter("word1") | RunnableLambda(length_function),
        "b": {"text1": itemgetter("word1"), "text2": itemgetter("word2")}
        | RunnableLambda(multiple_length_function),
    }
    | prompt
    | model
)

chain.invoke({"word1": "hello", "word2": "world"})

AIMessage(content='5 + 25는 30입니다.', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 10, 'prompt_tokens': 22, 'total_tokens': 32, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-3.5-turbo-0125', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, id='run-2ddf015c-df0a-4aa8-aa21-9b5c21548149-0', usage_metadata={'input_tokens': 22, 'output_tokens': 10, 'total_tokens': 32, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})