Bläddra i källkod

修改system_prompt

xueyiming 10 timmar sedan
förälder
incheckning
28d062cde8

+ 23 - 19
agents/find_agent/prompt/system_prompt.md

@@ -27,11 +27,13 @@
 对候选视频 `v`,定义三个彼此独立的命题:
 
 - `R(v)`(需求相关性):视频是否满足 `demand_word` 在标题和相关点所限定的具体意图;
-- `E(v)`(内容侧老年受众倾向):视频点赞用户画像中的 50+ 占比是否支持受众偏老;
+- `E(v)`(老年受众倾向):视频点赞用户画像或作者粉丝画像是否支持受众偏老;
+  **视频侧与作者侧均可作为参考,任一侧达标即可支撑 `E(v)` 成立**;
 - `S(v)`(分享价值):视频是否已经表现出值得转发的行为信号和内容理由。
 
-作者粉丝画像单独记为账号先验 `A(v)`,不得与 `E(v)` 相加或平均。`A(v)` 可以增强或
-降低结论置信度,但不能替代视频侧画像,也不能使视频侧 50+ 门槛失败的候选通过。
+视频侧画像记为 `C(v)`,作者粉丝画像记为 `A(v)`。两者不得相加或平均,但**任一侧**
+达到老年倾向门槛即可认定 `E(v)`;另一侧可增强或降低置信度。仅当双侧均缺失、均不达标
+或均明显偏年轻时,不得认定 `E(v)` 成立。
 
 最终寻找的是联合事件:
 
@@ -53,8 +55,8 @@
 1. 真实发布时间可用,且基础日期、节日、时段和内容新鲜度判断为有效;
 2. `duration_seconds >= min_duration_seconds`;
 3. 最新详情中的 `share_count >= min_share_count`;
-4. 视频点赞用户画像中的
-   `content_50_plus_ratio >= min_content_50_plus_ratio`
+4. 老年画像门槛:**视频侧** `content_50_plus_ratio >= min_content_50_plus_ratio`,
+   **或作者侧** `account_50_plus_ratio >= min_account_50_plus_ratio`,**任一侧满足即可**
 
 搜索工具会过滤已知时长不足 30 秒的结果;未知时长仍可能进入待补证候选,但必须通过
 `douyin_detail` 补齐后才能保存为 `primary`。
@@ -68,8 +70,8 @@ P0 门槛是底线,不是机械 checklist。判断时**必须参考**用户消
 
 - 缺失不等于不合格。接口失败、字段未返回、画像拉取失败都应视为“未知”,降低置信度,
   但不能把未知直接写成“不适合”或“证据不足必须拒绝”。
-- 当视频侧画像缺失但账号侧明显偏老、互动规模健康、内容与需求高度相关时,可以审慎
-  保留为 `primary`,并在 `decision_reason` 中说明依据的是哪类间接证据。
+- 当一侧画像缺失但另一侧明显偏老、互动规模健康、内容与需求高度相关时,可以审慎
+  保留为 `primary`,并在 `decision_reason` 中说明依据的是视频侧还是作者侧证据。
 - 当分享数、时长或发布时间缺失时,优先补证;补证仍失败时,结合标题、标签、点赞/
   播放规模和同类候选的相对位置判断这条内容是否仍值得推荐。
 - 明确不达标的数值不能靠“综合表现好”绕过;补偿只适用于未知,不适用于已知失败。
@@ -78,13 +80,13 @@ P0 门槛是底线,不是机械 checklist。判断时**必须参考**用户消
 保存层对部分缺失字段留有补偿空间。下列是**参考条件**,达到时可支持补偿通过,未完全
 达到但综合证据仍很强时,也可结合经验审慎判断,并在 `decision_reason` 中说明:
 
-1. **视频侧 50+ 占比缺失**:参考 `account_50_plus_ratio >= 0.35`,或账号侧达到
-   `min_account_50_plus_ratio` 且 `R/E/S` 综合优秀
-2. **时长缺失**:参考 `share_count >= 1.5 × min_share_count`,或 `R` 很高且 `R/E/S`
+1. **双侧画像均缺失**:参考任一侧 `50+` 达标的间接证据,或 `R/E/S` 综合优秀;
+2. **仅一侧画像缺失**:以另一侧是否达标为准;达标即可,不必强求双侧齐全
+3. **时长缺失**:参考 `share_count >= 1.5 × min_share_count`,或 `R` 很高且 `R/E/S`
    综合优秀;
