Ace-Step-Munk / docs /ko /Openrouter_API_DOC.md
OnyxMunk's picture
Add LoRA training assets: scripts, docs (no binaries), ui, my_dataset
bc9c638

A newer version of the Gradio SDK is available: 6.9.0

Upgrade

ACE-Step OpenRouter API λ¬Έμ„œ

AI μŒμ•… 생성을 μœ„ν•œ OpenAI Chat Completions ν˜Έν™˜ API

Base URL: http://{host}:{port} (κΈ°λ³Έκ°’ http://127.0.0.1:8002)


λͺ©μ°¨


인증

μ„œλ²„μ— API ν‚€κ°€ μ„€μ •λœ 경우(ν™˜κ²½ λ³€μˆ˜ OPENROUTER_API_KEY λ˜λŠ” --api-key ν”Œλž˜κ·Έ μ‚¬μš©), λͺ¨λ“  μš”μ²­μ€ λ‹€μŒ 헀더λ₯Ό 포함해야 ν•©λ‹ˆλ‹€:

Authorization: Bearer <your-api-key>

API ν‚€κ°€ μ„€μ •λ˜μ§€ μ•Šμ€ 경우 인증이 ν•„μš”ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.


μ—”λ“œν¬μΈνŠΈ

1. μŒμ•… 생성

POST /v1/chat/completions

μ±„νŒ… λ©”μ‹œμ§€λ‘œλΆ€ν„° μŒμ•…μ„ μƒμ„±ν•˜κ³  μ˜€λ””μ˜€ 데이터와 LM이 μƒμ„±ν•œ 메타데이터λ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€.

μš”μ²­ νŒŒλΌλ―Έν„°

ν•„λ“œ νƒ€μž… ν•„μˆ˜ κΈ°λ³Έκ°’ μ„€λͺ…
model string μ•„λ‹ˆμš” μžλ™ λͺ¨λΈ ID (/v1/modelsμ—μ„œ 확인)
messages array 예 - μ±„νŒ… λ©”μ‹œμ§€ 리슀트. μž…λ ₯ λͺ¨λ“œ μ°Έμ‘°
stream boolean μ•„λ‹ˆμš” false 슀트리밍 응닡 ν™œμ„±ν™”. 슀트리밍 응닡 μ°Έμ‘°
audio_config object μ•„λ‹ˆμš” null μ˜€λ””μ˜€ 생성 μ„€μ •. μ•„λž˜ μ°Έμ‘°
temperature float μ•„λ‹ˆμš” 0.85 LM μƒ˜ν”Œλ§ μ˜¨λ„
top_p float μ•„λ‹ˆμš” 0.9 LM nucleus sampling νŒŒλΌλ―Έν„°
seed int | string μ•„λ‹ˆμš” null 랜덀 μ‹œλ“œ. batch_size > 1일 λ•Œ μ‰Όν‘œλ‘œ κ΅¬λΆ„ν•˜μ—¬ μ—¬λŸ¬ 개 μ§€μ • κ°€λŠ₯ (예: "42,123,456")
lyrics string μ•„λ‹ˆμš” "" 직접 μ „λ‹¬λ˜λŠ” 가사 (λ©”μ‹œμ§€μ—μ„œ νŒŒμ‹±λœ 가사보닀 μš°μ„ ). μ„€μ • μ‹œ messages ν…μŠ€νŠΈλŠ” prompt둜 μ‚¬μš©
sample_mode boolean μ•„λ‹ˆμš” false LLM sample λͺ¨λ“œ ν™œμ„±ν™”. messages ν…μŠ€νŠΈκ°€ sample_query둜 LLM에 μ „λ‹¬λ˜μ–΄ prompt/lyrics μžλ™ 생성
thinking boolean μ•„λ‹ˆμš” false 더 κΉŠμ€ 좔둠을 μœ„ν•œ LLM thinking λͺ¨λ“œ ν™œμ„±ν™”
use_format boolean μ•„λ‹ˆμš” false μ‚¬μš©μžκ°€ prompt/lyricsλ₯Ό μ œκ³΅ν•  λ•Œ LLM ν¬λ§·νŒ…μœΌλ‘œ κ°œμ„ 
use_cot_caption boolean μ•„λ‹ˆμš” true CoTλ₯Ό 톡해 μŒμ•… μ„€λͺ…을 μž¬μž‘μ„±/κ°œμ„ 
use_cot_language boolean μ•„λ‹ˆμš” true CoTλ₯Ό 톡해 보컬 μ–Έμ–΄λ₯Ό μžλ™ 감지
guidance_scale float μ•„λ‹ˆμš” 7.0 Classifier-free guidance scale
batch_size int μ•„λ‹ˆμš” 1 생성할 μ˜€λ””μ˜€ 수
task_type string μ•„λ‹ˆμš” "text2music" μž‘μ—… μœ ν˜•. μ˜€λ””μ˜€ μž…λ ₯ μ°Έμ‘°
repainting_start float μ•„λ‹ˆμš” 0.0 repaint μ˜μ—­ μ‹œμž‘ μœ„μΉ˜(초)
repainting_end float μ•„λ‹ˆμš” null repaint μ˜μ—­ μ’…λ£Œ μœ„μΉ˜(초)
audio_cover_strength float μ•„λ‹ˆμš” 1.0 컀버 강도 (0.0~1.0)

