step6_langgraph_two_roles.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. """
  2. 第 6 步:用 LangGraph 做一个最小多角色旅行规划 Agent。
  3. 这一版先只做两个角色:
  4. 1. 交通规划师:负责交通
  5. 2. 住宿规划师:负责住宿
  6. 3. 总结节点:把两个角色结果合成一段旅行建议
  7. 运行:
  8. python step6_langgraph_two_roles.py
  9. """
  10. from __future__ import annotations
  11. import asyncio
  12. import os
  13. import sys
  14. from pathlib import Path
  15. from typing import TypedDict
  16. from dotenv import load_dotenv
  17. from langchain.agents import create_agent
  18. from langchain_mcp_adapters.client import MultiServerMCPClient
  19. from langchain_openai import ChatOpenAI
  20. from langgraph.graph import END, START, StateGraph
  21. class TravelState(TypedDict, total=False):
  22. """
  23. LangGraph 里的 state,可以理解成“多个角色之间传递的工作记录”。
  24. """
  25. user_request: str
  26. transport_plan: str
  27. hotel_plan: str
  28. final_answer: str
  29. def build_qwen_model() -> ChatOpenAI:
  30. load_dotenv()
  31. api_key = os.getenv("DASHSCOPE_API_KEY")
  32. if not api_key:
  33. raise RuntimeError("请先在 .env 里填写 DASHSCOPE_API_KEY。")
  34. return ChatOpenAI(
  35. api_key=api_key,
  36. base_url=os.getenv("QWEN_BASE_URL", "https://dashscope.aliyuncs.com/compatible-mode/v1"),
  37. model=os.getenv("QWEN_MODEL", "qwen-plus"),
  38. temperature=0.2,
  39. )
  40. async def build_mcp_tools():
  41. """
  42. 连接 my_mcp_demo.py 这个本地 MCP Server,并动态获取工具列表。
  43. """
  44. server_file = Path(__file__).with_name("my_mcp_demo.py").resolve()
  45. client = MultiServerMCPClient(
  46. {
  47. "travel-tools": {
  48. "transport": "stdio",
  49. "command": sys.executable,
  50. "args": [str(server_file)],
  51. }
  52. }
  53. )
  54. return await client.get_tools()
  55. async def call_role(model: ChatOpenAI, tools, system_prompt: str, user_request: str) -> str:
  56. """
  57. 创建一个角色 Agent,让它根据用户需求调用 MCP 工具。
  58. """
  59. agent = create_agent(
  60. model=model,
  61. tools=tools,
  62. system_prompt=system_prompt,
  63. )
  64. result = await agent.ainvoke(
  65. {
  66. "messages": [
  67. {
  68. "role": "user",
  69. "content": user_request,
  70. }
  71. ]
  72. }
  73. )
  74. return result["messages"][-1].content
  75. async def main() -> None:
  76. model = build_qwen_model()
  77. tools = await build_mcp_tools()
  78. print("LangGraph 可用 MCP 工具:", [tool.name for tool in tools])
  79. async def transport_node(state: TravelState) -> TravelState:
  80. """
  81. 第一个角色:交通规划师。
  82. """
  83. answer = await call_role(
  84. model=model,
  85. tools=tools,
  86. system_prompt=(
  87. "你是交通规划师。"
  88. "你只负责解决城市间交通问题。"
  89. "如果用户提到从哪里到哪里,请优先调用 search_transport 工具。"
  90. "回答要简洁,说明推荐交通方式和理由。"
  91. ),
  92. user_request=state["user_request"],
  93. )
  94. return {"transport_plan": answer}
  95. async def hotel_node(state: TravelState) -> TravelState:
  96. """
  97. 第二个角色:住宿规划师。
  98. """
  99. answer = await call_role(
  100. model=model,
  101. tools=tools,
  102. system_prompt=(
  103. "你是住宿规划师。"
  104. "你只负责解决住哪里、住几晚、预算多少的问题。"
  105. "如果用户提到住宿或几天几晚,请优先调用 search_hotels 工具。"
  106. "回答要简洁,说明住宿预算和区域建议。"
  107. ),
  108. user_request=state["user_request"],
  109. )
  110. return {"hotel_plan": answer}
  111. async def final_node(state: TravelState) -> TravelState:
  112. """
  113. 第三个节点:总规划师。
  114. 它不直接调 MCP 工具,只负责汇总前面两个角色的结果。
  115. """
  116. prompt = f"""
  117. 请把下面两个角色的结果整合成一份简洁旅行建议。
  118. 用户需求:
  119. {state["user_request"]}
  120. 交通规划师结果:
  121. {state["transport_plan"]}
  122. 住宿规划师结果:
  123. {state["hotel_plan"]}
  124. 请输出:
  125. 1. 交通建议
  126. 2. 住宿建议
  127. 3. 一句话总结
  128. """
  129. response = await model.ainvoke(prompt)
  130. return {"final_answer": response.content}
  131. graph = StateGraph(TravelState)
  132. graph.add_node("transport_planner", transport_node)
  133. graph.add_node("hotel_planner", hotel_node)
  134. graph.add_node("final_planner", final_node)
  135. graph.add_edge(START, "transport_planner")
  136. graph.add_edge("transport_planner", "hotel_planner")
  137. graph.add_edge("hotel_planner", "final_planner")
  138. graph.add_edge("final_planner", END)
  139. app = graph.compile()
  140. result = await app.ainvoke(
  141. {
  142. "user_request": (
  143. "我想 2026-08-15 从成都去重庆玩 3 天 2 晚,"
  144. "预算控制在 1500 元以内,帮我安排交通和住宿。"
  145. )
  146. }
  147. )
  148. print("\n========== LangGraph 最终结果 ==========\n")
  149. print(result["final_answer"])
  150. if __name__ == "__main__":
  151. asyncio.run(main())