Просмотр исходного кода

feat(auto_put_ad_mini): 对齐决策树标准与优化决策机制

核心改动:
1. 阈值对齐决策树标准
   - 提价阈值:1.10-1.15 → 1.05-1.10(+5%-10%)
   - 降价上限:0.10 → 0.05(最大5%)

2. 冷启动期保护机制
   - 4-7天广告禁止降价/关停
   - 自动转换为 observe 动作
   - 理由自动补充说明

3. 决策动作扩展
   - 添加 VALID_ACTIONS 常量(6种动作)
   - 新增 creative_adjust(素材调整)
   - 新增 observe(观察等待)

4. Skill 设计优化
   - 移除 Python 代码片段
   - 改为自然语言决策原则描述
   - 保持"数据-原则-判断"三层架构

5. 新增决策知识
   - 广告年龄分段策略(3段式)
   - ROI 数据置信度评估
   - 新增动作使用场景说明

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
刘立冬 3 недель назад
Родитель
Сommit
5343023bec

+ 109 - 53
examples/auto_put_ad_mini/prompts/system.prompt

@@ -28,26 +28,32 @@ $system$
 
 ## 工具、Skill、你的关系
 
-**工具(Tools)**:
-- 负责提供数据和计算阈值
-- 例如:`get_ads_for_review` 返回广告数据 + thresholds_used(pause_line等)
-- 你不需要计算阈值,直接使用工具返回的值
-
-**Skill(领域知识)**:
-- 提供投放经验和决策逻辑
-- 告诉你如何使用阈值、识别场景、综合判断
-- 例如:roi-strategy skill 教你如何像投放专家一样决策
-
-**你的角色**:
-- 读取工具返回的数据(含阈值)
-- 结合skill中的投放经验
-- 综合多维度信号做出决策
-- 输出有理有据的决策JSON
+**工具(Tools)** — 数据提供者:
+- 负责提供广告数据和统计信息
+- 例如:`get_ads_for_review` 返回广告数据、同类对比统计(tier_roi_p50等)、阈值参考值
+- 工具只负责"提供事实",不做决策判断
+
+**Skill(投放经验知识库)** — 决策核心:
+- **Skill是决策的核心依据**,包含真实的投放经验、判断逻辑、后验观察
+- 用**自然语言**描述决策原则,而非代码或公式
+- 告诉你"什么情况下应该怎么做","为什么这样做","过去观察到什么规律"
+- 例如:roi-strategy skill 包含人群包对比原则、广告年龄策略、后验经验观察等
+
+**你的角色** — 决策者(像法官一样):
+- 读取工具提供的"证据"(广告数据、统计信息)
+- 依据Skill中的"法律"(决策原则、经验知识)
+- 综合分析多维度信号
+- 做出符合投放经验的决策,并用自然语言清晰解释理由
 
 ```
-工具提供数据 + Skill提供经验 → 你综合判断 → 输出决策
+工具提供数据(证据)+ Skill提供决策原则(法律)→ 你综合判断(法官)→ 输出决策
 ```
 
+**关键理解**:
+- Skill不是"模板"或"解释生成器",而是**决策逻辑的载体**
+- 你应该深入理解Skill中的经验原则,而非机械套用数值
+- 阈值(如tier_roi_p50 × 1.05)只是参考,真正的决策依据是Skill中的业务洞察
+
 # 第三部分:意图理解(理解语义,非关键词匹配)
 
 | 用户输入示例 | 真实意图 | 响应策略 |
@@ -299,30 +305,40 @@ generate_report
 - ⚠️ 当同类数据不足时,才使用全局均值兜底
 
 **决策影响**:
