content_discovery_builder.py 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. from __future__ import annotations
  2. from datetime import datetime, timezone
  3. from typing import Any
  4. from content_agent.business_modules.content_discovery.platform_observable_performance import (
  5. performance_score,
  6. )
  7. from content_agent.business_modules.content_discovery.source_evidence import (
  8. build_source_evidence,
  9. )
  10. from content_agent.constants import EVIDENCE_BUNDLE_SCHEMA_VERSION, RUNTIME_RECORD_SCHEMA_VERSION
  11. from content_agent.interfaces import RuntimeFileStore
  12. from content_agent.record_payload import with_raw_payload
  13. def run(
  14. run_id: str,
  15. policy_run_id: str,
  16. platform_results: list[dict[str, Any]],
  17. source_context: dict[str, Any],
  18. runtime: RuntimeFileStore,
  19. write_runtime: bool = True,
  20. ) -> dict[str, list[dict[str, Any]]]:
  21. created_at = datetime.now(timezone.utc).isoformat()
  22. discovered_content_items: list[dict[str, Any]] = []
  23. content_media_records: list[dict[str, Any]] = []
  24. evidence_bundles: list[dict[str, Any]] = []
  25. for result in platform_results:
  26. discovered_content_item = _build_discovered_content_item(
  27. run_id, policy_run_id, result, created_at
  28. )
  29. content_media_record = _build_content_media_record(
  30. run_id, policy_run_id, result, created_at
  31. )
  32. evidence_bundles.append(
  33. _build_evidence_bundle(
  34. run_id, policy_run_id, discovered_content_item, result, source_context
  35. )
  36. )
  37. discovered_content_items.append(discovered_content_item)
  38. content_media_records.append(content_media_record)
  39. if write_runtime:
  40. runtime.append_jsonl(run_id, "content_media_records.jsonl", content_media_records)
  41. return {
  42. "discovered_content_items": discovered_content_items,
  43. "content_media_records": content_media_records,
  44. "evidence_bundles": evidence_bundles,
  45. }
  46. def _build_discovered_content_item(
  47. run_id: str,
  48. policy_run_id: str,
  49. result: dict[str, Any],
  50. created_at: str,
  51. ) -> dict[str, Any]:
  52. item = {
  53. "record_schema_version": RUNTIME_RECORD_SCHEMA_VERSION,
  54. "run_id": run_id,
  55. "policy_run_id": policy_run_id,
  56. "content_discovery_id": result["content_discovery_id"],
  57. "search_query_id": result["search_query_id"],
  58. "platform": result.get("platform", "douyin"),
  59. "platform_content_id": result["platform_content_id"],
  60. "platform_content_format": result.get("platform_content_format", "video"),
  61. "platform_content_url": result.get("platform_content_url"),
  62. "description": result["description"],
  63. "platform_author_id": result.get("platform_author_id", ""),
  64. "author_display_name": result.get("author_display_name", ""),
  65. "statistics": result["statistics"],
  66. "tags": result.get("tags", []),
  67. "discovery_start_source": result["discovery_start_source"],
  68. "previous_discovery_step": result["previous_discovery_step"],
  69. "created_at": created_at,
  70. }
  71. for field in [
  72. "text_extra",
  73. "create_time",
  74. "has_more",
  75. "next_cursor",
  76. "platform_auth_mode",
  77. "platform_raw_payload",
  78. "query_sources",
  79. "matched_search_query_ids",
  80. "matched_search_queries",
  81. "matched_search_query_generation_methods",
  82. "progressive_batch_id",
  83. "progressive_page_number",
  84. "progressive_batch_kind",
  85. "progressive_page_item_count",
  86. "progressive_item_rank_in_page",
  87. "progressive_original_has_more",
  88. "progressive_original_next_cursor",
  89. ]:
  90. if field in result:
  91. item[field] = result[field]
  92. return with_raw_payload(item)
  93. def _build_content_media_record(
  94. run_id: str,
  95. policy_run_id: str,
  96. result: dict[str, Any],
  97. created_at: str,
  98. ) -> dict[str, Any]:
  99. record = {
  100. "record_schema_version": RUNTIME_RECORD_SCHEMA_VERSION,
  101. "run_id": run_id,
  102. "policy_run_id": policy_run_id,
  103. "platform": result.get("platform", "douyin"),
  104. "platform_content_id": result["platform_content_id"],
  105. "content_media_status": "metadata_only",
  106. "content_metadata_source": result.get("content_metadata_source", "mock_platform_search"),
  107. "play_url": result.get("play_url"),
  108. "local_path": None,
  109. "oss_url": None,
  110. "created_at": created_at,
  111. }
  112. if not record["play_url"]:
  113. record["failure_reason"] = result.get("media_failure_reason", "no_play_url_returned")
  114. record = with_raw_payload(record)
  115. if result.get("video_url_candidates"):
  116. record["raw_payload"]["video_url_candidates"] = result["video_url_candidates"]
  117. platform_raw_payload = result.get("platform_raw_payload") or {}
  118. if isinstance(platform_raw_payload, dict):
  119. for field in [
  120. "no_valid_play_url",
  121. "selected_video_url_path",
  122. "selected_video_url_host",
  123. "selected_video_url_probe_status",
  124. "selected_video_url_content_type",
  125. "selected_video_url_content_range",
  126. "video_url_candidate_counts",
  127. "video_url_reject_reasons",
  128. "kuaishou_detail_fallback_attempted",
  129. "kuaishou_detail_fallback_status",
  130. "kuaishou_detail_fallback_exception_type",
  131. "kuaishou_detail_fallback_error",
  132. "douyin_detail_fallback_attempted",
  133. "douyin_detail_fallback_status",
  134. "douyin_detail_fallback_exception_type",
  135. "douyin_detail_fallback_error",
  136. "shipinhao_research_source",
  137. "shipinhao_research_status",
  138. "shipinhao_research_exception_type",
  139. "shipinhao_research_error",
  140. "shipinhao_research_item_count",
  141. ]:
  142. if field in platform_raw_payload:
  143. record["raw_payload"][field] = platform_raw_payload[field]
  144. return record
  145. def _build_evidence_bundle(
  146. run_id: str,
  147. policy_run_id: str,
  148. discovered_content_item: dict[str, Any],
  149. result: dict[str, Any],
  150. source_context: dict[str, Any],
  151. ) -> dict[str, Any]:
  152. source_evidence = build_source_evidence(
  153. run_id, policy_run_id, discovered_content_item, result, source_context
  154. )
  155. return {
  156. "schema_version": EVIDENCE_BUNDLE_SCHEMA_VERSION,
  157. "source_evidence": source_evidence,
  158. "content": {
  159. "decision_target_type": "content",
  160. "decision_target_id": discovered_content_item["platform_content_id"],
  161. "content_discovery_id": discovered_content_item["content_discovery_id"],
  162. "search_query_id": discovered_content_item["search_query_id"],
  163. "platform": discovered_content_item["platform"],
  164. "platform_content_id": discovered_content_item["platform_content_id"],
  165. "platform_content_format": discovered_content_item["platform_content_format"],
  166. "description": discovered_content_item["description"],
  167. "create_time": result.get("create_time"),
  168. "author": {
  169. "platform_author_id": discovered_content_item.get("platform_author_id", ""),
  170. "author_display_name": discovered_content_item.get("author_display_name", ""),
  171. },
  172. "platform_author_id": discovered_content_item.get("platform_author_id", ""),
  173. },
  174. "pattern_match_result": {
  175. "level": result.get("relevance_level", "related"),
  176. "score": result.get("score"),
  177. },
  178. "content_engagement_metrics": {
  179. "statistics": result["statistics"],
  180. **result["statistics"],
  181. "platform_performance": performance_score(
  182. result["statistics"],
  183. discovered_content_item["platform"],
  184. ),
  185. },
  186. "author_audience_profile": {
  187. "platform_author_id": discovered_content_item.get("platform_author_id", ""),
  188. "author": {
  189. "platform_author_id": discovered_content_item.get("platform_author_id", "")
  190. },
  191. },
  192. "content_risk_check": {
  193. "risk_level": result.get("risk_level", "unknown"),
  194. "age_50_plus_safety": result.get("age_50_plus_safety", "safe"),
  195. "availability": result.get("availability", "metadata_only"),
  196. },
  197. "discovery_path_context": {
  198. "discovery_start_source": discovered_content_item["discovery_start_source"],
  199. "previous_discovery_step": discovered_content_item["previous_discovery_step"],
  200. },
  201. "run_context": {
  202. "run_id": run_id,
  203. "policy_run_id": policy_run_id,
  204. "runtime_record_schema_version": RUNTIME_RECORD_SCHEMA_VERSION,
  205. "evidence_bundle_schema_version": EVIDENCE_BUNDLE_SCHEMA_VERSION,
  206. "decision_input_snapshot_id": (
  207. f"evidence_bundle:{run_id}:{discovered_content_item['content_discovery_id']}"
  208. ),
  209. },
  210. }