249 lines
7.3 KiB
Python
249 lines
7.3 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
|
||
"""
|
||
内容生成引擎
|
||
负责根据选题生成营销文案
|
||
"""
|
||
|
||
import logging
|
||
from typing import Dict, Any, Optional, Tuple
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
class ContentEngine:
|
||
"""
|
||
内容生成引擎
|
||
|
||
职责:
|
||
- 构建内容生成提示词
|
||
- 调用 LLM 生成内容
|
||
- 解析和格式化内容
|
||
"""
|
||
|
||
def __init__(self, llm_client=None, prompt_builder=None, db_accessor=None):
|
||
"""
|
||
初始化内容引擎
|
||
|
||
Args:
|
||
llm_client: LLM 客户端
|
||
prompt_builder: 提示词构建器
|
||
db_accessor: 数据库访问器
|
||
"""
|
||
self._llm = llm_client
|
||
self._prompt = prompt_builder
|
||
self._db = db_accessor
|
||
self.logger = logging.getLogger(f"{__name__}.ContentEngine")
|
||
|
||
async def generate(
|
||
self,
|
||
topic: Dict[str, Any],
|
||
style_id: Optional[int] = None,
|
||
audience_id: Optional[int] = None,
|
||
scenic_spot_id: Optional[int] = None,
|
||
product_id: Optional[int] = None,
|
||
**kwargs
|
||
) -> Dict[str, Any]:
|
||
"""
|
||
生成内容
|
||
|
||
Args:
|
||
topic: 选题信息
|
||
style_id: 风格 ID
|
||
audience_id: 受众 ID
|
||
scenic_spot_id: 景区 ID
|
||
product_id: 产品 ID
|
||
|
||
Returns:
|
||
生成的内容
|
||
"""
|
||
try:
|
||
self.logger.info(f"开始生成内容: topic={topic.get('title', 'unknown')}")
|
||
|
||
# 1. 增强选题信息
|
||
enhanced_topic = await self._enhance_topic(
|
||
topic, style_id, audience_id, scenic_spot_id, product_id
|
||
)
|
||
|
||
# 2. 构建提示词
|
||
system_prompt, user_prompt = self._build_prompts(enhanced_topic)
|
||
|
||
# 3. 调用 LLM
|
||
response = await self._llm.generate(
|
||
prompt=user_prompt,
|
||
system_prompt=system_prompt
|
||
)
|
||
|
||
# 4. 解析结果
|
||
content = self._parse_content(response, enhanced_topic)
|
||
|
||
self.logger.info("内容生成完成")
|
||
return content
|
||
|
||
except Exception as e:
|
||
self.logger.error(f"内容生成失败: {e}")
|
||
raise
|
||
|
||
async def generate_with_prompt(
|
||
self,
|
||
topic: Dict[str, Any],
|
||
system_prompt: str,
|
||
user_prompt: str
|
||
) -> Dict[str, Any]:
|
||
"""
|
||
使用预构建的提示词生成内容
|
||
|
||
Args:
|
||
topic: 选题信息
|
||
system_prompt: 系统提示词
|
||
user_prompt: 用户提示词
|
||
|
||
Returns:
|
||
生成的内容
|
||
"""
|
||
try:
|
||
response = await self._llm.generate(
|
||
prompt=user_prompt,
|
||
system_prompt=system_prompt
|
||
)
|
||
|
||
return self._parse_content(response, topic)
|
||
|
||
except Exception as e:
|
||
self.logger.error(f"内容生成失败: {e}")
|
||
raise
|
||
|
||
async def _enhance_topic(
|
||
self,
|
||
topic: Dict[str, Any],
|
||
style_id: Optional[int],
|
||
audience_id: Optional[int],
|
||
scenic_spot_id: Optional[int],
|
||
product_id: Optional[int]
|
||
) -> Dict[str, Any]:
|
||
"""增强选题信息"""
|
||
enhanced = topic.copy()
|
||
|
||
if self._db:
|
||
# 从选题中获取 ID(如果未提供)
|
||
style_id = style_id or topic.get('style_id')
|
||
audience_id = audience_id or topic.get('audience_id')
|
||
scenic_spot_id = scenic_spot_id or topic.get('scenic_spot_id')
|
||
product_id = product_id or topic.get('product_id')
|
||
|
||
if style_id:
|
||
style = await self._db.style.find_by_id(style_id)
|
||
if style:
|
||
enhanced['style'] = style
|
||
|
||
if audience_id:
|
||
audience = await self._db.audience.find_by_id(audience_id)
|
||
if audience:
|
||
enhanced['audience'] = audience
|
||
|
||
if scenic_spot_id:
|
||
spot = await self._db.scenic_spot.find_by_id(scenic_spot_id)
|
||
if spot:
|
||
enhanced['scenic_spot'] = spot
|
||
|
||
if product_id:
|
||
product = await self._db.product.find_by_id(product_id)
|
||
if product:
|
||
enhanced['product'] = product
|
||
|
||
return enhanced
|
||
|
||
def _build_prompts(self, topic: Dict[str, Any]) -> Tuple[str, str]:
|
||
"""构建提示词"""
|
||
if self._prompt:
|
||
system_prompt = self._prompt.get_system_prompt(
|
||
"content_generate",
|
||
**topic
|
||
)
|
||
user_prompt = self._prompt.get_user_prompt(
|
||
"content_generate",
|
||
**topic
|
||
)
|
||
return system_prompt, user_prompt
|
||
|
||
# 默认提示词
|
||
system_prompt = """你是一个专业的小红书内容创作者。
|
||
请根据选题信息创作吸引人的营销文案。
|
||
文案应该:
|
||
1. 符合小红书的风格
|
||
2. 包含吸引人的标题
|
||
3. 有清晰的结构
|
||
4. 适当使用 emoji
|
||
5. 包含相关话题标签
|
||
|
||
输出格式为 JSON,包含:title, content, tags"""
|
||
|
||
topic_title = topic.get('title', '')
|
||
topic_desc = topic.get('description', '')
|
||
spot_name = topic.get('scenic_spot', {}).get('name', '')
|
||
product_name = topic.get('product', {}).get('name', '')
|
||
style_name = topic.get('style', {}).get('name', '')
|
||
audience_name = topic.get('audience', {}).get('name', '')
|
||
|
||
user_prompt = f"""请根据以下选题创作小红书文案:
|
||
|
||
选题标题:{topic_title}
|
||
选题描述:{topic_desc}
|
||
景区:{spot_name}
|
||
产品:{product_name}
|
||
风格:{style_name}
|
||
目标受众:{audience_name}
|
||
|
||
请以 JSON 格式输出,包含:
|
||
- title: 文案标题(吸引眼球)
|
||
- content: 正文内容(500-800字)
|
||
- tags: 话题标签数组"""
|
||
|
||
return system_prompt, user_prompt
|
||
|
||
def _parse_content(self, response: str, topic: Dict[str, Any]) -> Dict[str, Any]:
|
||
"""解析 LLM 响应"""
|
||
import json
|
||
import re
|
||
|
||
# 尝试解析 JSON
|
||
content = None
|
||
|
||
# 尝试直接解析
|
||
try:
|
||
content = json.loads(response)
|
||
except:
|
||
pass
|
||
|
||
# 尝试提取 JSON
|
||
if not content:
|
||
patterns = [
|
||
r'\{[\s\S]*\}',
|
||
r'```json\s*([\s\S]*?)\s*```',
|
||
]
|
||
|
||
for pattern in patterns:
|
||
match = re.search(pattern, response)
|
||
if match:
|
||
try:
|
||
json_str = match.group(1) if '```' in pattern else match.group(0)
|
||
content = json.loads(json_str)
|
||
break
|
||
except:
|
||
continue
|
||
|
||
# 如果无法解析,使用原始响应
|
||
if not content:
|
||
content = {
|
||
'title': topic.get('title', ''),
|
||
'content': response,
|
||
'tags': []
|
||
}
|
||
|
||
# 添加选题信息
|
||
content['topic_index'] = topic.get('index', 0)
|
||
content['topic_title'] = topic.get('title', '')
|
||
|
||
return content
|