audio_config 객체

ν•„λ“œ νƒ€μž… κΈ°λ³Έκ°’ μ„€λͺ…
duration float null μ˜€λ””μ˜€ 길이(초). μƒλž΅ μ‹œ LM이 μžλ™ κ²°μ •
bpm integer null λΆ„λ‹Ή λΉ„νŠΈμˆ˜(BPM). μƒλž΅ μ‹œ LM이 μžλ™ κ²°μ •
vocal_language string "en" 보컬 μ–Έμ–΄ μ½”λ“œ (예: "ko", "en", "ja")
instrumental boolean null 보컬 μ—†λŠ” 연주곑 μ—¬λΆ€. μƒλž΅ μ‹œ 가사에 따라 μžλ™ νŒλ‹¨
format string "mp3" 좜λ ₯ μ˜€λ””μ˜€ 포맷
key_scale string null μ‘°μ„± (예: "C major")
time_signature string null λ°•μž (예: "4/4")

messages ν…μŠ€νŠΈμ˜ μ˜λ―ΈλŠ” λͺ¨λ“œμ— 따라 λ‹€λ¦…λ‹ˆλ‹€:

  • lyrics μ„€μ • μ‹œ β†’ messages ν…μŠ€νŠΈ = prompt (μŒμ•… μ„€λͺ…)
  • sample_mode: true μ„€μ • μ‹œ β†’ messages ν…μŠ€νŠΈ = sample_query (LLMμ—κ²Œ λͺ¨λ“  것을 μƒμ„±ν•˜λ„λ‘ 함)
  • λ‘˜ λ‹€ λ―Έμ„€μ • β†’ μžλ™ 감지: νƒœκ·Έκ°€ 있으면 νƒœκ·Έ λͺ¨λ“œ, κ°€μ‚¬μ²˜λŸΌ 보이면 가사 λͺ¨λ“œ, κ·Έ μ™Έ sample λͺ¨λ“œ

messages ν˜•μ‹

일반 ν…μŠ€νŠΈμ™€ λ©€ν‹°λͺ¨λ‹¬(ν…μŠ€νŠΈ + μ˜€λ””μ˜€) 두 κ°€μ§€ ν˜•μ‹μ„ μ§€μ›ν•©λ‹ˆλ‹€:

일반 ν…μŠ€νŠΈ:

{
  "messages": [
    {"role": "user", "content": "μž…λ ₯ λ‚΄μš©"}
  ]
}

λ©€ν‹°λͺ¨λ‹¬ (μ˜€λ””μ˜€ μž…λ ₯ 포함):

{
  "messages": [
    {
      "role": "user",
      "content": [
        {"type": "text", "text": "이 λ…Έλž˜λ₯Ό μ»€λ²„ν•΄μ€˜"},
        {
          "type": "input_audio",
          "input_audio": {
            "data": "<base64 μ˜€λ””μ˜€ 데이터>",
            "format": "mp3"
          }
        }
      ]
    }
  ]
}

λΉ„μŠ€νŠΈλ¦¬λ° 응닡 (stream: false)

