content_discovery_builder.py 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  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. platform_raw_payload = result.get("platform_raw_payload") or {}
  116. if isinstance(platform_raw_payload, dict):
  117. for field in [
  118. "no_valid_play_url",
  119. "selected_video_url_path",
  120. "selected_video_url_host",
  121. "selected_video_url_probe_status",
  122. "selected_video_url_content_type",
  123. "selected_video_url_content_range",
  124. "video_url_candidate_counts",
  125. "video_url_reject_reasons",
  126. "kuaishou_detail_fallback_attempted",
  127. "kuaishou_detail_fallback_status",
  128. "kuaishou_detail_fallback_exception_type",
  129. "kuaishou_detail_fallback_error",
  130. "shipinhao_research_source",
  131. "shipinhao_research_status",
  132. "shipinhao_research_exception_type",
  133. "shipinhao_research_error",
  134. "shipinhao_research_item_count",
  135. ]:
  136. if field in platform_raw_payload:
  137. record["raw_payload"][field] = platform_raw_payload[field]
  138. return record
  139. def _build_evidence_bundle(
  140. run_id: str,
  141. policy_run_id: str,
  142. discovered_content_item: dict[str, Any],
  143. result: dict[str, Any],
  144. source_context: dict[str, Any],
  145. ) -> dict[str, Any]:
  146. source_evidence = build_source_evidence(
  147. run_id, policy_run_id, discovered_content_item, result, source_context
  148. )
  149. return {
  150. "schema_version": EVIDENCE_BUNDLE_SCHEMA_VERSION,
  151. "source_evidence": source_evidence,
  152. "content": {
  153. "decision_target_type": "content",
  154. "decision_target_id": discovered_content_item["platform_content_id"],
  155. "content_discovery_id": discovered_content_item["content_discovery_id"],
  156. "search_query_id": discovered_content_item["search_query_id"],
  157. "platform": discovered_content_item["platform"],
  158. "platform_content_id": discovered_content_item["platform_content_id"],
  159. "platform_content_format": discovered_content_item["platform_content_format"],
  160. "description": discovered_content_item["description"],
  161. "create_time": result.get("create_time"),
  162. "author": {
  163. "platform_author_id": discovered_content_item.get("platform_author_id", ""),
  164. "author_display_name": discovered_content_item.get("author_display_name", ""),
  165. },
  166. "platform_author_id": discovered_content_item.get("platform_author_id", ""),
  167. },
  168. "pattern_match_result": {
  169. "level": result.get("relevance_level", "related"),
  170. "score": result.get("score"),
  171. },
  172. "content_engagement_metrics": {
  173. "statistics": result["statistics"],
  174. **result["statistics"],
  175. "platform_performance": performance_score(
  176. result["statistics"],
  177. discovered_content_item["platform"],
  178. ),
  179. },
  180. "author_audience_profile": {
  181. "platform_author_id": discovered_content_item.get("platform_author_id", ""),
  182. "author": {
  183. "platform_author_id": discovered_content_item.get("platform_author_id", "")
  184. },
  185. },
  186. "content_risk_check": {
  187. "risk_level": result.get("risk_level", "unknown"),
  188. "age_50_plus_safety": result.get("age_50_plus_safety", "safe"),
  189. "availability": result.get("availability", "metadata_only"),
  190. },
  191. "discovery_path_context": {
  192. "discovery_start_source": discovered_content_item["discovery_start_source"],
  193. "previous_discovery_step": discovered_content_item["previous_discovery_step"],
  194. },
  195. "run_context": {
  196. "run_id": run_id,
  197. "policy_run_id": policy_run_id,
  198. "runtime_record_schema_version": RUNTIME_RECORD_SCHEMA_VERSION,
  199. "evidence_bundle_schema_version": EVIDENCE_BUNDLE_SCHEMA_VERSION,
  200. "decision_input_snapshot_id": (
  201. f"evidence_bundle:{run_id}:{discovered_content_item['content_discovery_id']}"
  202. ),
  203. },
  204. }