-3. **分享数缺失**:参考 `like_count >= 5000` 或 `play_count >= 50000`,或 `S` 很高且
+4. **分享数缺失**:参考 `like_count >= 5000` 或 `play_count >= 50000`,或 `S` 很高且
    `R/E/S` 综合优秀;
-4. **发布时间缺失**:标题/标签无节日、时段、相对日期或事件表述,且其他指标支持时,
+5. **发布时间缺失**:标题/标签无节日、时段、相对日期或事件表述,且其他指标支持时,
    可按常青内容处理。
 
 `batch_update_video_discovery_candidates` 保存 `primary` 时会按 `quality_gate_rules`
@@ -146,9 +148,10 @@ P0 门槛是底线,不是机械 checklist。判断时**必须参考**用户消
 
 第 4 层不得单独形成老年倾向结论。视频画像代表“这条内容吸引了谁”,作者画像代表
 “这个账号通常触达谁”;两侧必须分别给出 `content_portrait_status` 和
-`account_portrait_status`。两者一致时增强置信度;冲突时保留 `portrait_conflict`,
-优先采信视频侧,但若视频侧缺失而账号侧、文本和互动证据共同指向偏老受众,可结合
-经验做审慎判断,不得静默平均,也不得编造画像数值。
+`account_portrait_status`。**任一侧达到 `min_content_50_plus_ratio` 或
+`min_account_50_plus_ratio` 即可认定老年倾向门槛通过**;两侧均达标时增强置信度;
+冲突时保留 `portrait_conflict`,以**达标侧**为主要依据,并在 `decision_reason`
+中说明采纳哪一侧,不得静默平均,也不得编造画像数值。
 
 年龄桶中,明确覆盖 `50岁及以上` 的桶才是直接老年信号;接口实际可能用 `50-`
 表示“50岁以上”,必须用 `normalize_age_portraits` 标准化后再判断。只提供
@@ -176,7 +179,8 @@ P0 门槛是底线,不是机械 checklist。判断时**必须参考**用户消
 这意味着任一维度接近零,整体价值都会被明显压低。评分用于保持排序一致,不得制造虚假精确性;**数据库保存与最终报告中的 `R/E/S/V` 均使用 `0~1` 小数,不做百分制换算**。
 
 `R/E/S/V` 用于语义判断和排序,不能替代程序硬门槛。候选是否语义相关仍由你判断;
-当你请求保存为 `primary` 时,保存层会校验时效、时长、分享数和视频侧 50+ 占比。
+当你请求保存为 `primary` 时,保存层会校验时效、时长、分享数,以及视频侧或作者侧
+50+ 占比(任一侧达标即可)。
 `R/E/S` 最多保留 6 位小数,`V` 最多保留 2 位小数,必须自行确保所有分数在 `0~1`
 内。
 
@@ -253,7 +257,7 @@ P0 门槛是底线,不是机械 checklist。判断时**必须参考**用户消
   `age_normalization`。工具会从标准化结果拆出视频侧和账号侧 50+ 指标,并在保存
   `primary` 前执行程序硬门槛;失败时根据返回的原因码补证或改为 `rejected`。工具不会
   新增候选、修改搜索记录或修改运行状态。`decision_reason` 必须分别覆盖相关性、视频
-  侧 50+、账号侧画像、分享价值、时间有效性和主要限制。
+  侧与作者侧 50+(说明采纳哪一侧或双侧均达标)、分享价值、时间有效性和主要限制。
   同一 `aweme_id` 对应多个 `candidate_id` 时必须分别判断和更新。
 - `update_video_discovery_run_status`:本轮搜索和评估流程结束后,单独把运行状态更新为
   `finished`,并保存意图摘要和停止原因。不得用它代替候选更新。
@@ -317,8 +321,8 @@ P0 门槛是底线,不是机械 checklist。判断时**必须参考**用户消
 简要说明对需求意图的理解,再报告本次形成的主推荐及其主要证据。按需概括主要淘汰原因、
 缺失数据、画像冲突或接口失败,不要求逐条列出 `rejected` 候选。
 
-语义相关性、分享动机和排序由 Agent 判断;时效、时长、分享数和视频侧 50+ 占比由
-程序按本次 `quality_gate_rules` 快照做准入校验。程序层对部分缺失字段留有补偿空间;
+语义相关性、分享动机和排序由 Agent 判断;时效、时长、分享数,以及视频侧或作者侧
+50+ 占比(任一侧达标即可)由程序按本次 `quality_gate_rules` 快照做准入校验。程序层对部分缺失字段留有补偿空间;
 不足 5 条时应相对择优补足,并参照 P0 缺失数据判断原则做审慎综合判断。
 
 禁止输出没有证据支撑的年龄结论,禁止把“内容讲老人”写成“观看者是老人”,禁止为凑满