{
  "id": "chatcmpl-a1b2c3d4e5f6g7h8",
  "object": "chat.completion",
  "created": 1706688000,
  "model": "acemusic/acestep-v15-turbo",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "## Metadata\n**Caption:** Upbeat pop song...\n**BPM:** 120\n**Duration:** 30s\n**Key:** C major\n\n## Lyrics\n[Verse 1]\nHello world...",
        "audio": [
          {
            "type": "audio_url",
            "audio_url": {
              "url": "data:audio/mpeg;base64,SUQzBAAAAAAAI1RTU0UAAAA..."
            }
          }
        ]
      },
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 10,
    "completion_tokens": 100,
    "total_tokens": 110
  }
}

응닡 ν•„λ“œ μ„€λͺ…:

ν•„λ“œ μ„€λͺ…
choices[0].message.content LM이 μƒμ„±ν•œ ν…μŠ€νŠΈ 정보. Metadata(Caption/BPM/Duration/Key/Time Signature/Language)와 Lyricsλ₯Ό 포함. LM이 κ΄€μ—¬ν•˜μ§€ μ•Šμ€ 경우 "Music generated successfully." λ°˜ν™˜
choices[0].message.audio μ˜€λ””μ˜€ 데이터 λ°°μ—΄. 각 ν•­λͺ©μ— type ("audio_url")κ³Ό audio_url.url (Base64 Data URL, ν˜•μ‹: data:audio/mpeg;base64,...)을 포함
choices[0].finish_reason "stop"은 정상 μ™„λ£Œλ₯Ό λ‚˜νƒ€λƒ„

μ˜€λ””μ˜€ λ””μ½”λ”© ν˜•μ‹:

audio_url.url 값은 Data URL ν˜•μ‹: data:audio/mpeg;base64,<base64_data>

μ‰Όν‘œ μ΄ν›„μ˜ base64 데이터 뢀뢄을 μΆ”μΆœν•˜μ—¬ λ””μ½”λ”©ν•˜λ©΄ MP3 νŒŒμΌμ„ 얻을 수 μžˆμŠ΅λ‹ˆλ‹€:

import base64

url = response["choices"][0]["message"]["audio"][0]["audio_url"]["url"]
# "data:audio/mpeg;base64," 접두사 제거
b64_data = url.split(",", 1)[1]
audio_bytes = base64.b64decode(b64_data)

with open("output.mp3", "wb") as f:
    f.write(audio_bytes)
const url = response.choices[0].message.audio[0].audio_url.url;
const b64Data = url.split(",")[1];
const audioBytes = atob(b64Data);
// Data URL을 <audio> νƒœκ·Έμ— 직접 μ‚¬μš© κ°€λŠ₯
const audio = new Audio(url);
audio.play();

2. λͺ¨λΈ λͺ©λ‘

GET /v1/models

μ‚¬μš© κ°€λŠ₯ν•œ λͺ¨λΈ 정보λ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€.

응닡

{
  "data": [
    {
      "id": "acemusic/acestep-v15-turbo",
      "name": "ACE-Step",
      "created": 1706688000,
      "description": "High-performance text-to-music generation model. Supports multiple styles, lyrics input, and various audio durations.",
      "input_modalities": ["text", "audio"],
      "output_modalities": ["audio", "text"],
      "context_length": 4096,
      "pricing": {"prompt": "0", "completion": "0", "request": "0"},
      "supported_sampling_parameters": ["temperature", "top_p"]
    }
  ]
}

3. ν—¬μŠ€ 체크

GET /health

응닡

{
  "status": "ok",
  "service": "ACE-Step OpenRouter API",
  "version": "1.0"
}

μž…λ ₯ λͺ¨λ“œ

μ‹œμŠ€ν…œμ€ λ§ˆμ§€λ§‰ user λ©”μ‹œμ§€μ˜ λ‚΄μš©μ— 따라 μž…λ ₯ λͺ¨λ“œλ₯Ό μžλ™μœΌλ‘œ μ„ νƒν•©λ‹ˆλ‹€. lyrics λ˜λŠ” sample_mode ν•„λ“œλ‘œ λͺ…μ‹œμ μœΌλ‘œ μ§€μ •ν•  μˆ˜λ„ μžˆμŠ΅λ‹ˆλ‹€.

λͺ¨λ“œ 1: νƒœκ·Έ λͺ¨λ“œ (ꢌμž₯)

<prompt>와 <lyrics> νƒœκ·Έλ₯Ό μ‚¬μš©ν•˜μ—¬ μŒμ•… μ„€λͺ…κ³Ό 가사λ₯Ό λͺ…μ‹œμ μœΌλ‘œ μ§€μ •ν•©λ‹ˆλ‹€:

