NarratoAI/app/services/llm/unified_service.py

263 lines
9.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
统一的大模型服务接口
提供简化的API接口方便现有代码迁移到新的架构
"""
from typing import List, Dict, Any, Optional, Union
from pathlib import Path
import PIL.Image
from loguru import logger
from .manager import LLMServiceManager
from .validators import OutputValidator
from .exceptions import LLMServiceError
# 提供商注册由 webui.py:main() 显式调用(见 LLM 提供商注册机制重构)
# 这样更可靠,错误也更容易调试
class UnifiedLLMService:
"""统一的大模型服务接口"""
@staticmethod
async def analyze_images(images: List[Union[str, Path, PIL.Image.Image]],
prompt: str,
provider: Optional[str] = None,
batch_size: int = 10,
**kwargs) -> List[str]:
"""
分析图片内容
Args:
images: 图片路径列表或PIL图片对象列表
prompt: 分析提示词
provider: 视觉模型提供商名称,如果不指定则使用配置中的默认值
batch_size: 批处理大小
**kwargs: 其他参数
Returns:
分析结果列表
Raises:
LLMServiceError: 服务调用失败时抛出
"""
try:
# 获取视觉模型提供商
vision_provider = LLMServiceManager.get_vision_provider(provider)
# 执行图片分析
results = await vision_provider.analyze_images(
images=images,
prompt=prompt,
batch_size=batch_size,
**kwargs
)
logger.info(f"图片分析完成,共处理 {len(images)} 张图片,生成 {len(results)} 个结果")
return results
except Exception as e:
logger.error(f"图片分析失败: {str(e)}")
raise LLMServiceError(f"图片分析失败: {str(e)}")
@staticmethod
async def generate_text(prompt: str,
system_prompt: Optional[str] = None,
provider: Optional[str] = None,
temperature: float = 1.0,
max_tokens: Optional[int] = None,
response_format: Optional[str] = None,
**kwargs) -> str:
"""
生成文本内容
Args:
prompt: 用户提示词
system_prompt: 系统提示词
provider: 文本模型提供商名称,如果不指定则使用配置中的默认值
temperature: 生成温度
max_tokens: 最大token数
response_format: 响应格式 ('json' 或 None)
**kwargs: 其他参数
Returns:
生成的文本内容
Raises:
LLMServiceError: 服务调用失败时抛出
"""
try:
# 获取文本模型提供商
text_provider = LLMServiceManager.get_text_provider(provider)
# 执行文本生成
result = await text_provider.generate_text(
prompt=prompt,
system_prompt=system_prompt,
temperature=temperature,
max_tokens=max_tokens,
response_format=response_format,
**kwargs
)
logger.info(f"文本生成完成,生成内容长度: {len(result)} 字符")
return result
except Exception as e:
logger.error(f"文本生成失败: {str(e)}")
raise LLMServiceError(f"文本生成失败: {str(e)}")
@staticmethod
async def generate_narration_script(prompt: str,
provider: Optional[str] = None,
temperature: float = 1.0,
validate_output: bool = True,
**kwargs) -> List[Dict[str, Any]]:
"""
生成解说文案
Args:
prompt: 提示词
provider: 文本模型提供商名称
temperature: 生成温度
validate_output: 是否验证输出格式
**kwargs: 其他参数
Returns:
解说文案列表
Raises:
LLMServiceError: 服务调用失败时抛出
"""
try:
# 生成文本
result = await UnifiedLLMService.generate_text(
prompt=prompt,
provider=provider,
temperature=temperature,
response_format="json",
**kwargs
)
# 验证输出格式
if validate_output:
narration_items = OutputValidator.validate_narration_script(result)
logger.info(f"解说文案生成并验证完成,共 {len(narration_items)} 个片段")
return narration_items
else:
# 简单的JSON解析
import json
parsed_result = json.loads(result)
if "items" in parsed_result:
return parsed_result["items"]
else:
return parsed_result
except Exception as e:
logger.error(f"解说文案生成失败: {str(e)}")
raise LLMServiceError(f"解说文案生成失败: {str(e)}")
@staticmethod
async def analyze_subtitle(subtitle_content: str,
provider: Optional[str] = None,
temperature: float = 1.0,
validate_output: bool = True,
**kwargs) -> str:
"""
分析字幕内容
Args:
subtitle_content: 字幕内容
provider: 文本模型提供商名称
temperature: 生成温度
validate_output: 是否验证输出格式
**kwargs: 其他参数
Returns:
分析结果
Raises:
LLMServiceError: 服务调用失败时抛出
"""
try:
# 构建分析提示词
system_prompt = "你是一位专业的剧本分析师和剧情概括助手。请仔细分析字幕内容,提取关键剧情信息。"
# 生成分析结果
result = await UnifiedLLMService.generate_text(
prompt=subtitle_content,
system_prompt=system_prompt,
provider=provider,
temperature=temperature,
**kwargs
)
# 验证输出格式
if validate_output:
validated_result = OutputValidator.validate_subtitle_analysis(result)
logger.info("字幕分析完成并验证通过")
return validated_result
else:
return result
except Exception as e:
logger.error(f"字幕分析失败: {str(e)}")
raise LLMServiceError(f"字幕分析失败: {str(e)}")
@staticmethod
def get_provider_info() -> Dict[str, Any]:
"""
获取所有提供商信息
Returns:
提供商信息字典
"""
return LLMServiceManager.get_provider_info()
@staticmethod
def list_vision_providers() -> List[str]:
"""
列出所有视觉模型提供商
Returns:
提供商名称列表
"""
return LLMServiceManager.list_vision_providers()
@staticmethod
def list_text_providers() -> List[str]:
"""
列出所有文本模型提供商
Returns:
提供商名称列表
"""
return LLMServiceManager.list_text_providers()
@staticmethod
def clear_cache():
"""清空提供商实例缓存"""
LLMServiceManager.clear_cache()
logger.info("已清空大模型服务缓存")
# 为了向后兼容,提供一些便捷函数
async def analyze_images_unified(images: List[Union[str, Path, PIL.Image.Image]],
prompt: str,
provider: Optional[str] = None,
batch_size: int = 10) -> List[str]:
"""便捷的图片分析函数"""
return await UnifiedLLMService.analyze_images(images, prompt, provider, batch_size)
async def generate_text_unified(prompt: str,
system_prompt: Optional[str] = None,
provider: Optional[str] = None,
temperature: float = 1.0,
response_format: Optional[str] = None) -> str:
"""便捷的文本生成函数"""
return await UnifiedLLMService.generate_text(
prompt, system_prompt, provider, temperature, response_format=response_format
)