yumoqing 113eb7e040 feat: add KTV pipeline handlers (17 step types, 3 production modes)
- handlers_ktv.py: 17 async step handlers for KTV production
  - Audio/Video preparation (ffmpeg)
  - Demucs vocal separation (GPU server SSH)
  - Lyric calibration (SenseVoice ASR + LLM)
  - Subtitle rendering (ASS karaoke format)
  - Lyric generation & evaluation (Mode C)
  - Music generation (Suno/MiniMax API)
  - Character design & image generation (wan2.7)
  - Storyboard generation (LLM)
  - Scene video generation (T2V/Ref2V)
  - Scene video evaluation (quality threshold)
  - Scene video concatenation (ffmpeg loop)
  - KTV synthesis (dual-track + MTV)

- llm_bridge.py: async LLM call bridge (harnessed_agent / OpenAI API)
- storage.py: extract deps from step_config JSON
- init.py: auto-register KTV handlers on load
2026-06-11 20:36:05 +08:00

63 lines
2.0 KiB
Python

"""LLM bridge for pipeline handlers.
Provides a simple async interface for handlers to call LLM APIs.
Uses harnessed_agent's llm_chat under the hood when available,
falls back to direct HTTP calls.
"""
import json
import logging
import os
logger = logging.getLogger("pipeline.llm_bridge")
async def llm_call(prompt: str, model: str = None, temperature: float = 0.7) -> str:
"""Call LLM and return text response.
Tries multiple backends:
1. harnessed_agent.llm_chat (if loaded in ServerEnv)
2. Direct OpenAI-compatible API call
"""
# Try harnessed_agent first
try:
from ahserver.serverenv import ServerEnv
env = ServerEnv()
if hasattr(env, 'llm_chat'):
result = await env.llm_chat(prompt, model=model, temperature=temperature)
if isinstance(result, dict):
return result.get("content", result.get("text", str(result)))
return str(result)
except Exception:
pass
# Fallback: direct HTTP call to OpenAI-compatible endpoint
import aiohttp
api_base = os.environ.get("LLM_API_BASE", "https://api.openai.com/v1")
api_key = os.environ.get("LLM_API_KEY", "")
model = model or os.environ.get("LLM_MODEL", "gpt-4o-mini")
if not api_key:
raise ValueError("No LLM API configured (set LLM_API_KEY env var)")
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
}
payload = {
"model": model,
"messages": [{"role": "user", "content": prompt}],
"temperature": temperature,
}
async with aiohttp.ClientSession() as session:
async with session.post(
f"{api_base}/chat/completions", headers=headers, json=payload, timeout=aiohttp.ClientTimeout(total=120)
) as resp:
if resp.status != 200:
text = await resp.text()
raise ValueError(f"LLM API error {resp.status}: {text[:200]}")
data = await resp.json()
return data["choices"][0]["message"]["content"]