{
  "messages": [
    {
      "role": "user",
      "content": "<prompt>A gentle acoustic ballad in C major, female vocal</prompt>\n<lyrics>[Verse 1]\nSunlight through the window\nA brand new day begins\n\n[Chorus]\nWe are the dreamers\nWe are the light</lyrics>"
    }
  ],
  "audio_config": {
    "duration": 30,
    "vocal_language": "en"
  }
}
  • <prompt>...</prompt> β€” μŒμ•… μŠ€νƒ€μΌ/μž₯λ©΄ μ„€λͺ… (caption)
  • <lyrics>...</lyrics> β€” 가사 λ‚΄μš©
  • ν•˜λ‚˜μ˜ νƒœκ·Έλ§Œ μ‚¬μš©ν•  μˆ˜λ„ 있음
  • use_format: true일 λ•Œ LLM이 prompt와 lyricsλ₯Ό μžλ™μœΌλ‘œ κ°œμ„ 

λͺ¨λ“œ 2: μžμ—°μ–΄ λͺ¨λ“œ (μƒ˜ν”Œ λͺ¨λ“œ)

μ›ν•˜λŠ” μŒμ•…μ„ μžμ—°μ–΄λ‘œ μ„€λͺ…ν•©λ‹ˆλ‹€. μ‹œμŠ€ν…œμ΄ LLM을 μ‚¬μš©ν•˜μ—¬ prompt와 lyricsλ₯Ό μžλ™μœΌλ‘œ μƒμ„±ν•©λ‹ˆλ‹€:

{
  "messages": [
    {"role": "user", "content": "여름과 여행에 κ΄€ν•œ μ‹ λ‚˜λŠ” νŒμ†‘μ„ λ§Œλ“€μ–΄μ€˜"}
  ],
  "sample_mode": true,
  "audio_config": {
    "vocal_language": "ko"
  }
}

트리거 쑰건: sample_mode: true, λ˜λŠ” λ©”μ‹œμ§€μ— νƒœκ·Έκ°€ μ—†κ³  κ°€μ‚¬μ²˜λŸΌ 보이지 μ•Šμ„ λ•Œ μžλ™ 트리거.

λͺ¨λ“œ 3: 가사 μ „μš© λͺ¨λ“œ

ꡬ쑰 λ§ˆμ»€κ°€ μžˆλŠ” 가사λ₯Ό 직접 μ „λ‹¬ν•˜λ©΄ μ‹œμŠ€ν…œμ΄ μžλ™μœΌλ‘œ μΈμ‹ν•©λ‹ˆλ‹€:

{
  "messages": [
    {
      "role": "user",
      "content": "[Verse 1]\nWalking down the street\nFeeling the beat\n\n[Chorus]\nDance with me tonight\nUnder the moonlight"
    }
  ],
  "audio_config": {"duration": 30}
}

트리거 쑰건: λ©”μ‹œμ§€μ— [Verse], [Chorus] λ“±μ˜ λ§ˆμ»€κ°€ ν¬ν•¨λ˜κ±°λ‚˜ μ—¬λŸ¬ μ€„μ˜ 짧은 ν…μŠ€νŠΈ ꡬ쑰λ₯Ό κ°€μ§„ 경우.

λͺ¨λ“œ 4: 가사 + Prompt 뢄리

lyrics ν•„λ“œλ‘œ 가사λ₯Ό 직접 μ „λ‹¬ν•˜κ³ , messages ν…μŠ€νŠΈλŠ” μžλ™μœΌλ‘œ prompt둜 μ‚¬μš©λ©λ‹ˆλ‹€:

{
  "messages": [
    {"role": "user", "content": "Energetic EDM with heavy bass drops"}
  ],
  "lyrics": "[Verse 1]\nFeel the rhythm in your soul\nLet the music take control\n\n[Drop]\n(instrumental break)",
  "audio_config": {
    "bpm": 128,
    "duration": 60
  }
}

연주곑 λͺ¨λ“œ

audio_config.instrumental: true μ„€μ •:

{
  "messages": [
    {"role": "user", "content": "<prompt>Epic orchestral cinematic score, dramatic and powerful</prompt>"}
  ],
  "audio_config": {
    "instrumental": true,
    "duration": 30
  }
}

μ˜€λ””μ˜€ μž…λ ₯

