supervisor_nodes.py 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. from __future__ import annotations
  2. """监督协调节点:汇总Validator和Reviewer的意见,根据校验结果与重试次数决定replan/finalize/terminate去向。"""
  3. from typing import Any, Literal
  4. from app.graph.state import TravelState
  5. SupervisorRoute = Literal[
  6. "replan",
  7. "finalize",
  8. "finalize_with_risks",
  9. "terminate",
  10. ]
  11. def _unique_messages(
  12. values: list[str],
  13. ) -> list[str]:
  14. """去除空字符串和重复修改意见。"""
  15. result: list[str] = []
  16. for value in values:
  17. normalized = value.strip()
  18. if normalized and normalized not in result:
  19. result.append(normalized)
  20. return result
  21. def collect_revision_feedback(
  22. state: TravelState,
  23. ) -> list[str]:
  24. """收集Validator与Reviewer提出的修改意见。"""
  25. feedback = list(
  26. state.get("revision_feedback", [])
  27. )
  28. validation = state.get(
  29. "plan_validation_result"
  30. )
  31. if validation is not None:
  32. feedback.extend(
  33. validation.revision_feedback
  34. )
  35. for issue in validation.issues:
  36. if (
  37. issue.severity == "error"
  38. and issue.message
  39. ):
  40. feedback.append(issue.message)
  41. review = state.get("trip_review_result")
  42. if review is not None:
  43. feedback.extend(
  44. review.revision_feedback
  45. )
  46. for issue in review.issues:
  47. if (
  48. issue.severity == "blocking"
  49. and issue.message
  50. ):
  51. feedback.append(
  52. issue.suggestion
  53. or issue.message
  54. )
  55. return _unique_messages(feedback)
  56. def make_supervisor_node(
  57. *,
  58. max_planning_attempts: int = 3,
  59. ):
  60. """创建多角色工作流协调节点。
  61. max_planning_attempts=3表示:
  62. 1次初始规划 + 最多2次修改。
  63. """
  64. if max_planning_attempts < 1:
  65. raise ValueError(
  66. "max_planning_attempts必须大于等于1。"
  67. )
  68. def supervisor_node(
  69. state: TravelState,
  70. ) -> dict[str, Any]:
  71. """监督节点:基于校验与评审结果及当前尝试次数,决策工作流的下一步——继续重规划、定稿(含风险)或终止。"""
  72. system_errors = state.get(
  73. "errors",
  74. [],
  75. )
  76. if system_errors:
  77. return {
  78. "supervisor_decision": "terminate",
  79. "supervisor_reason": (
  80. "工作流存在系统级错误,"
  81. "不能继续重新规划。"
  82. ),
  83. "workflow_status": "failed",
  84. }
  85. validation = state.get(
  86. "plan_validation_result"
  87. )
  88. review = state.get(
  89. "trip_review_result"
  90. )
  91. if validation is None or review is None:
  92. return {
  93. "supervisor_decision": "terminate",
  94. "supervisor_reason": (
  95. "Supervisor缺少Validator或"
  96. "Reviewer的结构化结果。"
  97. ),
  98. "workflow_status": "failed",
  99. "errors": [
  100. "Supervisor输入状态不完整。"
  101. ],
  102. }
  103. validation_passed = validation.is_valid
  104. review_passed = bool(
  105. state.get("review_passed", False)
  106. )
  107. planning_attempts = state.get(
  108. "planning_attempts",
  109. 0,
  110. )
  111. if validation_passed and review_passed:
  112. return {
  113. "supervisor_decision": "finalize",
  114. "supervisor_reason": (
  115. "确定性校验和独立审查均已通过。"
  116. ),
  117. "workflow_status": "approved",
  118. "revision_feedback": [],
  119. "final_answer": (
  120. "Supervisor批准当前方案,"
  121. "进入最终输出阶段。"
  122. ),
  123. }
  124. feedback = collect_revision_feedback(
  125. state
  126. )
  127. if not feedback:
  128. feedback = [
  129. "重新检查方案与用户需求、"
  130. "确定性校验结果和审查意见,"
  131. "修复所有阻塞问题。"
  132. ]
  133. if planning_attempts < max_planning_attempts:
  134. next_attempt = planning_attempts + 1
  135. return {
  136. "supervisor_decision": "replan",
  137. "supervisor_reason": (
  138. "当前方案未通过,且仍有"
  139. "剩余重新规划次数。"
  140. ),
  141. "workflow_status": "running",
  142. "revision_feedback": feedback,
  143. "validation_passed": False,
  144. "review_passed": False,
  145. "final_answer": (
  146. "Supervisor要求重新规划:"
  147. f"即将执行第{next_attempt}次规划。"
  148. ),
  149. }
  150. return {
  151. "supervisor_decision": (
  152. "finalize_with_risks"
  153. ),
  154. "supervisor_reason": (
  155. "方案仍存在问题,但已经达到"
  156. f"最大规划次数"
  157. f"{max_planning_attempts}次。"
  158. ),
  159. "workflow_status": (
  160. "completed_with_risks"
  161. ),
  162. "revision_feedback": feedback,
  163. "final_answer": (
  164. "已达到最大重新规划次数,"
  165. "系统将保留风险说明并输出"
  166. "当前最佳方案。"
  167. ),
  168. }
  169. return supervisor_node
  170. def route_after_supervisor(
  171. state: TravelState,
  172. ) -> SupervisorRoute:
  173. """根据Supervisor决策选择后续路径。"""
  174. decision = state.get(
  175. "supervisor_decision",
  176. "terminate",
  177. )
  178. if decision in {
  179. "replan",
  180. "finalize",
  181. "finalize_with_risks",
  182. "terminate",
  183. }:
  184. return decision
  185. return "terminate"