본문 바로가기

S급 프론트엔드 개발자로 회귀하다

랭체인이란? 랭체인 맛 사탕 핥아보기 - (011화 - S급 프론트엔드 개발자로 회귀하다)

반응형

개념 소개

랭체인이란... 무엇일까요?

우리가 만든 애플리케이션에 구글 제미나이같은 유명한 LLM 모델을 탑재한다고 하였을 때, 탑재하는 이유는 여러가지가 될 수 있습니다. 애플리케이션 사용법을 가이드해주는 챗봇 역할을 시킬 수도 있고, 이미지를 만들어주거나(요새는 멀티모달이니까요 : ) ), 감정적인 대화를 나누거나, 심지어 요새는 LLM 기반으로 타로 점사도 보더라구요.

이처럼, LLM을 탑재시키면 정말로 다양한 분야에 다양한 방식의 작업들을 하기 위한 기능들을 응용프로그램에 구현하여, 사용자들에게 색다른 경험을 할 수 있게 됩니다.

 

여기서 LLM을 활용한 애플리케이션 개발의 수요에 힘입어 등장한 개념이 LangChain 프레임워크입니다.

LangChain 프레임워크를 활용하면, LLM을 더 쉽게 연동할 수 있도록 라이브러리화 되어있어서, 사용자가 아주 쉽게 모델과 연동을 할 수 있습니다. 또한 에이전트를 만들어서 특정 기능을 아주 디테일하게 수행할 수 있도록 어려운 작업이 가능하도록 설계하는 등의 작업을 쉽게 해줍니다.

정리해서 한문장으로 다시 이야기해보면, 랭체인은 LLM(대규모 언어 모델)을 활용한 애플리케이션을 쉽게 개발할 수 있도록 도와주는 오픈소스 기반 프레임워크입니다. 파이썬 또는 자바스크립트 언어 기반으로 구현이 되어있습니다.

 

어떻게 구성되어있나요?

랭체인은  LLM을 잘 사용하기 위해 필요한 요소들 (파이썬의 함수나 클래스 따위가 되겠죠) Runnable 컴포넌트라는 상위 개념으로 묶어서 설계한 것이 가장 큰 특징 중 하나입니다.

이 컴포넌트를 LCEL이라는 언어로 체인처럼 묶을 수 있도록 하였고, 그래서 랭 + 체인 이라는 표현을 쓰지 않았을까 합니다.

 

구성요소 0. Prompt

랭체인에서, 프롬프트는 사용자의 질문이 될 수 있습니다. 다만, Text로 이루어진 문자열을 단순히 프롬프트라고 부르기보다는, 사용자에게 받은 입력을 미리 특정 탬플릿에 넣어서 포멧팅하고, 최종적으로 모델에게 입력할 텍스트 덩어리 형태로 가공된 것을 프롬프트라고 합니다.

from langchain.prompts import ChatPromptTemplate

# [일반 문자열] : 매번 전체 문장을 다 바꿔야 함 (관리 어려움)
text = "너는 번역가야. 'Hello'를 한국어로 번역해줘."

# [랭체인 템플릿] : 구조를 잡아두고 변수만 갈아끼움 (재사용성 높음)
template = ChatPromptTemplate.from_messages([
    ("system", "당신은 {language_to} 번역 전문가입니다."),
    ("human", "{text}를 번역해주세요."),
])

# 실행 시점에 데이터 주입
final_prompt = template.invoke({
    "language_to": "일본어",
    "text": "Hello"
})

# 결과: 
# System: 당신은 일본어 번역 전문가입니다.
# Human: Hello를 번역해주세요.

 

구성요소 1. Chain

랭체인에서, LLM 모델과 간단한 대화를 할 수 있도록 구현하고싶다면, 보통 컴포넌트들끼리 체인을 구성해서 입력과 결과를 얻습니다. 아래와 같은 pseudo-code 를 통해 동작 방식을 볼 수 있습니다. 아래 코드는 Prompt와 LLM 모델이라는 2개의 컴포넌트를 체이닝한 간단한 체인입니다. 약간의 추가설명이 필요하다면, Chain, LLM, Prompt와 같은 Runnable 컴포넌트는 invoke 같은 함수를 호출 할 수 있도록 프레임워크에 설계되어있습니다.

from langchain.chat_models import ChatOpenAI
from langchain.prompts import ChatPromptTemplate

# 1. 모델 준비 (뇌)
model = ChatOpenAI()

# 2. 프롬프트 준비 (지시사항)
prompt = ChatPromptTemplate.from_template("{topic}에 대해 짧게 설명해줘.")

# 3. 체인 생성 (파이프라인 연결: 프롬프트 -> 모델)
chain = prompt | model

# 4. 실행
result = chain.invoke({"topic": "인공지능"})
print(result) # 출력: "인공지능은 컴퓨터가 인간의 지능을 모방하는 기술입니다..."

 

구성요소 2. Agent