λ©€ν‹°λͺ¨λ‹¬ messagesλ₯Ό 톡해 μ˜€λ””μ˜€ 파일(base64 인코딩)을 μ „λ‹¬ν•˜μ—¬ cover, repaint λ“±μ˜ μž‘μ—…μ— μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

task_type μœ ν˜•

task_type μ„€λͺ… μ˜€λ””μ˜€ μž…λ ₯ ν•„μš”
text2music ν…μŠ€νŠΈμ—μ„œ μŒμ•… 생성 (κΈ°λ³Έκ°’) 선택 (reference둜)
cover 컀버/μŠ€νƒ€μΌ μ „ν™˜ src_audio ν•„μš”
repaint λΆ€λΆ„ λ¦¬νŽ˜μΈνŒ… src_audio ν•„μš”
lego μ˜€λ””μ˜€ μ ‘ν•© src_audio ν•„μš”
extract μ˜€λ””μ˜€ μΆ”μΆœ src_audio ν•„μš”
complete μ˜€λ””μ˜€ 이어쓰기 src_audio ν•„μš”

μ˜€λ””μ˜€ λΌμš°νŒ… κ·œμΉ™

μ—¬λŸ¬ input_audio 블둝은 μˆœμ„œλŒ€λ‘œ λ‹€λ₯Έ νŒŒλΌλ―Έν„°μ— λΌμš°νŒ…λ©λ‹ˆλ‹€ (닀쀑 이미지 μ—…λ‘œλ“œμ™€ μœ μ‚¬):

task_type audio[0] audio[1]
text2music reference_audio (μŠ€νƒ€μΌ μ°Έμ‘°) -
cover/repaint/lego/extract/complete src_audio (νŽΈμ§‘ λŒ€μƒ μ˜€λ””μ˜€) reference_audio (선택적 μŠ€νƒ€μΌ μ°Έμ‘°)

μ˜€λ””μ˜€ μž…λ ₯ 예제

Cover μž‘μ—… (컀버):

{
  "messages": [
    {
      "role": "user",
      "content": [
        {"type": "text", "text": "<prompt>Jazz style cover with saxophone</prompt>"},
        {
          "type": "input_audio",
          "input_audio": {"data": "<base64 원본 μ˜€λ””μ˜€>", "format": "mp3"}
        }
      ]
    }
  ],
  "task_type": "cover",
  "audio_cover_strength": 0.8,
  "audio_config": {"duration": 30}
}

Repaint μž‘μ—… (λΆ€λΆ„ λ¦¬νŽ˜μΈνŒ…):

{
  "messages": [
    {
      "role": "user",
      "content": [
        {"type": "text", "text": "<prompt>Replace with guitar solo</prompt>"},
        {
          "type": "input_audio",
          "input_audio": {"data": "<base64 원본 μ˜€λ””μ˜€>", "format": "mp3"}
        }
      ]
    }
  ],
  "task_type": "repaint",
  "repainting_start": 10.0,
  "repainting_end": 20.0,
  "audio_config": {"duration": 30}
}

슀트리밍 응닡

"stream": true둜 μ„€μ •ν•˜λ©΄ SSE(Server-Sent Events) 슀트리밍이 ν™œμ„±ν™”λ©λ‹ˆλ‹€.

이벀트 ν˜•μ‹

각 μ΄λ²€νŠΈλŠ” data: 둜 μ‹œμž‘ν•˜κ³  JSON이 λ’€λ”°λ₯΄λ©° 이쀑 μ€„λ°”κΏˆ \n\n으둜 λλ‚©λ‹ˆλ‹€:

data: {"id":"chatcmpl-xxx","object":"chat.completion.chunk","created":1706688000,"model":"acemusic/acestep-v15-turbo","choices":[{"index":0,"delta":{...},"finish_reason":null}]}

슀트리밍 이벀트 μˆœμ„œ

단계 delta λ‚΄μš© μ„€λͺ…
1. μ΄ˆκΈ°ν™” {"role":"assistant","content":""} μ—°κ²° 수립
2. LM μ½˜ν…μΈ  {"content":"\n\n## Metadata\n..."} LM μ‚¬μš© μ‹œ metadata와 lyrics 전솑
3. ν•˜νŠΈλΉ„νŠΈ {"content":"."} μ˜€λ””μ˜€ 생성 쀑 2μ΄ˆλ§ˆλ‹€ 전솑, μ—°κ²° μœ μ§€
4. μ˜€λ””μ˜€ 데이터 {"audio":[{"type":"audio_url","audio_url":{"url":"data:..."}}]} μ˜€λ””μ˜€ base64 데이터
5. μ™„λ£Œ finish_reason: "stop" 생성 μ™„λ£Œ
6. μ’…λ£Œ data: [DONE] 슀트림 μ’…λ£Œ 마컀

