langchain / / 2024. 6. 5. 15:00

[langchain] LCEL과 Runnable

LCEL은 무엇인가? LCEL은 LangChain Expression Language의 약어인데 기본적인 방식으로 Chain을 사용하도 문제가 없지만 코드를 좀 더 간략하게 사용하고 병렬처리, 비동기, 스트리밍 기능을 제공하기 위해 LCEL을 사용한다.

우선 예제를 하나 보자.

PromptTemplate을 사용하여 openai를 호출하는 방법은 아래와 같이 사용할 수 있다.

기본 방식

from dotenv import load_dotenv
from langchain.prompts import PromptTemplate
from langchain_openai import ChatOpenAI

load_dotenv()

template = "{language}로 1부터 10까지 출력하는 함수 만들어줘"

prompt_template = PromptTemplate.from_template(template)
prompt = prompt_template.format(language="Python")
llm = ChatOpenAI()
result = llm.invoke(prompt)
print(result)

출력결과

content='```python\ndef print_numbers():\n    for i in range(1, 11):\n        print(i)\n\nprint_numbers()\n```' response_metadata={'token_usage': {'completion_tokens': 26, 'prompt_tokens': 26, 'total_tokens': 52}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None} id='run-ea7f751f-00a6-4fc8-8a31-a18273dd71e8-0'

이 방식을 LCEL을 사용하여 바꿔보자.

LCEL 방식

template = "{language}로 1부터 10까지 출력하는 함수 만들어줘"

prompt = PromptTemplate.from_template(template)
llm = ChatOpenAI()
chain = prompt | llm | StrOutputParser()
result = chain.invoke({"language": "Python"})
print(result)

출력결과

Sure, here is a Python function that prints numbers from 1 to 10:

```python
def print_numbers():
    for i in range(1, 11):
        print(i)

print_numbers()
```

You can call the `print_numbers()` function to print the numbers from 1 to 10.

LCEL은 기본적으로 아래와 같은 방식으로 사용한다.

chain = prompt | llm | output_parser

위의 코드에서 각 단계를 파이프를 사용하여 연결한다. prompt를 model로 전달하고 model을 output_parser로 전달한다는 의미이다. 즉, 첫 번째 요소의 출력이 두 번째 요소의 입력으로 들어간다는 뜻이다.

Runnable

커스텀 체인을 쉽게 만들기 위해 Runnable 프로토콜을 사용한다. chat models, LLM, output parser, retriever, prompt template 등을 포함한 많은 langchain 컴포넌트가 Runnable 프로토콜을 사용한다.

이는 표준적인 방식으로 실행할 수 있고 커스텀 체인도 쉽게 만들 수 있는 인터페이스이다.

표준 인터페이스는 다음을 포함한다.

  • stream: 응답 청크를 스트림으로 출력
  • Invoke: 입력을 받아 체인을 호출
  • batch: 입력 목록으로 체인을 호출

RunnablePassthrough

위에서 LCEL로 작성한 chain을 한번 보자.

prompt = PromptTemplate.from_template("{num} x 9는?")
llm = ChatOpenAI()

chain = prompt | llm | StrOutputParser()

response = chain.invoke({"num": 9})
print(response)

실행결과

81입니다.

여기서 invoke 시 {"num": 9}인 Dictionary 타입으로 값을 넣었는데 num이 아닌 그냥 9로 입력하려면 RunnablePassthrough를 사용하면 된다. 즉, num을 RunnablePassthrough()로 지정하면 매개변수로 값을 넘길 수 있다는 것이다.

prompt = PromptTemplate.from_template("{num} x 9는?")
llm = ChatOpenAI()

# chain = prompt | llm | StrOutputParser()
chain = {"num": RunnablePassthrough()} | prompt | llm | StrOutputParser()

response = chain.invoke(3)
print(response)

실행결과

81입니다.

RunnableParallel

RunnableParallel으로 독립적인 Chain들을 병렬로 처리할 수 있다. 아래와 같이 2개의 체인을 병렬로 처리할 때 사용한다.

chain1 = (
    {"num": RunnablePassthrough()}
    | PromptTemplate.from_template("{num} x 9는? 숫자만")
    | ChatOpenAI()
    | StrOutputParser()
)
chain2 = (
    {"num": RunnablePassthrough()}
    | PromptTemplate.from_template("{num} / 9는? 숫자만")
    | ChatOpenAI()
    | StrOutputParser()
)

chains = RunnableParallel(a1=chain1, a2=chain2)
result = chains.invoke(9)
print(result)

실행결과

{'a1': '81', 'a2': '9'}

RunnableLambda

RunnableLambda를 통해 사용자 정의함수를 실행할 수 있다.

chain = (
    {"num": lambda x: x * x}
    | PromptTemplate.from_template("{num}/2 는? 숫자만 ")
    | ChatOpenAI()
    | StrOutputParser()
)

result = chain.invoke(8)
print(result)

위에서 템플릿에 {num}/2 는? 숫자만으로 되어 있는데 num에 8을 넘기면 8/2는? 숫자만으로 프롬프트가 작성되지만 위에서 {"num": lambda x: x * x}으로 num 값을 재정의(x = x*x)를 하였다. 그래서 실제 프롬프트는64/2? 숫자만으로 실행이 된다.

실행결과

32

참고

반응형
  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유