자바 개발자의 AI 입문기 (2편) - LangChain 기초, 체이닝과 프롬프트 템플릿
TL;DR
- LangChain: AI 호출을 체계적으로 관리하는 프레임 워크
- 프롬프트 템플릿: 재사용 가능한 질문 형식 (자바의 MessageFormat과 비슷)
- 체이닝: 여러 AI 호출을 연결 (자바의 Stream API 파이프라인과 비슷)
- 핵심: 복붙 대신 재사용 가능한 구조를 만드는 게 LangChain의 목적
- 한계: LCEL 체이닝은 직관적이지만 복잡한 분기 로직에서는 LangGraph 사용 권장
이전 편 요약
1편에서는 다음을 다뤘습니다:
- Cursor IDE 설치
- Python 환경 세팅
- OpenAI API 직접 호출
이번 편에서는 LangChain을 배웁니다. OpenAI API를 직접 호출하는 것보다 훨씬 체계적으로 AI를 다룰 수 있게 됩니다.
LangChain이 뭔가요?
한 줄 요약: AI 호출을 Spring처럼 체계화하는 프레임워크
1편에서 OpenAI API를 직접 호출했잖아요. 간단한 건 그걸로 충분합니다.
근데 실제 서비스에서는 이런 요구가 생겨요:
- 프롬프트를 재사용하고 싶다
- 여러 AI 호출을 순서대로 연결하고 싶다
- 출력 형식을 고정하고 싶다
- 외부 데이터(DB, 문서)를 AI에게 주고 싶다
이런 걸 체계적으로 하려면 LangChain이 필요합니다.
자바로 비유하면:
OpenAI API 직접 호출 = 순수 JDBC
LangChain 사용 = Spring Data JPA 사용LangChain 설치
가상환경이 활성화된 상태에서:
pip install langchain langchain-openailangchain: 핵심 라이브러리langchain-openai: OpenAI 연동 모듈
프롬프트 템플릿 - 재사용 가능한 질문 만들기
왜 필요한가?
1편에서 만든 코드 리뷰 함수를 보세요:
# 1편의 방식 - 프롬프트가 코드에 하드코딩
messages=[
{
"role": "system",
"content": """당신은 10년차 자바 시니어 개발자입니다.
주어진 코드를 리뷰하고, 개선점을 3가지 이내로 간결하게 제안하세요."""
},
{
"role": "user",
"content": f"다음 자바 코드를 리뷰해주세요:\n\n```java\n{code}\n```"
}
]문제점:
- 프롬프트가 코드 안에 섞여 있음
- 다른 언어 리뷰하려면 복붙해서 수정해야 함
- 버전 관리가 어려움
LangChain 프롬프트 템플릿 방식
from langchain_core.prompts import ChatPromptTemplate
# 재사용 가능한 템플릿 정의
code_review_template = ChatPromptTemplate.from_messages([
("system", """당신은 {experience}년차 {language} 시니어 개발자입니다.
주어진 코드를 리뷰하고, 개선점을 {max_points}가지 이내로 간결하게 제안하세요.
한국어로 답변하세요."""),
("user", "다음 {language} 코드를 리뷰해주세요:\n\n```{language}\n{code}\n```")
])
# 템플릿에 변수 채우기
messages = code_review_template.format_messages(
experience=10,
language="Java",
max_points=3,
code="public void test() { ... }"
)자바의 MessageFormat과 비슷하죠?
// Java - MessageFormat
String pattern = "{0}년차 {1} 개발자입니다.";
String result = MessageFormat.format(pattern, 10, "Java");실습: 다국어 코드 리뷰어
프롬프트 템플릿의 장점을 활용해서, 여러 언어를 리뷰할 수 있는 함수를 만들어봅시다.
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from dotenv import load_dotenv
load_dotenv()
# LLM 모델 설정
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.3)
# 프롬프트 템플릿
review_template = ChatPromptTemplate.from_messages([
("system", """당신은 {experience}년차 {language} 시니어 개발자입니다.
주어진 코드를 리뷰하고, 개선점을 {max_points}가지 이내로 간결하게 제안하세요.
한국어로 답변하세요."""),
("user", "다음 코드를 리뷰해주세요:\n\n```{language}\n{code}\n```")
])
def review_code(code: str, language: str = "Java",
experience: int = 10, max_points: int = 3) -> str:
"""다양한 언어의 코드를 리뷰하는 AI 함수"""
# 템플릿 + 모델 연결 (체이닝의 시작)
chain = review_template | llm
# 실행
response = chain.invoke({
"code": code,
"language": language,
"experience": experience,
"max_points": max_points
})
return response.content
# Java 코드 리뷰
java_code = """
public User findUser(Long id) {
return userRepository.findById(id).get();
}
"""
print("=== Java 코드 리뷰 ===")
print(review_code(java_code, language="Java"))
# Python 코드 리뷰 (같은 함수로!)
python_code = """
def find_user(id):
user = db.query(User).filter(User.id == id).first()
return user
"""
print("\n=== Python 코드 리뷰 ===")
print(review_code(python_code, language="Python"))하나의 템플릿으로 여러 언어를 리뷰할 수 있게 됐어요.
체이닝 - AI 호출을 파이프라인처럼 연결하기
체이닝이란?
위 코드에서 이 부분:
chain = review_template | llm| 연산자로 템플릿과 모델을 연결했어요. 이게 체이닝입니다.
자바의 Stream API와 비슷한 느낌:
// Java Stream - 데이터 파이프라인
list.stream()
.filter(x -> x > 0)
.map(x -> x * 2)
.collect(toList());
// LangChain - AI 파이프라인
# prompt | llm | output_parser실습: 코드 리뷰 → 개선 코드 생성 체인
두 단계의 AI 호출을 연결해봅시다:
- 코드 리뷰 (문제점 파악)
- 개선된 코드 생성
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from dotenv import load_dotenv
load_dotenv()
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.3)
# 1단계: 코드 리뷰 프롬프트
review_prompt = ChatPromptTemplate.from_messages([
("system", "당신은 자바 시니어 개발자입니다. 코드의 문제점을 3줄 이내로 짧게 지적하세요."),
("user", "코드:\n```java\n{code}\n```")
])
# 2단계: 개선 코드 생성 프롬프트
improve_prompt = ChatPromptTemplate.from_messages([
("system", "당신은 자바 시니어 개발자입니다. 리뷰 내용을 반영해서 개선된 코드만 출력하세요."),
("user", "원본 코드:\n```java\n{code}\n```\n\n리뷰 내용:\n{review}")
])
def review_and_improve(code: str) -> dict:
"""코드 리뷰 후 개선 코드 생성"""
# 1단계 체인: 리뷰 생성
review_chain = review_prompt | llm | StrOutputParser()
review_result = review_chain.invoke({"code": code})
# 2단계 체인: 개선 코드 생성
improve_chain = improve_prompt | llm | StrOutputParser()
improved_code = improve_chain.invoke({
"code": code,
"review": review_result
})
return {
"original": code,
"review": review_result,
"improved": improved_code
}
# 테스트
sample_code = """
public User findUser(Long id) {
User user = userRepository.findById(id).get();
if (user == null) {
throw new RuntimeException("User not found");
}
return user;
}
"""
result = review_and_improve(sample_code)
print("=== 리뷰 결과 ===")
print(result["review"])
print("\n=== 개선된 코드 ===")
print(result["improved"])실행 결과:
=== 리뷰 결과 ===
1. `.get()` 대신 `.orElseThrow()` 사용 필요
2. `RuntimeException` 대신 커스텀 예외 권장
3. `.get()` 이후 null 체크는 불필요
=== 개선된 코드 ===
public User findUser(Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new UserNotFoundException("User not found: " + id));
}두 단계의 AI 호출이 자연스럽게 연결됐어요.
StrOutputParser - 출력 정리하기
체인에서 StrOutputParser()가 보이셨죠?
chain = prompt | llm | StrOutputParser()LLM의 응답은 AIMessage 객체로 옵니다. .content를 매번 꺼내기 귀찮 으니까, 파서가 자동으로 문자열만 추출해주는 거예요.
# 파서 없이
response = (prompt | llm).invoke({"code": code})
print(response.content) # .content 필요
# 파서 있으면
response = (prompt | llm | StrOutputParser()).invoke({"code": code})
print(response) # 바로 문자열나중에 JSON이나 객체로 파싱하는 파서도 배울 거예요.
Spring과의 공통점 발견
LangChain을 쓰다 보면 Spring과 비슷한 패턴이 보여요.
| LangChain | Spring |
|---|---|
| 프롬프트 템플릿 | MessageSource, 템플릿 엔진 |
체이닝 (| 연산자) | HandlerInterceptor 체인 |
| LLM 주입 | 의존성 주입 (DI) |
| OutputParser | ResponseBodyAdvice |
결국 “관심사 분리”와 “재사용성”이라는 같은 원칙을 따르고 있는 거예요.
프롬프트는 프롬프트대로, 모델은 모델대로, 파싱은 파싱대로 분리해서 조합하는 방식이 LangChain의 철학입니다.
정리 - 이번 편에서 배운 것
- LangChain: AI 호출을 체계화하는 프레임워크
- 프롬프트 템플릿: 변수를 넣어 재사용 가능한 질문 형식
- 체이닝:
|연산자로 템플릿 → 모델 → 파서 연결 - StrOutputParser: LLM 응답에서 문자열만 추출
1편에서 만든 코드 리뷰 함수가, 이제는 어떤 언어든 리뷰할 수 있고, 개선 코드까지 자동 생성하는 파이프라인이 됐습니다.
다음 편 예고
다음 편에서는 RAG (Retrieval-Augmented Generation) 기초를 다룹니다.
RAG가 뭐냐면:
- AI가 모르는 정보(우리 회사 문서, 최신 데이터)를 검색해서 답변에 활용하는 기술
- “문서 → 벡터 변환 → 저장 → 검색 → 답변 생성” 파이프라인
다음 편 주요 내용:
- Embedding이란?
- Vector DB (ChromaDB) 사용법
- 문서를 벡터로 저장하는 방법
(3편) RAG 기초로 계속
실습 체크리스트
따라하고 나서 아래 항목들을 점검해보세요:
-
langchain langchain-openai설치 확인 (pip list | grep langchain) - 프롬프트 템플릿 변수 치환 동작 확인
- 체이닝 (
prompt | llm | StrOutputParser()) 정상 실행 확인 - Java/Python 코드 모두 동일 템플릿으로 리뷰 가능 확인
- 2단계 체인 (리뷰 → 개선 코드 생성) 실행 확인
참고:
LangChain 공식 문서: https://python.langchain.com/docs
LCEL (LangChain Expression Language): https://python.langchain.com/docs/concepts/lcel