슀트리밍 응닡 μ˜ˆμ‹œ

data: {"id":"chatcmpl-abc123","object":"chat.completion.chunk","created":1706688000,"model":"acemusic/acestep-v15-turbo","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]}

data: {"id":"chatcmpl-abc123","object":"chat.completion.chunk","created":1706688000,"model":"acemusic/acestep-v15-turbo","choices":[{"index":0,"delta":{"content":"\n\n## Metadata\n**Caption:** Upbeat pop\n**BPM:** 120"},"finish_reason":null}]}

data: {"id":"chatcmpl-abc123","object":"chat.completion.chunk","created":1706688000,"model":"acemusic/acestep-v15-turbo","choices":[{"index":0,"delta":{"content":"."},"finish_reason":null}]}

data: {"id":"chatcmpl-abc123","object":"chat.completion.chunk","created":1706688000,"model":"acemusic/acestep-v15-turbo","choices":[{"index":0,"delta":{"audio":[{"type":"audio_url","audio_url":{"url":"data:audio/mpeg;base64,..."}}]},"finish_reason":null}]}

data: {"id":"chatcmpl-abc123","object":"chat.completion.chunk","created":1706688000,"model":"acemusic/acestep-v15-turbo","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}

data: [DONE]

ν΄λΌμ΄μ–ΈνŠΈ μΈ‘ 슀트리밍 처리

import json
import httpx

with httpx.stream("POST", "http://127.0.0.1:8002/v1/chat/completions", json={
    "messages": [{"role": "user", "content": "κ²½μΎŒν•œ 기타 곑을 μƒμ„±ν•΄μ€˜"}],
    "sample_mode": True,
    "stream": True,
    "audio_config": {"instrumental": True}
}) as response:
    content_parts = []
    audio_url = None

    for line in response.iter_lines():
        if not line or not line.startswith("data: "):
            continue
        if line == "data: [DONE]":
            break

        chunk = json.loads(line[6:])
        delta = chunk["choices"][0]["delta"]

        if "content" in delta and delta["content"]:
            content_parts.append(delta["content"])

        if "audio" in delta and delta["audio"]:
            audio_url = delta["audio"][0]["audio_url"]["url"]

        if chunk["choices"][0].get("finish_reason") == "stop":
            print("생성 μ™„λ£Œ!")

    print("Content:", "".join(content_parts))
    if audio_url:
        import base64
        b64_data = audio_url.split(",", 1)[1]
        with open("output.mp3", "wb") as f:
            f.write(base64.b64decode(b64_data))
const response = await fetch("http://127.0.0.1:8002/v1/chat/completions", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    messages: [{ role: "user", content: "κ²½μΎŒν•œ 기타 곑을 μƒμ„±ν•΄μ€˜" }],
    sample_mode: true,
    stream: true,
    audio_config: { instrumental: true }
  })
});

const reader = response.body.getReader();
const decoder = new TextDecoder();
let audioUrl = null;
let content = "";

while (true) {
  const { done, value } = await reader.read();
  if (done) break;

  const text = decoder.decode(value);
  for (const line of text.split("\n")) {
    if (!line.startsWith("data: ") || line === "data: [DONE]") continue;

    const chunk = JSON.parse(line.slice(6));
    const delta = chunk.choices[0].delta;

    if (delta.content) content += delta.content;
    if (delta.audio) audioUrl = delta.audio[0].audio_url.url;
  }
}

// audioUrl은 <audio src="...">에 직접 μ‚¬μš© κ°€λŠ₯

예제

예제 1: μžμ—°μ–΄ 생성 (κ°€μž₯ κ°„λ‹¨ν•œ μ‚¬μš©λ²•)

curl -X POST http://127.0.0.1:8002/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "messages": [
      {"role": "user", "content": "κ³ ν–₯κ³Ό 좔얡에 κ΄€ν•œ λΆ€λ“œλŸ¬μš΄ 포크 솑"}
    ],
    "sample_mode": true,
    "audio_config": {"vocal_language": "ko"}
  }'

