binlog

MDE

추상적인 음악적 상상을 LLM으로 분석해 10개 필드의 MusicProfile과 4방향 창작 가이드로 구조화하는 음악 디렉션 엔진. 음악 추천 생성이 아닌, 제작 전 단계의 방향성 데이터를 만들어냅니다.

RoleAI 백엔드 개발, 풀스택Year2025
PythonFastAPINext.js 15TypeScriptTailwind CSS v4OpenRouterPollinations.aiSQLAlchemyPostgreSQL
MDE
01

Problem

음악 제작자는 "새벽에 혼자 운전하는 느낌"처럼 감정적 언어로 창작을 시작합니다. 그런데 이 언어는 제작 파라미터(BPM, 악기 편성, 믹싱 접근법)로 직접 연결되지 않습니다. 기존 레퍼런스 플레이리스트는 원하는 결과물을 보여줄 뿐 방향을 주지 않고, Suno나 Udio 같은 AI 음악 생성기는 완성 트랙을 만들 뿐 창작 방향을 구조화하지 않습니다. 음악 추천 시스템은 기존 음악을 제안할 뿐 새로운 제작 경로를 열어주지 못합니다. MDE는 이 제작 전 단계의 공백을 채웁니다.

02

Limitation

일반 AI 도구는 자연어로 답변하는 데 그칩니다. "폭발적인 에너지로 우울을 극복하는 펑크 사운드"라는 입력에 설명은 가능하지만, 제작에 필요한 구조화된 데이터로 변환하지는 못합니다. 음악 방향, 사운드 구성, 비주얼 무드가 각각 분리되어 있어 하나의 아이디어가 앨범 커버, 공연 무드, 콘텐츠 기획까지 이어지기 어렵습니다.

03

Solution

MDE는 감정 언어로 입력한 음악 아이디어를 LLM으로 분석해 10개 필드의 MusicProfile JSON으로 변환하고, 이를 기반으로 4방향 창작 가이드(DirectionExplanation)를 생성합니다. MusicProfile: emotion, energy, tempo_feel, genre, instrumentation, sound_direction, atmosphere, visual_association, listener_context, content_goal, summary 이를 기반으로 사용자는 다음 결과를 얻을 수 있습니다. 음악 방향 설명 사운드 엔지니어링 가이드 비주얼 / 앨범 아트 방향 콘텐츠 활용 전략

04

System Architecture

Natural Language Input: "새벽에 혼자 운전하는 느낌"
          |
          v
 [Stage 1] LLM (temperature 0.3)
 → MusicProfile JSON (10 fields)

 {
   emotion: ["melancholic", "lonely", "contemplative"],
   energy: "low",
   tempo_feel: "slow",
   genre: ["indie rock", "ambient"],
   instrumentation: ["clean guitar", "synth pad", "soft kick"],
   sound_direction: ["heavy reverb", "wide stereo pad"],
   atmosphere: ["rainy night", "empty street"],
   visual_association: ["blue neon", "wet road reflection"],
   listener_context: "새벽 드라이브",
   content_goal: "playlist_mood",
   summary: "고독한 새벽 드라이브의 몽환적 인디 사운드"
 }
          |
          v
 [Stage 2] LLM (temperature 0.7)
 → DirectionExplanation JSON (4 fields)
 ├── music_direction
 ├── sound_direction
 ├── visual_direction
 └── content_usage
          |
          v
 Result UI
 ├── JSON 시각화 (에너지 바, 템포 배지, 태그 카드)
 ├── 앨범 커버 목업 (Pollinations.ai Flux)
 └── 세션 저장 / 공유 링크
05

Key Implementation

