https://claremont.tistory.com/entry/%EC%83%9D%EC%84%B1%ED%98%95-AI-LangChain%EC%9D%B4%EB%9E%80
[생성형 AI] LangChain이란?
ㅁLangChain: LLM을 활용하여 응용 프로그램을 개발하는 프레임워크주로 OpenAI의 GPT, Google의 PaLM, Meta의 Llama 등 다양한 LLM과 통합하여 문서 검색, 데이터 분석, 자동화된 AI 응답 시스템 등을 구축하는
claremont.tistory.com
LangChain 기초: LLM과 프롬프트 엔지니어링 실습
https://claremont.tistory.com/entry/%EC%83%9D%EC%84%B1%ED%98%95-AI-LangChain%EC%9D%B4%EB%9E%80 [생성형 AI] LangChain이란?ㅁLangChain: LLM을 활용하여 응용 프로그램을 개발하는 프레임워크주로 OpenAI의 GPT, Google의 PaLM, Meta
claremont.tistory.com
ㅁLangChain Expression Language(LCEL): LangChain에서 체인을 더욱 간결하게 구성할 수 있도록 도와주는 문법
이전에는 LLM.invoke(prompt.format(___)) 형태로 실행했다면, LCEL에서는 체인의 구성 요소를 | (파이프) 연산자로 연결하여 보다 직관적인 형태로 체인을 구성할 수 있다!
이번 실습에서는 LCEL의 기본 개념과 활용법을 배우고, 다양한 방식으로 체인을 조합하는 방법을 익힌다
직접 코드를 실행하면서 깨달은 점과 트러블슈팅 과정도 함께 정리해 보았다 [:
1. LangChain 환경 설정 및 LLM 모델 준비
먼저, LangChain을 사용하기 위해 필요한 패키지를 설치한다.
pip install openai langchain langchain_openai -q
이후, OpenAI API 키를 환경 변수에 설정해야 한다.
import os
os.environ["OPENAI_API_KEY"] = "your_openai_api_key_here"
이제 LangChain에서 사용할 LLM 모델을 불러온다.
from langchain_openai import ChatOpenAI
from langchain.prompts import ChatPromptTemplate
llm = ChatOpenAI(temperature=0.5, model='gpt-4o-mini', max_tokens=1000)
이제 드디어 LCEL을 활용한 체인 구성을 실습해 보자!
2. LCEL의 기본 개념: 파이프(|) 연산자를 활용한 체인 구성
기존 방식에서는 다음과 같이 invoke()를 통해 LLM과 상호작용했다.
fun_chat_template = ChatPromptTemplate.from_messages([
('user', "tell me an English joke about {topic}, also, explain in Korean why it is fun.")
])
response = llm.invoke(fun_chat_template.format_messages(topic='AI'))
print(response.content)
LCEL을 사용하면 체인을 더욱 간결하게 만들 수 있다.
joke = fun_chat_template | llm
response = joke.invoke({'topic': 'apple'})
print(response.content)
LCEL을 사용했을 때의 차이점
- | (파이프)를 사용하여 프롬프트와 LLM을 연결한다
- invoke()의 입력값을 딕셔너리 형태로 전달한다
📌 실제 실행 후 느낀 점
- 기존보다 코드가 훨씬 직관적이고 가독성이 좋아짐을 체감할 수 있었다
- invoke({'topic': 'apple'})처럼 입력값이 1개일 때는 문자열로도 입력할 수가 있다 (joke.invoke('apple') 가능)
3. 다중 매개변수를 포함한 체인 구성
하나의 체인에서 여러 개의 입력값을 받을 수도 있다.
예를 들어, 영어와 한국어 농담을 동시에 생성하는 체인을 만들어 보자.
fun_chat_template1 = ChatPromptTemplate.from_messages([
('user', "tell me an English joke about {topic}, also, explain in Korean.")
])
fun_chat_template2 = ChatPromptTemplate.from_messages([
('user', "tell me a Korean joke about {topic}, also, explain in Korean.")
])
joke = fun_chat_template1 | fun_chat_template2 | llm
print(joke)
📌 여기서 배운 점
- 여러 개의 프롬프트를 순차적으로 연결 가능하다
- | 연산자를 사용하면 여러 개의 프롬프트를 하나의 체인으로 결합할 수가 있다
하지만, 실제로 실행해 보니 결과가 원하는 대로 나오지 않았다...
LLM이 어떤 템플릿을 먼저 실행해야 하는지 명확하게 구분하지 못하는 문제가 있었다 🥲
🔹 트러블슈팅
- 각각의 프롬프트를 개별적으로 실행한 후 응답을 조합하는 방식으로 개선
response1 = llm.invoke(fun_chat_template1.format_messages(topic='AI'))
response2 = llm.invoke(fun_chat_template2.format_messages(topic='AI'))
print(response1.content)
print(response2.content)
이 방식을 사용하면 프롬프트별로 개별적인 결과를 확인할 수 있어 보다 정확한 응답을 얻을 수 있었다
4. 파서를 활용한 체인 출력 형식 변환
LCEL에서는 출력 결과를 특정 형식으로 변환하는 파서(Parser)를 추가할 수 있다.
4-1. String 형식으로 변환하기
from langchain_core.output_parsers import StrOutputParser
recipe_template = ChatPromptTemplate.from_messages([
('system', '당신은 전 세계의 조리법을 아는 셰프입니다.'),
('user', '저는 {ingredient}를 이용한 환상적인 외국 음식을 만들고 싶습니다. 추천해주세요!')
])
recipe_chain = recipe_template | llm | StrOutputParser()
response = recipe_chain.invoke({'ingredient': '와인'})
print(response)
4-2. JSON 형식으로 변환하기
from langchain_core.output_parsers import JsonOutputParser
jsonparser = JsonOutputParser()
recipe_chain = recipe_template | llm | jsonparser
response = recipe_chain.invoke({'ingredient': '콜라'})
print(response)
📌 느낀 점
- JSON 형식으로 출력하면 AI 모델이 보다 구조적인(?) 응답을 생성할 수 있다
- 하지만 매번 형식이 달라지는 문제 발생 → 해결책으로 Pydantic을 활용한 스키마 설정을 사용해 보기로 하였다
5. Pydantic을 활용한 데이터 구조화
Pydantic을 사용하면 출력값의 구조를 강제할 수 있어, 모델이 예측 불가능한 답변을 하는 것을 방지할 수 있다.
from pydantic import BaseModel, Field
class Recipe(BaseModel):
name: str = Field(description="음식 이름")
difficulty: str = Field(description="만들기의 난이도")
origin: str = Field(description="원산지")
ingredients: list[str] = Field(description="재료")
instructions: list[str] = Field(description="조리법")
tip: str = Field(description="조리 과정 팁")
이제 Pydantic을 적용한 JSON 파서를 생성한다.
parser = JsonOutputParser(pydantic_object=Recipe)
structured_llm = llm.with_structured_output(Recipe)
response = structured_llm.invoke("생강으로 만들 수 있는 요리 레시피 알려주세요.")
print(response)
📌 트러블슈팅 과정
- 처음에는 LLM이 JSON 형식을 따르지 않는 문제가 발생했다
- Pydantic을 사용해 스키마를 정의한 후에는 보다 일관된 형식으로 결과를 얻을 수 있었다
+ LCEL의 고급 기능: RunnableParallel을 활용한 체인 병렬 실행
LCEL에서는 RunnableParallel을 사용하여 여러 개의 체인을 병렬로 실행할 수 있다.
예를 들어, 하나의 입력값으로 색깔과 음식 이름을 동시에 생성하는 체인을 만들어 보자.
from langchain_core.runnables import RunnableParallel
prompt1 = ChatPromptTemplate.from_template("색깔을 하나 알려주세요, 색깔만 출력하세요.")
prompt2 = ChatPromptTemplate.from_template("음식을 하나 알려주세요, 음식만 출력하세요.")
chain1 = prompt1 | llm | StrOutputParser()
chain2 = prompt2 | llm | StrOutputParser()
chain3 = RunnableParallel(color=chain1, food=chain2)
response = chain3.invoke({})
print(response)
📌 여기서 내가 배운 점
- LCEL의 RunnableParallel을 활용하면 여러 개의 요청을 동시에 실행할 수 있음
- 응답 속도가 빨라지고, 여러 개의 체인을 조합할 때 매우 유용
마무리하며: LCEL을 활용한 LangChain 최적화
이번 실습을 통해 LCEL을 활용하여 체인을 보다 직관적으로 구성하는 방법을 익혔다
특히, 파이프(|) 연산자, JSON 파싱, Pydantic을 활용한 데이터 구조화, RunnableParallel을 이용한 병렬 실행 등의 기능이 매우 유용했다.
💡 앞으로 시도해보고 싶은 것
- 벡터 데이터베이스(Vector DB)와 결합하여 RAG 모델을 LCEL 방식으로 최적화
- 체인의 디버깅을 쉽게 하기 위한 중간 출력값 로깅 기능 추가
LCEL(LangChain Expression Language)를 처음 배워봤는데 굉장히 강력한 문법임을 알게 되었다! 💪
'인공지능 > 생성형 AI' 카테고리의 다른 글
[생성형 AI] "Runners, Hi 챌린지" 캠페인 기획(w/프롬프트 엔지니어링) (6) | 2025.03.26 |
---|---|
[생성형 AI] LangChain과 멀티모달 모델을 활용한 스마트 냉장고 앱 만들기 실습 (2) | 2025.03.11 |
[생성형 AI] LangChain 기초: LLM과 프롬프트 엔지니어링 실습 (0) | 2025.03.11 |
[생성형 AI] 초보자도 쉽게 이해하는 벡터DB 개념 (0) | 2025.03.10 |
[생성형 AI] 초보자도 바로 이해하는 RAG(Retrieval-Augmented Generation) 개념 (0) | 2025.03.10 |