nexusbert commited on
Commit
d60836c
·
1 Parent(s): 9e750b8

feat: Enhance AI service and configuration for MiniMax Music 3 integration

Browse files
Files changed (4) hide show
  1. Dockerfile +1 -0
  2. app/core/config.py +26 -8
  3. app/services/ai_service.py +75 -26
  4. requirements.txt +6 -0
Dockerfile CHANGED
@@ -7,6 +7,7 @@ WORKDIR /app
7
  RUN apt-get update && apt-get install -y \
8
  build-essential \
9
  curl \
 
10
  && rm -rf /var/lib/apt/lists/*
11
 
12
  # Copy requirements and install
 
7
  RUN apt-get update && apt-get install -y \
8
  build-essential \
9
  curl \
10
+ git \
11
  && rm -rf /var/lib/apt/lists/*
12
 
13
  # Copy requirements and install
app/core/config.py CHANGED
@@ -1,19 +1,37 @@
1
- import os
2
- from pydantic_settings import BaseSettings
3
- from typing import List
4
 
5
  class Settings(BaseSettings):
 
 
6
  APP_NAME: str = "NaijaSound-Mini"
7
- DEBUG: bool = True
8
 
9
  # Groq for Lyrics
10
- GROQ_API_KEY: str = os.getenv("GROQ_API_KEY", "")
11
  GROQ_BASE_URL: str = "https://api.groq.com/openai/v1"
12
- GROQ_MODEL: str = "llama-3.3-70b-versatile"
 
13
 
14
- # MiniMax Music 3 Local Inference Server (SGLang-Omni)
15
- MINIMAX_SERVER_URL: str = os.getenv("MINIMAX_SERVER_URL", "http://127.0.0.1:8000")
 
 
 
 
 
16
 
17
  CORS_ORIGINS: List[str] = ["*"]
18
 
 
 
 
 
 
 
 
 
 
 
 
19
  settings = Settings()
 
1
+ from pydantic_settings import BaseSettings, SettingsConfigDict
2
+ from typing import List, Optional, Union
 
3
 
4
  class Settings(BaseSettings):
5
+ model_config = SettingsConfigDict(env_file=".env", extra="ignore")
6
+
7
  APP_NAME: str = "NaijaSound-Mini"
8
+ DEBUG: Union[bool, str] = True
9
 
10
  # Groq for Lyrics
11
+ GROQ_API_KEY: str = ""
12
  GROQ_BASE_URL: str = "https://api.groq.com/openai/v1"
13
+ GROQ_LYRICS_MODEL: str = "openai/gpt-oss-20b"
14
+ GROQ_MODEL: Optional[str] = None
15
 
16
+ # MiniMax Music 3 local Diffusers pipeline.
17
+ AI_API_BASE_URL: str = "http://127.0.0.1:8000/v1"
18
+ AI_MUSIC_MODEL: str = "MiniMaxAI/MiniMax-Music3"
19
+ MINIMAX_SERVER_URL: Optional[str] = None
20
+ MINIMAX_AUDIO_DURATION_SECONDS: float = 60.0
21
+ MINIMAX_SEED: int = 7
22
+ MINIMAX_LOW_VRAM: bool = False
23
 
24
  CORS_ORIGINS: List[str] = ["*"]
25
 
26
+ @property
27
+ def groq_model_name(self) -> str:
28
+ return self.GROQ_MODEL or self.GROQ_LYRICS_MODEL
29
+
30
+ @property
31
+ def minimax_audio_speech_url(self) -> str:
32
+ base_url = (self.MINIMAX_SERVER_URL or self.AI_API_BASE_URL).rstrip("/")
33
+ if base_url.endswith("/v1"):
34
+ return f"{base_url}/audio/speech"
35
+ return f"{base_url}/v1/audio/speech"
36
+
37
  settings = Settings()
app/services/ai_service.py CHANGED
@@ -1,3 +1,6 @@
 
 
 
1
  import logging
2
  import httpx
3
  from app.core.config import settings
@@ -7,6 +10,8 @@ logger = logging.getLogger(__name__)
7
  class AIService:
8
  def __init__(self):
9
  self.client = httpx.AsyncClient(timeout=120.0)
 
 
10
 
11
  async def generate_naija_lyrics(self, theme: str):
12
  """Generate Afrobeats lyrics using Groq."""
@@ -25,7 +30,7 @@ class AIService:
25
  f"{settings.GROQ_BASE_URL}/chat/completions",
26
  headers={"Authorization": f"Bearer {settings.GROQ_API_KEY}"},
27
  json={
28
- "model": settings.GROQ_MODEL,
29
  "messages": [{"role": "user", "content": prompt}]
30
  }
31
  )
@@ -33,10 +38,10 @@ class AIService:
33
  return response.json()["choices"][0]["message"]["content"]
34
  except Exception as e:
35
  logger.error(f"Groq error: {e}")
36
- return f"Lyrics generation failed: {str(e)}"
37
 
38
  async def generate_naija_music(self, prompt: str, lyrics: str):
39
- """Generate music using MiniMax Music 3 via a local SGLang-Omni server (no API key needed)."""
40
 
41
  # Construct a structured music description for a Naija theme as per MiniMax Music 3 guidelines
42
  music_description = (
@@ -50,34 +55,78 @@ class AIService:
50
  )
51
 
52
  try:
53
- # Using the /v1/audio/speech endpoint on the local SGLang-Omni server
54
- response = await self.client.post(
55
- f"{settings.MINIMAX_SERVER_URL}/v1/audio/speech",
56
- headers={"Content-Type": "application/json"},
57
- json={
58
- "model": "MiniMaxAI/MiniMax-Music3",
59
- "input": lyrics,
60
- "instructions": music_description,
61
- "response_format": "wav",
62
- "seed": 7,
63
- "max_new_tokens": 750,
64
- "stream": False
65
- }
66
- )
67
- response.raise_for_status()
68
-
69
- # The response is a binary WAV file
70
- import base64
71
- audio_b64 = base64.b64encode(response.content).decode("utf-8")
72
-
73
  return {
74
  "audio_base64": f"data:audio/wav;base64,{audio_b64}",
75
- "model": "MiniMax-Music3",
76
  "format": "wav"
77
  }
78
  except Exception as e:
79
- logger.error(f"MiniMax Local Server error: {e}")
80
- return {"error": f"Music generation failed. Ensure SGLang-Omni is running at {settings.MINIMAX_SERVER_URL}. Error: {str(e)}"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
 
82
  async def close(self):
83
  await self.client.aclose()
 
1
+ import asyncio
2
+ import base64
3
+ import io
4
  import logging
5
  import httpx
6
  from app.core.config import settings
 
10
  class AIService:
11
  def __init__(self):
12
  self.client = httpx.AsyncClient(timeout=120.0)
13
+ self.music_pipe = None
14
+ self.music_lock = asyncio.Lock()
15
 
16
  async def generate_naija_lyrics(self, theme: str):
17
  """Generate Afrobeats lyrics using Groq."""
 
30
  f"{settings.GROQ_BASE_URL}/chat/completions",
31
  headers={"Authorization": f"Bearer {settings.GROQ_API_KEY}"},
32
  json={
33
+ "model": settings.groq_model_name,
34
  "messages": [{"role": "user", "content": prompt}]
35
  }
36
  )
 
38
  return response.json()["choices"][0]["message"]["content"]
39
  except Exception as e:
40
  logger.error(f"Groq error: {e}")
41
+ return f"Error: Lyrics generation failed: {str(e)}"
42
 
43
  async def generate_naija_music(self, prompt: str, lyrics: str):
44
+ """Generate music using MiniMax Music 3 via the local Diffusers pipeline."""
45
 
46
  # Construct a structured music description for a Naija theme as per MiniMax Music 3 guidelines
47
  music_description = (
 
55
  )
56
 
57
  try:
58
+ async with self.music_lock:
59
+ audio_b64 = await asyncio.to_thread(
60
+ self._generate_music_wav_base64,
61
+ music_description,
62
+ lyrics,
63
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
  return {
65
  "audio_base64": f"data:audio/wav;base64,{audio_b64}",
66
+ "model": settings.AI_MUSIC_MODEL,
67
  "format": "wav"
68
  }
69
  except Exception as e:
70
+ logger.error(f"MiniMax Diffusers error: {e}")
71
+ return {"error": f"Music generation failed. Ensure Diffusers, Torch, SoundFile, and a CUDA GPU are available. Error: {str(e)}"}
72
+
73
+ def _load_music_pipe(self):
74
+ if self.music_pipe is not None:
75
+ return self.music_pipe
76
+
77
+ try:
78
+ import torch
79
+ from diffusers import ComponentsManager, ModularPipeline
80
+ from diffusers.hooks import apply_group_offloading
81
+ except ImportError as e:
82
+ raise RuntimeError(
83
+ "MiniMax local generation dependencies are missing. Install diffusers from the MiniMax PR commit plus transformers, accelerate, torch, and soundfile."
84
+ ) from e
85
+
86
+ if not torch.cuda.is_available():
87
+ raise RuntimeError("MiniMax Music 3 requires a CUDA GPU for local Diffusers generation.")
88
+
89
+ if settings.MINIMAX_LOW_VRAM:
90
+ manager = ComponentsManager()
91
+ manager.enable_auto_cpu_offload(device="cuda")
92
+ pipe = ModularPipeline.from_pretrained(
93
+ settings.AI_MUSIC_MODEL,
94
+ components_manager=manager,
95
+ )
96
+ else:
97
+ pipe = ModularPipeline.from_pretrained(settings.AI_MUSIC_MODEL)
98
+
99
+ pipe.load_components(dtype=torch.bfloat16)
100
+
101
+ if settings.MINIMAX_LOW_VRAM:
102
+ apply_group_offloading(
103
+ pipe.language_model,
104
+ onload_device=torch.device("cuda"),
105
+ offload_type="leaf_level",
106
+ use_stream=True,
107
+ )
108
+ else:
109
+ pipe.to("cuda")
110
+
111
+ self.music_pipe = pipe
112
+ return pipe
113
+
114
+ def _generate_music_wav_base64(self, prompt: str, lyrics: str) -> str:
115
+ import soundfile as sf
116
+ import torch
117
+
118
+ pipe = self._load_music_pipe()
119
+ audio = pipe(
120
+ prompt=prompt,
121
+ lyrics=lyrics,
122
+ audio_duration=settings.MINIMAX_AUDIO_DURATION_SECONDS,
123
+ generator=torch.Generator("cuda").manual_seed(settings.MINIMAX_SEED),
124
+ output="audios",
125
+ )[0]
126
+
127
+ wav_buffer = io.BytesIO()
128
+ sf.write(wav_buffer, audio.T.float().cpu().numpy(), pipe.sampling_rate, format="WAV")
129
+ return base64.b64encode(wav_buffer.getvalue()).decode("utf-8")
130
 
131
  async def close(self):
132
  await self.client.aclose()
requirements.txt CHANGED
@@ -4,3 +4,9 @@ uvicorn
4
  pydantic-settings
5
  httpx
6
  python-multipart
 
 
 
 
 
 
 
4
  pydantic-settings
5
  httpx
6
  python-multipart
7
+ openai
8
+ git+https://github.com/huggingface/diffusers@dafe3733fcfdbf3c48915fe77be3aef65b5d6a2d
9
+ transformers
10
+ accelerate
11
+ soundfile
12
+ torch