o2o-castad-backend/docs/analysis/lang_report.md

1706 lines
65 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# CastAD 백엔드 - LangChain, LangGraph, RAG 적용 설계 보고서
## 목차
1. [현재 시스템 분석](#1-현재-시스템-분석)
2. [LangChain 적용 설계](#2-langchain-적용-설계)
3. [LangGraph 적용 설계](#3-langgraph-적용-설계)
4. [RAG 적용 설계](#4-rag-적용-설계)
5. [통합 아키텍처](#5-통합-아키텍처)
6. [기대 효과](#6-기대-효과)
7. [구현 로드맵](#7-구현-로드맵)
8. [결론](#8-결론)
---
## 1. 현재 시스템 분석
### 1.1 프로젝트 개요
CastAD는 **AI 기반 광고 음악 및 영상 자동 생성 서비스**입니다. 네이버 지도에서 수집한 숙박시설 정보를 기반으로 마케팅용 자동 영상을 생성하는 통합 플랫폼입니다.
**핵심 파이프라인:**
```
사용자 입력 → 가사 자동 생성 → 음악 자동 생성 → 영상 자동 생성
```
### 1.2 현재 기술 스택
| 구분 | 기술 |
|------|------|
| Backend Framework | FastAPI (async/await 기반) |
| ORM | SQLAlchemy 2.0 (비동기) |
| Database | MySQL (asyncmy 드라이버) |
| Cache | Redis |
| AI/API | OpenAI ChatGPT, Suno AI, Creatomate |
| Storage | Azure Blob Storage |
### 1.3 현재 핵심 흐름
```
┌─────────────────────────────────────────────────────────────────┐
│ 현재 파이프라인 구조 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ POST /crawling (선택) │
│ │ │
│ ▼ │
│ POST /lyric/generate ──────► ChatGPT API ──────► 가사 저장 │
│ │ │
│ ▼ │
│ POST /song/generate ───────► Suno API ─────────► 음악 저장 │
│ │ │ │
│ │ 클라이언트 폴링 │
│ │ │ │
│ ▼ ▼ │
│ POST /video/generate ──────► Creatomate API ───► 영상 저장 │
│ │ │ │
│ │ 클라이언트 폴링 │
│ ▼ ▼ │
│ GET /video/download ◄──────── 완료 ──────────► Azure Blob │
│ │
└─────────────────────────────────────────────────────────────────┘
```
### 1.4 현재 시스템의 한계점
| 문제점 | 설명 |
|--------|------|
| **분산된 상태 관리** | 각 API 호출마다 독립적인 상태 관리, 전체 파이프라인 추적 어려움 |
| **클라이언트 의존적 폴링** | 음악/영상 생성 완료 여부를 클라이언트가 반복 확인해야 함 |
| **하드코딩된 프롬프트** | ChatGPT 프롬프트가 코드에 직접 작성, 유연성 부족 |
| **에러 복구 제한적** | 단순 실패 패턴 검사만 수행, 자동 복구 메커니즘 없음 |
| **과거 데이터 미활용** | 성공한 가사/마케팅 사례 재활용 불가 |
| **일관성 없는 품질** | 동일 조건에서도 결과물 품질 편차 존재 |
---
## 2. LangChain 적용 설계
### 2.1 적용 대상 및 목적
LangChain은 **LLM 애플리케이션 개발을 위한 프레임워크**로, 프롬프트 관리, 체인 구성, 출력 파싱 등을 체계화합니다.
**적용 대상:**
1. 가사 생성 서비스 (`ChatgptService`)
2. 마케팅 분석 서비스
3. 다국어 처리 로직
### 2.2 설계 1: 프롬프트 템플릿 시스템
**현재 문제:**
```python
# 현재: chatgpt_prompt.py
prompt = f"""
[ROLE] You are a marketing expert...
[INPUT] Customer: {customer_name}, Region: {region}...
"""
```
**개선 설계:**
```python
# 개선: langchain 적용
from langchain.prompts import PromptTemplate, ChatPromptTemplate
from langchain_openai import ChatOpenAI
# 가사 생성 프롬프트 템플릿
LYRIC_PROMPT = ChatPromptTemplate.from_messages([
("system", """[ROLE] You are a marketing expert and professional lyricist.
You specialize in creating catchy, emotional lyrics for travel and accommodation marketing.
[LANGUAGE REQUIREMENT]
Output MUST be 100% in {language}. No other languages allowed."""),
("human", """[INPUT]
Customer Name: {customer_name}
Region: {region}
Detailed Information: {detail_info}
[OUTPUT REQUIREMENTS]
- 8-12 lines of lyrics
- Focus on: relaxation, healing, beautiful scenery, memorable experiences
- Style: warm, inviting, poetic
- Include location-specific imagery
Generate lyrics now:""")
])
# 체인 구성
lyric_chain = LYRIC_PROMPT | ChatOpenAI(model="gpt-4o-mini") | StrOutputParser()
# 사용
result = await lyric_chain.ainvoke({
"customer_name": "스테이뫰",
"region": "강원도 속초",
"detail_info": "해변 근처 펜션",
"language": "Korean"
})
```
**이점:**
- 프롬프트 버전 관리 용이
- A/B 테스팅 지원
- 입력 변수 명확한 정의
### 2.3 설계 2: 다단계 마케팅 분석 체인
**목적:** 복잡한 마케팅 분석을 단계별로 수행하여 품질 향상
```python
from langchain.chains import SequentialChain
from langchain.prompts import PromptTemplate
from langchain_openai import ChatOpenAI
# Step 1: 경쟁사 분석 체인
competitor_prompt = PromptTemplate(
input_variables=["region", "business_type"],
template="""
{region} 지역의 {business_type} 업종에 대해 분석하세요:
- 주요 경쟁사 특성
- 차별화 포인트
- 시장 포지셔닝
"""
)
competitor_chain = competitor_prompt | ChatOpenAI() | StrOutputParser()
# Step 2: 타겟 고객 분석 체인
audience_prompt = PromptTemplate(
input_variables=["region", "competitor_analysis"],
template="""
경쟁사 분석 결과: {competitor_analysis}
{region} 지역의 주요 타겟 고객층을 분석하세요:
- 연령대 및 특성
- 주요 니즈
- 결정 요인
"""
)
audience_chain = audience_prompt | ChatOpenAI() | StrOutputParser()
# Step 3: 마케팅 전략 종합 체인
strategy_prompt = PromptTemplate(
input_variables=["customer_name", "competitor_analysis", "audience_analysis"],
template="""
경쟁사 분석: {competitor_analysis}
타겟 고객: {audience_analysis}
{customer_name}을 위한 마케팅 전략을 제안하세요:
- 핵심 메시지
- 차별화 전략
- 추천 가사 방향
"""
)
strategy_chain = strategy_prompt | ChatOpenAI() | StrOutputParser()
# 통합 순차 체인
marketing_analysis_chain = (
{"region": RunnablePassthrough(), "business_type": RunnablePassthrough()}
| competitor_chain
| {"competitor_analysis": RunnablePassthrough(), "region": RunnablePassthrough()}
| audience_chain
| {"competitor_analysis": ..., "audience_analysis": RunnablePassthrough(), "customer_name": ...}
| strategy_chain
)
```
**이점:**
- 분석의 깊이와 체계성 향상
- 각 단계별 결과 추적 가능
- 중간 결과 캐싱 가능
### 2.4 설계 3: 출력 파싱 및 검증
**목적:** ChatGPT 응답의 구조화 및 자동 검증
```python
from langchain.output_parsers import PydanticOutputParser
from langchain_core.output_parsers import OutputFixingParser
from pydantic import BaseModel, Field, validator
# 가사 출력 스키마
class LyricOutput(BaseModel):
title: str = Field(description="가사의 제목 (선택)")
lyrics: list[str] = Field(description="가사 각 줄", min_items=8, max_items=12)
mood: str = Field(description="가사의 분위기: warm, energetic, romantic 등")
@validator('lyrics')
def validate_line_count(cls, v):
if len(v) < 8:
raise ValueError("가사는 최소 8줄 이상이어야 합니다")
return v
# 파서 생성
parser = PydanticOutputParser(pydantic_object=LyricOutput)
# 자동 수정 파서 (파싱 실패 시 LLM으로 재시도)
fixing_parser = OutputFixingParser.from_llm(
parser=parser,
llm=ChatOpenAI(model="gpt-4o-mini")
)
# 프롬프트에 포맷 지시 추가
prompt_with_format = LYRIC_PROMPT.partial(
format_instructions=parser.get_format_instructions()
)
```
**이점:**
- 응답 형식 일관성 보장
- 자동 오류 복구
- 타입 안전성 확보
### 2.5 설계 4: Few-Shot 다국어 프롬프트
**목적:** 각 언어별 고품질 예시 제공으로 번역/생성 품질 향상
```python
from langchain.prompts import FewShotPromptTemplate, PromptTemplate
# 언어별 예시
LANGUAGE_EXAMPLES = {
"Korean": [
{
"input": "강원도 속초 해변 펜션",
"output": """푸른 바다 물결 위에
새벽빛이 춤을 추고
당신의 하루를 담아
스테이뫰에서 쉬어가요"""
}
],
"English": [
{
"input": "Sokcho beach pension, Gangwon-do",
"output": """Where ocean waves meet morning light
A peaceful haven comes in sight
Let your worries drift away
At Stay Meoum, find your stay"""
}
],
"Japanese": [
{
"input": "江原道束草ビーチペンション",
"output": """青い海の波の上に
朝の光が踊る時
あなたの一日を包み込む
ステイメウムで休んでいこう"""
}
],
"Chinese": [...],
"Thai": [...],
"Vietnamese": [...]
}
# Few-Shot 프롬프트 생성
def create_multilingual_prompt(language: str):
example_prompt = PromptTemplate(
input_variables=["input", "output"],
template="입력: {input}\n가사:\n{output}"
)
return FewShotPromptTemplate(
examples=LANGUAGE_EXAMPLES.get(language, LANGUAGE_EXAMPLES["Korean"]),
example_prompt=example_prompt,
prefix="다음 예시를 참고하여 고품질 가사를 생성하세요:",
suffix="입력: {customer_info}\n가사:",
input_variables=["customer_info"]
)
```
**이점:**
- 언어별 문화적 뉘앙스 반영
- 일관된 스타일 유지
- 번역 품질 대폭 향상
---
## 3. LangGraph 적용 설계
### 3.1 적용 대상 및 목적
LangGraph는 **복잡한 다단계 워크플로우를 상태 기계(State Machine)로 관리**하는 프레임워크입니다.
**적용 대상:**
1. 전체 영상 생성 파이프라인 (가사 → 음악 → 영상)
2. 비동기 폴링 자동화
3. 에러 처리 및 재시도 로직
### 3.2 설계 1: 통합 파이프라인 그래프
**핵심 설계:**
```python
from langgraph.graph import StateGraph, END
from typing import TypedDict, Optional, Literal
from datetime import datetime
# 파이프라인 상태 정의
class PipelineState(TypedDict):
# 입력
task_id: str
customer_name: str
region: str
detail_info: str
language: str
images: list[str]
orientation: Literal["vertical", "horizontal"]
# 중간 결과
lyric: Optional[str]
lyric_status: Optional[str]
song_url: Optional[str]
song_task_id: Optional[str]
song_status: Optional[str]
song_duration: Optional[float]
video_url: Optional[str]
video_render_id: Optional[str]
video_status: Optional[str]
# 메타데이터
error: Optional[str]
error_step: Optional[str]
started_at: datetime
completed_at: Optional[datetime]
retry_count: int
# 그래프 빌더
def build_video_pipeline() -> StateGraph:
graph = StateGraph(PipelineState)
# ===== 노드 정의 =====
# 1. 가사 생성 노드
async def generate_lyric(state: PipelineState) -> PipelineState:
"""ChatGPT로 가사 생성 (동기)"""
try:
lyric_chain = create_lyric_chain() # LangChain 체인
lyric = await lyric_chain.ainvoke({
"customer_name": state["customer_name"],
"region": state["region"],
"detail_info": state["detail_info"],
"language": state["language"]
})
# DB 저장
await save_lyric_to_db(state["task_id"], lyric)
return {
**state,
"lyric": lyric,
"lyric_status": "completed"
}
except Exception as e:
return {
**state,
"error": str(e),
"error_step": "lyric_generation",
"lyric_status": "failed"
}
# 2. 음악 생성 요청 노드
async def request_song(state: PipelineState) -> PipelineState:
"""Suno API에 음악 생성 요청"""
try:
suno = SunoAPIClient()
task_id = await suno.generate(
prompt=state["lyric"],
genre="K-Pop, Emotional"
)
# DB 저장
await save_song_request_to_db(state["task_id"], task_id)
return {
**state,
"song_task_id": task_id,
"song_status": "processing"
}
except Exception as e:
return {
**state,
"error": str(e),
"error_step": "song_request",
"song_status": "failed"
}
# 3. 음악 폴링 노드
async def poll_song_status(state: PipelineState) -> PipelineState:
"""Suno 상태 폴링 (최대 5분)"""
suno = SunoAPIClient()
max_attempts = 60 # 5초 간격 × 60 = 5분
for attempt in range(max_attempts):
result = await suno.get_task_status(state["song_task_id"])
if result["status"] == "SUCCESS":
audio_url = result["clips"][0]["audio_url"]
duration = result["clips"][0]["duration"]
# DB 업데이트
await update_song_status(
state["task_id"],
"completed",
audio_url,
duration
)
return {
**state,
"song_url": audio_url,
"song_duration": duration,
"song_status": "completed"
}
elif result["status"] == "FAILED":
return {
**state,
"error": "Suno generation failed",
"error_step": "song_polling",
"song_status": "failed"
}
await asyncio.sleep(5) # 5초 대기
return {
**state,
"error": "Song generation timeout",
"error_step": "song_polling",
"song_status": "timeout"
}
# 4. 영상 생성 요청 노드
async def request_video(state: PipelineState) -> PipelineState:
"""Creatomate API에 영상 렌더링 요청"""
try:
creatomate = CreatomateClient()
render_id = await creatomate.render(
images=state["images"],
music_url=state["song_url"],
lyrics=state["lyric"],
duration=state["song_duration"],
orientation=state["orientation"]
)
# DB 저장
await save_video_request_to_db(state["task_id"], render_id)
return {
**state,
"video_render_id": render_id,
"video_status": "processing"
}
except Exception as e:
return {
**state,
"error": str(e),
"error_step": "video_request",
"video_status": "failed"
}
# 5. 영상 폴링 노드
async def poll_video_status(state: PipelineState) -> PipelineState:
"""Creatomate 상태 폴링 (최대 10분)"""
creatomate = CreatomateClient()
max_attempts = 120 # 5초 간격 × 120 = 10분
for attempt in range(max_attempts):
result = await creatomate.get_render_status(state["video_render_id"])
if result["status"] == "succeeded":
video_url = result["url"]
# Azure Blob 업로드
blob_url = await upload_to_azure(video_url, state["task_id"])
# DB 업데이트
await update_video_status(state["task_id"], "completed", blob_url)
return {
**state,
"video_url": blob_url,
"video_status": "completed",
"completed_at": datetime.now()
}
elif result["status"] == "failed":
return {
**state,
"error": "Creatomate rendering failed",
"error_step": "video_polling",
"video_status": "failed"
}
await asyncio.sleep(5)
return {
**state,
"error": "Video generation timeout",
"error_step": "video_polling",
"video_status": "timeout"
}
# 6. 에러 처리 노드
async def handle_error(state: PipelineState) -> PipelineState:
"""에러 로깅 및 알림"""
await log_pipeline_error(
task_id=state["task_id"],
error=state["error"],
step=state["error_step"]
)
# 선택: 슬랙/이메일 알림
await send_error_notification(state)
return state
# ===== 노드 추가 =====
graph.add_node("generate_lyric", generate_lyric)
graph.add_node("request_song", request_song)
graph.add_node("poll_song", poll_song_status)
graph.add_node("request_video", request_video)
graph.add_node("poll_video", poll_video_status)
graph.add_node("handle_error", handle_error)
# ===== 엣지 정의 =====
# 시작점
graph.set_entry_point("generate_lyric")
# 조건부 분기: 가사 생성 후
def route_after_lyric(state: PipelineState):
if state.get("error"):
return "handle_error"
return "request_song"
graph.add_conditional_edges(
"generate_lyric",
route_after_lyric,
{
"request_song": "request_song",
"handle_error": "handle_error"
}
)
# 조건부 분기: 음악 요청 후
def route_after_song_request(state: PipelineState):
if state.get("error"):
return "handle_error"
return "poll_song"
graph.add_conditional_edges(
"request_song",
route_after_song_request
)
# 조건부 분기: 음악 폴링 후
def route_after_song_poll(state: PipelineState):
if state.get("error") or state["song_status"] in ["failed", "timeout"]:
return "handle_error"
return "request_video"
graph.add_conditional_edges(
"poll_song",
route_after_song_poll
)
# 조건부 분기: 영상 요청 후
graph.add_conditional_edges(
"request_video",
lambda s: "handle_error" if s.get("error") else "poll_video"
)
# 조건부 분기: 영상 폴링 후
graph.add_conditional_edges(
"poll_video",
lambda s: "handle_error" if s.get("error") else END
)
# 에러 핸들러는 항상 종료
graph.add_edge("handle_error", END)
return graph.compile()
```
**그래프 시각화:**
```
┌─────────────────┐
│ generate_lyric │
└────────┬────────┘
┌────────▼────────┐
┌─────┤ route check ├─────┐
│ └─────────────────┘ │
[error] [success]
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ handle_error │ │ request_song │
└────────┬────────┘ └────────┬────────┘
│ │
▼ ▼
END ┌─────────────────┐
│ poll_song │
└────────┬────────┘
┌────────▼────────┐
┌─────┤ route check ├─────┐
│ └─────────────────┘ │
[error/timeout] [success]
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ handle_error │ │ request_video │
└────────┬────────┘ └────────┬────────┘
│ │
▼ ▼
END ┌─────────────────┐
│ poll_video │
└────────┬────────┘
┌────────▼────────┐
┌─────┤ route check ├─────┐
│ └─────────────────┘ │
[error] [success]
│ │
▼ ▼
┌─────────────────┐ END
│ handle_error │ (파이프라인 완료)
└────────┬────────┘
END
```
### 3.3 설계 2: 재시도 및 폴백 메커니즘
```python
from langgraph.graph import StateGraph
class RetryState(TypedDict):
task_id: str
retry_count: int
max_retries: int
last_error: Optional[str]
# ... 기타 필드
def build_retry_aware_pipeline():
graph = StateGraph(RetryState)
async def generate_song_with_retry(state: RetryState) -> RetryState:
"""재시도 로직이 포함된 음악 생성"""
try:
# 1차 시도: Suno API
result = await suno_generate(state["lyric"])
return {**state, "song_url": result, "retry_count": 0}
except SunoRateLimitError:
# 재시도 1: 딜레이 후 재시도
if state["retry_count"] < state["max_retries"]:
await asyncio.sleep(30) # 30초 대기
return {
**state,
"retry_count": state["retry_count"] + 1,
"last_error": "rate_limit"
}
except SunoAPIError as e:
# 재시도 2: 프롬프트 수정 후 재시도
if "invalid lyrics" in str(e) and state["retry_count"] < 2:
simplified_lyric = await simplify_lyrics(state["lyric"])
return {
**state,
"lyric": simplified_lyric,
"retry_count": state["retry_count"] + 1
}
# 폴백: 대체 서비스 사용
try:
result = await alternative_music_service(state["lyric"])
return {**state, "song_url": result, "used_fallback": True}
except:
pass
return {
**state,
"error": "All music generation attempts failed",
"song_status": "failed"
}
# 조건부 재시도 엣지
def should_retry_song(state: RetryState):
if state.get("song_url"):
return "next_step"
if state["retry_count"] < state["max_retries"]:
return "retry_song"
return "handle_error"
graph.add_conditional_edges(
"generate_song",
should_retry_song,
{
"retry_song": "generate_song", # 자기 자신으로 루프
"next_step": "request_video",
"handle_error": "handle_error"
}
)
return graph.compile()
```
### 3.4 설계 3: 병렬 처리 지원
```python
from langgraph.types import Send
from langgraph.graph import StateGraph
class ParallelState(TypedDict):
task_id: str
images: list[str]
analyzed_images: list[dict] # 병렬 분석 결과
# ...
def build_parallel_pipeline():
graph = StateGraph(ParallelState)
# 이미지 분석을 병렬로 수행
async def analyze_single_image(state: dict) -> dict:
"""단일 이미지 분석"""
image_url = state["image_url"]
analysis = await vision_model.analyze(image_url)
return {
"image_url": image_url,
"analysis": analysis,
"mood": analysis.get("mood"),
"colors": analysis.get("dominant_colors")
}
# 팬아웃: 여러 이미지를 병렬로 분석
def fanout_images(state: ParallelState):
return [
Send("analyze_image", {"image_url": img, "task_id": state["task_id"]})
for img in state["images"]
]
# 팬인: 분석 결과 수집
async def collect_analyses(state: ParallelState) -> ParallelState:
# LangGraph가 자동으로 병렬 결과를 수집
return state
graph.add_node("analyze_image", analyze_single_image)
graph.add_node("collect", collect_analyses)
graph.add_conditional_edges(
"start",
fanout_images # 여러 Send 반환 → 병렬 실행
)
return graph.compile()
```
### 3.5 FastAPI 통합
```python
# main.py 또는 video/api/routers/v1/video.py
from fastapi import APIRouter, BackgroundTasks
from langgraph.graph import StateGraph
router = APIRouter()
pipeline = build_video_pipeline()
@router.post("/video/generate-full")
async def generate_full_video(
request: FullVideoRequest,
background_tasks: BackgroundTasks
):
"""단일 API 호출로 전체 파이프라인 실행"""
initial_state: PipelineState = {
"task_id": str(uuid7()),
"customer_name": request.customer_name,
"region": request.region,
"detail_info": request.detail_info,
"language": request.language,
"images": request.images,
"orientation": request.orientation,
"lyric": None,
"lyric_status": None,
"song_url": None,
"song_task_id": None,
"song_status": None,
"song_duration": None,
"video_url": None,
"video_render_id": None,
"video_status": None,
"error": None,
"error_step": None,
"started_at": datetime.now(),
"completed_at": None,
"retry_count": 0
}
# 백그라운드에서 파이프라인 실행
background_tasks.add_task(run_pipeline_async, initial_state)
return {
"task_id": initial_state["task_id"],
"status": "processing",
"message": "Pipeline started. Use GET /video/pipeline-status/{task_id} to check progress."
}
async def run_pipeline_async(initial_state: PipelineState):
"""백그라운드에서 LangGraph 파이프라인 실행"""
try:
final_state = await pipeline.ainvoke(initial_state)
# 결과 DB 저장
await save_pipeline_result(final_state)
# 완료 알림 (웹훅, 이메일 등)
if final_state.get("video_url"):
await send_completion_notification(final_state)
except Exception as e:
await log_pipeline_error(initial_state["task_id"], str(e))
@router.get("/video/pipeline-status/{task_id}")
async def get_pipeline_status(task_id: str):
"""파이프라인 진행 상태 조회"""
status = await get_status_from_db(task_id)
return {
"task_id": task_id,
"lyric_status": status.lyric_status,
"song_status": status.song_status,
"video_status": status.video_status,
"overall_status": determine_overall_status(status),
"video_url": status.video_url if status.video_status == "completed" else None,
"error": status.error
}
```
---
## 4. RAG 적용 설계
### 4.1 적용 대상 및 목적
RAG(Retrieval-Augmented Generation)는 **외부 지식 기반을 검색하여 LLM 응답 품질을 향상**시키는 기법입니다.
**적용 대상:**
1. 마케팅 지식베이스 (성공 사례)
2. 지역별/업종별 가사 예시
3. 이미지 메타데이터 활용
4. 프롬프트 최적화
### 4.2 설계 1: 마케팅 지식베이스 RAG
**아키텍처:**
```
┌─────────────────────────────────────────────────────────────┐
│ 마케팅 지식베이스 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Document Store (벡터 DB) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Collection: marketing_knowledge │ │
│ │ │ │
│ │ ┌──────────────────────────────────────────────┐ │ │
│ │ │ 문서 1: 강원도 속초 펜션 마케팅 성공 사례 │ │ │
│ │ │ - 가사 예시 │ │ │
│ │ │ - 타겟 고객 분석 │ │ │
│ │ │ - 효과적인 키워드 │ │ │
│ │ │ - 영상 조회수/반응 │ │ │
│ │ └──────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ ┌──────────────────────────────────────────────┐ │ │
│ │ │ 문서 2: 제주도 게스트하우스 마케팅 사례 │ │ │
│ │ │ ... │ │ │
│ │ └──────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ ... (수백 개의 사례) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
│ 유사도 검색
┌─────────────────────────────────────────────────────────────┐
│ 가사 생성 프롬프트 │
├─────────────────────────────────────────────────────────────┤
│ "다음 유사 사례를 참고하여 가사를 생성하세요: │
│ [검색된 성공 사례 1] │
│ [검색된 성공 사례 2] │
│ ..." │
└─────────────────────────────────────────────────────────────┘
```
**구현 코드:**
```python
from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.vectorstores import Chroma
from langchain.document_loaders import JSONLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.schema import Document
# 1. 임베딩 모델 설정
embeddings = OpenAIEmbeddings(
model="text-embedding-3-small",
openai_api_key=settings.CHATGPT_API_KEY
)
# 2. 벡터 스토어 초기화
vector_store = Chroma(
collection_name="marketing_knowledge",
embedding_function=embeddings,
persist_directory="./data/chroma_db"
)
# 3. 마케팅 사례 문서 구조
class MarketingCase(BaseModel):
case_id: str
region: str
business_type: str # "pension", "guesthouse", "hotel"
target_audience: str
successful_lyrics: str
keywords: list[str]
performance_metrics: dict # views, engagement, conversions
created_at: datetime
# 4. 문서 추가 함수
async def add_marketing_case(case: MarketingCase):
"""성공한 마케팅 사례를 벡터 스토어에 추가"""
# 메타데이터와 함께 문서 생성
document = Document(
page_content=f"""
지역: {case.region}
업종: {case.business_type}
타겟 고객: {case.target_audience}
성공 가사:
{case.successful_lyrics}
효과적인 키워드: {', '.join(case.keywords)}
성과: 조회수 {case.performance_metrics.get('views', 0)},
참여율 {case.performance_metrics.get('engagement', 0)}%
""",
metadata={
"case_id": case.case_id,
"region": case.region,
"business_type": case.business_type,
"created_at": case.created_at.isoformat()
}
)
vector_store.add_documents([document])
vector_store.persist()
# 5. RAG 기반 가사 생성
async def generate_lyrics_with_rag(
customer_name: str,
region: str,
business_type: str,
language: str
) -> str:
"""RAG를 활용한 고품질 가사 생성"""
# 유사 사례 검색
query = f"{region} {business_type} 마케팅 가사"
similar_cases = vector_store.similarity_search(
query,
k=3,
filter={"business_type": business_type} # 같은 업종만
)
# 검색 결과를 프롬프트에 포함
examples_text = "\n\n".join([
f"### 참고 사례 {i+1}\n{doc.page_content}"
for i, doc in enumerate(similar_cases)
])
# LangChain 프롬프트 구성
rag_prompt = ChatPromptTemplate.from_messages([
("system", """당신은 마케팅 전문가이자 작사가입니다.
다음 성공 사례를 참고하여 새로운 가사를 생성하세요.
참고 사례의 스타일과 키워드를 활용하되, 고유한 내용을 만드세요.
{examples}"""),
("human", """
고객명: {customer_name}
지역: {region}
언어: {language}
위 정보를 바탕으로 8-12줄의 감성적인 마케팅 가사를 생성하세요.
""")
])
chain = rag_prompt | ChatOpenAI(model="gpt-4o") | StrOutputParser()
result = await chain.ainvoke({
"examples": examples_text,
"customer_name": customer_name,
"region": region,
"language": language
})
return result
```
### 4.3 설계 2: 지역별 특화 RAG
**목적:** 각 지역의 특성(문화, 관광지, 특산물 등)을 반영한 가사 생성
```python
# 지역 정보 문서 구조
class RegionInfo(BaseModel):
region_name: str
province: str
famous_attractions: list[str]
local_foods: list[str]
cultural_keywords: list[str]
seasonal_events: list[dict] # {"season": "summer", "event": "해수욕장 개장"}
atmosphere: list[str] # ["고즈넉한", "활기찬", "낭만적인"]
# 지역 정보 벡터 스토어
region_store = Chroma(
collection_name="region_knowledge",
embedding_function=embeddings,
persist_directory="./data/chroma_region"
)
# 지역 정보 추가 예시
regions_data = [
RegionInfo(
region_name="속초",
province="강원도",
famous_attractions=["설악산", "속초해변", "영금정", "아바이마을"],
local_foods=["오징어순대", "물회", "생선구이"],
cultural_keywords=["동해바다", "일출", "산과 바다", "청정자연"],
seasonal_events=[
{"season": "summer", "event": "속초해변 피서"},
{"season": "autumn", "event": "설악산 단풍"}
],
atmosphere=["시원한", "청량한", "자연친화적", "힐링"]
),
# ... 더 많은 지역
]
async def enrich_lyrics_with_region_info(
base_lyrics: str,
region: str
) -> str:
"""지역 정보로 가사 보강"""
# 지역 정보 검색
region_docs = region_store.similarity_search(region, k=1)
if not region_docs:
return base_lyrics
region_info = region_docs[0].page_content
# 가사에 지역 특성 반영
enrichment_prompt = ChatPromptTemplate.from_messages([
("system", """당신은 가사 편집 전문가입니다.
주어진 기본 가사에 지역의 특성을 자연스럽게 녹여내세요.
지역 정보:
{region_info}"""),
("human", """기본 가사:
{base_lyrics}
위 가사에 지역의 특성(명소, 분위기, 키워드)을 2-3개 자연스럽게 추가하세요.
원래 가사의 운율과 분위기를 유지하세요.""")
])
chain = enrichment_prompt | ChatOpenAI() | StrOutputParser()
return await chain.ainvoke({
"region_info": region_info,
"base_lyrics": base_lyrics
})
```
### 4.4 설계 3: 이미지 메타데이터 RAG
**목적:** 업로드된 이미지의 분석 결과를 저장하고, 영상 생성 시 최적의 이미지 순서 결정
```python
from langchain_openai import ChatOpenAI
# Vision 모델로 이미지 분석
vision_model = ChatOpenAI(model="gpt-4o")
# 이미지 분석 문서 구조
class ImageAnalysis(BaseModel):
image_url: str
task_id: str
description: str
dominant_colors: list[str]
mood: str # "warm", "cool", "neutral"
scene_type: str # "interior", "exterior", "nature", "food"
suggested_position: str # "opening", "middle", "closing"
quality_score: float # 0.0 ~ 1.0
# 이미지 메타데이터 벡터 스토어
image_store = Chroma(
collection_name="image_metadata",
embedding_function=embeddings,
persist_directory="./data/chroma_images"
)
async def analyze_and_store_image(image_url: str, task_id: str):
"""이미지 분석 후 벡터 스토어에 저장"""
# GPT-4o Vision으로 이미지 분석
analysis_response = await vision_model.ainvoke([
{
"type": "text",
"text": """이미지를 분석하고 다음 JSON 형식으로 응답하세요:
{
"description": "이미지 설명 (2-3문장)",
"dominant_colors": ["색상1", "색상2"],
"mood": "warm/cool/neutral 중 하나",
"scene_type": "interior/exterior/nature/food 중 하나",
"suggested_position": "opening/middle/closing 중 하나 (영상에서 적합한 위치)",
"quality_score": 0.0~1.0 (이미지 품질/선명도)
}"""
},
{
"type": "image_url",
"image_url": {"url": image_url}
}
])
analysis = json.loads(analysis_response.content)
# 문서 생성 및 저장
document = Document(
page_content=f"""
이미지 설명: {analysis['description']}
분위기: {analysis['mood']}
장면 유형: {analysis['scene_type']}
추천 위치: {analysis['suggested_position']}
색상: {', '.join(analysis['dominant_colors'])}
""",
metadata={
"image_url": image_url,
"task_id": task_id,
**analysis
}
)
image_store.add_documents([document])
image_store.persist()
return ImageAnalysis(image_url=image_url, task_id=task_id, **analysis)
async def get_optimal_image_order(
task_id: str,
music_mood: str, # 음악 분위기
lyrics_theme: str # 가사 주제
) -> list[str]:
"""음악과 가사에 맞는 최적의 이미지 순서 결정"""
# 해당 task의 모든 이미지 조회
all_images = image_store.get(
where={"task_id": task_id}
)
# 음악/가사 분위기에 맞는 이미지 우선 검색
query = f"{music_mood} {lyrics_theme} 마케팅 영상"
sorted_images = image_store.similarity_search(
query,
k=len(all_images),
filter={"task_id": task_id}
)
# 이미지 순서 결정 로직
opening_images = [img for img in sorted_images if img.metadata["suggested_position"] == "opening"]
middle_images = [img for img in sorted_images if img.metadata["suggested_position"] == "middle"]
closing_images = [img for img in sorted_images if img.metadata["suggested_position"] == "closing"]
# 품질 점수로 정렬
opening_images.sort(key=lambda x: x.metadata["quality_score"], reverse=True)
closing_images.sort(key=lambda x: x.metadata["quality_score"], reverse=True)
# 최종 순서
ordered = (
opening_images[:2] + # 시작 2장
middle_images + # 중간 이미지들
closing_images[:1] # 마무리 1장
)
return [img.metadata["image_url"] for img in ordered]
```
### 4.5 설계 4: 프롬프트 히스토리 RAG
**목적:** 과거 성공/실패한 프롬프트를 학습하여 프롬프트 품질 지속 개선
```python
# 프롬프트 결과 문서 구조
class PromptResult(BaseModel):
prompt_id: str
prompt_text: str
result_text: str
success: bool
failure_reason: Optional[str]
category: str # "lyric", "marketing_analysis", "region_enrichment"
metrics: dict # {"length": 10, "contains_region_keyword": True, ...}
created_at: datetime
# 프롬프트 히스토리 벡터 스토어
prompt_store = Chroma(
collection_name="prompt_history",
embedding_function=embeddings,
persist_directory="./data/chroma_prompts"
)
async def log_prompt_result(result: PromptResult):
"""프롬프트 결과 기록"""
document = Document(
page_content=f"""
프롬프트: {result.prompt_text}
결과: {result.result_text[:500]}...
성공 여부: {'성공' if result.success else '실패'}
실패 사유: {result.failure_reason or 'N/A'}
""",
metadata={
"prompt_id": result.prompt_id,
"success": result.success,
"category": result.category,
"created_at": result.created_at.isoformat(),
**result.metrics
}
)
prompt_store.add_documents([document])
async def get_improved_prompt(
base_prompt: str,
category: str
) -> str:
"""과거 결과를 기반으로 프롬프트 개선"""
# 유사한 성공 프롬프트 검색
successful_prompts = prompt_store.similarity_search(
base_prompt,
k=3,
filter={"success": True, "category": category}
)
# 유사한 실패 프롬프트 검색 (피해야 할 패턴)
failed_prompts = prompt_store.similarity_search(
base_prompt,
k=2,
filter={"success": False, "category": category}
)
# 프롬프트 개선 요청
improvement_prompt = ChatPromptTemplate.from_messages([
("system", """당신은 프롬프트 엔지니어링 전문가입니다.
다음 성공/실패 사례를 참고하여 주어진 프롬프트를 개선하세요.
### 성공 사례 (참고):
{successful_examples}
### 실패 사례 (피할 것):
{failed_examples}
### 개선 원칙:
1. 성공 사례의 패턴을 따르세요
2. 실패 사례의 패턴을 피하세요
3. 명확하고 구체적인 지시를 포함하세요
4. 출력 형식을 명시하세요"""),
("human", """개선할 프롬프트:
{base_prompt}
위 프롬프트를 개선하세요. 개선된 프롬프트만 출력하세요.""")
])
chain = improvement_prompt | ChatOpenAI() | StrOutputParser()
improved = await chain.ainvoke({
"successful_examples": "\n---\n".join([doc.page_content for doc in successful_prompts]),
"failed_examples": "\n---\n".join([doc.page_content for doc in failed_prompts]),
"base_prompt": base_prompt
})
return improved
```
---
## 5. 통합 아키텍처
### 5.1 전체 시스템 아키텍처
```
┌──────────────────────────────────────────────────────────────────────────┐
│ FastAPI 라우터 │
│ /lyric/generate, /song/generate, /video/generate, /video/generate-full │
└───────────────────────────────────┬──────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────┐
│ LangGraph 파이프라인 │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ StateGraph (상태 기계) │ │
│ │ │ │
│ │ [generate_lyric] → [request_song] → [poll_song] │ │
│ │ │ │ │ │
│ │ │ ▼ │ │
│ │ │ [request_video] → [poll_video] → END │ │
│ │ │ │ │ │
│ │ └─────────────────────┴──────────→ [handle_error] │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌───────────────┼───────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ LangChain │ │ RAG │ │ External │ │
│ │ Components │ │ Vector DBs │ │ APIs │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└──────────────────────────────────────────────────────────────────────────┘
│ │ │
┌───────────┘ ┌──────────┘ ┌──────────┘
│ │ │
▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Prompt │ │ Chroma │ │ OpenAI │
│ Templates │ │ Vector │ │ Suno │
│ Chains │ │ Store │ │ Creatomate │
│ Parsers │ │ │ │ │
└─────────────┘ └─────────────┘ └─────────────┘
│ │ │
└────────────────┴───────────────┘
┌─────────────────────────┐
│ MySQL + Azure Blob │
│ (영구 저장소) │
└─────────────────────────┘
```
### 5.2 데이터 흐름
```
┌─────────────────────────────────────────────────────────────────────────┐
│ 데이터 흐름 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. 사용자 요청 │
│ ├── 고객 정보 (이름, 지역, 상세정보) │
│ └── 이미지 URL 리스트 │
│ │ │
│ ▼ │
│ 2. RAG 검색 (병렬) │
│ ├── 마케팅 지식베이스 → 유사 성공 사례 │
│ ├── 지역 정보베이스 → 지역 특성 │
│ └── 이미지 메타데이터 → 이미지 분석 │
│ │ │
│ ▼ │
│ 3. LangChain 프롬프트 구성 │
│ ├── 기본 템플릿 로드 │
│ ├── RAG 결과 주입 │
│ ├── Few-shot 예시 추가 │
│ └── 출력 형식 지정 │
│ │ │
│ ▼ │
│ 4. LangGraph 파이프라인 실행 │
│ ├── 가사 생성 (ChatGPT) │
│ ├── 음악 생성 (Suno, 폴링 자동화) │
│ └── 영상 생성 (Creatomate, 폴링 자동화) │
│ │ │
│ ▼ │
│ 5. 결과 저장 │
│ ├── MySQL: 메타데이터, 상태 │
│ ├── Azure Blob: 영상 파일 │
│ └── Chroma: 성공 사례 피드백 │
│ │ │
│ ▼ │
│ 6. 사용자 응답 │
│ └── 영상 URL, 상태, 메타데이터 │
│ │
└─────────────────────────────────────────────────────────────────────────┘
```
### 5.3 디렉토리 구조 (신규)
```
app/
├── langchain/ # LangChain 관련
│ ├── __init__.py
│ ├── prompts/
│ │ ├── __init__.py
│ │ ├── lyric_prompts.py # 가사 생성 프롬프트
│ │ ├── marketing_prompts.py # 마케팅 분석 프롬프트
│ │ └── examples/ # Few-shot 예시
│ │ ├── korean.json
│ │ ├── english.json
│ │ └── ...
│ ├── chains/
│ │ ├── __init__.py
│ │ ├── lyric_chain.py # 가사 생성 체인
│ │ └── marketing_chain.py # 마케팅 분석 체인
│ └── parsers/
│ ├── __init__.py
│ └── lyric_parser.py # 가사 출력 파서
├── langgraph/ # LangGraph 관련
│ ├── __init__.py
│ ├── states/
│ │ ├── __init__.py
│ │ └── pipeline_state.py # 파이프라인 상태 정의
│ ├── nodes/
│ │ ├── __init__.py
│ │ ├── lyric_node.py # 가사 생성 노드
│ │ ├── song_node.py # 음악 생성 노드
│ │ ├── video_node.py # 영상 생성 노드
│ │ └── error_node.py # 에러 처리 노드
│ └── graphs/
│ ├── __init__.py
│ └── video_pipeline.py # 메인 파이프라인 그래프
├── rag/ # RAG 관련
│ ├── __init__.py
│ ├── stores/
│ │ ├── __init__.py
│ │ ├── marketing_store.py # 마케팅 지식베이스
│ │ ├── region_store.py # 지역 정보베이스
│ │ ├── image_store.py # 이미지 메타데이터
│ │ └── prompt_store.py # 프롬프트 히스토리
│ ├── loaders/
│ │ ├── __init__.py
│ │ └── case_loader.py # 사례 데이터 로더
│ └── retrievers/
│ ├── __init__.py
│ └── hybrid_retriever.py # 하이브리드 검색
└── data/ # 데이터 저장소
└── chroma_db/ # Chroma 벡터 DB
├── marketing_knowledge/
├── region_knowledge/
├── image_metadata/
└── prompt_history/
```
---
## 6. 기대 효과
### 6.1 정량적 기대 효과
| 지표 | 현재 | 목표 | 개선율 |
|------|------|------|--------|
| **가사 생성 품질** | 70% 만족도 | 90% 만족도 | +29% |
| **재작업률** | 30% | 10% | -67% |
| **파이프라인 실패율** | 15% | 5% | -67% |
| **평균 처리 시간** | 10분 (수동 개입 필요) | 8분 (완전 자동) | -20% |
| **다국어 품질** | 60% | 85% | +42% |
| **프롬프트 튜닝 시간** | 2시간/버전 | 30분/버전 | -75% |
### 6.2 정성적 기대 효과
#### 6.2.1 개발 생산성 향상
| 영역 | 효과 |
|------|------|
| **코드 유지보수** | 프롬프트와 비즈니스 로직 분리로 수정 용이 |
| **테스트 용이성** | 각 체인/노드 단위 테스트 가능 |
| **디버깅** | 상태 기계 기반으로 문제 지점 명확히 파악 |
| **확장성** | 새로운 AI 서비스 추가 시 노드만 추가하면 됨 |
#### 6.2.2 품질 향상
| 영역 | 효과 |
|------|------|
| **일관성** | 동일 조건에서 일관된 품질의 결과물 생성 |
| **지역 맞춤화** | RAG로 지역별 특성 자동 반영 |
| **학습 효과** | 성공 사례 축적으로 시간이 지날수록 품질 향상 |
| **에러 복구** | 자동 재시도 및 폴백으로 안정성 강화 |
#### 6.2.3 운영 효율성
| 영역 | 효과 |
|------|------|
| **모니터링** | 파이프라인 상태 추적으로 병목 지점 파악 |
| **비용 최적화** | 불필요한 API 호출 감소, 캐싱 활용 |
| **확장 대응** | 부하 증가 시 노드별 스케일링 가능 |
### 6.3 비즈니스 가치
```
┌────────────────────────────────────────────────────────────────┐
│ 비즈니스 가치 │
├────────────────────────────────────────────────────────────────┤
│ │
│ 1. 고객 만족도 향상 │
│ └── 고품질 가사/영상으로 마케팅 효과 증대 │
│ │
│ 2. 서비스 차별화 │
│ └── 지역 맞춤 콘텐츠로 경쟁사 대비 우위 │
│ │
│ 3. 운영 비용 절감 │
│ └── 자동화로 수동 개입 최소화 │
│ │
│ 4. 확장 가능성 │
│ └── 새로운 지역/업종 추가 시 RAG 학습만으로 대응 │
│ │
│ 5. 데이터 자산화 │
│ └── 축적된 성공 사례가 진입 장벽 역할 │
│ │
└────────────────────────────────────────────────────────────────┘
```
---
## 7. 구현 로드맵
### 7.1 Phase 1: 기초 (1-2주)
**목표:** LangChain 기본 구조 구축
| 작업 | 설명 | 우선순위 |
|------|------|----------|
| 의존성 설치 | langchain, langchain-openai, chromadb | P0 |
| 프롬프트 템플릿 작성 | 가사 생성 프롬프트 이관 | P0 |
| 기본 체인 구현 | ChatGPT 서비스 LangChain으로 래핑 | P0 |
| 출력 파서 구현 | 가사 응답 검증 및 파싱 | P1 |
| 테스트 작성 | 체인 단위 테스트 | P1 |
**산출물:**
- `app/langchain/` 디렉토리 구조
- 가사 생성 LangChain 체인
- 기본 테스트 코드
### 7.2 Phase 2: 파이프라인 (2-3주)
**목표:** LangGraph 파이프라인 구축
| 작업 | 설명 | 우선순위 |
|------|------|----------|
| 상태 정의 | PipelineState TypedDict 작성 | P0 |
| 노드 구현 | 가사, 음악, 영상 생성 노드 | P0 |
| 그래프 구성 | 엣지 및 조건부 분기 정의 | P0 |
| 폴링 통합 | Suno, Creatomate 폴링 자동화 | P0 |
| 에러 처리 | 에러 노드 및 재시도 로직 | P1 |
| FastAPI 통합 | 새 엔드포인트 추가 | P1 |
**산출물:**
- `app/langgraph/` 디렉토리 구조
- 통합 파이프라인 그래프
- `/video/generate-full` 엔드포인트
### 7.3 Phase 3: RAG (2-3주)
**목표:** 지식베이스 구축 및 RAG 통합
| 작업 | 설명 | 우선순위 |
|------|------|----------|
| Chroma 설정 | 벡터 스토어 초기화 | P0 |
| 마케팅 사례 수집 | 기존 성공 사례 데이터화 | P0 |
| 지역 정보 구축 | 주요 지역 정보 입력 | P1 |
| 검색 통합 | 가사 생성 시 RAG 적용 | P0 |
| 이미지 분석 | Vision API 연동 | P2 |
| 프롬프트 히스토리 | 자동 학습 파이프라인 | P2 |
**산출물:**
- `app/rag/` 디렉토리 구조
- 마케팅/지역 지식베이스
- RAG 통합 가사 생성
### 7.4 Phase 4: 고도화 (2-3주)
**목표:** 최적화 및 모니터링
| 작업 | 설명 | 우선순위 |
|------|------|----------|
| 성능 최적화 | 캐싱, 병렬 처리 개선 | P1 |
| 모니터링 | 파이프라인 상태 대시보드 | P1 |
| A/B 테스팅 | 프롬프트 버전 비교 | P2 |
| 문서화 | API 문서, 운영 가이드 | P1 |
| 부하 테스트 | 동시 요청 처리 검증 | P2 |
**산출물:**
- 최적화된 파이프라인
- 모니터링 대시보드
- 완성된 문서
---
## 8. 결론
### 8.1 요약
CastAD 백엔드에 LangChain, LangGraph, RAG를 적용하면:
1. **LangChain**: 프롬프트 관리 체계화, 다단계 체인 구성, 출력 검증 자동화
2. **LangGraph**: 복잡한 파이프라인 상태 관리, 폴링 자동화, 에러 처리 강화
3. **RAG**: 과거 성공 사례 활용, 지역별 맞춤화, 지속적 품질 개선
### 8.2 핵심 가치
```
┌───────────────────────────────────────────────────────────┐
│ │
│ 현재: 각 단계가 독립적 → 상태 관리 어려움 │
│ 개선: 통합 파이프라인 → 자동화된 상태 추적 │
│ │
│ 현재: 하드코딩 프롬프트 → 수정 어려움 │
│ 개선: 템플릿 기반 → 유연한 프롬프트 관리 │
│ │
│ 현재: 과거 데이터 미활용 → 일관성 없는 품질 │
│ 개선: RAG 지식베이스 → 축적된 노하우 활용 │
│ │
└───────────────────────────────────────────────────────────┘
```
### 8.3 권장 사항
1. **단계적 도입**: Phase 1(LangChain)부터 시작하여 검증 후 확장
2. **기존 API 유지**: 새 엔드포인트 추가 방식으로 호환성 보장
3. **데이터 축적 우선**: RAG 효과를 위해 초기 사례 데이터 확보 중요
4. **모니터링 병행**: 각 단계별 성과 측정으로 ROI 검증
---
## 부록
### A. 필요 의존성
```toml
# pyproject.toml 추가 의존성
[project.dependencies]
langchain = ">=0.1.0"
langchain-openai = ">=0.0.5"
langchain-community = ">=0.0.20"
langgraph = ">=0.0.30"
chromadb = ">=0.4.22"
tiktoken = ">=0.5.2"
```
### B. 환경 변수 추가
```env
# .env 추가
CHROMA_PERSIST_DIR=./data/chroma_db
LANGCHAIN_TRACING_V2=true # 선택: LangSmith 모니터링
LANGCHAIN_API_KEY=xxx # 선택: LangSmith 모니터링
```
### C. 참고 자료
- [LangChain Documentation](https://python.langchain.com/)
- [LangGraph Documentation](https://langchain-ai.github.io/langgraph/)
- [Chroma Documentation](https://docs.trychroma.com/)
- [OpenAI Cookbook](https://cookbook.openai.com/)
---
*이 보고서는 CastAD 백엔드 프로젝트 분석을 기반으로 작성되었습니다.*
*작성일: 2025-12-28*