review_nodes.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. from __future__ import annotations
  2. """Trip Reviewer节点:调用TripReviewerAgent从风险角度独立评审行程,生成阻塞问题与优化建议。"""
  3. from typing import Any, Literal
  4. from app.agents.reviewer_agent import (
  5. TripReviewerAgent,
  6. )
  7. from app.graph.state import TravelState
  8. from app.schemas.review import TripReviewResult
  9. def route_after_validation(
  10. state: TravelState,
  11. ) -> Literal[
  12. "review_plan",
  13. "end",
  14. ]:
  15. """确定性校验执行后进入Reviewer。
  16. 即使方案校验失败,也允许Reviewer读取错误,
  17. 生成更完整的修改意见。
  18. """
  19. if state.get("errors"):
  20. return "end"
  21. if not state.get(
  22. "plan_validation_result"
  23. ):
  24. return "end"
  25. if not state.get("itinerary_plan"):
  26. return "end"
  27. return "review_plan"
  28. def _unique_feedback(
  29. values: list[str],
  30. ) -> list[str]:
  31. """对修改意见列表去重并去除空字符串,保持原有顺序。"""
  32. result: list[str] = []
  33. for value in values:
  34. normalized = value.strip()
  35. if (
  36. normalized
  37. and normalized not in result
  38. ):
  39. result.append(normalized)
  40. return result
  41. def make_trip_reviewer_node(
  42. agent: TripReviewerAgent,
  43. ):
  44. """创建Trip Reviewer节点。"""
  45. async def trip_reviewer_node(
  46. state: TravelState,
  47. ) -> dict[str, Any]:
  48. """评审节点:调用ReviewerAgent独立审查行程方案,将评审结论与修改意见写入state。"""
  49. attempts = (
  50. state.get("review_attempts", 0)
  51. + 1
  52. )
  53. validation = state[
  54. "plan_validation_result"
  55. ]
  56. try:
  57. result = await agent.review(
  58. request=state["travel_request"],
  59. plan=state["itinerary_plan"],
  60. validation=validation,
  61. map_result=state[
  62. "map_research_result"
  63. ],
  64. route_result=state[
  65. "route_evaluation_result"
  66. ],
  67. )
  68. structured_response = result.get(
  69. "structured_response"
  70. )
  71. if structured_response is None:
  72. message = (
  73. "Trip Reviewer没有返回"
  74. "结构化结果。"
  75. )
  76. existing_errors = list(
  77. state.get("errors", [])
  78. )
  79. existing_errors.append(message)
  80. return {
  81. "review_attempts": attempts,
  82. "reviewer_messages": (
  83. result.get("messages", [])
  84. ),
  85. "review_passed": False,
  86. "errors": existing_errors,
  87. "final_answer": message,
  88. }
  89. if not isinstance(
  90. structured_response,
  91. TripReviewResult,
  92. ):
  93. structured_response = (
  94. TripReviewResult.model_validate(
  95. structured_response
  96. )
  97. )
  98. # Reviewer不能覆盖Validator结果。
  99. review_passed = (
  100. validation.is_valid
  101. and structured_response.approved
  102. )
  103. combined_feedback = (
  104. validation.revision_feedback
  105. + structured_response
  106. .revision_feedback
  107. )
  108. combined_feedback = _unique_feedback(
  109. combined_feedback
  110. )
  111. if review_passed:
  112. combined_feedback = []
  113. message = (
  114. "Trip Reviewer审查通过:"
  115. f"发现"
  116. f"{len(structured_response.issues)}"
  117. "项非阻塞问题,"
  118. "方案可以进入最终输出阶段。"
  119. )
  120. else:
  121. message = (
  122. "Trip Reviewer审查未通过:"
  123. f"Validator错误"
  124. f"{validation.error_count}项,"
  125. "Reviewer发现"
  126. f"{len(structured_response.issues)}"
  127. "项问题;"
  128. "方案需要重新规划。"
  129. )
  130. return {
  131. "trip_review_result": (
  132. structured_response
  133. ),
  134. "reviewer_messages": result.get(
  135. "messages",
  136. [],
  137. ),
  138. "review_passed": review_passed,
  139. "review_attempts": attempts,
  140. "revision_feedback": (
  141. combined_feedback
  142. ),
  143. "final_answer": message,
  144. }
  145. except Exception as exc:
  146. message = (
  147. "Trip Reviewer执行失败:"
  148. f"{type(exc).__name__}: {exc}"
  149. )
  150. existing_errors = list(
  151. state.get("errors", [])
  152. )
  153. existing_errors.append(message)
  154. return {
  155. "review_attempts": attempts,
  156. "review_passed": False,
  157. "errors": existing_errors,
  158. "final_answer": message,
  159. }
  160. return trip_reviewer_node