test_walk_engine_author.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. import copy
  2. from content_agent.business_modules.walk_engine import run_bounded_walk
  3. from tests.p6_walk_helpers import FakeWalkPlatformClient, build_initial_walk_context, set_v4_allow_walk
  4. def test_walk_engine_author_works_reenter_content_decision_flow(tmp_path):
  5. context = build_initial_walk_context(tmp_path)
  6. client = FakeWalkPlatformClient()
  7. result = run_bounded_walk(platform_client=client, **context)
  8. assert client.author_calls
  9. assert any(item["previous_discovery_step"] == "author_works" for item in result["discovered_content_items"])
  10. assert all(decision["decision_target_type"] == "content" for decision in result["rule_decisions"])
  11. assert any(row["edge_id"] == "author_to_works" for row in result["walk_actions"])
  12. def test_walk_engine_author_edge_skips_missing_author_id(tmp_path):
  13. context = build_initial_walk_context(tmp_path)
  14. context["discovered_content_items"][0]["platform_author_id"] = ""
  15. context["discovered_content_items"][0]["has_more"] = False
  16. context["discovered_content_items"][0]["tags"] = []
  17. client = FakeWalkPlatformClient()
  18. run_bounded_walk(platform_client=client, **context)
  19. assert client.author_calls == []
  20. def _override_decisions(context, action, effect_status):
  21. for decision in context["rule_decisions"]:
  22. decision["decision_action"] = action
  23. decision["search_query_effect_status"] = effect_status
  24. def test_author_edge_skips_rejected_content(tmp_path):
  25. context = build_initial_walk_context(tmp_path)
  26. _override_decisions(context, "REJECT_CONTENT", "rule_blocked")
  27. client = FakeWalkPlatformClient()
  28. result = run_bounded_walk(platform_client=client, **context)
  29. assert client.author_calls == []
  30. skipped = [
  31. row for row in result["walk_actions"]
  32. if row["edge_id"] == "author_to_works" and row["walk_status"] == "skipped"
  33. ]
  34. assert len(skipped) == 1
  35. assert skipped[0]["reason_code"] == "blocked_by_rule_decision"
  36. assert skipped[0]["budget_tier"] == "blocked"
  37. assert skipped[0]["raw_payload"]["decision_action"] == "REJECT_CONTENT"
  38. def test_author_edge_allows_add_content_pool(tmp_path):
  39. context = build_initial_walk_context(tmp_path)
  40. # M3: mock judgment scores 60 (review/low_budget); promote the seed decision to
  41. # pool so the author edge runs at normal budget, which is what this test asserts.
  42. _override_decisions(context, "ADD_TO_CONTENT_POOL", "success")
  43. client = FakeWalkPlatformClient()
  44. result = run_bounded_walk(platform_client=client, **context)
  45. assert client.author_calls
  46. author_actions = [
  47. row for row in result["walk_actions"]
  48. if row["edge_id"] == "author_to_works" and row["walk_status"] == "success"
  49. ]
  50. assert author_actions
  51. assert author_actions[0]["budget_tier"] == "normal"
  52. # M4 砍包受控变化:future 包 binding 已删,戳回退内容包(=executed_rule_pack_id)。
  53. assert author_actions[0]["rule_pack_id"] == "douyin_content_discovery_rule_pack_v1"
  54. def test_v4_author_edge_denies_allow_walk_false(tmp_path):
  55. context = build_initial_walk_context(tmp_path)
  56. set_v4_allow_walk(context["rule_decisions"][0], False)
  57. client = FakeWalkPlatformClient()
  58. result = run_bounded_walk(platform_client=client, **context)
  59. assert client.author_calls == []
  60. skipped = [
  61. row for row in result["walk_actions"]
  62. if row["edge_id"] == "author_to_works" and row["walk_status"] == "skipped"
  63. ]
  64. assert skipped
  65. assert skipped[0]["reason_code"] == "v4_allow_walk_denied"
  66. assert skipped[0]["raw_payload"]["decision_id"] == context["rule_decisions"][0]["decision_id"]
  67. assert skipped[0]["raw_payload"]["allow_walk"] is False
  68. assert skipped[0]["raw_payload"]["walk_gate_snapshot"]
  69. def test_v4_author_edge_filters_denied_before_consuming_author_budget(tmp_path):
  70. context = build_initial_walk_context(tmp_path)
  71. base_item = context["discovered_content_items"][0]
  72. base_decision = context["rule_decisions"][0]
  73. items = []
  74. decisions = []
  75. for index in range(4):
  76. item = copy.deepcopy(base_item)
  77. item["platform_content_id"] = f"73900000000000004{index:02d}"
  78. item["platform_author_id"] = f"author_{index}"
  79. item["has_more"] = False
  80. item["tags"] = []
  81. decision = copy.deepcopy(base_decision)
  82. decision["decision_id"] = f"decision_{index}"
  83. decision["decision_target_id"] = item["platform_content_id"]
  84. set_v4_allow_walk(decision, index == 3)
  85. items.append(item)
  86. decisions.append(decision)
  87. context["discovered_content_items"] = items
  88. context["rule_decisions"] = decisions
  89. client = FakeWalkPlatformClient()
  90. result = run_bounded_walk(platform_client=client, **context)
  91. # M8 深游走:首轮只有 author_3 allow_walk,被拒的 author_0/1/2 不得消费作者预算/被抓取。
  92. # (author_3 带回的作品作者会在更深一层被走,属深游走正常行为,不在本断言范围。)
  93. fetched_authors = [call["platform_author_id"] for call in client.author_calls]
  94. assert "author_3" in fetched_authors
  95. assert not ({"author_0", "author_1", "author_2"} & set(fetched_authors))
  96. skipped = [
  97. row for row in result["walk_actions"]
  98. if row["edge_id"] == "author_to_works" and row["walk_status"] == "skipped"
  99. ]
  100. assert [row["reason_code"] for row in skipped[:3]] == [
  101. "v4_allow_walk_denied",
  102. "v4_allow_walk_denied",
  103. "v4_allow_walk_denied",
  104. ]
  105. assert not any(
  106. row["reason_code"] == "budget_exhausted" and row["raw_payload"].get("allow_walk") is True
  107. for row in skipped
  108. )
  109. def test_non_v4_author_edge_keeps_legacy_permission(tmp_path):
  110. context = build_initial_walk_context(tmp_path)
  111. decision = context["rule_decisions"][0]
  112. decision["scorecard"]["schema_version"] = "v3_scorecard.v1"
  113. decision["decision_replay_data"].pop("allow_walk", None)
  114. _override_decisions(context, "ADD_TO_CONTENT_POOL", "success")
  115. client = FakeWalkPlatformClient()
  116. run_bounded_walk(platform_client=client, **context)
  117. assert client.author_calls
  118. def test_author_edge_keeps_review_low_budget(tmp_path):
  119. context = build_initial_walk_context(tmp_path)
  120. _override_decisions(context, "KEEP_CONTENT_FOR_REVIEW", "pending")
  121. client = FakeWalkPlatformClient()
  122. result = run_bounded_walk(platform_client=client, **context)
  123. assert client.author_calls
  124. author_actions = [
  125. row for row in result["walk_actions"]
  126. if row["edge_id"] == "author_to_works" and row["walk_status"] == "success"
  127. ]
  128. assert author_actions
  129. assert author_actions[0]["budget_tier"] == "low_budget"
  130. assert author_actions[0]["raw_payload"]["rule_pack_execution"]["executed"] is True
  131. def test_author_works_skip_already_discovered_content(tmp_path):
  132. # 真实 E2E(v1_run_e6ba21f7543b)实证:作者近期作品包含首轮已发现的同一条视频,
  133. # 不去重会撞 DB 唯一索引 uk_ca_items_run_policy_content。
  134. context = build_initial_walk_context(tmp_path)
  135. context["discovered_content_items"][0]["has_more"] = False
  136. context["discovered_content_items"][0]["tags"] = []
  137. first_round_id = context["discovered_content_items"][0]["platform_content_id"]
  138. class OverlappingWorksClient(FakeWalkPlatformClient):
  139. def fetch_author_works(self, query):
  140. self.author_calls.append(dict(query))
  141. duplicate = _dup_platform_result(query, first_round_id)
  142. fresh = _dup_platform_result(query, "7390000000000000399")
  143. return [duplicate, fresh]
  144. client = OverlappingWorksClient()
  145. result = run_bounded_walk(platform_client=client, **context)
  146. assert client.author_calls
  147. content_ids = [item["platform_content_id"] for item in result["discovered_content_items"]]
  148. # 重复视频不得二次进入 discovered;新作品正常进入。
  149. assert content_ids.count(first_round_id) == 1
  150. assert "7390000000000000399" in content_ids
  151. def _dup_platform_result(query, platform_content_id):
  152. from tests.p6_walk_helpers import _platform_result
  153. return _platform_result(query, platform_content_id, "作者作品", [])
  154. def test_author_works_have_search_query_lineage(tmp_path):
  155. # 真实 E2E(v1_run_3a3bc9f0d72d)实证:作者作品内容引用合成 query id 但
  156. # search_queries 无此行,validate_run 报 missing_search_query_ref 等 8 条 fail。
  157. # 血缘补全后:query 行落盘 → pattern_to_search_query 路径与 search_clue 经既有机制生成。
  158. from content_agent.business_modules import run_record
  159. from content_agent.business_modules.run_record.validation import validate_run
  160. context = build_initial_walk_context(tmp_path)
  161. context["discovered_content_items"][0]["has_more"] = False
  162. context["discovered_content_items"][0]["tags"] = []
  163. client = FakeWalkPlatformClient()
  164. result = run_bounded_walk(platform_client=client, **context)
  165. author_queries = [
  166. q for q in result["search_queries"]
  167. if q["search_query_generation_method"] == "author_works"
  168. ]
  169. assert len(author_queries) == 1
  170. author_query = author_queries[0]
  171. assert author_query["search_query_id"].startswith("author_")
  172. assert author_query["previous_discovery_step"] == "author_to_works"
  173. # 作者作品内容引用的 query id 必须真实存在。
  174. author_items = [
  175. i for i in result["discovered_content_items"]
  176. if i.get("previous_discovery_step") == "author_works"
  177. ]
  178. assert author_items
  179. assert all(i["search_query_id"] == author_query["search_query_id"] for i in author_items)
  180. # 端到端血缘:M4 后终端边+血缘由 run_bounded_walk 一体产出,record_run 后 validate_run 必须 pass。
  181. record = run_record.run(
  182. context["run_id"],
  183. context["policy_run_id"],
  184. result["search_queries"],
  185. result["discovered_content_items"],
  186. result["rule_decisions"],
  187. result["source_path_record_basis"],
  188. context["policy_bundle"],
  189. context["runtime"],
  190. walk_actions=result["walk_actions"],
  191. )
  192. from content_agent.business_modules import learning_review, result_source_lookup
  193. result_source_lookup.run(
  194. context["run_id"],
  195. context["policy_run_id"],
  196. context["policy_bundle"],
  197. result["discovered_content_items"],
  198. result["content_media_records"],
  199. result["rule_decisions"],
  200. record["source_path_records"],
  201. record["search_clues"],
  202. context["runtime"],
  203. )
  204. learning_review.run(context["run_id"], context["policy_run_id"], context["runtime"])
  205. validation = validate_run(context["run_id"], context["runtime"])
  206. fails = [f for f in validation["findings"] if f["level"] == "fail"]
  207. assert validation["status"] == "pass", fails