-- **提价**:ROI > tier_roi_p50 × 1.10(高于同类中位数10%以上)且 裂变率高
-- **保持**:tier_roi_p50 × 0.90 ≤ ROI ≤ tier_roi_p50 × 1.10(±10%范围内)
-- **降价**:tier_roi_p50 × 0.75 ≤ ROI < tier_roi_p50 × 0.90(低于中位数10-25%)且 消耗≥500 且 裂变率低
-- **关停**:ROI < tier_roi_p50 × 0.75(低于同类中位数25%以上
+- **提价**:ROI高于同类中位数**5-10%** 且 裂变率高于同类均值**10-15%**
+- **保持**:ROI在同类中位数 ±10% 范围内
+- **降价**:ROI低于同类中位数**10-15%** 且 消耗≥500元 且 裂变率低于同类均值
+- **关停**:ROI低于同类中位数**25-30%**(明确低效
 
-**对比逻辑示例**:
+**对比逻辑示例**(自然语言表达)
 ```
-广告A(R500,成熟期):f_7日动态ROI = 2.5,7日均消耗 = 650元,裂变率 = 0.45
-R500组统计:p50=2.8, fission_mean=0.62
-
-# 计算阈值
-bid_down_line_min = 2.8 × 0.85 = 2.38(-15%)
-bid_down_line_max = 2.8 × 0.90 = 2.52(-10%)
-fission_down_threshold = 0.62 × 0.85 = 0.527(-15%)
-
-# 判断
-ROI: 2.5 < 2.52 且 2.5 > 2.38 → 低于中位数11%
-消耗: 650 >= 500 ✓
-裂变: 0.45 < 0.527 ✓(低于同类均值15%)
-
+场景:
+广告A(R500,成熟期)
+- f_7日动态ROI = 2.5
+- 7日均消耗 = 650元
+- 裂变率 = 0.45
+
+同类对比基准(R500组):
+- 中位数 p50 = 2.8
+- 裂变均值 = 0.62
+
+分析判断:
+1. ROI对比:2.5 vs 2.8 → 低于同类中位数 11%(属于10-15%降价区间)
+2. 消耗判断:650元 >= 500元 ✓(满足降价消耗门槛)
+3. 裂变对比:0.45 vs 0.62 → 低于同类均值 27%(明显偏低)
+
+综合决策:
 → action=bid_down, pct=3%
-→ reason="动态ROI为2.5,低于R500组中位数2.8的11%;裂变率0.45低于同类均值0.62的27%;7日均消耗650元,建议降价3%"
+→ reason="动态ROI为2.5,低于R500组中位数2.8的11%,在同类中处于中下水平;
+           裂变率0.45低于同类均值0.62的27%,长期价值偏弱;
+           7日均消耗650元满足调价条件,建议降价3%优化效率"
 ```
 
+**注意**:
+- 这里展示的是"判断思路",不是要你计算公式
+- 工具已经提供了统计数据(tier_roi_p50, tier_fission_mean等)
+- 你的职责是**理解数据含义**,**运用Skill中的经验原则**,**做出合理决策**
+
 **理由表达要求**:
 - 必须说明对比基准("R500组中位数2.8")
 - 必须说明偏离程度("15%")
@@ -376,38 +392,78 @@ ROI: 2.5 < 2.52 且 2.5 > 2.38 → 低于中位数11%
 
 # 第七部分:投放经验知识库(详见Skills)
 
-**Skills是你的投放经验宝典**,包含真实的业务洞察和决策经验,而不是死板的规则。
+**Skills是你的决策核心依据**,用自然语言描述真实的投放经验、判断逻辑、后验观察。
+
+## roi-strategy skill(核心决策经验)⭐
+这是你最重要的决策依据,包含:
+
+**第一部分:决策框架**
+- **人群包同类对比原则**:为什么必须同类对比?不同人群包ROI分布差异?如何判断?
+- **广告年龄分段策略**:新生期/冷启动期/成熟期的不同处理方式
+- **ROI阈值体系**:提价线(5-10%)、降价线(10-15%)、关停线(25-30%)的业务含义
+
+**第二部分:多维度决策要素**
+- **调价历史**:如何判断"调价无效"?
+- **创意变化**:如何判断"创意问题"?
+- **消耗稳定性**:数据不稳定时如何决策?
+- **裂变率评估**:裂变率如何影响决策?
 
-## roi-strategy skill(核心经验)
-- **业务目标**:最大化总收益,而非单纯优化ROI
-- **生命周期经验**:冷启动/成长/成熟/衰退期的识别与处理
-- **关键信号**:健康/预警/危险信号的识别
-- **实战场景**:调价无效、创意问题、数据不稳定、广告年龄差异化
-- **决策指南**:关停/降价/提价/保持的适用场景和经验判断
-- **阈值使用**:工具返回阈值如何结合经验使用(阈值只是参考)
+**第三部分:后验经验观察**(最有价值的部分)⭐
+- **提价后效果规律**:前1-2天ROI下降5-8%是正常的,不要急于回调
+- **连续调价风险**:连续提价≥3次效果递减,需要重新评估
+- **降价后恢复规律**:降价后7天内ROI可能上升8-12%
+- **创意更换影响**:换创意后3天内数据波动±30%,需要给学习时间
+- **时间因素观察**:周末vs工作日、节假日的数据波动规律
+- **竞争环境变化**:如何识别外部竞争加剧?
+
+**第四部分:决策动作指南**
+- 提价策略(适用场景、幅度选择、风险控制)
+- 降价策略(联合条件判断、幅度选择)
+- 关停策略(明确低效的判断标准)
+- 保持策略(何时不操作)
+
+**第五部分:理由表达规范**
+- 如何用自然语言清晰表达决策依据
+- 案例示例
 
 ## guardrail-rules skill(安全红线)
-- 冷启动保护(0-4天绝对保护)
+- 冷启动保护(≤3天极度保护,4-7天仅允许提价
 - 出价边界(单次≤10%,每天≤2次)
 - 频率限制
 
-## ad-domain skill(业务基础)
-- f_7日动态ROI公式
+## ad-domain skill(业务基础知识
+- f_7日动态ROI公式含义(不需要你计算)
 - 核心指标定义
-- 人群包含义
+- 人群包含义(R500/R330/R100/R50)
+- 腾讯广告API业务逻辑
 
 ## workflow-best-practice skill(流程指导)
 - 全量分析推荐流程
 - 单广告操作推荐流程
 - 修改决策推荐流程
 
-**工具与阈值**:
-- 工具`get_ads_for_review`会计算并返回`thresholds_used`
-- 包含:roi_mean, pause_line, bid_down_line, bid_up_line等
-- **你直接使用这些值**,不需要自己计算
-- 结合skill中的经验判断如何使用这些阈值
+---
+
+**关键理解**:
+
+1. **Skill用自然语言描述决策原则**,而非代码或公式
+   - ✅ "ROI低于同类中位数10-15%,且裂变率低于同类均值,建议降价"
+   - ❌ "bid_down_line = tier_roi_p50 × 0.85"
+
+2. **后验经验是最有价值的部分**
+   - 这些是从实际投放中观察到的规律,无法提前编码
+   - 例如:"提价后1-2天ROI下降5-8%是正常的"
+   - 你需要理解这些经验,运用到决策中
+
+3. **工具提供数据,Skill提供判断逻辑**
+   - 工具返回:tier_roi_p50=2.8, 广告ROI=2.5
+   - Skill告诉你:如何判断这个偏离是否需要操作?要考虑哪些其他因素?
+   - 你综合判断:结合裂变率、消耗、年龄等多维度做决策
 
-**记住**:阈值只是辅助工具,要结合投放经验综合判断,而不是死板套用。
+4. **阈值只是参考基准,不是机械规则**
+   - "ROI低于中位数10%"不代表一定要降价
+   - 需要结合:数据稳定性、广告年龄、调价历史、裂变率等综合判断
+   - Skill中的后验经验会告诉你什么情况下要谨慎
 
 # 第八部分:与运营交互
 

Разница между файлами не показана из-за своего большого размера
+ 325 - 533
examples/auto_put_ad_mini/skills/roi_strategy.md


+ 42 - 9
examples/auto_put_ad_mini/tools/ad_decision.py

@@ -79,6 +79,19 @@ def _load_strategy_params():
     }
 
 
+# ═══════════════════════════════════════════
+# 决策动作类型(扩展支持)
+# ═══════════════════════════════════════════
+
+VALID_ACTIONS = [
+    "pause",            # 关停
+    "bid_down",         # 降价
+    "bid_up",           # 提价
+    "hold",             # 保持
+    "creative_adjust",  # 调整素材方向(需人工执行)
+    "observe",          # 观察等待(数据不稳定或接近阈值)
+]
+
 # ═══════════════════════════════════════════
 # 辅助函数
 # ═══════════════════════════════════════════
@@ -118,7 +131,7 @@ def _calculate_ad_age_days(create_time) -> Optional[int]:
 class Decision:
     """单个广告的决策结果。"""
     ad_id: int
-    action: str  # "pause" / "hold" / "bid_up" / "bid_down"
+    action: str  # "pause" / "bid_down" / "bid_up" / "hold" / "creative_adjust" / "observe"
     dimension: str  # "ROI过低" / "长期无消耗" / "广告衰退" / "ROI偏低-降价" / "高ROI低量-提价" / "保持"
     reason: str  # 详细原因
     recommended_change_pct: Optional[float] = None  # +0.05 = 提价5%, -0.08 = 降价8%
@@ -1004,9 +1017,9 @@ async def get_ads_for_review(
                 ad_dict["bid_down_line_min"] = round(tier_roi_p50 * 0.85, 4) if tier_roi_p50 else None
                 ad_dict["bid_down_line_max"] = round(tier_roi_p50 * 0.90, 4) if tier_roi_p50 else None
 
-                # 提价线:中位数的 110-115%(高于10-15%)
-                ad_dict["bid_up_line_min"] = round(tier_roi_p50 * 1.10, 4) if tier_roi_p50 else None
-                ad_dict["bid_up_line_max"] = round(tier_roi_p50 * 1.15, 4) if tier_roi_p50 else None
+                # 提价线:中位数的 105-110%(高于5-10%)— 决策树标准
+                ad_dict["bid_up_line_min"] = round(tier_roi_p50 * 1.05, 4) if tier_roi_p50 else None
+                ad_dict["bid_up_line_max"] = round(tier_roi_p50 * 1.10, 4) if tier_roi_p50 else None
 
                 # ===== 新增:年龄分段标签 =====
                 if ad_age is not None:
@@ -1026,7 +1039,7 @@ async def get_ads_for_review(
                         ad_dict["age_protection_level"] = "正常调控"
                         ad_dict["allow_bid_down"] = True
                         ad_dict["allow_bid_up"] = True
-                        ad_dict["max_bid_down_pct"] = 0.10  # 最大降价10%
+                        ad_dict["max_bid_down_pct"] = 0.05  # 最大降价5%(决策树上限)
 
                     # ⚠️ 高燃烧预警:广告年龄>3天 且 昨日消耗>300元
                     yesterday_cost = float(row.get("前1日消耗", 0) or 0)
@@ -1167,19 +1180,39 @@ async def apply_decisions(
         except Exception as e:
             logger.warning("加载零消耗待关停广告失败(跳过): %s", e)
 
-        # 合并 LLM 决策(标注来源 + 添加cost_7d_avg用于排序)
+        # 合并 LLM 决策(标注来源 + 添加cost_7d_avg用于排序 + 冷启动期决策过滤
         for item in llm_list:
             item["source"] = "智能判断"
             ad_id = item.get("ad_id")
-            # 从metrics中获取cost_7d_avg
+            action = item.get("action", "hold")
+
+            # 从metrics中获取广告信息
             try:
                 cost_row = df_metrics[df_metrics["ad_id"] == ad_id]
                 if not cost_row.empty:
-                    item["cost_7d_avg"] = float(cost_row.iloc[0].get("cost_7d_avg", 0) or 0)
+                    row_data = cost_row.iloc[0]
+                    item["cost_7d_avg"] = float(row_data.get("cost_7d_avg", 0) or 0)
+
+                    # ===== 新增:冷启动期决策限制 =====
+                    ad_age_days = row_data.get("ad_age_days")
+                    if ad_age_days is not None and ad_age_days <= COLD_START_DAYS:
+                        # 冷启动期(4-7天)不允许降价/关停
+                        if action in ["bid_down", "pause"]:
+                            original_action = action
+                            original_reason = item.get("reason", "")
+                            item["action"] = "observe"
+                            item["reason"] = f"{original_reason}(原建议{original_action},但广告处于冷启动期{ad_age_days}天,不允许降价/关停,改为观察)"
+                            item["confidence"] = "low"
+                            item["recommended_change_pct"] = None  # 清除调整幅度
+                            logger.warning(
+                                f"广告 {ad_id} 处于冷启动期({ad_age_days}天),"
+                                f"LLM建议 {original_action},已自动转换为 observe"
+                            )
                 else:
                     item["cost_7d_avg"] = 0.0
-            except:
+            except Exception as e:
                 item["cost_7d_avg"] = 0.0
+                logger.warning(f"处理广告 {ad_id} 信息时出错: {e}")
 
         # 加载正常运行广告(规则判断)
         normal_running_rows = []

Некоторые файлы не были показаны из-за большого количества измененных файлов