예제 2: νƒœκ·Έ λͺ¨λ“œ + νŒŒλΌλ―Έν„° μ§€μ •

curl -X POST http://127.0.0.1:8002/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "messages": [
      {
        "role": "user",
        "content": "<prompt>Energetic EDM track with heavy bass drops and synth leads</prompt><lyrics>[Verse 1]\nFeel the rhythm in your soul\nLet the music take control\n\n[Drop]\n(instrumental break)</lyrics>"
      }
    ],
    "audio_config": {
      "bpm": 128,
      "duration": 60,
      "vocal_language": "en"
    }
  }'

예제 3: 연주곑 + LM κ°œμ„  λΉ„ν™œμ„±ν™”

curl -X POST http://127.0.0.1:8002/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "messages": [
      {
        "role": "user",
        "content": "<prompt>Peaceful piano solo, slow tempo, jazz harmony</prompt>"
      }
    ],
    "use_cot_caption": false,
    "audio_config": {
      "instrumental": true,
      "duration": 45
    }
  }'

예제 4: 슀트리밍 μš”μ²­

curl -X POST http://127.0.0.1:8002/v1/chat/completions \
  -H "Content-Type: application/json" \
  -N \
  -d '{
    "messages": [
      {"role": "user", "content": "생일 μΆ•ν•˜ λ…Έλž˜λ₯Ό λ§Œλ“€μ–΄μ€˜"}
    ],
    "sample_mode": true,
    "stream": true
  }'

예제 5: λ©€ν‹° μ‹œλ“œ 배치 생성

curl -X POST http://127.0.0.1:8002/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "messages": [
      {"role": "user", "content": "<prompt>Lo-fi hip hop beat</prompt>"}
    ],
    "batch_size": 3,
    "seed": "42,123,456",
    "audio_config": {
      "instrumental": true,
      "duration": 30
    }
  }'

μ—λŸ¬ μ½”λ“œ

HTTP μƒνƒœ μ½”λ“œ μ„€λͺ…
400 잘λͺ»λœ μš”μ²­ ν˜•μ‹ λ˜λŠ” μœ νš¨ν•œ μž…λ ₯ λˆ„λ½
401 API ν‚€ λˆ„λ½ λ˜λŠ” μœ νš¨ν•˜μ§€ μ•ŠμŒ
429 μ„œλΉ„μŠ€ κ³ΌλΆ€ν•˜, 큐 가득 μ°Έ
500 μŒμ•… 생성 쀑 λ‚΄λΆ€ 였λ₯˜ λ°œμƒ
503 λͺ¨λΈμ΄ 아직 μ΄ˆκΈ°ν™”λ˜μ§€ μ•ŠμŒ
504 생성 νƒ€μž„μ•„μ›ƒ

μ—λŸ¬ 응닡 ν˜•μ‹:

{
  "detail": "μ—λŸ¬ μ„€λͺ… λ©”μ‹œμ§€"
}

μ„œλ²„ μ„€μ • (ν™˜κ²½ λ³€μˆ˜)

λ‹€μŒ ν™˜κ²½ λ³€μˆ˜λ‘œ μ„œλ²„λ₯Ό μ„€μ •ν•  수 μžˆμŠ΅λ‹ˆλ‹€ (운영 참고용):

λ³€μˆ˜λͺ… κΈ°λ³Έκ°’ μ„€λͺ…
OPENROUTER_API_KEY μ—†μŒ API 인증 ν‚€
OPENROUTER_HOST 127.0.0.1 리슨 μ£Όμ†Œ
OPENROUTER_PORT 8002 리슨 포트
ACESTEP_CONFIG_PATH acestep-v15-turbo DiT λͺ¨λΈ μ„€μ • 경둜
ACESTEP_DEVICE auto μΆ”λ‘  λ””λ°”μ΄μŠ€
ACESTEP_LM_MODEL_PATH acestep-5Hz-lm-0.6B LLM λͺ¨λΈ 경둜
ACESTEP_LM_BACKEND vllm LLM μΆ”λ‘  λ°±μ—”λ“œ
ACESTEP_QUEUE_MAXSIZE 200 μž‘μ—… 큐 μ΅œλŒ€ μš©λŸ‰
ACESTEP_GENERATION_TIMEOUT 600 λΉ„μŠ€νŠΈλ¦¬λ° μš”μ²­ νƒ€μž„μ•„μ›ƒ(초)