허세임
허세임
⚔️ 베테랑 파트너
📹 SNS 찐친

[8기 랭체인] LCEL의 기본개념 과 CookBook 1 따라해보기


LCEL

https://python.langchain.com/docs/expression_language/

LangChain Expression Language (LCEL):

  • 목적: 복잡한 체인을 쉽게 구성하고 관리하기 위한 도구.

  • 적용 범위: 프로토타입부터 프로덕션 단계까지.


주요 기능

  1. 스트리밍 지원

    • 개요: 체인의 첫 번째 토큰을 빠르게 받을 수 있도록 함.

    • 작동 원리:

      • 예) LLM에서 스트리밍 출력 구문 분석기로 토큰을 직접 스트리밍.

      • 결과: 원시 토큰 출력과 동일한 속도로 구문 분석된 증분 출력 청크를 수신.

  2. 비동기 지원

    • 개요: 동기 및 비동기 API 모두에서 체인 호출 가능.

    • 응용:

      • 동기 API: 프로토타이핑 중 Jupyter 노트북에서 사용.

      • 비동기 API: LangServe 서버에서 사용.

    • 장점: 프로토타입과 프로덕션에서 동일한 코드 사용, 높은 성능과 많은 동시 요청 처리 가능.

  3. 최적화된 병렬 실행

    • 개요: 병렬 실행 가능한 단계는 자동으로 최적화됨.

    • 응용: 예를 들어 여러 검색기에서 문서를 동시에 가져옴.

    • 장점: 대기 시간 최소화.

  4. 재시도 및 폴백

    • 개요: 모든 체인 부분에 대해 재시도 및 폴백 구성 가능.

    • 장점: 대규모로 체인을 안정적으로 만드는 데 유용.

  5. 중간 결과 액세스

    • 개요: 복잡한 체인에서 중간 단계의 결과에 액세스 가능.

    • 용도: 사용자에게 상태 알림, 디버깅에 유용.

    • 기능: 중간 결과 스트리밍, 모든 LangServe 서버에서 사용 가능.

  6. 입력 및 출력 스키마

    • 개요: 체인의 구조에서 추론된 Pydantic 및 JSONSchema 스키마 제공.

    • 용도: 입력 및 출력의 유효성 검사, LangServe에서 필수적 사용.

  7. 원활한 LangSmith 추적 통합

    • 개요: 모든 단계에서 발생하는 일을 LangSmith에 자동으로 기록.

    • 장점: 관찰 가능성과 디버깅 가능성 극대화.

  8. 원활한 LangServe 배포 통합

    • 개요: LCEL로 생성된 모든 체인은 LangServe를 통해 쉽게 배포 가능.

    • 장점: LangServe 활용으로 체인 관리와 배포 간소화.




CookBook1 따라해보기

대부분의 구성은 이렇게 세 묶음으로 나뉜다고 한다.

프롬포트 → LLM → 파서

PromptTemplate / ChatPromptTemplate → LLM / ChatModel → OutputParser

1. PromptTemplate + LLM

먼저 세 묶음 중 앞 두묶음 부터 예제를 시작해본다. 참고예제🔗

from dotenv import load_dotenv

load_dotenv()
from langchain.chat_models import ChatOpenAI
from langchain.prompts import ChatPromptTemplate

prompt = ChatPromptTemplate.from_template("{foo}에 관한 명언을 말해줘")
model = ChatOpenAI()

chain = prompt | model

result = chain.invoke({"foo": "시간"})
print(result)

프롬포트와 모델을 만들고, 체인에 두개를 넣어주었다.

하나만 해줘도 되는데 꽤 친절한 녀석.

Stop Sequence

하나만 말하고 멈추게 하기 위해 스탑 시퀀스 모델에 바인딩해준다.

chain = prompt | model.bind(stop=["\\n"])

Attaching Function Call information

이번에는 모델에 펑션콜을 바인딩 하도록해본다.

모델에서 생성되는 명언이 setup 에, 그리고 그에 대한 펀치라인이 punchline에 오도록 하는 함수이다.

functions = [
    {
        "name": "joke",
        "description": "A joke",
        "parameters": {
            "type": "object",
            "properties": {
                "setup": {"type": "string", "description": "The setup for the joke"},
                "punchline": {
                    "type": "string",
                    "description": "The punchline for the joke",
                },
            },
            "required": ["setup", "punchline"],
        },
    }
]
chain = prompt | model.bind(function_call={"name": "joke"}, functions=functions)

result = chain.invoke({"foo": "시간"})

라임이 잘 살아있다.



2. PromptTemplate + LLM + OutputParser

이제 결과를 예쁘게 보여주는 세번째 부분, 파서를 붙여보기로 한다.

결과를 리턴하는 함수가 명확히 있을경우 체인 마지막에 파서를 붙여서 예쁘게 결과를 받아 볼 수 있다.

from langchain.output_parsers.openai_functions import JsonOutputFunctionsParser

chain = (
    prompt
    | model.bind(function_call={"name": "joke"}, functions=functions)
    | JsonOutputFunctionsParser()
)

함수에 정의된 setup과 punchline만 예쁘게 json 형태로 나온다.

여기서 아예 원하는 값만 나오게 할 수 도있다. JsonKeyOutputFunctionsParser를 이용하면!

from langchain.output_parsers.openai_functions import JsonKeyOutputFunctionsParser

chain = (
    prompt
    | model.bind(function_call={"name": "joke"}, functions=functions)
    | JsonKeyOutputFunctionsParser(key_name="setup")
)

파서의 인풋에 내가 받고자 하는 변수명을 key_name으로 넘겨준다.

원하는것 하나만 깔끔하게 딱 나온다. 근데 답이 너무 궁금하다 시계는 왜 학교에 가지 않았을까…

Simplifying input

chain을 invoke하는 부분에 넣어야할 인풋을 간단히하기 위해 RunnableMap 이란걸 사용할 수 있다.

그러면 chain.invoke({"foo": "시간"}) → chain.invoke("시간") 이렇게 “시간” 한개만 달랑 넣어보낼수 있다.

map_ = RunnableMap(foo=RunnablePassthrough())
chain = (
    map_
    | prompt
    | model.bind(function_call={"name": "joke"}, functions=functions)
    | JsonOutputFunctionsParser(key_name="setup")
)

map 에 “시간”이 들어갈 foo 변수에 RunnablePassthrough()함수를 할당하고, 체인 맨 앞에 붙여주면 된다.

역시 결과가 잘 나왔다. 그런데 이거 계속 llm 콜 하는거 맞아? 왜 똑같은 문장이 계속 나오는거야?

시간을 비타민으로 바꿔서 넣어봤더니 또 다르게 잘 나온다. 아까 같게 나온건 우연으 ㅣ일치라고 넘어가도록 한다.

sugar

맵이라고 뭐 안만들고 체인 위에 그냥 바로 {"foo": RunnablePassthrough()} 뿌려주기만 해도 가능이라고 한다.

chain = (
    {"foo": RunnablePassthrough()}
    | prompt
    | model.bind(function_call={"name": "joke"}, functions=functions)
    | JsonKeyOutputFunctionsParser(key_name="setup")
)




Insights

이 플로우를 기본으로 여러가지 더 해보고싶다.

7기 때 기본도 모르고 무지성으로 일단 해봤는데 역시 예제를 차근차근 따라해보는게 전체적인 구성과 흐름을 아는데에 큰 도움이 될것같다.





.

6
1개의 답글

👉 이 게시글도 읽어보세요