LLM을 잘 써보자

LCEL (Lang Chain Expression Language)

 

LCEL은 LangChain 에서 체인과 루틴을 선언적 방식으로 정의할 수 있도록 도와주는 표현 언어이다.

 

기존 Python 코드 기반 체인 구성보다 직관적이고 간결하게 워크플로우를 만들 수 있도록 설계됨

 

 

  1. 함수형 프로그래밍 스타일
    • 기존 LangChain에서 체인을 구성하려면 Python 클래스 직접 조합해야 했지만, LCEL 사용하면 간결한 표현식으로 정의 가능
  2. 연산자 기반 조합
    • | 파이프 연산자로 체인 연결
    • + 더하기 연산자로 병렬 실행

 

# 예제 
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

 

JsonOutputParserStrOutputParser 사용하여 모델의 응답을 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 객체이기 때문이다.

 

 

  1. 데이터를 처리하는 모듈화된 구성요소
    • 프롬프트 템플릿, 모델, 파서 등을 독립적으로 실행
  2. 입출력을 함수형으로 연결 가능
    • 파이프 연산자를 통해 여러 Runnables을 연결하여 체인 구성 가능
  3. 비동기 실행 invoke_async()
    • 여러 요청을 동시에 처리할 수 있도록 비동기 방식 지원
  4. 함수형 인터페이스 (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?

 

PydanticPython의 데이터 검증 및 설정 관리 라이브러리로, 타입 힌트를 기반으로 데이터 유효성 검사를 수행하는 데 사용된다.

 

주로 FastAPI와 함께 사용되며, 데이터 모델을 정의하고 검증하는 데 유용하다.

 

 

Pydantic 특징

  1. 자동 데이터 검증
  2. 데이터 변환
    • JSON 문자열을 Python 객체로 변환 가능
  3. 직렬화, 역직렬화
    • .dict(), .json() 메서드를 통해 Python 객체와 JSON 변환 지원
  4. 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 을 활용한 챔피언 별 상성, 전략을 제공해주는 기능을 만들어보려한다.