LCEL (Lang Chain Expression Language)
LCEL은 LangChain 에서 체인과 루틴을 선언적 방식으로 정의할 수 있도록 도와주는 표현 언어이다.
기존 Python 코드 기반 체인 구성보다 직관적이고 간결하게 워크플로우를 만들 수 있도록 설계됨
- 함수형 프로그래밍 스타일
- 기존 LangChain에서 체인을 구성하려면 Python 클래스 직접 조합해야 했지만, LCEL 사용하면 간결한 표현식으로 정의 가능
- 연산자 기반 조합
|
파이프 연산자로 체인 연결+
더하기 연산자로 병렬 실행
# 예제
from langchain_core.prompts import PromptTemplate
from langchain_core.runnables import RunnableLamda
from langchain_openai import ChatOpenAI
# 프롬프트 템플릿 정의
prompt = PromptTemplate.from_template("Tell me a joke about {topic}")
# LLM 모델 정의
llm = ChatOpenAI()
# LCEL을 이용한 체인 구성
chain = prompt | llm
# 실행
print(chain.invoke({"topic": "cats"}))
response parsing
JsonOutputParser
와 StrOutputParser
사용하여 모델의 응답을 JSON 또는 문자열 형식으로 쉽게 변환할 수 있다.
String 형식으로 res 받기
StrOutputParser
는 모델의 출력을 단순한 문자열 형식으로 변환하는 역할을 한다.
간단한 응답을 받을 때 유용하다.
from langchain_openai import ChatOpenAI
from langchain.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
# LLM 모델 정의
llm = ChatOpenAI()
# JSON 출력 파서 설정
parser = StrOutputParser()
# 챔피언별 전략을 Str 형식으로 제공하는 프롬프트
lol_template = ChatPromptTemplate.from_messages(
[
("system", "당신은 리그오브레전드 프로게이머입니다."),
(
"user",
"저는 {champion}을 활용하여 승리를 거두고 싶습니다. 어떻게 하면 될까요?",
),
]
)
# 체인 구성
lol_chain = lol_template | llm | parser
# 실행
response = lol_chain.invoke({"champion": "리 신"})
# 문자열 응답 출력
print(response)
아래는 받은 응답이다.
리 신은 높은 기동력과 적 챔피언을 겨냥하는 능력을 가진 어쌔신(assassin) 챔피언으로 유용하게 사용될 수 있습니다.
승리를 거두기 위해선 몇 가지 전략을 고려해볼 수 있습니다.
1. **로밍 효율화**: 리 신은 빠른 기동력을 활용하여 다른 라인으로 로밍하여 적을 견제하거나 회전을 도와줄 수 있습니다. 적 챔피언들을 겨냥하여 로밍을 시도하는 것이 좋습니다.
2. **비전 관리**: 리 신은 적 챔피언을 겨냥하는 능력을 가지고 있기 때문에 적의 움직임을 파악하기 위해 비전을 관리하는 것이 중요합니다. 제어 와드를 설치하거나 스위퍼 렌즈를 이용하여 적 비전을 제거하는 것이 승리에 도움이 될 수 있습니다.
3. **타겟 선정**: 게임 내에서 중요한 적 챔피언을 잡는 것이 승리에 중요한 요소입니다. 적에 따라 중요한 우선 순위를 정하고 그에 맞추어 전투에 참여하는 것이 필요합니다.
4. **적 조합 파악**: 상대편 챔피언들의 능력과 조합을 파악하여 전략을 계획하는 것이 중요합니다. 상황에 따라 유동적으로 전략을 변화시키는 것이 승리에 도움이 될 수 있습니다. 이러한 전략들을 활용하여 리 신을 효과적으로 활용하여 승리를 거두어보세요. 또한 게임을 플레이하면서 계속해서 연습하고 경험을 쌓는 것도 중요합니다. 행운을 빕니다!
JSON 형식으로 res 받기
JsonOutputParser
는 LLM이 생성한 응답을 구조화된 JSON 형식으로 변환하는 데 사용된다.
이를 통해 데이터를 파싱하고 다룰 때보다 쉽게 사용할 수 있다.
또한 체인에서 output을 다음 LLM의 input으로 넣을 때 구조화된 입출력을 보장할 수 있다.
from langchain_openai import ChatOpenAI
from langchain.prompts import ChatPromptTemplate
from langchain_core.output_parsers import JsonOutputParser
# LLM 모델 정의
llm = ChatOpenAI()
# JSON 출력 파서 설정
parser = JsonOutputParser()
# 챔피언별 전략을 JSON 형식으로 제공하는 프롬프트
lol_template = ChatPromptTemplate.from_messages(
[
("system", "당신은 리그 오브 레전드 프로게이머입니다."),
(
"user",
"""저는 {champion}을 활용하여 승리를 거두고 싶습니다.
JSON 형식으로 제공해주세요.
예시: {{ "early_game": "...", "mid_game": "...", "late_game": "..." }}""",
),
]
)
# 체인 구성
lol_chain = lol_template | llm | parser
# 실행
response = lol_chain.invoke({"champion": "리 신"})
# JSON 응답 출력
print(response)
아래는 JSON 형태로 구조화된 출력이다.
{
'early_game': '리 신은 조직전투에 강점을 가지고 있으므로 초기에는 상대를 견제하고 교전을 선호해야 합니다. 상대의 피를 꺾어주는 역할을 잘 해내면 좋습니다.',
'mid_game': '중반에는 리 신의 스킬 다운과 스타트를 활용하여 상대를 견제하고 소모전투를 통해 적에게 압박을 가해야 합니다. 로밍을 통해 다른 라인에 도움을 줄 수도 있습니다.',
'late_game': '팀전에서는 리 신의 탱킹 역할을 수행해야 하며, 백라인을 도와주는 역할이 중요합니다. 적 전령 라인을 관리하고 드래곤 장악을 제거하여 상대를 위협할 수 있습니다.'
}
Runnables
Runnables
은 체인을 구성하는 기본적인 실행 단위이다.
함수형 프로그래밍을 스타일을 따르며, 데이터를 입력받아 변환 후 출력하는 방식으로 동작한다.
위의 예시에서 lol_chain = lol_template | llm | parser
처럼 파이프 연산자를 사용할 수 있는 이유는 각 요소가 Runnable
객체이기 때문이다.
- 데이터를 처리하는 모듈화된 구성요소
- 프롬프트 템플릿, 모델, 파서 등을 독립적으로 실행
- 입출력을 함수형으로 연결 가능
- 파이프 연산자를 통해 여러
Runnables
을 연결하여 체인 구성 가능
- 파이프 연산자를 통해 여러
- 비동기 실행
invoke_async()
- 여러 요청을 동시에 처리할 수 있도록 비동기 방식 지원
- 함수형 인터페이스 (
RunnableLamda
)- 커스텀 처리 로직 추가 가능
계속해서 위의 예제를 발전시켜보자.
from langchain_openai import ChatOpenAI
from langchain.prompts import ChatPromptTemplate
from langchain_core.output_parsers import JsonOutputParser
from langchain_core.runnables import RunnableLambda
from pydantic import BaseModel
# Pydantic 모델 정의
class ChampionStrategy(BaseModel):
early_game: str
mid_game: str
late_game: str
# LLM 모델 정의
llm = ChatOpenAI()
# JSON 출력 파서 설정
parser = JsonOutputParser()
# 챔피언별 전략을 JSON 형식으로 제공하는 프롬프트
lol_template = ChatPromptTemplate.from_messages(
[
("system", "당신은 리그 오브 레전드 프로게이머입니다."),
(
"user",
"""저는 {champion}을 활용하여 승리를 거두고 싶습니다.
JSON 형식으로 제공해주세요.
예시: {{ "early_game": "...", "mid_game": "...", "late_game": "..." }}""",
),
]
)
# Pydantic을 활용한 데이터 검증을 Runnables로 구현
def validate_response(response):
return ChampionStrategy(**response)
validate_runnable = RunnableLambda(validate_response)
# 체인 구성 (모든 요소가 Runnables)
lol_chain = lol_template | llm | parser | validate_runnable
# 실행
response = lol_chain.invoke({"champion": "리 신"})
# JSON 형태로 변환하여 출력
print(response.dict())
RunnablePassthrough
RunnablePassthrough
은 LangChain 에서 입력값을 그대로 반환하는 간단한 Runnable
이다.
입력된 값을 변환하지 않고 통과시키는 역할을 한다.
주로 중간 처리 없이 값만 전달할 때 사용되며, 복잡한 데이터 파이프라인에서 특정 시점에 데이터가 어떠한 형태로 존재하는지 로깅 역할로 많이 사용한다.
그리고 데이터의 변환 없이 검증 후 반환할 때 유용하게 사용될 수 있다.
from langchain_core.runnables import RunnablePassthrough
# 입력값을 그대로 반환하는 RunnablePassthrough 사용
passthrough_chain = RunnablePassthrough()
# 입력값을 그대로 전달
response = passthrough_chain.invoke({"champion": "리 신", "strategy": "초반 갱킹 위주"})
# 결과 출력
print(response)
RunnableParallel
여러 입력을 동시에 처리하는 경우, parallel()
을 사용할 수 있다.
여러 개의 Runnabl
을 병렬로 실행할 수 있도록 돕는 LangChain 기능이다.
여러 개의 체인이나 태스크를 동시에 실행할 때 유용하다.
from langchain_core.runnables import RunnableParallel
# 여러 챔피언에 대한 전략을 병렬로 실행
parallel_chain = RunnableParallel(
champion1=lol_chain,
champion2=lol_chain
)
responses = parallel_chain.invoke({
"champion1": {"champion": "리 신"},
"champion2": {"champion": "야스오"}
})
print(responses["champion1"].dict()) # 리 신의 전략
print(responses["champion2"].dict()) # 야스오의 전략
RunnableParallel
을 사용하면 여러 개의 LLM 요청을 동시에 실행 가능하며, 다양한 Runnable
을 조합 가능하다.
LLM 호출 뿐만 아니라 데이터 검증, 사전/사후 처리, 변환 등의 작업을 병렬로 수행 가능하다
Pydantic 구조화
Pydatic?
Pydantic
은 Python
의 데이터 검증 및 설정 관리 라이브러리로, 타입 힌트를 기반으로 데이터 유효성 검사를 수행하는 데 사용된다.
주로 FastAPI
와 함께 사용되며, 데이터 모델을 정의하고 검증하는 데 유용하다.
Pydantic
특징
- 자동 데이터 검증
- 데이터 변환
- JSON 문자열을 Python 객체로 변환 가능
- 직렬화, 역직렬화
.dict()
,.json()
메서드를 통해 Python 객체와 JSON 변환 지원
- IDE 지원
- 타입 힌트 기반으로 코드 자동완성 및 오류 감지
LLM response를 구조화된 Pydantic
을 통해 구조화 해서 받을 수 있다.
위의 리그오브레전드 예시에 Pydantic
을 적용해보자
모델의 응답으로 받던 챔피언별 전략을 구조화된 모델로 정의하여 LLM 의 JSON 응답을 Pydantic
을 통해 검증
from langchain_openai import ChatOpenAI
from langchain.prompts import ChatPromptTemplate
from langchain_core.output_parsers import JsonOutputParser
from pydantic import BaseModel
# Pydantic 모델 정의
class ChampionStrategy(BaseModel):
early_game: str
mid_game: str
late_game: str
# LLM 모델 정의
llm = ChatOpenAI()
# JSON 출력 파서 설정
parser = JsonOutputParser()
# 챔피언별 전략을 JSON 형식으로 제공하는 프롬프트
lol_template = ChatPromptTemplate.from_messages(
[
("system", "당신은 리그 오브 레전드 프로게이머입니다."),
(
"user",
"""저는 {champion}을 활용하여 승리를 거두고 싶습니다.
JSON 형식으로 제공해주세요.
예시: {{ "early_game": "...", "mid_game": "...", "late_game": "..." }}""",
),
]
)
# 체인 구성
lol_chain = lol_template | llm | parser
# 실행
response = lol_chain.invoke({"champion": "리 신"})
# Pydantic 모델로 변환하여 검증
try:
strategy = ChampionStrategy(**response)
print(strategy.dict()) # Pydantic 객체 → Dict 변환하여 출력
except Exception as e:
print(f"데이터 검증 실패: {e}")
Pydantic
을 통해 JSON 응답이 올바른 형태인지 확인 가능하며, 구조화된 데이터로 쉽게 정의된 데이터에 접근할 수 있다.
또한 JSON 응답이 잘못되었을 때 예외 처리를 통해 빠르게 디버깅 가능하다.
학습한 내용을 바탕으로 전에 했던 OP.GG와 같은 리그오브레전드 전적 검색 웹에 LLM 을 활용한 챔피언 별 상성, 전략을 제공해주는 기능을 만들어보려한다.