LLM을 대화 인터페이스가 아닌 구조화 엔진으로 사용했습니다. 사용자의 감정 언어를 MusicProfile JSON 스키마로 강제 변환하고, 이를 기반으로 음악·사운드·비주얼·콘텐츠 방향을 생성하도록 두 단계 순차 파이프라인을 설계했습니다. MusicProfile(10개 필드, temperature 0.3) → DirectionExplanation(4개 방향, temperature 0.7) 순서로 호출해 구조화 정밀도와 창의적 표현을 분리했습니다. LLM 응답에서 JSON을 추출하는 3단계 폴백(직접 파싱 → 마크다운 제거 → 정규식 추출)을 구현해 추론 모델의 불규칙한 출력에 대응했습니다. 또한 Pollinations.ai Flux 모델로 visual_association 필드를 기반으로 앨범 커버 목업 이미지를 자동 생성하고, 세션 저장·공유 링크·데모 모드로 서비스 안정성을 확보했습니다.

06

Key Code

01direction_service.py — 두 단계 LLM 파이프라인python
1async def generate_all_directions(
2 input_text: str,
3 options: dict | None = None,
4) -> dict[str, Any]:
5 """Generate MusicProfile and DirectionExplanation sequentially."""
6 profile_msg = _build_profile_user_message(input_text, options)
7
8 # Stage 1: MusicProfile 생성 (구조화 우선, temperature 0.3)
9 profile_raw = await call_gemini(PROFILE_SYSTEM_PROMPT, profile_msg, temperature=0.3)
10 music_profile = extract_json(profile_raw)
11
12 # Stage 2: DirectionExplanation 생성 (창의성 우선, temperature 0.7)
13 explanation_msg = json.dumps(music_profile, ensure_ascii=False, indent=2)
14 try:
15 expl_raw = await call_gemini(
16 EXPLANATION_SYSTEM_PROMPT,
17 explanation_msg,
18 temperature=0.7,
19 max_tokens=1024,
20 )
21 explanation = extract_json(expl_raw)
22 except Exception as e:
23 logger.warning("Explanation generation failed, using fallback: %s", e)
24 explanation = _make_fallback_explanation(music_profile)
25
26 return {"musicProfile": music_profile, "explanation": explanation}
27
28
29def extract_json(text: str) -> dict:
30 """3단계 폴백으로 LLM 응답에서 JSON을 추출합니다."""
31 try:
32 return json.loads(text) # 1. 직접 파싱
33 except json.JSONDecodeError:
34 pass
35 clean = re.sub(r"```(?:json)?s*", "", text).strip().rstrip("`").strip()
36 try:
37 return json.loads(clean) # 2. 마크다운 제거
38 except json.JSONDecodeError:
39 pass
40 match = re.search(r"{[sS]*}", text)
41 if match:
42 return json.loads(match.group()) # 3. 정규식 추출
43 raise ValueError(f"Could not extract JSON: {text[:200]}")

두 단계 순차 LLM 파이프라인입니다. Stage 1은 temperature 0.3으로 10개 필드의 MusicProfile JSON을 정밀하게 구조화하고, Stage 2는 temperature 0.7로 그 결과를 받아 음악·사운드·비주얼·콘텐츠 4방향 창작 가이드를 생성합니다. extract_json은 추론 모델이 사고 과정을 앞에 출력하는 특성에 대응하는 3단계 폴백 파싱을 구현합니다.

07

Result & Learnings

LLM에게 JSON 스키마를 시스템 프롬프트로 강제하면, 자연어 답변이 아닌 재사용 가능한 구조화 데이터를 얻을 수 있습니다. MDE를 통해 이 접근이 감정 언어를 제작 파라미터로 변환하는 데 실질적으로 유효함을 확인했습니다. MusicProfile(구조화, 정밀도 우선)과 DirectionExplanation(서사, 창의성 우선)을 별도 LLM 호출로 분리하면 각 스키마의 복잡도를 낮추고 파싱 안정성을 높일 수 있습니다. LLM 응답 파싱에서 3단계 폴백을 구현하면서, 추론 모델이 답변 전에 사고 과정을 출력하는 특성이 JSON 추출에 영향을 준다는 점을 경험했습니다. MusicProfile의 visual_association 필드가 앨범 커버 프롬프트로 그대로 활용되는 설계에서, 데이터 구조 하나가 여러 다운스트림 도구에 연결되는 확장성을 확인했습니다.

Links