planner_builder.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. from __future__ import annotations
  2. """完整多角色工作流主图:编排从需求解析→资源查询→候选筛选→地图研究→路线评估→行程规划→校验→评审→最终定稿的全部节点与条件边。"""
  3. from langchain_core.tools import BaseTool
  4. from langgraph.graph import (
  5. END,
  6. START,
  7. StateGraph,
  8. )
  9. from app.agents.itinerary_agent import (
  10. ItineraryPlannerAgent,
  11. )
  12. from app.agents.map_agent import (
  13. MapResearchAgent,
  14. )
  15. from app.agents.requirement_agent import (
  16. RequirementAgent,
  17. )
  18. from app.agents.resource_agent import (
  19. ResourceSearchAgent,
  20. )
  21. from app.graph.map_nodes import (
  22. make_map_research_node,
  23. )
  24. from app.graph.nodes import (
  25. clarification_node,
  26. error_node,
  27. make_parse_request_node,
  28. route_after_parse,
  29. )
  30. from app.graph.planner_nodes import (
  31. make_itinerary_planner_node,
  32. route_after_route_evaluation,
  33. )
  34. from app.graph.resource_nodes import (
  35. make_resource_search_node,
  36. )
  37. from app.graph.route_nodes import (
  38. make_route_evaluation_node,
  39. route_after_map_research,
  40. route_after_selection,
  41. )
  42. from app.graph.selection_nodes import (
  43. make_candidate_selection_node,
  44. route_after_resources_to_selection,
  45. )
  46. from app.graph.state import TravelState
  47. from app.mcp_client import load_mcp_tools
  48. from app.graph.validation_nodes import (
  49. make_plan_validation_node,
  50. route_after_planning,
  51. )
  52. from app.agents.reviewer_agent import (
  53. TripReviewerAgent,
  54. )
  55. from app.graph.review_nodes import (
  56. make_trip_reviewer_node,
  57. route_after_validation,
  58. )
  59. from app.graph.final_nodes import (
  60. make_finalization_node,
  61. )
  62. from app.graph.supervisor_nodes import (
  63. make_supervisor_node,
  64. route_after_supervisor,
  65. )
  66. def find_tool(
  67. tools: list[BaseTool],
  68. *names: str,
  69. ) -> BaseTool | None:
  70. """按名称查找已经加载的MCP工具。"""
  71. for name in names:
  72. for tool in tools:
  73. if tool.name == name:
  74. return tool
  75. return None
  76. async def build_planner_graph():
  77. """构建直到行程规划完成的多角色工作流。"""
  78. tool_bundle = await load_mcp_tools()
  79. return_flight_tool = find_tool(
  80. tool_bundle.travel_tools,
  81. "search_return_flights",
  82. )
  83. distance_tool = find_tool(
  84. tool_bundle.amap_tools,
  85. "maps_distance",
  86. )
  87. around_search_tool = find_tool(
  88. tool_bundle.amap_tools,
  89. "maps_around_search",
  90. "maps_search_around",
  91. )
  92. if return_flight_tool is None:
  93. raise RuntimeError(
  94. "没有加载search_return_flights工具。"
  95. )
  96. if distance_tool is None:
  97. raise RuntimeError(
  98. "没有加载maps_distance工具。"
  99. )
  100. requirement_agent = RequirementAgent()
  101. resource_agent = ResourceSearchAgent(
  102. tools=tool_bundle.travel_tools,
  103. )
  104. map_agent = MapResearchAgent(
  105. tools=tool_bundle.amap_tools,
  106. )
  107. planner_agent = ItineraryPlannerAgent()
  108. reviewer_agent = TripReviewerAgent()
  109. builder = StateGraph(TravelState)
  110. builder.add_node(
  111. "parse_request",
  112. make_parse_request_node(
  113. requirement_agent
  114. ),
  115. )
  116. builder.add_node(
  117. "clarify",
  118. clarification_node,
  119. )
  120. builder.add_node(
  121. "error",
  122. error_node,
  123. )
  124. builder.add_node(
  125. "search_resources",
  126. make_resource_search_node(
  127. resource_agent
  128. ),
  129. )
  130. builder.add_node(
  131. "select_candidates",
  132. make_candidate_selection_node(
  133. return_flight_tool,
  134. max_outbound_queries=2,
  135. max_returns_per_outbound=5,
  136. ),
  137. )
  138. builder.add_node(
  139. "research_destination",
  140. make_map_research_node(
  141. map_agent
  142. ),
  143. )
  144. builder.add_node(
  145. "evaluate_routes",
  146. make_route_evaluation_node(
  147. distance_tool=distance_tool,
  148. around_search_tool=(
  149. around_search_tool
  150. ),
  151. max_hotels=3,
  152. max_attractions=4,
  153. ),
  154. )
  155. builder.add_node(
  156. "plan_itinerary",
  157. make_itinerary_planner_node(
  158. planner_agent
  159. ),
  160. )
  161. builder.add_node(
  162. "validate_plan",
  163. make_plan_validation_node(),
  164. )
  165. builder.add_node(
  166. "review_plan",
  167. make_trip_reviewer_node(
  168. reviewer_agent
  169. ),
  170. )
  171. builder.add_node(
  172. "supervise",
  173. make_supervisor_node(
  174. max_planning_attempts=3,
  175. ),
  176. )
  177. builder.add_node(
  178. "finalize_response",
  179. make_finalization_node(),
  180. )
  181. builder.add_edge(
  182. START,
  183. "parse_request",
  184. )
  185. builder.add_conditional_edges(
  186. "parse_request",
  187. route_after_parse,
  188. {
  189. "clarify": "clarify",
  190. "error": "error",
  191. "ready": "search_resources",
  192. },
  193. )
  194. builder.add_edge("clarify", END)
  195. builder.add_edge("error", END)
  196. builder.add_conditional_edges(
  197. "search_resources",
  198. route_after_resources_to_selection,
  199. {
  200. "select_candidates": (
  201. "select_candidates"
  202. ),
  203. "end": END,
  204. },
  205. )
  206. builder.add_conditional_edges(
  207. "select_candidates",
  208. route_after_selection,
  209. {
  210. "research_destination": (
  211. "research_destination"
  212. ),
  213. "end": END,
  214. },
  215. )
  216. builder.add_conditional_edges(
  217. "research_destination",
  218. route_after_map_research,
  219. {
  220. "evaluate_routes": (
  221. "evaluate_routes"
  222. ),
  223. "end": END,
  224. },
  225. )
  226. builder.add_conditional_edges(
  227. "evaluate_routes",
  228. route_after_route_evaluation,
  229. {
  230. "plan_itinerary": (
  231. "plan_itinerary"
  232. ),
  233. "end": END,
  234. },
  235. )
  236. builder.add_conditional_edges(
  237. "plan_itinerary",
  238. route_after_planning,
  239. {
  240. "validate_plan": "validate_plan",
  241. "end": END,
  242. },
  243. )
  244. builder.add_conditional_edges(
  245. "validate_plan",
  246. route_after_validation,
  247. {
  248. "review_plan": "review_plan",
  249. "end": END,
  250. },
  251. )
  252. builder.add_edge(
  253. "review_plan",
  254. "supervise",
  255. )
  256. builder.add_conditional_edges(
  257. "supervise",
  258. route_after_supervisor,
  259. {
  260. "replan": "plan_itinerary",
  261. "finalize": "finalize_response",
  262. "finalize_with_risks": (
  263. "finalize_response"
  264. ),
  265. "terminate": END,
  266. },
  267. )
  268. builder.add_edge(
  269. "finalize_response",
  270. END,
  271. )
  272. return builder.compile()