""" 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 英文提示词,强调:masterpiece, best quality, 8k, soft lighting, ins style。 返回 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 英文提示词。 返回 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", } 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}"} resp = requests.get(url, headers=headers, timeout=10) resp.raise_for_status() data = resp.json() return [item["id"] for item in data.get("data", [])] 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()