from __future__ import annotations """路线评估阶段子图:组合候选航班与酒店,调用高德距离工具评估酒店位置与景点路线。""" from langchain_core.tools import BaseTool from langgraph.graph import ( END, START, StateGraph, ) from app.agents.map_agent import ( MapResearchAgent, ) from app.agents.requirement_agent import ( RequirementAgent, ) from app.agents.resource_agent import ( ResourceSearchAgent, ) from app.graph.map_nodes import ( make_map_research_node, ) from app.graph.nodes import ( clarification_node, error_node, make_parse_request_node, route_after_parse, ) from app.graph.resource_nodes import ( make_resource_search_node, ) from app.graph.route_nodes import ( make_route_evaluation_node, route_after_map_research, route_after_selection, ) from app.graph.selection_nodes import ( make_candidate_selection_node, route_after_resources_to_selection, ) from app.graph.state import TravelState from app.mcp_client import load_mcp_tools def find_tool( tools: list[BaseTool], *names: str, ) -> BaseTool | None: """按名称在MCP工具列表中查找目标工具,未找到返回None。""" for name in names: for tool in tools: if tool.name == name: return tool return None async def build_route_graph(): """构建从需求解析到酒店路线评估的完整流程。""" tool_bundle = await load_mcp_tools() return_flight_tool = find_tool( tool_bundle.travel_tools, "search_return_flights", ) distance_tool = find_tool( tool_bundle.amap_tools, "maps_distance", ) around_search_tool = find_tool( tool_bundle.amap_tools, "maps_around_search", "maps_search_around", ) if return_flight_tool is None: raise RuntimeError( "没有加载search_return_flights工具。" ) if distance_tool is None: raise RuntimeError( "没有加载maps_distance工具。" ) requirement_agent = RequirementAgent() resource_agent = ResourceSearchAgent( tools=tool_bundle.travel_tools, ) map_agent = MapResearchAgent( tools=tool_bundle.amap_tools, ) builder = StateGraph(TravelState) builder.add_node( "parse_request", make_parse_request_node( requirement_agent ), ) builder.add_node( "clarify", clarification_node, ) builder.add_node( "error", error_node, ) builder.add_node( "search_resources", make_resource_search_node( resource_agent ), ) builder.add_node( "select_candidates", make_candidate_selection_node( return_flight_tool, max_outbound_queries=2, max_returns_per_outbound=5, ), ) builder.add_node( "research_destination", make_map_research_node( map_agent ), ) builder.add_node( "evaluate_routes", make_route_evaluation_node( distance_tool=distance_tool, around_search_tool=( around_search_tool ), max_hotels=3, max_attractions=4, ), ) builder.add_edge( START, "parse_request", ) builder.add_conditional_edges( "parse_request", route_after_parse, { "clarify": "clarify", "error": "error", "ready": "search_resources", }, ) builder.add_edge("clarify", END) builder.add_edge("error", END) builder.add_conditional_edges( "search_resources", route_after_resources_to_selection, { "select_candidates": ( "select_candidates" ), "end": END, }, ) builder.add_conditional_edges( "select_candidates", route_after_selection, { "research_destination": ( "research_destination" ), "end": END, }, ) builder.add_conditional_edges( "research_destination", route_after_map_research, { "evaluate_routes": ( "evaluate_routes" ), "end": END, }, ) builder.add_edge( "evaluate_routes", END, ) return builder.compile()