|
@@ -43,6 +43,7 @@ from config import (
|
|
|
FEISHU_APP_SECRET,
|
|
FEISHU_APP_SECRET,
|
|
|
FEISHU_OPERATOR_OPEN_ID,
|
|
FEISHU_OPERATOR_OPEN_ID,
|
|
|
FEISHU_OPERATOR_CHAT_ID,
|
|
FEISHU_OPERATOR_CHAT_ID,
|
|
|
|
|
+ FEISHU_AD_PROJECT_CHAT_ID, # 新增:投放项目群聊
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
logger = logging.getLogger(__name__)
|
|
@@ -111,6 +112,88 @@ def _generate_approval_xlsx(df_tier2_3: pd.DataFrame, request_id: str) -> Path:
|
|
|
# ═══════════════════════════════════════════
|
|
# ═══════════════════════════════════════════
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+def _format_project_notification_message(df_tier2: pd.DataFrame, df_tier1: pd.DataFrame, request_id: str) -> str:
|
|
|
|
|
+ """格式化投放项目群聊通知消息(仅通知,不需要审批)"""
|
|
|
|
|
+ lines = [
|
|
|
|
|
+ "📊 广告调控决策通知",
|
|
|
|
|
+ f"请求ID: {request_id}",
|
|
|
|
|
+ f"时间: {datetime.now().strftime('%Y-%m-%d %H:%M')}",
|
|
|
|
|
+ "",
|
|
|
|
|
+ ]
|
|
|
|
|
+
|
|
|
|
|
+ # 需审批操作统计
|
|
|
|
|
+ if not df_tier2.empty:
|
|
|
|
|
+ total_count = len(df_tier2)
|
|
|
|
|
+ lines.append(f"🔶 待审批操作({total_count} 个):")
|
|
|
|
|
+ lines.append("-" * 40)
|
|
|
|
|
+
|
|
|
|
|
+ # 统计各操作类型
|
|
|
|
|
+ action_counts = df_tier2.get("final_action", df_tier2.get("action", "")).value_counts().to_dict()
|
|
|
|
|
+ for action, count in action_counts.items():
|
|
|
|
|
+ if action == "pause":
|
|
|
|
|
+ lines.append(f" ⏸️ 暂停: {count} 个")
|
|
|
|
|
+ elif action == "bid_down":
|
|
|
|
|
+ lines.append(f" ⬇️ 降价: {count} 个")
|
|
|
|
|
+ elif action == "bid_up":
|
|
|
|
|
+ lines.append(f" ⬆️ 提价: {count} 个")
|
|
|
|
|
+ else:
|
|
|
|
|
+ lines.append(f" {action}: {count} 个")
|
|
|
|
|
+ lines.append("")
|
|
|
|
|
+
|
|
|
|
|
+ # 简化展示(只显示前3个)
|
|
|
|
|
+ lines.append("前 3 个示例:")
|
|
|
|
|
+ for i, (_, row) in enumerate(df_tier2.head(3).iterrows()):
|
|
|
|
|
+ ad_id = row.get("ad_id", "")
|
|
|
|
|
+ action = row.get("final_action", row.get("action", ""))
|
|
|
|
|
+ ad_name = str(row.get("ad_name", ""))[:20]
|
|
|
|
|
+ cost_avg = row.get("cost_7d_avg", 0)
|
|
|
|
|
+ roi = row.get("动态ROI_7日均值", 0)
|
|
|
|
|
+
|
|
|
|
|
+ if action == "pause":
|
|
|
|
|
+ action_label = "⏸️ 暂停"
|
|
|
|
|
+ elif action == "bid_down":
|
|
|
|
|
+ pct = row.get("recommended_change_pct", 0)
|
|
|
|
|
+ if isinstance(pct, str):
|
|
|
|
|
+ try:
|
|
|
|
|
+ pct = float(pct)
|
|
|
|
|
+ except ValueError:
|
|
|
|
|
+ pct = 0
|
|
|
|
|
+ action_label = f"⬇️ 降价{abs(pct)*100:.0f}%"
|
|
|
|
|
+ elif action == "bid_up":
|
|
|
|
|
+ pct = row.get("recommended_change_pct", 0)
|
|
|
|
|
+ if isinstance(pct, str):
|
|
|
|
|
+ try:
|
|
|
|
|
+ pct = float(pct)
|
|
|
|
|
+ except ValueError:
|
|
|
|
|
+ pct = 0
|
|
|
|
|
+ action_label = f"⬆️ 提价{pct*100:.0f}%"
|
|
|
|
|
+ else:
|
|
|
|
|
+ action_label = action
|
|
|
|
|
+
|
|
|
|
|
+ lines.append(f" [{ad_id}] {ad_name}")
|
|
|
|
|
+ lines.append(f" 操作: {action_label} | 日均消耗: {cost_avg:.0f}元 | ROI: {roi:.2f}")
|
|
|
|
|
+ lines.append("")
|
|
|
|
|
+
|
|
|
|
|
+ if total_count > 3:
|
|
|
|
|
+ lines.append(f" ...还有 {total_count - 3} 个(查看在线表格)")
|
|
|
|
|
+ lines.append("")
|
|
|
|
|
+
|
|
|
|
|
+ # 已自动执行操作
|
|
|
|
|
+ if not df_tier1.empty:
|
|
|
|
|
+ lines.append(f"✅ 已自动执行({len(df_tier1)} 个)")
|
|
|
|
|
+ lines.append("")
|
|
|
|
|
+
|
|
|
|
|
+ lines.extend([
|
|
|
|
|
+ "-" * 40,
|
|
|
|
|
+ "ℹ️ 说明:",
|
|
|
|
|
+ " • 此消息为智能决策结果通知",
|
|
|
|
|
+ " • 运营审批通过后才会实际执行",
|
|
|
|
|
+ " • 详情请查看在线表格(自动发送)",
|
|
|
|
|
+ ])
|
|
|
|
|
+
|
|
|
|
|
+ return "\n".join(lines)
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
def _format_approval_message(df_tier2: pd.DataFrame, df_tier1: pd.DataFrame, request_id: str) -> str:
|
|
def _format_approval_message(df_tier2: pd.DataFrame, df_tier1: pd.DataFrame, request_id: str) -> str:
|
|
|
lines = [
|
|
lines = [
|
|
|
"📊 广告调控审批请求",
|
|
"📊 广告调控审批请求",
|
|
@@ -368,38 +451,78 @@ async def send_approval_request(
|
|
|
|
|
|
|
|
# ─── 通过飞书 API 发送审批消息(文本 + Excel) ───
|
|
# ─── 通过飞书 API 发送审批消息(文本 + Excel) ───
|
|
|
feishu_sent = False
|
|
feishu_sent = False
|
|
|
|
|
+ feishu_sent_to_project_chat = False
|
|
|
sent_time_sec = str(int(time.time())) # 飞书 API start_time 单位:秒
|
|
sent_time_sec = str(int(time.time())) # 飞书 API start_time 单位:秒
|
|
|
try:
|
|
try:
|
|
|
- # 消息 1:文本摘要
|
|
|
|
|
- result = _feishu.send_message(to=FEISHU_OPERATOR_CHAT_ID, text=message)
|
|
|
|
|
- feishu_sent = True
|
|
|
|
|
- logger.info("飞书审批消息发送成功: message_id=%s", result.message_id)
|
|
|
|
|
|
|
+ # 消息 1a:发送到个人(FEISHU_OPERATOR_OPEN_ID)
|
|
|
|
|
+ if FEISHU_OPERATOR_OPEN_ID:
|
|
|
|
|
+ try:
|
|
|
|
|
+ result_personal = _feishu.send_message(to=FEISHU_OPERATOR_OPEN_ID, text=message)
|
|
|
|
|
+ logger.info("飞书审批消息发送成功(个人): message_id=%s", result_personal.message_id)
|
|
|
|
|
+ except Exception as e:
|
|
|
|
|
+ logger.warning("发送到个人失败: %s", e)
|
|
|
|
|
+
|
|
|
|
|
+ # 消息 1b:发送到投放项目群聊(如果配置了)— 临时禁用
|
|
|
|
|
+ # if FEISHU_AD_PROJECT_CHAT_ID:
|
|
|
|
|
+ # try:
|
|
|
|
|
+ # result_project = _feishu.send_message(to=FEISHU_AD_PROJECT_CHAT_ID, text=message)
|
|
|
|
|
+ # feishu_sent_to_project_chat = True
|
|
|
|
|
+ # feishu_sent = True
|
|
|
|
|
+ # logger.info("飞书审批消息发送成功(项目群): message_id=%s", result_project.message_id)
|
|
|
|
|
+ # except Exception as e:
|
|
|
|
|
+ # logger.warning("发送到项目群聊失败: %s", e)
|
|
|
|
|
|
|
|
# 消息 2:导入为飞书在线表格(决策详情,含hold参考)
|
|
# 消息 2:导入为飞书在线表格(决策详情,含hold参考)
|
|
|
try:
|
|
try:
|
|
|
xlsx_path = _generate_approval_xlsx(df_for_review, request_id)
|
|
xlsx_path = _generate_approval_xlsx(df_for_review, request_id)
|
|
|
|
|
|
|
|
- # 导入飞书在线表格并发送链接
|
|
|
|
|
|
|
+ # 导入飞书在线表格并发送链接(项目群)— 临时禁用
|
|
|
from feishu_doc import import_to_feishu
|
|
from feishu_doc import import_to_feishu
|
|
|
- import_result = await import_to_feishu(
|
|
|
|
|
- ctx=ctx,
|
|
|
|
|
- xlsx_path=str(xlsx_path),
|
|
|
|
|
- send_im=True,
|
|
|
|
|
- chat_id=FEISHU_OPERATOR_CHAT_ID
|
|
|
|
|
- )
|
|
|
|
|
|
|
|
|
|
- if import_result.metadata and import_result.metadata.get("url"):
|
|
|
|
|
- sheet_url = import_result.metadata["url"]
|
|
|
|
|
- logger.info("飞书审批表格导入成功: %s", sheet_url)
|
|
|
|
|
- else:
|
|
|
|
|
- logger.warning("飞书在线表格导入失败,回退到文件附件模式")
|
|
|
|
|
- # 回退:发送文件附件
|
|
|
|
|
- file_result = _feishu.send_file(
|
|
|
|
|
- to=FEISHU_OPERATOR_CHAT_ID,
|
|
|
|
|
- file=str(xlsx_path),
|
|
|
|
|
- file_name=f"审批决策表_{request_id}.xlsx",
|
|
|
|
|
- )
|
|
|
|
|
- logger.info("飞书审批 Excel(文件)发送成功: message_id=%s", file_result.message_id)
|
|
|
|
|
|
|
+ # 发送到项目群 — 临时禁用
|
|
|
|
|
+ # if FEISHU_AD_PROJECT_CHAT_ID:
|
|
|
|
|
+ # import_result = await import_to_feishu(
|
|
|
|
|
+ # ctx=ctx,
|
|
|
|
|
+ # xlsx_path=str(xlsx_path),
|
|
|
|
|
+ # send_im=True,
|
|
|
|
|
+ # chat_id=FEISHU_AD_PROJECT_CHAT_ID
|
|
|
|
|
+ # )
|
|
|
|
|
+ #
|
|
|
|
|
+ # if import_result.metadata and import_result.metadata.get("url"):
|
|
|
|
|
+ # sheet_url = import_result.metadata["url"]
|
|
|
|
|
+ # logger.info("飞书审批表格导入成功(项目群): %s", sheet_url)
|
|
|
|
|
+ # else:
|
|
|
|
|
+ # logger.warning("飞书在线表格导入失败(项目群),回退到文件附件模式")
|
|
|
|
|
+ # # 回退:发送文件附件(项目群)
|
|
|
|
|
+ # file_result = _feishu.send_file(
|
|
|
|
|
+ # to=FEISHU_AD_PROJECT_CHAT_ID,
|
|
|
|
|
+ # file=str(xlsx_path),
|
|
|
|
|
+ # file_name=f"审批决策表_{request_id}.xlsx",
|
|
|
|
|
+ # )
|
|
|
|
|
+ # logger.info("飞书审批 Excel(文件)发送成功(项目群): message_id=%s", file_result.message_id)
|
|
|
|
|
+
|
|
|
|
|
+ # 发送到个人
|
|
|
|
|
+ if FEISHU_OPERATOR_OPEN_ID:
|
|
|
|
|
+ try:
|
|
|
|
|
+ personal_import_result = await import_to_feishu(
|
|
|
|
|
+ ctx=ctx,
|
|
|
|
|
+ xlsx_path=str(xlsx_path),
|
|
|
|
|
+ send_im=True,
|
|
|
|
|
+ chat_id=FEISHU_OPERATOR_OPEN_ID
|
|
|
|
|
+ )
|
|
|
|
|
+ if personal_import_result.metadata and personal_import_result.metadata.get("url"):
|
|
|
|
|
+ logger.info("飞书审批表格发送成功(个人): %s", personal_import_result.metadata["url"])
|
|
|
|
|
+ else:
|
|
|
|
|
+ # 回退:发送文件附件(个人)
|
|
|
|
|
+ file_result_personal = _feishu.send_file(
|
|
|
|
|
+ to=FEISHU_OPERATOR_OPEN_ID,
|
|
|
|
|
+ file=str(xlsx_path),
|
|
|
|
|
+ file_name=f"决策表_{request_id}.xlsx",
|
|
|
|
|
+ )
|
|
|
|
|
+ logger.info("飞书决策 Excel(文件)发送成功(个人): message_id=%s", file_result_personal.message_id)
|
|
|
|
|
+ except Exception as e:
|
|
|
|
|
+ logger.warning("发送表格到个人失败: %s", e)
|
|
|
|
|
+
|
|
|
except Exception as e:
|
|
except Exception as e:
|
|
|
logger.warning("飞书在线表格导入失败(不影响审批流程): %s", e)
|
|
logger.warning("飞书在线表格导入失败(不影响审批流程): %s", e)
|
|
|
except Exception as e:
|
|
except Exception as e:
|
|
@@ -449,69 +572,78 @@ async def send_approval_request(
|
|
|
if poll_count % 10 == 0:
|
|
if poll_count % 10 == 0:
|
|
|
logger.info("飞书审批轮询 #%d,剩余 %.1f 分钟", poll_count, remaining)
|
|
logger.info("飞书审批轮询 #%d,剩余 %.1f 分钟", poll_count, remaining)
|
|
|
|
|
|
|
|
- # 读取飞书聊天中审批消息之后的回复
|
|
|
|
|
|
|
+ # 读取个人和项目群的审批回复
|
|
|
try:
|
|
try:
|
|
|
- result = _feishu.get_message_list(
|
|
|
|
|
- chat_id=FEISHU_OPERATOR_CHAT_ID,
|
|
|
|
|
- start_time=sent_time_sec,
|
|
|
|
|
- page_size=10,
|
|
|
|
|
- )
|
|
|
|
|
- if result and result.get("items"):
|
|
|
|
|
- for msg in result["items"]:
|
|
|
|
|
- sender_id = msg.get("sender_id", "")
|
|
|
|
|
- sender_type = msg.get("sender_type", "")
|
|
|
|
|
-
|
|
|
|
|
- logger.debug(
|
|
|
|
|
- "飞书消息: sender_type=%s, sender_id=%s, content=%s",
|
|
|
|
|
- sender_type, sender_id, str(msg.get("content", ""))[:100],
|
|
|
|
|
- )
|
|
|
|
|
-
|
|
|
|
|
- # 只看指定运营的用户消息(非机器人)
|
|
|
|
|
- if sender_type != "user" or sender_id != FEISHU_OPERATOR_OPEN_ID:
|
|
|
|
|
- continue
|
|
|
|
|
-
|
|
|
|
|
- # 框架已自动解析 text 消息的 JSON -> 纯文本
|
|
|
|
|
- text = msg.get("content", "")
|
|
|
|
|
- if not text.strip():
|
|
|
|
|
- continue
|
|
|
|
|
-
|
|
|
|
|
- # 检测到运营回复,返回原文给 Agent 理解
|
|
|
|
|
- parsed = _parse_approval_reply(text, tier2_ad_ids)
|
|
|
|
|
- if parsed["status"] != "unknown":
|
|
|
|
|
- _approval_requests[request_id].update({
|
|
|
|
|
- "status": "replied",
|
|
|
|
|
- "reply_content": text,
|
|
|
|
|
- "reply_at": datetime.now().isoformat(),
|
|
|
|
|
- "ad_ids": tier2_ad_ids,
|
|
|
|
|
- })
|
|
|
|
|
-
|
|
|
|
|
- logger.info("飞书审批收到运营回复: %s", text[:200])
|
|
|
|
|
-
|
|
|
|
|
- ad_ids_str = ", ".join(str(x) for x in tier2_ad_ids[:10])
|
|
|
|
|
- if len(tier2_ad_ids) > 10:
|
|
|
|
|
- ad_ids_str += f"...共{len(tier2_ad_ids)}个"
|
|
|
|
|
|
|
+ # ✅ 修改:监听个人私聊和项目群聊的消息 — 临时只监听个人
|
|
|
|
|
+ chat_ids_to_check = []
|
|
|
|
|
+ if FEISHU_OPERATOR_OPEN_ID:
|
|
|
|
|
+ chat_ids_to_check.append(FEISHU_OPERATOR_OPEN_ID)
|
|
|
|
|
+ # 临时禁用项目群聊监听
|
|
|
|
|
+ # if FEISHU_AD_PROJECT_CHAT_ID:
|
|
|
|
|
+ # chat_ids_to_check.append(FEISHU_AD_PROJECT_CHAT_ID)
|
|
|
|
|
+
|
|
|
|
|
+ for chat_id in chat_ids_to_check:
|
|
|
|
|
+ result = _feishu.get_message_list(
|
|
|
|
|
+ chat_id=chat_id,
|
|
|
|
|
+ start_time=sent_time_sec,
|
|
|
|
|
+ page_size=10,
|
|
|
|
|
+ )
|
|
|
|
|
+ if result and result.get("items"):
|
|
|
|
|
+ for msg in result["items"]:
|
|
|
|
|
+ sender_id = msg.get("sender_id", "")
|
|
|
|
|
+ sender_type = msg.get("sender_type", "")
|
|
|
|
|
+
|
|
|
|
|
+ logger.debug(
|
|
|
|
|
+ "飞书消息 [%s]: sender_type=%s, sender_id=%s, content=%s",
|
|
|
|
|
+ chat_id, sender_type, sender_id, str(msg.get("content", ""))[:100],
|
|
|
|
|
+ )
|
|
|
|
|
|
|
|
- return ToolResult(
|
|
|
|
|
- title="运营已回复",
|
|
|
|
|
- output=(
|
|
|
|
|
- f"运营飞书回复原文: {text}\n"
|
|
|
|
|
- f"等待审批的广告ID: {ad_ids_str}\n"
|
|
|
|
|
- f"等待时间: {poll_count * poll_interval_seconds} 秒\n\n"
|
|
|
|
|
- f"请根据运营的自然语言回复判断后续操作:\n"
|
|
|
|
|
- f"- 运营同意/批准/通过 → 调用 execute_decisions 执行\n"
|
|
|
|
|
- f"- 运营拒绝/驳回 → 停止执行,告知原因\n"
|
|
|
|
|
- f"- 运营要求修改(如\"广告X不要暂停\")→ 进入 Mode 3: modify_decisions → validate → 重新审批\n"
|
|
|
|
|
- f"- 运营部分批准(如\"只批准降价的\")→ 相应过滤后 execute_decisions"
|
|
|
|
|
- ),
|
|
|
|
|
- metadata={
|
|
|
|
|
- "request_id": request_id,
|
|
|
|
|
- "feishu_sent": feishu_sent,
|
|
|
|
|
- "msg_path": str(msg_path),
|
|
|
|
|
- "poll_count": poll_count,
|
|
|
|
|
- "raw_reply": text,
|
|
|
|
|
|
|
+ # ✅ 修改:接受任何用户的回复(不限制特定个人)
|
|
|
|
|
+ if sender_type != "user":
|
|
|
|
|
+ continue
|
|
|
|
|
+
|
|
|
|
|
+ # 框架已自动解析 text 消息的 JSON -> 纯文本
|
|
|
|
|
+ text = msg.get("content", "")
|
|
|
|
|
+ if not text.strip():
|
|
|
|
|
+ continue
|
|
|
|
|
+
|
|
|
|
|
+ # 检测到运营回复,返回原文给 Agent 理解
|
|
|
|
|
+ parsed = _parse_approval_reply(text, tier2_ad_ids)
|
|
|
|
|
+ if parsed["status"] != "unknown":
|
|
|
|
|
+ _approval_requests[request_id].update({
|
|
|
|
|
+ "status": "replied",
|
|
|
|
|
+ "reply_content": text,
|
|
|
|
|
+ "reply_at": datetime.now().isoformat(),
|
|
|
"ad_ids": tier2_ad_ids,
|
|
"ad_ids": tier2_ad_ids,
|
|
|
- },
|
|
|
|
|
- )
|
|
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ logger.info("飞书审批收到运营回复: %s", text[:200])
|
|
|
|
|
+
|
|
|
|
|
+ ad_ids_str = ", ".join(str(x) for x in tier2_ad_ids[:10])
|
|
|
|
|
+ if len(tier2_ad_ids) > 10:
|
|
|
|
|
+ ad_ids_str += f"...共{len(tier2_ad_ids)}个"
|
|
|
|
|
+
|
|
|
|
|
+ return ToolResult(
|
|
|
|
|
+ title="运营已回复",
|
|
|
|
|
+ output=(
|
|
|
|
|
+ f"运营飞书回复原文: {text}\n"
|
|
|
|
|
+ f"等待审批的广告ID: {ad_ids_str}\n"
|
|
|
|
|
+ f"等待时间: {poll_count * poll_interval_seconds} 秒\n\n"
|
|
|
|
|
+ f"请根据运营的自然语言回复判断后续操作:\n"
|
|
|
|
|
+ f"- 运营同意/批准/通过 → 调用 execute_decisions 执行\n"
|
|
|
|
|
+ f"- 运营拒绝/驳回 → 停止执行,告知原因\n"
|
|
|
|
|
+ f"- 运营要求修改(如\"广告X不要暂停\")→ 进入 Mode 3: modify_decisions → validate → 重新审批\n"
|
|
|
|
|
+ f"- 运营部分批准(如\"只批准降价的\")→ 相应过滤后 execute_decisions"
|
|
|
|
|
+ ),
|
|
|
|
|
+ metadata={
|
|
|
|
|
+ "request_id": request_id,
|
|
|
|
|
+ "feishu_sent": feishu_sent,
|
|
|
|
|
+ "msg_path": str(msg_path),
|
|
|
|
|
+ "poll_count": poll_count,
|
|
|
|
|
+ "raw_reply": text,
|
|
|
|
|
+ "ad_ids": tier2_ad_ids,
|
|
|
|
|
+ },
|
|
|
|
|
+ )
|
|
|
except Exception as e:
|
|
except Exception as e:
|
|
|
logger.debug("飞书读消息失败(将重试): %s", e)
|
|
logger.debug("飞书读消息失败(将重试): %s", e)
|
|
|
|
|
|
|
@@ -587,56 +719,67 @@ async def check_approval_status(
|
|
|
created_at_sec = str(int(
|
|
created_at_sec = str(int(
|
|
|
datetime.fromisoformat(request["created_at"]).timestamp()
|
|
datetime.fromisoformat(request["created_at"]).timestamp()
|
|
|
))
|
|
))
|
|
|
- result = _feishu.get_message_list(
|
|
|
|
|
- chat_id=FEISHU_OPERATOR_CHAT_ID,
|
|
|
|
|
- start_time=created_at_sec,
|
|
|
|
|
- page_size=10,
|
|
|
|
|
- )
|
|
|
|
|
|
|
|
|
|
- if result and result.get("items"):
|
|
|
|
|
- for msg in result["items"]:
|
|
|
|
|
- sender_id = msg.get("sender_id", "")
|
|
|
|
|
- sender_type = msg.get("sender_type", "")
|
|
|
|
|
|
|
+ # ✅ 修改:监听个人私聊和项目群聊的消息 — 临时只监听个人
|
|
|
|
|
+ chat_ids_to_check = []
|
|
|
|
|
+ if FEISHU_OPERATOR_OPEN_ID:
|
|
|
|
|
+ chat_ids_to_check.append(FEISHU_OPERATOR_OPEN_ID)
|
|
|
|
|
+ # 临时禁用项目群聊监听
|
|
|
|
|
+ # if FEISHU_AD_PROJECT_CHAT_ID:
|
|
|
|
|
+ # chat_ids_to_check.append(FEISHU_AD_PROJECT_CHAT_ID)
|
|
|
|
|
|
|
|
- logger.debug(
|
|
|
|
|
- "飞书消息(check): sender_type=%s, sender_id=%s, content=%s",
|
|
|
|
|
- sender_type, sender_id, str(msg.get("content", ""))[:100],
|
|
|
|
|
- )
|
|
|
|
|
|
|
+ for chat_id in chat_ids_to_check:
|
|
|
|
|
+ result = _feishu.get_message_list(
|
|
|
|
|
+ chat_id=chat_id,
|
|
|
|
|
+ start_time=created_at_sec,
|
|
|
|
|
+ page_size=10,
|
|
|
|
|
+ )
|
|
|
|
|
|
|
|
- if sender_type != "user" or sender_id != FEISHU_OPERATOR_OPEN_ID:
|
|
|
|
|
- continue
|
|
|
|
|
-
|
|
|
|
|
- text = msg.get("content", "")
|
|
|
|
|
- if not text.strip():
|
|
|
|
|
- continue
|
|
|
|
|
-
|
|
|
|
|
- # 检测到运营回复,返回原文给 Agent 理解
|
|
|
|
|
- parsed = _parse_approval_reply(text, request["ad_ids"])
|
|
|
|
|
- if parsed["status"] != "unknown":
|
|
|
|
|
- request.update({
|
|
|
|
|
- "status": "replied",
|
|
|
|
|
- "reply_content": text,
|
|
|
|
|
- "reply_at": datetime.now().isoformat(),
|
|
|
|
|
- })
|
|
|
|
|
-
|
|
|
|
|
- ad_ids = request["ad_ids"]
|
|
|
|
|
- ad_ids_str = ", ".join(str(x) for x in ad_ids[:10])
|
|
|
|
|
- if len(ad_ids) > 10:
|
|
|
|
|
- ad_ids_str += f"...共{len(ad_ids)}个"
|
|
|
|
|
-
|
|
|
|
|
- return ToolResult(
|
|
|
|
|
- title="运营已回复",
|
|
|
|
|
- output=(
|
|
|
|
|
- f"运营飞书回复原文: {text}\n"
|
|
|
|
|
- f"等待审批的广告ID: {ad_ids_str}\n\n"
|
|
|
|
|
- f"请根据运营的自然语言回复判断后续操作:\n"
|
|
|
|
|
- f"- 运营同意/批准/通过 → 调用 execute_decisions 执行\n"
|
|
|
|
|
- f"- 运营拒绝/驳回 → 停止执行,告知原因\n"
|
|
|
|
|
- f"- 运营要求修改 → 进入 Mode 3: modify_decisions → validate → 重新审批\n"
|
|
|
|
|
- f"- 运营部分批准 → 相应过滤后 execute_decisions"
|
|
|
|
|
- ),
|
|
|
|
|
- metadata={"request_id": request_id, "raw_reply": text, "ad_ids": ad_ids},
|
|
|
|
|
|
|
+ if result and result.get("items"):
|
|
|
|
|
+ for msg in result["items"]:
|
|
|
|
|
+ sender_id = msg.get("sender_id", "")
|
|
|
|
|
+ sender_type = msg.get("sender_type", "")
|
|
|
|
|
+
|
|
|
|
|
+ logger.debug(
|
|
|
|
|
+ "飞书消息(check) [%s]: sender_type=%s, sender_id=%s, content=%s",
|
|
|
|
|
+ chat_id, sender_type, sender_id, str(msg.get("content", ""))[:100],
|
|
|
)
|
|
)
|
|
|
|
|
+
|
|
|
|
|
+ # ✅ 修改:接受任何用户的回复(不限制特定个人)
|
|
|
|
|
+ if sender_type != "user":
|
|
|
|
|
+ continue
|
|
|
|
|
+
|
|
|
|
|
+ text = msg.get("content", "")
|
|
|
|
|
+ if not text.strip():
|
|
|
|
|
+ continue
|
|
|
|
|
+
|
|
|
|
|
+ # 检测到运营回复,返回原文给 Agent 理解
|
|
|
|
|
+ parsed = _parse_approval_reply(text, request["ad_ids"])
|
|
|
|
|
+ if parsed["status"] != "unknown":
|
|
|
|
|
+ request.update({
|
|
|
|
|
+ "status": "replied",
|
|
|
|
|
+ "reply_content": text,
|
|
|
|
|
+ "reply_at": datetime.now().isoformat(),
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ ad_ids = request["ad_ids"]
|
|
|
|
|
+ ad_ids_str = ", ".join(str(x) for x in ad_ids[:10])
|
|
|
|
|
+ if len(ad_ids) > 10:
|
|
|
|
|
+ ad_ids_str += f"...共{len(ad_ids)}个"
|
|
|
|
|
+
|
|
|
|
|
+ return ToolResult(
|
|
|
|
|
+ title="运营已回复",
|
|
|
|
|
+ output=(
|
|
|
|
|
+ f"运营飞书回复原文: {text}\n"
|
|
|
|
|
+ f"等待审批的广告ID: {ad_ids_str}\n\n"
|
|
|
|
|
+ f"请根据运营的自然语言回复判断后续操作:\n"
|
|
|
|
|
+ f"- 运营同意/批准/通过 → 调用 execute_decisions 执行\n"
|
|
|
|
|
+ f"- 运营拒绝/驳回 → 停止执行,告知原因\n"
|
|
|
|
|
+ f"- 运营要求修改 → 进入 Mode 3: modify_decisions → validate → 重新审批\n"
|
|
|
|
|
+ f"- 运营部分批准 → 相应过滤后 execute_decisions"
|
|
|
|
|
+ ),
|
|
|
|
|
+ metadata={"request_id": request_id, "raw_reply": text, "ad_ids": ad_ids},
|
|
|
|
|
+ )
|
|
|
except Exception as e:
|
|
except Exception as e:
|
|
|
logger.debug("飞书读消息失败: %s", e)
|
|
logger.debug("飞书读消息失败: %s", e)
|
|
|
|
|
|