+ 44 - 8
supply_infra/video_discovery_gates.py

@@ -587,17 +587,53 @@ def evaluate_candidate_gate(
         low_code="SHARE_COUNT_TOO_LOW",
         compensate_missing=_can_compensate_missing_shares(candidate),
     )
-    threshold_check(
-        "content_50_plus_ratio",
-        content_ratio,
-        min_content_ratio,
-        missing_code="CONTENT_PORTRAIT_MISSING",
-        low_code="CONTENT_50_PLUS_TOO_LOW",
-        compensate_missing=_can_compensate_missing_portrait(
+
+    content_pass = (
+        content_ratio is not None and content_ratio >= min_content_ratio
+    )
+    account_pass = (
+        account_ratio is not None and account_ratio >= min_account_ratio
+    )
+    portrait_compensated = False
+    if content_pass or account_pass:
+        portrait_status = "pass"
+        portrait_reason_code = None
+    elif content_ratio is None and account_ratio is None:
+        portrait_compensated = _can_compensate_missing_portrait(
             candidate,
             account_ratio,
             min_account_ratio=min_account_ratio,
-        ),
+        )
+        if portrait_compensated:
+            portrait_status = "pass"
+            portrait_reason_code = None
+        else:
+            portrait_status = "fail"
+            portrait_reason_code = "CONTENT_PORTRAIT_MISSING"
+    else:
+        portrait_status = "fail"
+        if content_ratio is not None and account_ratio is not None:
+            portrait_reason_code = "PORTRAIT_50_PLUS_TOO_LOW"
+        elif content_ratio is None:
+            portrait_reason_code = "CONTENT_PORTRAIT_MISSING"
+        else:
+            portrait_reason_code = "ACCOUNT_50_PLUS_TOO_LOW"
+
+    checks.append(
+        {
+            "name": "elder_portrait",
+            "status": portrait_status,
+            "reason_code": portrait_reason_code,
+            "actual": {
+                "content_50_plus_ratio": content_ratio,
+                "account_50_plus_ratio": account_ratio,
+            },
+            "threshold": {
+                "min_content_50_plus_ratio": min_content_ratio,
+                "min_account_50_plus_ratio": min_account_ratio,
+            },
+            "compensated": portrait_compensated,
+        }
     )
 
     failed_codes = [

+ 21 - 2
tests/supply_infra/scheduler/test_discover_videos_from_demands.py

@@ -336,10 +336,10 @@ def test_p0_gate_enforces_boundaries_and_time_context() -> None:
     assert "SHARE_COUNT_TOO_LOW" in low_share["failed_reason_codes"]
 
     low_elder = evaluate_candidate_gate(
-        _p0_candidate(content_50_plus_ratio=0.199),
+        _p0_candidate(content_50_plus_ratio=0.199, account_50_plus_ratio=0.19),
         _P0_RULES,
     )
-    assert "CONTENT_50_PLUS_TOO_LOW" in low_elder["failed_reason_codes"]
+    assert "PORTRAIT_50_PLUS_TOO_LOW" in low_elder["failed_reason_codes"]
 
     morning = evaluate_candidate_gate(
         _p0_candidate(title="早上好,送给家人的祝福"),
@@ -377,6 +377,25 @@ def test_p0_gate_keeps_content_and_account_portraits_separate() -> None:
     assert account_only["primary_eligible"] is True
     assert "CONTENT_PORTRAIT_MISSING" not in account_only["failed_reason_codes"]
 
+    content_only = evaluate_candidate_gate(
+        _p0_candidate(
+            content_50_plus_ratio=0.28,
+            account_50_plus_ratio=0.08,
+        ),
+        _P0_RULES,
+    )
+    assert content_only["primary_eligible"] is True
+
+    both_low = evaluate_candidate_gate(
+        _p0_candidate(
+            content_50_plus_ratio=0.10,
+            account_50_plus_ratio=0.08,
+        ),
+        _P0_RULES,
+    )
+    assert both_low["primary_eligible"] is False
+    assert "PORTRAIT_50_PLUS_TOO_LOW" in both_low["failed_reason_codes"]
+
 
 def test_p0_gate_compensates_missing_fields_with_strong_signals() -> None:
     missing_publish = evaluate_candidate_gate(