개발자가 특정 답변을 하는 LLM을 만들고 싶을 때, 그럼 일일이 체인을 만들어서 여러가지 상황에 대처할 수 있도록 기나긴 체인을 하나하나 만들어줘야할까요? 갑자기 뭔가 최신자료를 요청했을 때, 알아서 위키에서 검색을 한다거나, 인터넷에서 자료를 찾아보고 그 내용을 반영해서 최신화된 답변을 하게 할 수 도 있겠죠. 개발자가 일일이 이런것들을 하지않아도, Tool 형태로 랭체인에게 여러가지 도구와 무기를 쥐어주는 것 만으로도, AI가 알아서 해당 행동을 판단하고 진행할 수 있도록 할 수 있습니다. 비로소 단순한 채팅머신에서 에이전트로 변모하는것이죠.

from langchain.agents import initialize_agent, AgentType
from langchain.tools import WikipediaTool, CalculatorTool

# 1. 도구 준비 (검색, 계산기)
tools = [WikipediaTool(), CalculatorTool()]

# 2. 에이전트 초기화 (모델 + 도구들)
# 'zero-shot-react-description': 질문을 보고 설명을 읽은 뒤 적절한 도구를 선택하는 방식
agent = initialize_agent(
    tools, 
    model, 
    agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION
)

# 3. 실행 (복합 질문)
# 에이전트의 사고 과정: 
# 1) "미국 대통령 나이" 검색(Wikipedia) -> 81세 확인
# 2) "81 * 1.5" 계산(Calculator) -> 121.5 도출
agent.run("현재 미국 대통령의 나이에 1.5를 곱하면 얼마야?")

 

구성요소 3. Memory

LLM은 사실 기억력이 없습니다. 방금 한 질문이 마음에 안들어서 뭔가 다른 답변을 요구한다면, 최악의 경우에는 똑같은 식의 잘못된 답변을 하게 될 수 있습니다. 우리가 사용하는 ChatGPT나, Gemini 서비스에는 사실 똑똑한 LLM 기반 에이전트가 이미 탑재되어있는 것이죠. 우리도 LLM을 똑똑하게 쓰기위해서는 기억력을 부여할 필요가 있습니다. 아래 코드는 LLM에 메모리 기능을 탑재하는 예시 코드입니다.

from langchain.memory import ConversationBufferMemory
from langchain.chains import ConversationChain

# 1. 메모리 생성 (대화 기록장)
memory = ConversationBufferMemory()

# 2. 대화형 체인 생성 (모델 + 메모리 결합)
# 이제 이 체인은 대화를 할 때마다 memory를 참고하고 업데이트합니다.
conversation = ConversationChain(
    llm=model, 
    memory=memory
)

# 3. 대화 실행
conversation.predict(input="안녕, 나는 철수야.") 
conversation.predict(input="내 이름이 뭐지?") 

# 출력: "당신의 이름은 철수입니다." (앞의 대화를 기억함)

 

또 다른 랭체인의 특징은 없나요?

- 이외에도, LLM 모델이 출력하는 결과가 String이 아니기때문에, 출력 결과를 더 잘 처리하기 위해 존재하는 StringParser라던지, Json 형태로 결과값을 변환시켜주는 JsonParser같은 녀석들이 있습니다. 물론 이녀석들도 전부 랭체인에 존재하는 Runnable 컴포넌트들입니다.

 

+ LLM 모델에는 생각보다 사전에 설정할 수 있는 옵션들이 많이 있습니다.

 

 

 

 

 

 

+ invoke함수처럼, Runnable 컴포넌트에서 실행가능한 함수들이 있는데, 이를 Runnable 인터페이스라고 합니다.

아래는 다양한 인터페이스 함수들입니다.

invoke(input):

  • 가장 기본적인 실행 함수입니다.
  • 입력값 하나를 넣으면, 체인을 끝까지 통과한 후 최종 결과값 하나를 반환합니다.
  • 예: chain.invoke({"topic": "AI"})

stream(input):

  • 결과를 한 번에 주지 않고, 생성되는 조각(Chunk) 단위로 실시간 반환합니다. (Iterator 반환)
  • ChatGPT처럼 글자가 타자 치듯 나오는 효과를 구현할 때 필수적입니다.
  • 예: for chunk in chain.stream({...}): print(chunk)

batch(inputs):

  • 입력값의 리스트를 받아 병렬로 처리합니다.
  • 여러 질문을 한꺼번에 처리해야 할 때, for 문을 돌리는 것보다 훨씬 빠릅니다.
  • 예: chain.batch([{"topic": "AI"}, {"topic": "요리"}])

ainvoke(input):

  • invoke의 비동기 버전입니다.
  • 예: await chain.ainvoke(...)

astream(input):

  • stream의 비동기 버전입니다.
  • 예: async for chunk in chain.astream(...):

abatch(inputs):

  • batch의 비동기 버전입니다.

stream_log(input):

  • 최종 결과뿐만 아니라, 중간 단계의 상태(State) 변화까지 스트리밍으로 보여줍니다.
  • 예: "검색은 끝났고, 지금 답변 생성 중" 같은 상세 로그를 UI에 띄울 때 유용합니다.

bind(**kwargs):

  • 체인을 실행할 때 특정 파라미터를 런타임에 고정해서 전달합니다.
  • 예: 모델에 stop 토큰을 지정하거나, OpenAI Function Calling 스키마를 붙일 때 사용합니다.
  • 예: model.bind(stop=["END"])
반응형