xhs_factory/llm_service.py
zhoujie dbe695b551 feat(automation): 新增自动化运营防重复机制与统计功能
- 新增操作历史记录,防止对同一笔记重复评论、点赞、收藏和回复
- 新增每日操作统计与限额管理,包含评论、点赞、收藏、发布和回复的独立上限
- 新增错误冷却机制,连续错误后自动暂停操作一段时间
- 新增运营时段控制,允许设置每日自动运营的开始和结束时间
- 新增收藏功能,支持一键收藏和定时自动收藏
- 新增随机人设池,提供25种预设小红书博主风格人设,支持随机切换
- 扩充主题池、风格池和评论关键词池,增加运营多样性
- 优化自动化调度器,显示下次执行时间和实时统计摘要
- 优化发布功能,增加本地备份机制,失败时保留文案和图片

🐛 fix(llm): 修复绘图提示词中的人物特征要求

- 在绘图提示词模板中明确要求人物必须是东亚面孔的中国人
- 添加具体的人物特征描述,如黑发、深棕色眼睛、精致五官等
- 禁止出现西方人或欧美人特征
- 调整整体画面风格偏向东方审美、清新淡雅和小红书风格
2026-02-09 20:50:05 +08:00

249 lines
9.9 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.

"""
LLM 服务模块
封装对 OpenAI 兼容 API 的调用,包含文案生成、热点分析、评论回复等 Prompt
"""
import requests
import json
import re
import logging
logger = logging.getLogger(__name__)
# ================= Prompt 模板 =================
PROMPT_COPYWRITING = """
你是一个小红书爆款内容专家。请根据用户主题生成内容。
【标题规则】(严格执行)
1. 长度限制:必须控制在 18 字以内含Emoji绝对不能超过 20 字!
2. 格式要求Emoji + 爆点关键词 + 核心痛点。
3. 禁忌:禁止使用"第一""""顶级"等绝对化广告法违禁词。
4. 风格:二极管标题(震惊/后悔/必看/避雷/哭了),具有强烈的点击欲望。
【正文规则】:
1. 口语化多用Emoji分段清晰不堆砌长句。
2. 正文控制在 600 字以内(小红书限制 1000 字)。
3. 结尾必须有 5 个以上相关话题标签(#)。
【绘图 Prompt】
生成对应的 Stable Diffusion 英文提示词,适配 JuggernautXL 模型,强调:
- 人物要求(最重要!):如果画面中有人物,必须是东亚面孔的中国人,使用 asian girl/boy, chinese, east asian features, black hair, dark brown eyes, delicate facial features, fair skin, slim figure 等描述,绝对禁止出现西方人/欧美人特征
- 质量词masterpiece, best quality, ultra detailed, 8k uhd, high resolution
- 光影natural lighting, soft shadows, studio lighting, golden hour 等(根据场景选择)
- 风格photorealistic, cinematic, editorial photography, ins style, chinese social media aesthetic
- 构图dynamic angle, depth of field, bokeh 等
- 细节detailed skin texture, sharp focus, vivid colors
- 审美偏向:整体画面风格偏向东方审美、清新淡雅、小红书风格
注意:不要使用括号权重语法,直接用英文逗号分隔描述。
返回 JSON 格式:
{"title": "...", "content": "...", "sd_prompt": "...", "tags": ["标签1", "标签2", ...]}
"""
PROMPT_HOTSPOT_ANALYSIS = """
你是一个小红书运营数据分析专家。下面是搜索到的热门笔记信息:
{feed_data}
请分析这些热门笔记,总结以下内容:
1. **热门选题方向**:提炼 3-5 个最火的细分选题
2. **标题套路**:总结高赞标题的共同特征和写作模板
3. **内容结构**:分析爆款笔记的内容组织方式
4. **推荐模仿方案**:基于分析结果,给出 3 个具体的模仿选题建议
返回 JSON 格式:
{{"hot_topics": ["...", "..."], "title_patterns": ["...", "..."], "content_structure": "...", "suggestions": [{{"topic": "...", "reason": "..."}}]}}
"""
PROMPT_COMMENT_REPLY = """
你是一个小红书博主,人设为:{persona}
有人在你的笔记下评论了,请你用符合人设的口吻回复。
【规则】:
1. 回复简洁,控制在 50 字以内
2. 语气亲切自然,像和朋友聊天
3. 适当加入 1-2 个 Emoji
4. 如果是质疑,礼貌回应;如果是夸奖,真诚感谢
笔记标题:{post_title}
用户评论:{comment}
直接返回回复内容,不需要 JSON 格式。
"""
PROMPT_PROACTIVE_COMMENT = """
你是一个小红书活跃用户,人设为:{persona}
你正在浏览一篇笔记,想要留下一条真诚、有价值的评论,以提升互动和曝光。
【笔记信息】:
标题:{post_title}
正文摘要:{post_content}
【已有评论参考(可能为空)】:
{existing_comments}
【评论规则】:
1. 评论简洁自然,控制在 30-80 字,不要像机器人
2. 体现你对笔记内容的真实感受或个人经验
3. 可以提问、分享类似经历、或表达共鸣
4. 适当加入 1-2 个 Emoji不要过多
5. 不要重复已有评论的观点,找新角度
6. 不要生硬带货或自我推广
7. 语气因内容而异:教程类→请教/补充;种草类→分享体验;生活类→表达共鸣
直接返回评论内容,不需要 JSON 格式。
"""
PROMPT_COPY_WITH_REFERENCE = """
你是一个小红书爆款内容专家。参考以下热门笔记的风格和结构,创作全新原创内容。
【参考笔记】:
{reference_notes}
【创作主题】:{topic}
【风格要求】:{style}
【标题规则】:
1. 长度限制:必须控制在 18 字以内含Emoji绝对不能超过 20 字!
2. 借鉴参考笔记的标题套路但内容必须原创。
【正文规则】:
1. 口语化多用Emoji分段清晰。
2. 正文控制在 600 字以内。
3. 结尾有 5 个以上话题标签(#)。
【绘图 Prompt】
生成 Stable Diffusion 英文提示词,适配 JuggernautXL 模型:
- 人物要求(最重要!):如果画面中有人物,必须是东亚面孔的中国人,使用 asian girl/boy, chinese, east asian features, black hair, dark brown eyes, delicate facial features, fair skin, slim figure 等描述,绝对禁止出现西方人/欧美人特征
- 必含质量词masterpiece, best quality, ultra detailed, 8k uhd
- 风格photorealistic, cinematic, editorial photography, chinese social media aesthetic
- 光影和细节natural lighting, sharp focus, vivid colors, detailed skin texture
- 审美偏向:整体画面风格偏向东方审美、清新淡雅、小红书风格
- 用英文逗号分隔,不用括号权重语法。
返回 JSON 格式:
{{"title": "...", "content": "...", "sd_prompt": "...", "tags": ["标签1", "标签2", ...]}}
"""
class LLMService:
"""LLM API 服务封装"""
def __init__(self, api_key: str, base_url: str, model: str = "gpt-3.5-turbo"):
self.api_key = api_key
self.base_url = base_url.rstrip("/")
self.model = model
def _chat(self, system_prompt: str, user_message: str,
json_mode: bool = True, temperature: float = 0.8) -> str:
"""底层聊天接口"""
headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json",
}
if json_mode:
user_message = user_message + "\n请以json格式返回。"
payload = {
"model": self.model,
"messages": [
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_message},
],
"temperature": temperature,
}
if json_mode:
payload["response_format"] = {"type": "json_object"}
try:
resp = requests.post(
f"{self.base_url}/chat/completions",
headers=headers, json=payload, timeout=90
)
resp.raise_for_status()
content = resp.json()["choices"][0]["message"]["content"]
return content
except requests.exceptions.Timeout:
raise TimeoutError("LLM 请求超时,请检查网络或换一个模型")
except requests.exceptions.HTTPError as e:
raise ConnectionError(f"LLM API 错误 ({resp.status_code}): {resp.text[:200]}")
except Exception as e:
raise RuntimeError(f"LLM 调用异常: {e}")
def _parse_json(self, text: str) -> dict:
"""从 LLM 返回文本中解析 JSON"""
cleaned = re.sub(r"```json\s*|```", "", text).strip()
return json.loads(cleaned)
# ---------- 业务方法 ----------
def get_models(self) -> list[str]:
"""获取可用模型列表"""
url = f"{self.base_url}/models"
headers = {"Authorization": f"Bearer {self.api_key}"}
try:
resp = requests.get(url, headers=headers, timeout=10)
resp.raise_for_status()
text = resp.text.strip()
if not text:
logger.warning("GET %s 返回空响应", url)
return []
data = resp.json()
return [item["id"] for item in data.get("data", [])]
except Exception as e:
logger.warning("获取模型列表失败 (%s): %s", url, e)
return []
def generate_copy(self, topic: str, style: str) -> dict:
"""生成小红书文案"""
content = self._chat(
PROMPT_COPYWRITING,
f"主题:{topic}\n风格:{style}"
)
data = self._parse_json(content)
# 强制标题长度限制
title = data.get("title", "")
if len(title) > 20:
title = title[:20]
data["title"] = title
return data
def generate_copy_with_reference(self, topic: str, style: str,
reference_notes: str) -> dict:
"""参考热门笔记生成文案"""
prompt = PROMPT_COPY_WITH_REFERENCE.format(
reference_notes=reference_notes, topic=topic, style=style
)
content = self._chat(prompt, f"请创作关于「{topic}」的小红书笔记")
data = self._parse_json(content)
title = data.get("title", "")
if len(title) > 20:
data["title"] = title[:20]
return data
def analyze_hotspots(self, feed_data: str) -> dict:
"""分析热门内容趋势"""
prompt = PROMPT_HOTSPOT_ANALYSIS.format(feed_data=feed_data)
content = self._chat(prompt, "请分析以上热门笔记数据")
return self._parse_json(content)
def generate_reply(self, persona: str, post_title: str, comment: str) -> str:
"""AI 生成评论回复"""
prompt = PROMPT_COMMENT_REPLY.format(
persona=persona, post_title=post_title, comment=comment
)
return self._chat(prompt, "请生成回复", json_mode=False, temperature=0.9).strip()
def generate_proactive_comment(self, persona: str, post_title: str,
post_content: str, existing_comments: str = "") -> str:
"""AI 生成主动评论"""
prompt = PROMPT_PROACTIVE_COMMENT.format(
persona=persona, post_title=post_title,
post_content=post_content,
existing_comments=existing_comments or "暂无评论",
)
return self._chat(prompt, "请生成评论", json_mode=False, temperature=0.9).strip()