graph.py 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. from __future__ import annotations
  2. from dataclasses import dataclass
  3. from typing import Any
  4. from langgraph.graph import END, START, StateGraph
  5. from content_agent.business_modules import (
  6. content_discovery,
  7. learning_review,
  8. platform_access,
  9. policy_version,
  10. result_source_lookup,
  11. rule_judgment,
  12. run_record,
  13. search_intent,
  14. walk_engine,
  15. source_seed,
  16. walk_strategy,
  17. )
  18. from content_agent.business_modules.content_discovery import pattern_recall
  19. from content_agent.interfaces import (
  20. CategoryMatchClient,
  21. DecodeClient,
  22. PlatformSearchClient,
  23. PolicyBundleStore,
  24. QueryVariantClient,
  25. RuntimeFileStore,
  26. )
  27. from content_agent.models import RunState
  28. @dataclass(frozen=True)
  29. class RunDependencies:
  30. runtime: RuntimeFileStore
  31. platform_client: PlatformSearchClient
  32. policy_store: PolicyBundleStore
  33. query_variant_client: QueryVariantClient
  34. decode_client: DecodeClient
  35. category_match_client: CategoryMatchClient
  36. pattern_recall_max_wait_seconds: float = 1200.0
  37. pattern_recall_poll_interval_seconds: float = 5.0
  38. def build_run_graph(deps: RunDependencies):
  39. graph = StateGraph(RunState)
  40. def load_source(state: RunState) -> dict[str, Any]:
  41. result = source_seed.run(
  42. state["run_id"], state["policy_run_id"], state.get("source"), deps.runtime
  43. )
  44. return {**result, "current_step": "load_source"}
  45. def plan_queries(state: RunState) -> dict[str, Any]:
  46. search_queries = search_intent.run(
  47. state["run_id"],
  48. state["policy_run_id"],
  49. state["pattern_seed_pack"],
  50. deps.runtime,
  51. deps.query_variant_client,
  52. )
  53. return {"search_queries": search_queries, "current_step": "plan_queries"}
  54. def search_platform(state: RunState) -> dict[str, Any]:
  55. result = platform_access.run(state["search_queries"], deps.platform_client)
  56. return {**result, "current_step": "search_platform"}
  57. def build_discovered_content(state: RunState) -> dict[str, Any]:
  58. result = content_discovery.run(
  59. state["run_id"],
  60. state["policy_run_id"],
  61. state["platform_results"],
  62. state["source_context"],
  63. deps.runtime,
  64. )
  65. return {**result, "current_step": "build_discovered_content"}
  66. def recall_pattern(state: RunState) -> dict[str, Any]:
  67. result = pattern_recall.run(
  68. state["run_id"],
  69. state["policy_run_id"],
  70. state["discovered_content_items"],
  71. state["content_media_records"],
  72. state["evidence_bundles"],
  73. state["source_context"],
  74. state["pattern_seed_pack"],
  75. deps.runtime,
  76. deps.decode_client,
  77. deps.category_match_client,
  78. max_wait_seconds=deps.pattern_recall_max_wait_seconds,
  79. poll_interval_seconds=deps.pattern_recall_poll_interval_seconds,
  80. )
  81. return {**result, "current_step": "recall_pattern"}
  82. def load_policy(state: RunState) -> dict[str, Any]:
  83. bundle = policy_version.run(state["strategy_version"], deps.policy_store)
  84. return {
  85. "policy_bundle": bundle,
  86. "policy_bundle_id": bundle["policy_bundle_id"],
  87. "strategy_version": bundle["strategy_version"],
  88. "strategy_source_ref": bundle["strategy_source_ref"],
  89. "current_step": "load_policy",
  90. }
  91. def evaluate_rules(state: RunState) -> dict[str, Any]:
  92. decisions = rule_judgment.run(
  93. state["run_id"],
  94. state["policy_run_id"],
  95. state["evidence_bundles"],
  96. state["policy_bundle"],
  97. deps.runtime,
  98. )
  99. return {"rule_decisions": decisions, "current_step": "evaluate_rules"}
  100. def execute_walk(state: RunState) -> dict[str, Any]:
  101. result = walk_engine.run_bounded_walk(
  102. run_id=state["run_id"],
  103. policy_run_id=state["policy_run_id"],
  104. pattern_seed_pack=state["pattern_seed_pack"],
  105. source_context=state["source_context"],
  106. search_queries=state["search_queries"],
  107. discovered_content_items=state["discovered_content_items"],
  108. content_media_records=state["content_media_records"],
  109. evidence_bundles=state["evidence_bundles"],
  110. rule_decisions=state["rule_decisions"],
  111. policy_bundle=state["policy_bundle"],
  112. platform_client=deps.platform_client,
  113. runtime=deps.runtime,
  114. decode_client=deps.decode_client,
  115. category_match_client=deps.category_match_client,
  116. max_wait_seconds=deps.pattern_recall_max_wait_seconds,
  117. poll_interval_seconds=deps.pattern_recall_poll_interval_seconds,
  118. )
  119. return {**result, "current_step": "execute_walk"}
  120. def plan_walk(state: RunState) -> dict[str, Any]:
  121. result = walk_strategy.run(
  122. state["pattern_seed_pack"],
  123. state["search_queries"],
  124. state["discovered_content_items"],
  125. state["rule_decisions"],
  126. )
  127. result["walk_actions"] = [*state.get("walk_actions", []), *result["walk_actions"]]
  128. return {**result, "current_step": "plan_walk"}
  129. def record_run(state: RunState) -> dict[str, Any]:
  130. result = run_record.run(
  131. state["run_id"],
  132. state["policy_run_id"],
  133. state["search_queries"],
  134. state["discovered_content_items"],
  135. state["rule_decisions"],
  136. state["source_path_record_basis"],
  137. state["policy_bundle"],
  138. deps.runtime,
  139. walk_actions=state["walk_actions"],
  140. query_failures=state.get("query_failures", []),
  141. )
  142. return {**result, "current_step": "record_run"}
  143. def commit_results(state: RunState) -> dict[str, Any]:
  144. final_output = result_source_lookup.run(
  145. state["run_id"],
  146. state["policy_run_id"],
  147. state["policy_bundle"],
  148. state["discovered_content_items"],
  149. state["content_media_records"],
  150. state["rule_decisions"],
  151. state["source_path_records"],
  152. state["search_clues"],
  153. deps.runtime,
  154. )
  155. return {"final_output": final_output, "current_step": "commit_results"}
  156. def review_strategy(state: RunState) -> dict[str, Any]:
  157. review = learning_review.run(state["run_id"], state["policy_run_id"], deps.runtime)
  158. return {"strategy_review": review, "current_step": "review_strategy", "status": "success"}
  159. graph.add_node("load_source", load_source)
  160. graph.add_node("plan_queries", plan_queries)
  161. graph.add_node("search_platform", search_platform)
  162. graph.add_node("build_discovered_content", build_discovered_content)
  163. graph.add_node("recall_pattern", recall_pattern)
  164. graph.add_node("load_policy", load_policy)
  165. graph.add_node("evaluate_rules", evaluate_rules)
  166. graph.add_node("execute_walk", execute_walk)
  167. graph.add_node("plan_walk", plan_walk)
  168. graph.add_node("record_run", record_run)
  169. graph.add_node("commit_results", commit_results)
  170. graph.add_node("review_strategy", review_strategy)
  171. graph.add_edge(START, "load_source")
  172. graph.add_edge("load_source", "plan_queries")
  173. graph.add_edge("plan_queries", "search_platform")
  174. graph.add_edge("search_platform", "build_discovered_content")
  175. graph.add_edge("build_discovered_content", "recall_pattern")
  176. graph.add_edge("recall_pattern", "load_policy")
  177. graph.add_edge("load_policy", "evaluate_rules")
  178. graph.add_edge("evaluate_rules", "execute_walk")
  179. graph.add_edge("execute_walk", "plan_walk")
  180. graph.add_edge("plan_walk", "record_run")
  181. graph.add_edge("record_run", "commit_results")
  182. graph.add_edge("commit_results", "review_strategy")
  183. graph.add_edge("review_strategy", END)
  184. return graph.compile()