Files
lesson-highlights/src/core/llm.py
T
hmo aad1548348 refactor: extract config.py, add burn_only, fix title_segments and font size
- Extract all path/API config to config.py (single source of truth)
- Add run.py / burn_only.py / run.bat / burn.bat entry points
- burn_only: skip transcription/subtitle gen, fast reburn existing SRTs
- Fix title_segments: use transcript keyword time for split point
- Fix subtitle: each overlapping title shows max title_duration (not full clip)
- Fix burn_only font size: default from 90 to 60
- Delete old run_lesson1.bat/py, temp debug scripts
- Update README, ARCHITECTURE, CHANGELOG, add USAGE.md
2026-05-03 23:22:10 +08:00

103 lines
3.1 KiB
Python

# -*- coding: utf-8 -*-
"""
LLM调用封装
统一管理火山方舟API调用,包含重试和错误处理
"""
import os
import time
import logging
from .constants import (
DEFAULT_API_HOST, LLM_MODEL, LLM_TIMEOUT,
LLM_MAX_RETRIES, LLM_TITLE_TIMEOUT, LLM_VALIDATE_TIMEOUT,
get_api_key
)
logger = logging.getLogger(__name__)
import requests
class LLMClient:
"""LLM客户端封装"""
def __init__(self, api_key=None, api_host=None):
# 优先使用传入的参数,其次使用环境变量
self.api_key = api_key or get_api_key()
self.api_host = api_host or DEFAULT_API_HOST
if not self.api_key:
logger.warning("No API key configured - LLM calls will be skipped")
def chat(self, prompt, max_tokens=500, timeout=LLM_TIMEOUT):
"""
发送聊天请求到LLM
Args:
prompt: 提示词
max_tokens: 最大token数
timeout: 超时时间
Returns:
LLM回复文本,失败返回None
"""
if not self.api_key:
logger.info("LLM: No API key, skipping")
return None
url = f"{self.api_host}/chat/completions"
headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
}
payload = {
"model": LLM_MODEL,
"messages": [{"role": "user", "content": prompt}],
"max_tokens": max_tokens
}
logger.info(f"[LLM] request chars={len(prompt)}, max_tokens={max_tokens}")
for attempt in range(LLM_MAX_RETRIES):
try:
response = requests.post(url, headers=headers, json=payload, timeout=timeout)
# 401错误立即停止,不重试
if response.status_code == 401:
logger.error(f"LLM: 401 Unauthorized - API key invalid, stopping immediately")
return None
response.raise_for_status()
result = response.json()
choices = result.get("choices", [])
if not choices:
logger.warning(f"LLM: No choices in response (attempt {attempt+1})")
continue
content = choices[0].get("message", {}).get("content", "").strip()
if content:
logger.info(f"[LLM] response chars={len(content)}")
return content
logger.warning(f"LLM: Empty content (attempt {attempt+1})")
except requests.exceptions.Timeout:
logger.warning(f"LLM: Timeout (attempt {attempt+1}/{LLM_MAX_RETRIES})")
if attempt < LLM_MAX_RETRIES - 1:
time.sleep(1)
except Exception as e:
logger.error(f"LLM: Error - {e}")
if attempt < LLM_MAX_RETRIES - 1:
time.sleep(1)
return None
# 全局LLM客户端实例
_llm_client = None
def get_llm_client():
"""获取LLM客户端单例"""
global _llm_client
if _llm_client is None:
_llm_client = LLMClient()
return _llm_client