LCEL
https://python.langchain.com/docs/expression_language/
LangChain Expression Language (LCEL):
목적: 복잡한 체인을 쉽게 구성하고 관리하기 위한 도구.
적용 범위: 프로토타입부터 프로덕션 단계까지.
주요 기능
스트리밍 지원
개요: 체인의 첫 번째 토큰을 빠르게 받을 수 있도록 함.
작동 원리:
예) LLM에서 스트리밍 출력 구문 분석기로 토큰을 직접 스트리밍.
결과: 원시 토큰 출력과 동일한 속도로 구문 분석된 증분 출력 청크를 수신.
비동기 지원
개요: 동기 및 비동기 API 모두에서 체인 호출 가능.
응용:
동기 API: 프로토타이핑 중 Jupyter 노트북에서 사용.
비동기 API: LangServe 서버에서 사용.
장점: 프로토타입과 프로덕션에서 동일한 코드 사용, 높은 성능과 많은 동시 요청 처리 가능.
최적화된 병렬 실행
개요: 병렬 실행 가능한 단계는 자동으로 최적화됨.
응용: 예를 들어 여러 검색기에서 문서를 동시에 가져옴.
장점: 대기 시간 최소화.
재시도 및 폴백
개요: 모든 체인 부분에 대해 재시도 및 폴백 구성 가능.
장점: 대규모로 체인을 안정적으로 만드는 데 유용.
중간 결과 액세스
개요: 복잡한 체인에서 중간 단계의 결과에 액세스 가능.
용도: 사용자에게 상태 알림, 디버깅에 유용.
기능: 중간 결과 스트리밍, 모든 LangServe 서버에서 사용 가능.
입력 및 출력 스키마
개요: 체인의 구조에서 추론된 Pydantic 및 JSONSchema 스키마 제공.
용도: 입력 및 출력의 유효성 검사, LangServe에서 필수적 사용.
원활한 LangSmith 추적 통합
개요: 모든 단계에서 발생하는 일을 LangSmith에 자동으로 기록.
장점: 관찰 가능성과 디버깅 가능성 극대화.
원활한 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기 때 기본도 모르고 무지성으로 일단 해봤는데 역시 예제를 차근차근 따라해보는게 전체적인 구성과 흐름을 아는데에 큰 도움이 될것같다.
.