diff --git a/src/ai_client.py b/src/ai_client.py index a689273..27afbdb 100644 --- a/src/ai_client.py +++ b/src/ai_client.py @@ -86,7 +86,9 @@ def expand_promo_detail(ai_cfg: AIConfig, sender: str, subject: str, body: str) return result.get("details", []) -MORE_REPLIES_PROMPT = """你是一个邮件回复助手。根据以下邮件内容生成3个完全不同的建议回复。 +MORE_REPLIES_PROMPT_SHORT = """你是一个邮件回复助手。根据以下邮件内容生成3个完全不同的简洁回复。 + +简洁模式:直接说要点,不需要称呼、落款、邮件格式。像微信/短信一样简短。 以下是一些已经生成过的回复,请生成全新的回复,不要与已有回复重复。 @@ -101,22 +103,41 @@ MORE_REPLIES_PROMPT = """你是一个邮件回复助手。根据以下邮件内 每条回复控制在 50 字以内。 只返回 JSON,不要包含任何其他文字。""" +MORE_REPLIES_PROMPT_FULL = """你是一个邮件回复助手。根据以下邮件内容生成3个不同的正式邮件回复。 + +完整模式:生成完整的邮件格式,包括称呼、正文、落款。语气正式专业,适合商务邮件。 + +以下是一些已经生成过的回复,请生成全新的回复,不要与已有回复重复。 + +返回 JSON: +{ + "suggestions": [ + {"text": "回复内容"}, + {"text": "回复内容"}, + {"text": "回复内容"} + ] +} +每条回复控制在 300 字以内。 +只返回 JSON,不要包含任何其他文字。""" + @retry() def generate_more_replies(ai_cfg: AIConfig, sender: str, subject: str, body: str, - previous_suggestions: list[dict], user_hint: str = "") -> list[dict]: - logger.info("AI 换批请求: sender=%s subject=%s user_hint=%s prev=%d 条", - sender, subject, user_hint or "(无)", len(previous_suggestions)) + previous_suggestions: list[dict], user_hint: str = "", + style: str = "short") -> list[dict]: + logger.info("AI 换批请求: sender=%s subject=%s user_hint=%s prev=%d 条 style=%s", + sender, subject, user_hint or "(无)", len(previous_suggestions), style) content = f"发件人: {sender}\n主题: {subject}\n正文:\n{body}\n\n已生成过的回复:\n" for i, s in enumerate(previous_suggestions, 1): content += f"{i}. {s['text']}\n" if user_hint: content += f"\n用户要求: {user_hint}" + prompt = MORE_REPLIES_PROMPT_FULL if style == "full" else MORE_REPLIES_PROMPT_SHORT payload = { "model": ai_cfg.model, "messages": [ - {"role": "system", "content": MORE_REPLIES_PROMPT}, + {"role": "system", "content": prompt}, {"role": "user", "content": content}, ], "response_format": {"type": "json_object"}, diff --git a/src/tg_bot.py b/src/tg_bot.py index ddd0bc3..090ad3e 100644 --- a/src/tg_bot.py +++ b/src/tg_bot.py @@ -169,7 +169,8 @@ def _escape(text: str) -> str: CALLBACK_VIEW_ORIG = "v" CALLBACK_VIEW_SUMM = "s" CALLBACK_REPLY = "r" -CALLBACK_AI_REPLY = "a" +CALLBACK_AI_REPLY_FULL = "af" +CALLBACK_AI_REPLY_SHORT = "as" CALLBACK_SELECT_REPLY = "sr" CALLBACK_REGEN = "rg" CALLBACK_HINT = "h" @@ -190,7 +191,10 @@ def _summary_keyboard(can_reply: bool = True, is_promotion: bool = False, links: ], ] if can_reply: - kb.append([{"text": "AI回复", "callback_data": CALLBACK_AI_REPLY}]) + kb.append([ + {"text": "AI完整回复", "callback_data": CALLBACK_AI_REPLY_FULL}, + {"text": "AI简洁回复", "callback_data": CALLBACK_AI_REPLY_SHORT}, + ]) for link in (links or []): url = link.get("url", "") text = link.get("text", "打开链接")[:30] @@ -208,7 +212,10 @@ def _orig_keyboard(can_reply: bool = True) -> dict: ], ] if can_reply: - kb.append([{"text": "AI回复", "callback_data": CALLBACK_AI_REPLY}]) + kb.append([ + {"text": "AI完整回复", "callback_data": CALLBACK_AI_REPLY_FULL}, + {"text": "AI简洁回复", "callback_data": CALLBACK_AI_REPLY_SHORT}, + ]) return {"inline_keyboard": kb} @@ -252,10 +259,11 @@ def _tg_req(tg_cfg: TelegramConfig, method: str, payload: dict) -> dict: def _do_regen(cfg: Config, chat_id: int, msg_id: int, ctx: dict, conv: dict): """在 AI 线程池中执行换一批,不阻塞按钮轮询""" + style = conv.get("reply_style", "short") try: new_suggestions = generate_more_replies( cfg.ai, ctx["sender"], ctx["subject"], ctx["original_body"], - conv.get("all_suggestions", []), + conv.get("all_suggestions", []), style=style, ) except Exception as e: send_text(cfg.telegram, str(chat_id), f"生成失败: {e}") @@ -264,16 +272,17 @@ def _do_regen(cfg: Config, chat_id: int, msg_id: int, ctx: dict, conv: dict): conv["ai_suggestions"] = new_suggestions conv["all_suggestions"].extend(new_suggestions) save_conversation(chat_id, conv) - logger.info("[ai_regen] 新生成 %d 条回复", len(new_suggestions)) + logger.info("[ai_regen] 新生成 %d 条回复 (style=%s)", len(new_suggestions), style) _show_ai_suggestions(cfg.telegram, chat_id, msg_id, new_suggestions) def _do_ai_hint(cfg: Config, chat_id: int, conv: dict, ctx: dict, hint: str): """在 AI 线程池中根据用户提示生成回复""" + style = conv.get("reply_style", "short") try: new_suggestions = generate_more_replies( cfg.ai, ctx["sender"], ctx["subject"], ctx["original_body"], - conv.get("all_suggestions", []), user_hint=hint, + conv.get("all_suggestions", []), user_hint=hint, style=style, ) except Exception as e: send_text(cfg.telegram, str(chat_id), f"生成失败: {e}") @@ -346,8 +355,9 @@ def _handle_callback(tg_cfg: TelegramConfig, cfg: Config, cb: dict): }) logger.info(" 进入回复流程, prompt_msg_id=%d", prompt_msg_id) - elif action == CALLBACK_AI_REPLY and ctx: + elif action in (CALLBACK_AI_REPLY_FULL, CALLBACK_AI_REPLY_SHORT) and ctx: suggestions = ctx["summary_data"].get("reply_suggestions", []) + style = "full" if action == CALLBACK_AI_REPLY_FULL else "short" if not suggestions: send_text(tg_cfg, str(chat_id), "此邮件无需回复。") logger.info(" AI 判定无需回复,隐藏") @@ -358,8 +368,9 @@ def _handle_callback(tg_cfg: TelegramConfig, cfg: Config, cb: dict): "summary_msg_id": msg_id, "ai_suggestions": suggestions, "all_suggestions": list(suggestions), + "reply_style": style, }) - logger.info(" 显示 AI 回复建议 (%d 条)", len(suggestions)) + logger.info(" 显示 AI 回复建议 (%d 条, style=%s)", len(suggestions), style) _show_ai_suggestions(tg_cfg, chat_id, msg_id, suggestions) elif action == CALLBACK_SELECT_REPLY and ctx: