test_supervisor_nodes.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. from __future__ import annotations
  2. from app.graph.supervisor_nodes import (
  3. make_supervisor_node,
  4. )
  5. from app.schemas.review import (
  6. ReviewIssue,
  7. TripReviewResult,
  8. )
  9. from app.schemas.validation import (
  10. BudgetEstimate,
  11. PlanValidationResult,
  12. ValidationIssue,
  13. )
  14. def build_validation(
  15. *,
  16. valid: bool,
  17. ) -> PlanValidationResult:
  18. issues = []
  19. if not valid:
  20. issues = [
  21. ValidationIssue(
  22. code="TEST_ERROR",
  23. severity="error",
  24. message="测试校验错误。",
  25. )
  26. ]
  27. return PlanValidationResult(
  28. is_valid=valid,
  29. error_count=0 if valid else 1,
  30. warning_count=0,
  31. issues=issues,
  32. budget=BudgetEstimate(
  33. currency="CNY",
  34. known_total=3000,
  35. user_budget=8000,
  36. remaining_known_budget=5000,
  37. status="within_known_budget",
  38. coverage_note="测试预算。",
  39. ),
  40. revision_feedback=(
  41. []
  42. if valid
  43. else ["修复测试校验错误。"]
  44. ),
  45. )
  46. def build_review(
  47. *,
  48. approved: bool,
  49. ) -> TripReviewResult:
  50. if approved:
  51. return TripReviewResult(
  52. approved=True,
  53. summary="方案通过。",
  54. )
  55. return TripReviewResult(
  56. approved=False,
  57. summary="方案未通过。",
  58. issues=[
  59. ReviewIssue(
  60. category="pace",
  61. severity="blocking",
  62. message="行程过于紧凑。",
  63. suggestion="减少每日景点数量。",
  64. )
  65. ],
  66. revision_feedback=[
  67. "减少每日景点数量。",
  68. ],
  69. )
  70. def test_supervisor_approves_valid_plan() -> None:
  71. node = make_supervisor_node(
  72. max_planning_attempts=3
  73. )
  74. result = node(
  75. {
  76. "planning_attempts": 1,
  77. "plan_validation_result": (
  78. build_validation(valid=True)
  79. ),
  80. "trip_review_result": (
  81. build_review(approved=True)
  82. ),
  83. "review_passed": True,
  84. "errors": [],
  85. }
  86. )
  87. assert (
  88. result["supervisor_decision"]
  89. == "finalize"
  90. )
  91. assert (
  92. result["workflow_status"]
  93. == "approved"
  94. )
  95. def test_supervisor_requests_replan() -> None:
  96. node = make_supervisor_node(
  97. max_planning_attempts=3
  98. )
  99. result = node(
  100. {
  101. "planning_attempts": 1,
  102. "plan_validation_result": (
  103. build_validation(valid=False)
  104. ),
  105. "trip_review_result": (
  106. build_review(approved=False)
  107. ),
  108. "review_passed": False,
  109. "revision_feedback": [],
  110. "errors": [],
  111. }
  112. )
  113. assert (
  114. result["supervisor_decision"]
  115. == "replan"
  116. )
  117. assert result["revision_feedback"]
  118. def test_supervisor_stops_after_max_attempts(
  119. ) -> None:
  120. node = make_supervisor_node(
  121. max_planning_attempts=3
  122. )
  123. result = node(
  124. {
  125. "planning_attempts": 3,
  126. "plan_validation_result": (
  127. build_validation(valid=False)
  128. ),
  129. "trip_review_result": (
  130. build_review(approved=False)
  131. ),
  132. "review_passed": False,
  133. "revision_feedback": [],
  134. "errors": [],
  135. }
  136. )
  137. assert (
  138. result["supervisor_decision"]
  139. == "finalize_with_risks"
  140. )
  141. assert (
  142. result["workflow_status"]
  143. == "completed_with_risks"
  144. )
  145. def test_supervisor_terminates_system_error(
  146. ) -> None:
  147. node = make_supervisor_node(
  148. max_planning_attempts=3
  149. )
  150. result = node(
  151. {
  152. "planning_attempts": 1,
  153. "errors": [
  154. "MCP连接失败。",
  155. ],
  156. }
  157. )
  158. assert (
  159. result["supervisor_decision"]
  160. == "terminate"
  161. )
  162. assert (
  163. result["workflow_status"]
  164. == "failed"
  165. )