이미지 로딩 중...
AI Generated
2025. 11. 11. · 4 Views
Python으로 AI 에이전트 만들기 1편 - LangChain 기초와 개발 환경 설정
LangChain을 사용하여 실전 AI 에이전트를 만드는 첫 단계입니다. 개발 환경 설정부터 기본 개념까지, 초급 개발자도 쉽게 따라할 수 있도록 구성했습니다. 실무에서 바로 활용 가능한 예제와 팁을 제공합니다.
목차
1. LangChain이란_무엇인가
시작하며
여러분이 ChatGPT API를 사용해서 챗봇을 만들려고 했던 경험이 있나요? 처음에는 간단해 보이지만, 막상 시작하면 "이전 대화를 어떻게 기억하게 하지?", "여러 개의 API 호출을 어떻게 연결하지?", "외부 도구를 어떻게 사용하게 만들지?" 같은 문제에 부딪히게 됩니다.
이런 문제들은 실제 AI 애플리케이션을 개발할 때 반복적으로 나타나는 패턴입니다. 매번 바닥부터 코드를 작성하다 보면 시간도 오래 걸리고, 코드도 복잡해집니다.
특히 프롬프트 관리, 대화 기록 저장, 여러 API 연결 같은 것들을 직접 구현하면 수백 줄의 코드가 필요합니다. 바로 이럴 때 필요한 것이 LangChain입니다.
LangChain은 이러한 반복 작업을 표준화된 방식으로 쉽게 처리할 수 있게 해주는 프레임워크로, 여러분이 AI의 핵심 로직에만 집중할 수 있도록 도와줍니다.
개요
간단히 말해서, LangChain은 대규모 언어 모델(LLM)을 활용한 애플리케이션을 쉽게 개발할 수 있도록 돕는 Python 프레임워크입니다. 왜 LangChain이 필요한가요?
LLM을 단순히 호출하는 것을 넘어서, 실제 비즈니스 문제를 해결하려면 여러 컴포넌트를 조합해야 합니다. 예를 들어, 고객 지원 챗봇을 만든다면 이전 대화 내용을 기억하고, 데이터베이스에서 정보를 가져오고, 적절한 답변을 생성해야 합니다.
기존에는 이 모든 것을 직접 구현해야 했다면, 이제는 LangChain의 표준화된 컴포넌트를 사용할 수 있습니다. 마치 웹 개발에서 Django나 Flask가 있듯이, AI 개발에도 LangChain이라는 강력한 도구가 생긴 것입니다.
LangChain의 핵심 특징은 다음과 같습니다. 첫째, 모듈화된 구조로 필요한 부분만 선택해서 사용할 수 있습니다.
둘째, 다양한 LLM 제공자(OpenAI, Anthropic, Hugging Face 등)를 통일된 인터페이스로 사용할 수 있습니다. 셋째, 프롬프트 관리, 메모리, 체인, 에이전트 같은 고급 기능을 간단하게 구현할 수 있습니다.
이러한 특징들이 개발 시간을 획기적으로 단축시켜주고, 코드의 유지보수성을 높여줍니다.
코드 예제
# LangChain의 기본 구조를 보여주는 간단한 예제
from langchain.llms import OpenAI
from langchain.prompts import PromptTemplate
from langchain.chains import LLMChain
# LLM 모델 초기화 - OpenAI GPT 사용
llm = OpenAI(temperature=0.7)
# 프롬프트 템플릿 정의 - 재사용 가능한 형태
template = "다음 주제에 대해 간단히 설명해주세요: {topic}"
prompt = PromptTemplate(template=template, input_variables=["topic"])
# 체인 생성 - 프롬프트와 LLM을 연결
chain = LLMChain(llm=llm, prompt=prompt)
# 실행 - 주제를 입력하면 자동으로 답변 생성
result = chain.run(topic="인공지능")
print(result)
설명
이것이 하는 일: 위 코드는 LangChain의 가장 기본적인 사용법을 보여줍니다. 사용자가 입력한 주제에 대해 AI가 자동으로 설명을 생성하는 간단한 프로그램입니다.
첫 번째로, 필요한 모듈들을 import합니다. OpenAI는 GPT 모델을 사용하기 위한 클래스이고, PromptTemplate은 재사용 가능한 프롬프트를 만들기 위한 도구입니다.
LLMChain은 이 둘을 연결해주는 역할을 합니다. 이렇게 모듈화된 구조 덕분에 각 부분을 독립적으로 관리하고 테스트할 수 있습니다.
두 번째로, LLM 객체를 생성하고 프롬프트 템플릿을 정의합니다. temperature=0.7은 답변의 창의성을 조절하는 파라미터로, 0에 가까울수록 일관된 답변을, 1에 가까울수록 창의적인 답변을 생성합니다.
프롬프트 템플릿의 {topic} 부분은 나중에 실제 값으로 대체됩니다. 이렇게 하면 같은 형식의 질문을 여러 번 할 때 매번 프롬프트를 새로 작성할 필요가 없습니다.
세 번째로, LLMChain을 만들어서 프롬프트와 LLM을 연결합니다. 이것이 LangChain의 핵심 개념인 "체인"입니다.
여러 작업을 순차적으로 연결하여 복잡한 워크플로우를 만들 수 있습니다. 마지막으로 chain.run()을 호출하면 전체 프로세스가 자동으로 실행됩니다.
여러분이 이 코드를 사용하면 API 호출, 프롬프트 포맷팅, 응답 파싱 같은 반복적인 작업을 신경 쓰지 않아도 됩니다. 코드가 짧고 읽기 쉬워서 유지보수가 편하고, 나중에 기능을 추가하기도 쉽습니다.
예를 들어, 메모리 기능을 추가하고 싶다면 단 몇 줄만 추가하면 됩니다.
실전 팁
💡 LangChain을 처음 배울 때는 공식 문서의 Quickstart 가이드부터 시작하세요. 실무에서 자주 쓰이는 패턴들이 잘 정리되어 있습니다.
💡 개발 초기에는 temperature를 높게 설정해서 다양한 답변을 테스트해보고, 프로덕션에서는 낮춰서 일관성을 유지하세요.
💡 API 호출 비용을 절약하려면 개발 단계에서는 더 저렴한 모델(gpt-3.5-turbo)을 사용하고, 필요할 때만 고급 모델(gpt-4)로 전환하세요.
💡 LangChain의 버전 업데이트가 빠른 편이므로, 프로젝트 시작 시 requirements.txt에 정확한 버전을 명시해두는 것이 좋습니다.
2. 개발_환경_설정
시작하며
AI 프로젝트를 시작할 때 가장 먼저 마주치는 벽이 바로 개발 환경 설정입니다. "어떤 Python 버전을 써야 하지?", "라이브러리 버전 충돌은 어떻게 해결하지?", "왜 내 컴퓨터에서는 안 되는 걸까?" 같은 질문들이 끊임없이 나옵니다.
특히 AI 개발은 의존성이 복잡한 편입니다. LangChain만 해도 OpenAI, tiktoken, pydantic 같은 여러 라이브러리에 의존하고 있고, 버전이 맞지 않으면 예상치 못한 에러가 발생합니다.
실제로 많은 초보 개발자들이 코딩보다 환경 설정에서 더 많은 시간을 소비합니다. 바로 이럴 때 필요한 것이 체계적인 환경 설정 프로세스입니다.
한 번 제대로 설정해두면 나중에 프로젝트를 확장하거나, 다른 컴퓨터로 옮기거나, 팀원과 협업할 때도 문제없이 작동합니다.
개요
간단히 말해서, 개발 환경 설정은 Python 가상환경을 만들고 필요한 라이브러리를 설치하는 과정입니다. 왜 가상환경이 필요한가요?
프로젝트마다 필요한 라이브러리와 버전이 다르기 때문입니다. A 프로젝트에서는 LangChain 0.0.200을 쓰고, B 프로젝트에서는 0.1.0을 써야 한다면, 가상환경 없이는 충돌이 발생합니다.
또한 시스템 전역에 라이브러리를 설치하면 나중에 정리하기도 어렵고, 다른 프로그램에 영향을 줄 수도 있습니다. 기존에는 시스템 Python에 직접 설치했다면, 이제는 venv나 conda로 독립된 환경을 만들어 사용합니다.
이렇게 하면 프로젝트를 깔끔하게 관리할 수 있고, requirements.txt 파일 하나로 전체 환경을 재현할 수 있습니다. Python 가상환경의 핵심 특징은 다음과 같습니다.
첫째, 프로젝트별로 완전히 독립된 Python 환경을 제공합니다. 둘째, 필요한 패키지를 프로젝트 단위로 관리할 수 있습니다.
셋째, requirements.txt로 환경을 쉽게 공유하고 복제할 수 있습니다. 이러한 특징들이 팀 협업과 배포를 훨씬 수월하게 만들어줍니다.
코드 예제
# Python 가상환경 생성 및 LangChain 설치
# 터미널에서 실행하는 명령어들
# 1. 프로젝트 디렉토리 생성 및 이동
mkdir langchain-agent-project
cd langchain-agent-project
# 2. Python 가상환경 생성 (venv 사용)
python -m venv venv
# 3. 가상환경 활성화 (Windows)
venv\Scripts\activate
# 가상환경 활성화 (Mac/Linux)
source venv/bin/activate
# 4. pip 업그레이드 - 최신 버전 사용 권장
pip install --upgrade pip
# 5. LangChain과 필수 라이브러리 설치
pip install langchain openai python-dotenv
# 6. 설치된 패키지 목록 저장 - 환경 재현용
pip freeze > requirements.txt
설명
이것이 하는 일: 위 명령어들은 LangChain 프로젝트를 위한 깨끗한 개발 환경을 구축하는 전체 과정을 보여줍니다. 단계별로 실행하면 누구나 동일한 환경을 만들 수 있습니다.
첫 번째로, 프로젝트 전용 디렉토리를 만들고 그 안에 가상환경을 생성합니다. python -m venv venv 명령어는 현재 디렉토리에 'venv'라는 이름의 가상환경 폴더를 만듭니다.
이 폴더 안에는 독립된 Python 인터프리터와 pip가 포함되어 있습니다. 왜 이렇게 하냐면, 이 프로젝트에서 설치하는 모든 패키지가 이 폴더 안에만 저장되어 시스템 Python과 완전히 분리되기 때문입니다.
두 번째로, 가상환경을 활성화합니다. Windows와 Mac/Linux의 명령어가 다르니 주의하세요.
활성화가 성공하면 터미널 프롬프트 앞에 (venv)가 표시됩니다. 이 상태에서 pip로 설치하는 모든 패키지는 가상환경에만 설치됩니다.
pip를 최신 버전으로 업그레이드하는 이유는 새로운 패키지 형식을 지원하고 설치 속도도 빠르기 때문입니다. 세 번째로, 필요한 라이브러리들을 설치합니다.
langchain은 메인 프레임워크, openai는 OpenAI API를 사용하기 위한 공식 라이브러리, python-dotenv는 환경변수를 안전하게 관리하기 위한 도구입니다. 이 세 가지는 기본적으로 항상 필요합니다.
마지막으로 pip freeze > requirements.txt를 실행하면 현재 설치된 모든 패키지와 정확한 버전이 파일로 저장됩니다. 여러분이 이 환경 설정을 제대로 해두면 몇 가지 큰 이점을 얻을 수 있습니다.
첫째, 프로젝트를 다른 컴퓨터나 서버로 옮길 때 pip install -r requirements.txt 한 줄이면 똑같은 환경을 재현할 수 있습니다. 둘째, 팀원들과 협업할 때 "내 컴퓨터에서는 되는데"라는 문제가 사라집니다.
셋째, 나중에 Docker로 배포할 때도 이 requirements.txt를 그대로 사용할 수 있어서 배포가 훨씬 쉬워집니다.
실전 팁
💡 가상환경 폴더(venv)는 .gitignore에 추가하세요. Git에 올릴 필요 없이 requirements.txt만 공유하면 됩니다.
💡 conda를 사용한다면 conda create -n langchain python=3.10 같은 방식으로 Python 버전까지 지정할 수 있어 더 안전합니다.
💡 프로젝트를 처음 받은 동료가 환경을 구축할 때를 대비해서 README.md에 설치 명령어를 정리해두는 습관을 들이세요.
💡 Windows에서 가상환경 활성화가 안 된다면 PowerShell 실행 정책 때문일 수 있습니다. Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser를 관리자 권한으로 실행해보세요.
💡 주기적으로 pip list --outdated로 업데이트 가능한 패키지를 확인하되, 메이저 버전 업그레이드는 테스트 후에 진행하세요.
3. API_키_관리
시작하며
코드를 열심히 작성해서 GitHub에 올렸는데, 갑자기 OpenAI로부터 "비정상적인 API 사용이 감지되었습니다"라는 메일을 받은 경험이 있나요? 누군가가 여러분의 코드에서 API 키를 발견해서 무단으로 사용한 것입니다.
더 심각한 경우, 몇 시간 만에 수백 달러의 요금이 청구되기도 합니다. 이런 보안 사고는 생각보다 자주 발생합니다.
특히 초보 개발자들은 API 키를 코드에 직접 입력하거나, .env 파일을 실수로 Git에 커밋하는 실수를 범합니다. 한 번 노출된 키는 영구적으로 위험하므로 즉시 재발급해야 하고, 그 사이에 발생한 비용은 모두 여러분이 부담해야 합니다.
바로 이럴 때 필요한 것이 환경변수를 활용한 안전한 API 키 관리입니다. 키를 코드와 분리하고, Git에서 제외하며, 운영 환경마다 다른 키를 사용하는 것이 현대 개발의 기본 원칙입니다.
개요
간단히 말해서, API 키 관리는 민감한 정보를 코드 밖에서 안전하게 저장하고 불러오는 방법입니다. 왜 환경변수를 사용해야 하나요?
보안이 첫 번째 이유입니다. API 키는 여러분의 계정을 대표하는 비밀번호와 같습니다.
코드에 직접 쓰면 GitHub, 로그 파일, 에러 메시지 등을 통해 쉽게 노출됩니다. 두 번째 이유는 유연성입니다.
개발 환경에서는 테스트용 키를, 프로덕션에서는 실제 서비스용 키를 사용해야 하는데, 환경변수를 쓰면 코드 수정 없이 환경만 바꾸면 됩니다. 기존에는 키를 하드코딩했다면, 이제는 .env 파일에 저장하고 python-dotenv로 불러옵니다.
.env 파일은 .gitignore에 추가해서 절대 Git에 올라가지 않도록 합니다. 대신 .env.example 파일을 만들어서 어떤 환경변수가 필요한지 팀원들에게 알려줍니다.
환경변수 관리의 핵심 특징은 다음과 같습니다. 첫째, 코드와 설정을 완전히 분리하여 보안을 강화합니다.
둘째, 환경별로 다른 설정을 쉽게 적용할 수 있습니다. 셋째, 팀원마다 자신의 키를 사용하면서도 같은 코드베이스를 공유할 수 있습니다.
이러한 특징들이 안전하고 확장 가능한 애플리케이션의 기초가 됩니다.
코드 예제
# .env 파일 내용 (프로젝트 루트에 생성)
OPENAI_API_KEY=sk-proj-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
OPENAI_MODEL=gpt-3.5-turbo
TEMPERATURE=0.7
# Python 코드에서 환경변수 안전하게 불러오기
import os
from dotenv import load_dotenv
from langchain.llms import OpenAI
# .env 파일에서 환경변수 로드
load_dotenv()
# 환경변수 가져오기 - 없으면 에러 발생
api_key = os.getenv("OPENAI_API_KEY")
if not api_key:
raise ValueError("OPENAI_API_KEY가 설정되지 않았습니다. .env 파일을 확인하세요.")
# LangChain에서 환경변수 자동 인식
# 명시적으로 전달하지 않아도 OPENAI_API_KEY를 자동으로 찾음
llm = OpenAI(temperature=float(os.getenv("TEMPERATURE", "0.7")))
설명
이것이 하는 일: 위 코드는 OpenAI API 키를 안전하게 관리하고 사용하는 표준적인 방법을 보여줍니다. 키가 코드에 직접 노출되지 않으면서도 쉽게 사용할 수 있습니다.
첫 번째로, .env 파일을 프로젝트 루트 디렉토리에 만듭니다. 이 파일에는 KEY=VALUE 형식으로 환경변수를 저장합니다.
OpenAI API 키는 sk-로 시작하는 긴 문자열인데, OpenAI 웹사이트의 API Keys 섹션에서 발급받을 수 있습니다. 여기에는 키뿐만 아니라 자주 변경하는 설정값(모델 이름, temperature 등)도 함께 저장하면 편리합니다.
왜냐하면 나중에 코드 수정 없이 설정만 바꿔서 테스트할 수 있기 때문입니다. 두 번째로, Python 코드에서 load_dotenv()를 호출합니다.
이 함수는 .env 파일을 찾아서 그 안의 모든 환경변수를 메모리에 로드합니다. 기본적으로 현재 디렉토리와 상위 디렉토리에서 .env 파일을 찾습니다.
os.getenv()로 개별 환경변수를 가져올 수 있는데, 두 번째 인자로 기본값을 지정할 수 있습니다. API 키처럼 필수적인 값은 없을 때 명확한 에러 메시지를 표시하는 것이 좋습니다.
세 번째로, LangChain은 환경변수를 자동으로 인식하는 똑똑한 기능이 있습니다. OPENAI_API_KEY라는 이름의 환경변수가 있으면 OpenAI 클래스가 자동으로 찾아서 사용합니다.
그래서 OpenAI(openai_api_key=api_key) 같은 코드를 쓸 필요가 없습니다. 하지만 명시적으로 전달하고 싶다면 그렇게 해도 됩니다.
temperature 같은 값은 문자열로 저장되므로 float()로 변환해서 사용합니다. 여러분이 이 방식을 사용하면 여러 가지 보안 위험을 피할 수 있습니다.
첫째, Git에 .env를 추가하지 않는 한 키가 절대 공개되지 않습니다. 둘째, 로그에 키가 찍힐 위험도 줄어듭니다.
셋째, 서버 배포 시에는 환경변수를 서버 설정에서 직접 지정하므로 .env 파일 자체가 필요 없어집니다. 넷째, 팀원들이 각자의 개인 키를 사용할 수 있어서 비용 추적도 쉬워집니다.
실전 팁
💡 .gitignore에 .env를 반드시 추가하세요. 실수로 커밋했다면 즉시 키를 재발급하고 Git 히스토리에서도 제거해야 합니다.
💡 .env.example 파일을 만들어서 필요한 환경변수 목록을 공유하세요. 값은 비우고 키 이름만 적습니다: OPENAI_API_KEY=
💡 여러 환경(개발, 스테이징, 프로덕션)을 관리한다면 .env.development, .env.production 같은 파일로 분리하고 load_dotenv() 호출 시 경로를 지정하세요.
💡 AWS나 Azure 같은 클라우드에서는 환경변수를 서비스 설정에서 직접 관리하므로 .env 파일 없이도 작동합니다. 로컬 개발용으로만 .env를 사용하세요.
💡 API 키 외에도 데이터베이스 비밀번호, JWT 시크릿 같은 모든 민감 정보를 환경변수로 관리하는 습관을 들이세요.
4. LLM_모델_연결
시작하며
API 키도 준비했고, 코드도 작성했는데 막상 실행하면 "RateLimitError" 또는 "InvalidRequestError" 같은 에러가 나타나는 경험을 해보셨나요? OpenAI API를 제대로 사용하려면 단순히 키를 입력하는 것 이상의 이해가 필요합니다.
모델 선택, 파라미터 설정, 에러 처리까지 고려해야 할 것이 많습니다. 실제 프로젝트에서는 비용과 성능의 균형을 맞추는 것이 중요합니다.
GPT-4는 강력하지만 비싸고, GPT-3.5-turbo는 저렴하지만 정확도가 떨어질 수 있습니다. 또한 API 호출 제한, 타임아웃, 네트워크 오류 같은 실전 문제들을 어떻게 처리할지 미리 계획해야 합니다.
바로 이럴 때 필요한 것이 LangChain의 LLM 추상화 레이어입니다. 다양한 모델을 통일된 인터페이스로 사용하고, 재시도 로직, 폴백 전략, 스트리밍 응답 같은 고급 기능을 쉽게 구현할 수 있습니다.
개요
간단히 말해서, LLM 모델 연결은 LangChain을 통해 OpenAI GPT 같은 대규모 언어 모델을 프로그램에서 사용할 수 있도록 초기화하는 과정입니다. 왜 직접 API를 호출하지 않고 LangChain을 사용하나요?
첫째, 코드가 훨씬 간결해집니다. OpenAI SDK를 직접 쓰면 요청 포맷팅, 응답 파싱, 에러 처리를 모두 직접 해야 하지만, LangChain은 이를 자동화합니다.
둘째, 나중에 다른 LLM(Anthropic Claude, Google PaLM 등)으로 전환할 때 코드 수정이 최소화됩니다. 셋째, LangChain의 다른 기능들(체인, 에이전트, 메모리)과 자연스럽게 통합됩니다.
기존에는 requests나 openai 라이브러리로 직접 API를 호출했다면, 이제는 LangChain의 LLM 클래스를 사용합니다. 모델 이름과 몇 가지 파라미터만 지정하면 복잡한 설정은 자동으로 처리됩니다.
LangChain LLM의 핵심 특징은 다음과 같습니다. 첫째, 다양한 LLM 제공자를 통일된 인터페이스로 사용할 수 있습니다.
둘째, temperature, max_tokens 같은 파라미터를 직관적으로 설정할 수 있습니다. 셋째, 스트리밍, 배치 처리, 비동기 호출 같은 고급 기능을 간단하게 사용할 수 있습니다.
이러한 특징들이 개발 생산성을 크게 향상시킵니다.
코드 예제
from langchain.llms import OpenAI
from langchain.chat_models import ChatOpenAI
from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler
import os
# 기본 LLM 초기화 - 간단한 텍스트 생성용
basic_llm = OpenAI(
temperature=0.7, # 0~1: 창의성 조절 (높을수록 창의적)
max_tokens=500, # 생성할 최대 토큰 수
model_name="gpt-3.5-turbo-instruct" # 모델 선택
)
# 채팅용 LLM 초기화 - 대화형 애플리케이션에 적합
chat_llm = ChatOpenAI(
temperature=0.7,
model_name="gpt-3.5-turbo", # 채팅 전용 모델
streaming=True, # 스트리밍 응답 활성화
callbacks=[StreamingStdOutCallbackHandler()] # 실시간 출력
)
# 간단한 텍스트 생성 예제
response = basic_llm.predict("AI 에이전트란 무엇인가요?")
print(response)
# 비용 절약을 위한 설정 - 개발 단계에서 유용
cheap_llm = OpenAI(
temperature=0.3, # 낮은 temperature로 일관성 확보
max_tokens=200, # 토큰 제한으로 비용 절감
request_timeout=30 # 타임아웃 설정 (초)
)
설명
이것이 하는 일: 위 코드는 다양한 상황에 맞는 LLM 초기화 방법을 보여줍니다. 간단한 텍스트 생성부터 실시간 스트리밍까지 커버합니다.
첫 번째로, 기본 LLM 객체를 만듭니다. OpenAI 클래스는 단순한 텍스트 완성(completion) 작업에 적합합니다.
temperature는 0에 가까울수록 결정론적이고 일관된 답변을, 1에 가까울수록 창의적이고 다양한 답변을 생성합니다. 기술 문서 작성처럼 정확성이 중요하면 0.3 정도로, 창의적인 글쓰기는 0.8 이상으로 설정합니다.
max_tokens는 비용과 직결되므로 필요한 만큼만 설정하세요. 1000 토큰은 대략 750 단어 정도입니다.
두 번째로, 채팅용 모델을 초기화합니다. ChatOpenAI는 대화형 인터페이스에 최적화되어 있고, 시스템 메시지, 사용자 메시지, AI 메시지를 구분할 수 있습니다.
streaming=True를 설정하면 응답을 한꺼번에 받지 않고 생성되는 대로 실시간으로 받습니다. ChatGPT처럼 글자가 하나씩 나타나는 효과를 만들 수 있어서 사용자 경험이 좋아집니다.
StreamingStdOutCallbackHandler는 스트리밍된 내용을 터미널에 즉시 출력합니다. 세 번째로, predict() 메서드로 간단하게 응답을 받습니다.
이 메서드는 문자열을 입력받아 문자열을 반환하므로 매우 직관적입니다. 내부적으로는 API 호출, JSON 파싱, 에러 처리가 모두 자동으로 이루어집니다.
request_timeout을 설정하면 네트워크 문제로 무한정 기다리는 것을 방지할 수 있습니다. 특히 프로덕션 환경에서는 반드시 타임아웃을 설정해야 합니다.
여러분이 이렇게 설정을 잘 해두면 여러 이점이 있습니다. 첫째, 개발 단계에서는 저렴한 모델과 낮은 토큰 제한으로 빠르게 테스트하고, 프로덕션에서는 고급 모델로 전환할 수 있습니다.
둘째, 스트리밍을 활용하면 사용자가 긴 응답을 기다리는 동안 지루해하지 않습니다. 셋째, temperature 같은 파라미터를 조정해서 용도에 맞는 최적의 결과를 얻을 수 있습니다.
넷째, 여러 LLM 객체를 만들어서 작업별로 다른 설정을 사용할 수 있습니다.
실전 팁
💡 gpt-3.5-turbo는 gpt-4보다 10배 이상 저렴하고 속도도 빠릅니다. 처음에는 3.5로 시작해서 필요할 때만 4로 업그레이드하세요.
💡 개발 중에는 max_tokens을 낮게 설정해서 비용을 절약하세요. 디버깅할 때는 짧은 응답만 있어도 충분합니다.
💡 OpenAI API는 분당 요청 수(RPM)와 분당 토큰 수(TPM)에 제한이 있습니다. 대량 요청 시 tenacity 라이브러리로 자동 재시도를 구현하세요.
💡 LangChain의 캐싱 기능(langchain.cache)을 활성화하면 동일한 요청에 대해 API를 재호출하지 않아 비용과 시간을 절약할 수 있습니다.
💡 에러 처리를 위해 try-except 블록으로 OpenAIError를 캐치하고, 사용자에게 친절한 메시지를 보여주세요. 특히 rate limit 에러는 재시도 로직을 구현하는 것이 좋습니다.
5. 프롬프트_템플릿
시작하며
AI 챗봇을 만들 때 "안녕하세요, 저는 {user_name}입니다. {question}에 대해 알려주세요."처럼 비슷한 형식의 질문을 수십 번 반복해서 작성하는 자신을 발견한 적 있나요?
프롬프트를 하드코딩하면 코드가 지저분해지고, 수정할 때마다 여러 곳을 고쳐야 하며, 나중에 프롬프트만 따로 관리하기도 어렵습니다. 실제 프로젝트에서는 프롬프트가 점점 복잡해집니다.
사용자 정보, 대화 기록, 시스템 지시사항, 출력 형식 등 여러 요소가 조합되어야 하는데, 이를 문자열 연결로 처리하면 버그가 생기기 쉽고 가독성도 떨어집니다. 또한 프롬프트 엔지니어링 과정에서 A/B 테스트를 하려면 프롬프트를 쉽게 교체할 수 있어야 합니다.
바로 이럴 때 필요한 것이 LangChain의 프롬프트 템플릿입니다. 재사용 가능하고, 변수 치환이 간편하며, 검증 로직까지 포함된 프로페셔널한 프롬프트 관리 시스템을 제공합니다.
개요
간단히 말해서, 프롬프트 템플릿은 동적으로 변하는 부분을 변수로 처리하여 재사용 가능한 프롬프트를 만드는 도구입니다. 왜 프롬프트 템플릿이 필요한가요?
첫째, DRY(Don't Repeat Yourself) 원칙을 지킬 수 있습니다. 같은 형식의 프롬프트를 여러 번 작성하지 않고 한 번만 정의하면 됩니다.
둘째, 프롬프트와 로직을 분리할 수 있습니다. 프롬프트 내용만 바꾸고 싶을 때 Python 코드를 건드리지 않아도 됩니다.
셋째, 변수 검증과 타입 체크를 자동으로 해줘서 런타임 에러를 줄여줍니다. 기존에는 f-string이나 format()으로 문자열을 조합했다면, 이제는 PromptTemplate으로 구조화된 프롬프트를 만듭니다.
템플릿은 텍스트 파일로 저장할 수도 있어서 버전 관리와 협업이 훨씬 쉬워집니다. 프롬프트 템플릿의 핵심 특징은 다음과 같습니다.
첫째, 중괄호 {variable} 구문으로 변수를 선언하고 나중에 값을 채웁니다. 둘째, 필수 변수와 선택 변수를 지정할 수 있습니다.
셋째, 부분 변수 채우기(partial)로 일부는 미리 채우고 나머지는 나중에 채울 수 있습니다. 넷째, 여러 메시지 타입(시스템, 사용자, AI)을 조합한 ChatPromptTemplate도 지원합니다.
이러한 특징들이 복잡한 프롬프트를 체계적으로 관리할 수 있게 해줍니다.
코드 예제
from langchain.prompts import PromptTemplate, ChatPromptTemplate
from langchain.prompts import HumanMessagePromptTemplate, SystemMessagePromptTemplate
# 기본 프롬프트 템플릿 - 간단한 질문 생성
basic_template = PromptTemplate(
input_variables=["topic", "difficulty"], # 필수 변수 정의
template="'{topic}'에 대해 {difficulty} 수준으로 설명해주세요."
)
# 템플릿 사용 - 변수에 값 채우기
prompt = basic_template.format(topic="파이썬 데코레이터", difficulty="초급자")
print(prompt) # '파이썬 데코레이터'에 대해 초급자 수준으로 설명해주세요.
# 채팅용 프롬프트 템플릿 - 시스템 메시지 + 사용자 메시지
chat_template = ChatPromptTemplate.from_messages([
SystemMessagePromptTemplate.from_template(
"당신은 {role} 전문가입니다. 항상 {style} 말투로 답변하세요."
),
HumanMessagePromptTemplate.from_template("{question}")
])
# 채팅 프롬프트 생성
chat_prompt = chat_template.format_messages(
role="Python 개발",
style="친근하고 격려하는",
question="LangChain을 배우기 어려울까요?"
)
# 부분 변수 채우기 - 고정값은 미리 설정
partial_template = PromptTemplate(
input_variables=["user_question"],
template="회사명: {company}\n질문: {user_question}\n답변:",
partial_variables={"company": "테크스타트업"} # 고정값
)
설명
이것이 하는 일: 위 코드는 프롬프트 템플릿의 세 가지 주요 사용 패턴을 보여줍니다. 기본 템플릿, 채팅 템플릿, 부분 변수 채우기를 각각 다룹니다.
첫 번째로, 가장 기본적인 PromptTemplate을 만듭니다. input_variables에 필요한 변수 이름을 리스트로 지정하고, template에는 실제 프롬프트 텍스트를 작성합니다.
중괄호 {}로 둘러싸인 부분이 변수가 됩니다. format() 메서드를 호출하면 변수가 실제 값으로 치환된 문자열을 반환합니다.
만약 필수 변수를 빠뜨리면 에러가 발생해서 실수를 미리 방지할 수 있습니다. 이 방식은 f-string보다 훨씬 안전하고 명시적입니다.
두 번째로, 채팅 애플리케이션에 적합한 ChatPromptTemplate을 사용합니다. 챗봇은 보통 시스템 메시지(AI의 역할 정의)와 사용자 메시지로 구성됩니다.
from_messages()로 여러 메시지 템플릿을 리스트로 전달하면 자동으로 올바른 형식으로 조합됩니다. 시스템 메시지로 AI의 페르소나를 설정하면 응답의 톤과 스타일을 일관되게 유지할 수 있습니다.
예를 들어 "전문적인 말투" 또는 "유머러스한 말투"처럼 지정할 수 있습니다. 세 번째로, partial_variables를 사용해서 일부 변수는 미리 고정값으로 설정합니다.
예를 들어 회사명, 서비스명, 기본 정책 같은 것들은 모든 프롬프트에서 동일하므로 partial로 설정하면 매번 전달할 필요가 없습니다. 나중에 format()을 호출할 때는 나머지 변수만 전달하면 됩니다.
이렇게 하면 코드 중복이 줄어들고, 설정 변경도 한 곳에서만 하면 됩니다. 여러분이 프롬프트 템플릿을 잘 활용하면 여러 장점이 있습니다.
첫째, 프롬프트를 별도의 파일로 분리해서 버전 관리할 수 있습니다. 예를 들어 prompts/customer_support.txt 같은 파일을 만들고 PromptTemplate.from_file()로 로드하면 됩니다.
둘째, A/B 테스트가 쉬워집니다. 같은 변수를 받지만 표현만 다른 여러 템플릿을 만들어서 어느 것이 더 좋은 결과를 내는지 비교할 수 있습니다.
셋째, 다국어 지원도 간단합니다. 언어별로 템플릿만 바꾸면 로직은 그대로 사용할 수 있습니다.
실전 팁
💡 복잡한 프롬프트는 여러 줄로 작성하세요. Python의 삼중 따옴표(""")를 사용하면 가독성이 좋아집니다.
💡 프롬프트에 예시(few-shot examples)를 포함하면 AI의 답변 품질이 크게 향상됩니다. 템플릿에 예시 섹션을 추가하세요.
💡 validate_template=True 옵션으로 템플릿 생성 시점에 변수 문법을 검증할 수 있습니다. 프로덕션에서는 활성화하세요.
💡 LangChain Hub(https://smith.langchain.com/hub)에서 커뮤니티가 공유한 검증된 프롬프트 템플릿을 찾아보세요. 바퀴를 재발명하지 마세요.
💡 프롬프트에 출력 형식을 명시하면(예: "JSON 형식으로 답변하세요") 파싱이 훨씬 쉬워집니다. OutputParser와 함께 사용하면 강력합니다.
6. 체인(Chain)_개념
시작하며
"AI한테 질문하고, 그 답변을 요약하고, 요약본을 번역하는" 같은 다단계 작업을 구현할 때 어떻게 하시나요? 보통은 첫 번째 LLM을 호출하고, 결과를 받아서 변수에 저장하고, 그걸 다시 두 번째 LLM에 전달하고...
이런 식으로 코드를 작성하게 됩니다. 코드가 길어지고, 에러 처리도 복잡하고, 재사용하기도 어렵습니다.
실제 AI 애플리케이션은 단일 호출로 끝나는 경우가 거의 없습니다. 데이터를 가져오고, 전처리하고, LLM에 전달하고, 결과를 후처리하고, 저장하는 등의 여러 단계가 필요합니다.
이런 워크플로우를 직접 관리하면 코드가 스파게티처럼 엉키고, 중간에 실패하면 디버깅도 어렵습니다. 바로 이럴 때 필요한 것이 LangChain의 체인(Chain) 개념입니다.
여러 컴포넌트를 순차적으로 연결하여 복잡한 워크플로우를 선언적으로 정의하고, 자동으로 데이터가 흐르게 만들 수 있습니다.
개요
간단히 말해서, 체인(Chain)은 여러 LLM 호출이나 처리 단계를 연결하여 하나의 파이프라인으로 만드는 LangChain의 핵심 개념입니다. 왜 체인을 사용해야 하나요?
첫째, 코드가 훨씬 깔끔해집니다. 중간 변수 없이 입력에서 출력까지 한 번에 정의할 수 있습니다.
둘째, 재사용성이 높아집니다. 한 번 만든 체인을 여러 곳에서 함수처럼 호출할 수 있습니다.
셋째, 디버깅과 모니터링이 쉽습니다. 각 단계의 입출력을 자동으로 추적할 수 있습니다.
넷째, 복잡한 워크플로우도 모듈처럼 조립할 수 있습니다. 기존에는 함수를 연속으로 호출하고 변수로 값을 전달했다면, 이제는 체인으로 선언적으로 정의합니다.
데이터가 체인을 따라 자동으로 흐르므로 개발자는 각 단계의 로직에만 집중하면 됩니다. 체인의 핵심 특징은 다음과 같습니다.
첫째, LLMChain은 프롬프트와 LLM을 연결하는 가장 기본적인 체인입니다. 둘째, SequentialChain은 여러 체인을 순차적으로 실행합니다.
셋째, SimpleSequentialChain은 이전 체인의 출력을 다음 체인의 입력으로 자동 전달합니다. 넷째, LCEL(LangChain Expression Language)로 | 연산자를 사용해서 더 직관적으로 체인을 구성할 수 있습니다.
이러한 특징들이 복잡한 AI 워크플로우를 우아하게 구현할 수 있게 해줍니다.
코드 예제
from langchain.llms import OpenAI
from langchain.prompts import PromptTemplate
from langchain.chains import LLMChain, SimpleSequentialChain
# 첫 번째 체인: 주제에 대한 설명 생성
llm = OpenAI(temperature=0.7)
explain_prompt = PromptTemplate(
input_variables=["topic"],
template="{topic}에 대해 3문장으로 설명해주세요."
)
explain_chain = LLMChain(llm=llm, prompt=explain_prompt)
# 두 번째 체인: 설명을 더 간단하게 요약
summary_prompt = PromptTemplate(
input_variables=["explanation"],
template="다음 설명을 한 문장으로 요약하세요: {explanation}"
)
summary_chain = LLMChain(llm=llm, prompt=summary_prompt)
# 체인 연결: 설명 → 요약의 순차적 실행
overall_chain = SimpleSequentialChain(
chains=[explain_chain, summary_chain],
verbose=True # 각 단계의 출력을 보여줌
)
# 실행: 하나의 입력으로 전체 파이프라인 실행
result = overall_chain.run("LangChain 에이전트")
print("최종 결과:", result)
설명
이것이 하는 일: 위 코드는 "설명 생성 → 요약"이라는 2단계 워크플로우를 체인으로 구현한 예제입니다. 사용자는 주제만 입력하면 나머지는 자동으로 처리됩니다.
첫 번째로, 각 단계를 별도의 LLMChain으로 만듭니다. explain_chain은 주제를 받아서 3문장 설명을 생성하고, summary_chain은 그 설명을 받아서 1문장으로 압축합니다.
각 체인은 독립적으로 테스트하고 디버깅할 수 있습니다. 예를 들어 explain_chain.run("AI")처럼 개별적으로 실행해서 결과를 확인할 수 있습니다.
이렇게 모듈화하면 코드 재사용성이 높아집니다. 두 번째로, SimpleSequentialChain으로 두 체인을 연결합니다.
이 체인은 매우 간단한 규칙을 따릅니다: 첫 번째 체인의 출력을 자동으로 두 번째 체인의 입력으로 전달합니다. 변수명을 맞추지 않아도 자동으로 연결되므로 편리합니다.
verbose=True를 설정하면 각 단계에서 무슨 일이 일어나는지 터미널에 출력되어 디버깅할 때 매우 유용합니다. 중간에 어떤 값이 전달되는지 눈으로 확인할 수 있습니다.
세 번째로, overall_chain.run()을 호출하면 전체 파이프라인이 한 번에 실행됩니다. 내부적으로는 다음과 같이 동작합니다: 1) "LangChain 에이전트"를 explain_chain에 전달, 2) 생성된 3문장 설명을 받음, 3) 그 설명을 자동으로 summary_chain에 전달, 4) 최종 1문장 요약을 반환.
이 모든 과정이 한 줄의 코드로 처리됩니다. 만약 체인 없이 구현했다면 최소 10줄 이상의 코드가 필요했을 것입니다.
여러분이 체인을 활용하면 여러 강력한 패턴을 구현할 수 있습니다. 첫째, "질문 → 답변 → 팩트체크 → 최종답변" 같은 다단계 검증 시스템을 만들 수 있습니다.
둘째, "텍스트 → 번역 → 요약 → 키워드 추출" 같은 문서 처리 파이프라인을 구축할 수 있습니다. 셋째, 중간 단계를 쉽게 추가하거나 제거할 수 있어서 실험이 빠릅니다.
넷째, 각 체인의 LLM을 다르게 설정할 수 있습니다. 예를 들어 설명 생성은 GPT-4로, 요약은 저렴한 GPT-3.5로 하면 비용을 절약할 수 있습니다.
실전 팁
💡 더 복잡한 경우에는 SequentialChain을 사용하세요. 여러 입력/출력 변수를 명시적으로 관리할 수 있습니다.
💡 LCEL 문법(prompt | llm | output_parser)을 익히면 체인을 더 직관적으로 작성할 수 있습니다. 파이프 연산자가 마치 Unix 명령어처럼 동작합니다.
💡 체인 실행 시간이 길다면 중간 결과를 캐싱하거나, 병렬 처리가 가능한 부분은 RunnableParallel을 사용하세요.
💡 프로덕션에서는 각 체인에 에러 핸들러를 추가하세요. 한 단계에서 실패해도 전체가 멈추지 않도록 폴백 전략을 구현할 수 있습니다.
💡 langchain.debug = True로 설정하면 모든 체인의 내부 동작을 상세하게 볼 수 있어 디버깅에 도움이 됩니다.
7. 메모리(Memory)_활용
시작하며
챗봇을 만들어서 "내 이름은 철수야"라고 말했는데, 다음 대화에서 "내 이름이 뭐였지?"라고 물으면 "죄송하지만 이전 대화를 기억하지 못합니다"라는 답변을 받은 경험이 있나요? LLM은 기본적으로 상태가 없기 때문에(stateless) 이전 대화를 기억하지 못합니다.
매 호출마다 독립적으로 처리되므로 맥락이 유지되지 않습니다. 실제 대화형 AI를 만들려면 대화 기록을 저장하고 관리하는 메커니즘이 필수입니다.
사용자 정보, 이전 질문, 대화 흐름 같은 컨텍스트가 없으면 AI는 매번 처음 만난 것처럼 행동합니다. 직접 구현하려면 데이터베이스 설계, 토큰 제한 관리, 오래된 메시지 정리 등 복잡한 문제들을 해결해야 합니다.
바로 이럴 때 필요한 것이 LangChain의 메모리(Memory) 시스템입니다. 대화 기록을 자동으로 관리하고, 토큰 제한 내에서 적절한 양의 컨텍스트를 유지하며, 다양한 저장 방식을 지원합니다.
개요
간단히 말해서, 메모리(Memory)는 LLM이 이전 대화를 기억하고 맥락을 유지할 수 있도록 대화 기록을 저장하고 관리하는 LangChain의 기능입니다. 왜 메모리가 필요한가요?
첫째, 자연스러운 대화를 위해서입니다. 사람은 이전에 말한 내용을 기억하고 그에 기반해서 답변하는데, AI도 마찬가지여야 합니다.
둘째, 개인화된 경험을 제공하기 위해서입니다. 사용자의 선호도, 이전 질문, 제공된 정보를 기억하면 더 나은 답변을 줄 수 있습니다.
셋째, 복잡한 태스크를 수행하기 위해서입니다. 여러 단계로 이루어진 작업은 각 단계의 결과를 기억해야 합니다.
기존에는 대화 기록을 리스트나 데이터베이스에 수동으로 저장하고 관리했다면, 이제는 LangChain의 Memory 클래스들이 자동으로 처리합니다. 토큰 제한을 넘지 않도록 조절하고, 중요한 정보는 유지하면서 불필요한 부분은 제거합니다.
메모리의 핵심 특징은 다음과 같습니다. 첫째, ConversationBufferMemory는 모든 대화를 저장합니다.
둘째, ConversationBufferWindowMemory는 최근 N개의 대화만 유지합니다. 셋째, ConversationSummaryMemory는 오래된 대화를 요약해서 토큰을 절약합니다.
넷째, 메모리를 Redis나 MongoDB 같은 외부 저장소에 연결할 수 있습니다. 이러한 특징들이 다양한 상황에 맞는 메모리 전략을 선택할 수 있게 해줍니다.
코드 예제
from langchain.llms import OpenAI
from langchain.chains import ConversationChain
from langchain.memory import ConversationBufferMemory, ConversationBufferWindowMemory
# 기본 메모리: 모든 대화 기록 저장
basic_memory = ConversationBufferMemory()
# 대화 체인 생성 - 메모리 포함
llm = OpenAI(temperature=0.7)
conversation = ConversationChain(
llm=llm,
memory=basic_memory,
verbose=True # 메모리 내용 확인용
)
# 첫 번째 대화
response1 = conversation.predict(input="안녕하세요, 제 이름은 철수입니다.")
print(response1)
# 두 번째 대화 - 이전 정보를 기억함
response2 = conversation.predict(input="제 이름이 뭐였죠?")
print(response2) # "철수"라고 답변할 것임
# 윈도우 메모리: 최근 2개 대화만 유지 (토큰 절약)
window_memory = ConversationBufferWindowMemory(k=2)
conversation_window = ConversationChain(
llm=llm,
memory=window_memory
)
# 메모리 내용 직접 확인
print("저장된 대화:", basic_memory.load_memory_variables({}))
# 메모리 초기화 (새로운 대화 세션 시작)
basic_memory.clear()
설명
이것이 하는 일: 위 코드는 LangChain의 메모리 시스템을 사용하여 대화 컨텍스트를 유지하는 챗봇을 만드는 방법을 보여줍니다. 이전 대화를 자동으로 기억합니다.
첫 번째로, ConversationBufferMemory를 생성합니다. 이것은 가장 간단한 메모리 타입으로, 모든 대화(사용자 입력과 AI 응답)를 순서대로 저장합니다.
그리고 ConversationChain을 만들 때 이 메모리 객체를 전달합니다. 이렇게 하면 체인이 자동으로 각 대화를 메모리에 저장하고, 다음 호출 시 이전 대화를 프롬프트에 포함시킵니다.
개발자가 직접 대화 기록을 관리할 필요가 전혀 없습니다. 두 번째로, 실제 대화를 진행합니다.
predict() 메서드는 사용자 입력을 받아서 LLM에 전달하는데, 이때 메모리에 저장된 이전 대화도 함께 프롬프트에 포함됩니다. 그래서 두 번째 질문인 "제 이름이 뭐였죠?"에 AI가 정확하게 "철수"라고 답변할 수 있습니다.
verbose=True를 설정하면 내부적으로 LLM에 전달되는 전체 프롬프트를 볼 수 있어서, 메모리가 어떻게 작동하는지 이해하기 쉽습니다. 세 번째로, ConversationBufferWindowMemory를 소개합니다.
이것은 최근 K개의 대화 턴만 유지하는 메모리입니다. 예를 들어 k=2로 설정하면 가장 최근 2번의 사용자 입력과 AI 응답만 기억합니다.
왜 이렇게 하냐면, 대화가 길어지면 토큰 수가 증가해서 API 비용이 늘어나고 응답 속도도 느려지기 때문입니다. 윈도우 메모리는 비용과 맥락 유지 사이의 균형을 잡는 실용적인 방법입니다.
네 번째로, load_memory_variables()로 메모리에 저장된 내용을 직접 확인할 수 있습니다. 이것은 디버깅할 때 매우 유용합니다.
또한 clear() 메서드로 메모리를 초기화하면 새로운 대화 세션을 시작할 수 있습니다. 예를 들어 사용자가 로그아웃하거나 "새 대화 시작" 버튼을 누르면 메모리를 클리어하는 것이 좋습니다.
여러분이 메모리를 잘 활용하면 훨씬 자연스러운 AI 경험을 만들 수 있습니다. 첫째, 고객 지원 챗봇에서 사용자가 이미 제공한 정보를 반복해서 물어보지 않아도 됩니다.
둘째, 교육용 AI에서 학생의 이전 질문과 이해도를 기반으로 맞춤 설명을 제공할 수 있습니다. 셋째, 복잡한 작업(예: 여행 계획)에서 단계별로 정보를 모으면서 진행할 수 있습니다.
넷째, 사용자별로 메모리를 분리하면 멀티 유저 환경에서도 각자의 대화 흐름을 유지할 수 있습니다.
실전 팁
💡 프로덕션에서는 메모리를 Redis나 데이터베이스에 저장하세요. 서버가 재시작되어도 대화 기록이 유지됩니다.
💡 긴 대화에는 ConversationSummaryMemory를 사용하세요. 오래된 메시지를 요약해서 토큰을 절약하면서도 맥락은 유지합니다.
💡 사용자 ID를 키로 사용해서 메모리를 저장하면 여러 사용자의 대화를 독립적으로 관리할 수 있습니다.
💡 민감한 정보(비밀번호, 카드번호)는 메모리에서 자동으로 필터링하는 로직을 추가하세요. GDPR 같은 개인정보 보호 규정을 준수해야 합니다.
💡 return_messages=True 옵션을 사용하면 메모리 내용을 문자열이 아닌 메시지 객체 리스트로 받을 수 있어 더 세밀한 제어가 가능합니다.
8. 에이전트(Agent)_기초
시작하며
"오늘 날씨 알려줘", "그럼 그 날씨에 맞는 옷차림 추천해줘", "그 옷을 살 수 있는 쇼핑몰 찾아줘"처럼 연속된 질문을 하면, AI가 스스로 필요한 도구를 선택해서 사용하고 최종 답변을 주면 얼마나 편할까요? 지금까지 배운 체인은 미리 정해진 순서대로만 실행되지만, 실제 세상의 문제는 상황에 따라 유연하게 대응해야 합니다.
전통적인 챗봇은 정해진 시나리오만 처리할 수 있습니다. 하지만 실제 사용자의 요청은 예측 불가능하고, 여러 단계의 추론과 도구 사용이 필요합니다.
"파이썬으로 피보나치 수열 코드 작성하고, 실행해보고, 결과 설명해줘" 같은 복잡한 요청을 처리하려면 AI가 스스로 계획을 세우고 실행할 수 있어야 합니다. 바로 이럴 때 필요한 것이 LangChain의 에이전트(Agent)입니다.
LLM의 추론 능력과 외부 도구를 결합하여, 복잡한 작업을 자율적으로 수행하는 AI 시스템을 만들 수 있습니다.
개요
간단히 말해서, 에이전트(Agent)는 LLM이 스스로 어떤 도구를 사용할지 결정하고, 여러 단계의 작업을 자율적으로 수행하는 LangChain의 고급 기능입니다. 왜 에이전트가 필요한가요?
첫째, 동적인 의사결정이 필요하기 때문입니다. 체인은 고정된 순서로 실행되지만, 에이전트는 상황에 따라 다음 행동을 선택합니다.
둘째, 외부 도구와의 통합이 필요하기 때문입니다. 검색 엔진, 계산기, 데이터베이스, API 같은 도구를 사용해야 정확한 답변을 줄 수 있습니다.
셋째, 복잡한 문제 해결을 위해서입니다. 여러 단계의 추론과 정보 수집이 필요한 작업을 자동화할 수 있습니다.
기존에는 if-else로 분기 처리를 하드코딩했다면, 이제는 LLM이 스스로 "이 질문에 답하려면 먼저 검색이 필요하겠다"라고 판단하고 적절한 도구를 선택합니다. 마치 인간 비서가 상황을 판단해서 일하는 것과 유사합니다.
에이전트의 핵심 특징은 다음과 같습니다. 첫째, ReAct(Reasoning + Acting) 패턴으로 동작합니다.
생각하고(Thought), 행동하고(Action), 관찰하고(Observation), 반복합니다. 둘째, 다양한 도구(Tools)를 등록하면 에이전트가 필요에 따라 선택해서 사용합니다.
셋째, 최대 반복 횟수를 설정해서 무한 루프를 방지할 수 있습니다. 넷째, AgentExecutor가 전체 실행 흐름을 관리합니다.
이러한 특징들이 진정한 의미의 "자율적인 AI"를 가능하게 합니다.
코드 예제
from langchain.agents import initialize_agent, Tool, AgentType
from langchain.llms import OpenAI
from langchain.utilities import SerpAPIWrapper
# LLM 초기화
llm = OpenAI(temperature=0) # 에이전트는 낮은 temperature 권장
# 도구 정의: 계산기
def calculator(expression: str) -> str:
"""수학 계산을 수행합니다."""
try:
return str(eval(expression))
except:
return "계산 오류"
# 도구 정의: 검색 (SerpAPI 필요)
# search = SerpAPIWrapper()
# 도구 리스트 생성
tools = [
Tool(
name="Calculator",
func=calculator,
description="수학 계산이 필요할 때 사용합니다. 입력은 계산식이어야 합니다."
),
# Tool(name="Search", func=search.run, description="최신 정보가 필요할 때 사용")
]
# 에이전트 초기화 - ReAct 타입 사용
agent = initialize_agent(
tools=tools,
llm=llm,
agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, # 도구 설명 기반 선택
verbose=True, # 사고 과정 출력
max_iterations=3 # 최대 3번 반복
)
# 에이전트 실행: 자동으로 계산기 도구 선택
result = agent.run("25 곱하기 4는 얼마인가요?")
print("결과:", result)
설명
이것이 하는 일: 위 코드는 도구를 사용할 수 있는 자율 에이전트를 만드는 방법을 보여줍니다. 계산이 필요한 질문을 받으면 자동으로 계산기 도구를 선택해서 사용합니다.
첫 번째로, 에이전트가 사용할 수 있는 도구들을 정의합니다. Tool 객체는 이름, 실행 함수, 설명으로 구성됩니다.
여기서 description이 매우 중요한데, LLM은 이 설명을 읽고 어떤 상황에서 이 도구를 써야 할지 판단합니다. 명확하고 구체적으로 작성해야 에이전트가 올바른 선택을 합니다.
예를 들어 "계산기"라고만 쓰는 것보다 "수학 계산이 필요할 때 사용합니다. 입력은 Python 표현식이어야 합니다"라고 쓰는 것이 훨씬 좋습니다.
두 번째로, initialize_agent()로 에이전트를 생성합니다. AgentType.ZERO_SHOT_REACT_DESCRIPTION은 도구 설명만 보고 선택하는 타입입니다(별도의 예제 없이).
ReAct 패턴은 다음과 같이 동작합니다: 1) Thought: "이 질문에 답하려면 계산이 필요하다", 2) Action: 계산기 도구 사용, 3) Observation: "결과는 100이다", 4) 최종 답변: "25 곱하기 4는 100입니다". 이 모든 과정을 LLM이 스스로 진행합니다.
세 번째로, max_iterations를 설정해서 무한 루프를 방지합니다. 가끔 에이전트가 올바른 답을 찾지 못하고 계속 도구를 호출하는 경우가 있는데, 최대 반복 횟수를 제한하면 안전합니다.
verbose=True는 에이전트의 사고 과정을 모두 출력하므로 디버깅과 학습에 매우 유용합니다. 어떤 생각을 하고, 어떤 도구를 선택했고, 결과를 어떻게 해석했는지 모두 볼 수 있습니다.
네 번째로, agent.run()을 호출하면 에이전트가 자율적으로 작동합니다. "25 곱하기 4"라는 질문을 받으면 에이전트는 다음과 같이 생각합니다: "이것은 수학 계산 문제다 → Calculator 도구를 사용해야겠다 → 입력은 '25*4'로 해야겠다 → 실행 → 결과 100을 받음 → 사용자에게 '100입니다'라고 답변".
이 모든 과정이 자동으로 이루어집니다. 여러분이 에이전트를 마스터하면 정말 강력한 AI 애플리케이션을 만들 수 있습니다.
첫째, 연구 보조 에이전트를 만들어서 웹 검색, 논문 검색, 데이터 분석을 자동으로 수행하게 할 수 있습니다. 둘째, 코딩 어시스턴트를 만들어서 코드 작성, 실행, 디버깅을 자율적으로 하게 할 수 있습니다.
셋째, 데이터 분석 에이전트로 SQL 쿼리 작성, 실행, 결과 시각화를 자동화할 수 있습니다. 넷째, 여러 API를 통합하는 개인 비서를 만들어서 일정 관리, 이메일 전송, 정보 검색을 통합할 수 있습니다.
실전 팁
💡 에이전트는 temperature를 0이나 매우 낮게 설정하세요. 일관된 추론이 중요하기 때문입니다.
💡 도구 설명은 가능한 구체적으로 작성하세요. "언제 사용해야 하는지", "입력 형식은 무엇인지", "어떤 결과를 반환하는지" 명시하세요.
💡 커스텀 도구를 만들 때는 에러 처리를 반드시 포함하세요. 도구에서 예외가 발생하면 에이전트가 중단될 수 있습니다.
💡 handle_parsing_errors=True 옵션으로 파싱 에러를 자동 복구할 수 있습니다. 에이전트가 잘못된 형식으로 출력했을 때 유용합니다.
💡 실전에서는 AgentExecutor.from_agent_and_tools()로 더 세밀한 제어를 할 수 있습니다. 타임아웃, 콜백, 에러 핸들러 등을 커스터마이즈하세요.