| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189 |
- """
- 第 6 步:用 LangGraph 做一个最小多角色旅行规划 Agent。
- 这一版先只做两个角色:
- 1. 交通规划师:负责交通
- 2. 住宿规划师:负责住宿
- 3. 总结节点:把两个角色结果合成一段旅行建议
- 运行:
- python step6_langgraph_two_roles.py
- """
- from __future__ import annotations
- import asyncio
- import os
- import sys
- from pathlib import Path
- from typing import TypedDict
- from dotenv import load_dotenv
- from langchain.agents import create_agent
- from langchain_mcp_adapters.client import MultiServerMCPClient
- from langchain_openai import ChatOpenAI
- from langgraph.graph import END, START, StateGraph
- class TravelState(TypedDict, total=False):
- """
- LangGraph 里的 state,可以理解成“多个角色之间传递的工作记录”。
- """
- user_request: str
- transport_plan: str
- hotel_plan: str
- final_answer: str
- def build_qwen_model() -> ChatOpenAI:
- load_dotenv()
- api_key = os.getenv("DASHSCOPE_API_KEY")
- if not api_key:
- raise RuntimeError("请先在 .env 里填写 DASHSCOPE_API_KEY。")
- return ChatOpenAI(
- api_key=api_key,
- base_url=os.getenv("QWEN_BASE_URL", "https://dashscope.aliyuncs.com/compatible-mode/v1"),
- model=os.getenv("QWEN_MODEL", "qwen-plus"),
- temperature=0.2,
- )
- async def build_mcp_tools():
- """
- 连接 my_mcp_demo.py 这个本地 MCP Server,并动态获取工具列表。
- """
- server_file = Path(__file__).with_name("my_mcp_demo.py").resolve()
- client = MultiServerMCPClient(
- {
- "travel-tools": {
- "transport": "stdio",
- "command": sys.executable,
- "args": [str(server_file)],
- }
- }
- )
- return await client.get_tools()
- async def call_role(model: ChatOpenAI, tools, system_prompt: str, user_request: str) -> str:
- """
- 创建一个角色 Agent,让它根据用户需求调用 MCP 工具。
- """
- agent = create_agent(
- model=model,
- tools=tools,
- system_prompt=system_prompt,
- )
- result = await agent.ainvoke(
- {
- "messages": [
- {
- "role": "user",
- "content": user_request,
- }
- ]
- }
- )
- return result["messages"][-1].content
- async def main() -> None:
- model = build_qwen_model()
- tools = await build_mcp_tools()
- print("LangGraph 可用 MCP 工具:", [tool.name for tool in tools])
- async def transport_node(state: TravelState) -> TravelState:
- """
- 第一个角色:交通规划师。
- """
- answer = await call_role(
- model=model,
- tools=tools,
- system_prompt=(
- "你是交通规划师。"
- "你只负责解决城市间交通问题。"
- "如果用户提到从哪里到哪里,请优先调用 search_transport 工具。"
- "回答要简洁,说明推荐交通方式和理由。"
- ),
- user_request=state["user_request"],
- )
- return {"transport_plan": answer}
- async def hotel_node(state: TravelState) -> TravelState:
- """
- 第二个角色:住宿规划师。
- """
- answer = await call_role(
- model=model,
- tools=tools,
- system_prompt=(
- "你是住宿规划师。"
- "你只负责解决住哪里、住几晚、预算多少的问题。"
- "如果用户提到住宿或几天几晚,请优先调用 search_hotels 工具。"
- "回答要简洁,说明住宿预算和区域建议。"
- ),
- user_request=state["user_request"],
- )
- return {"hotel_plan": answer}
- async def final_node(state: TravelState) -> TravelState:
- """
- 第三个节点:总规划师。
- 它不直接调 MCP 工具,只负责汇总前面两个角色的结果。
- """
- prompt = f"""
- 请把下面两个角色的结果整合成一份简洁旅行建议。
- 用户需求:
- {state["user_request"]}
- 交通规划师结果:
- {state["transport_plan"]}
- 住宿规划师结果:
- {state["hotel_plan"]}
- 请输出:
- 1. 交通建议
- 2. 住宿建议
- 3. 一句话总结
- """
- response = await model.ainvoke(prompt)
- return {"final_answer": response.content}
- graph = StateGraph(TravelState)
- graph.add_node("transport_planner", transport_node)
- graph.add_node("hotel_planner", hotel_node)
- graph.add_node("final_planner", final_node)
- graph.add_edge(START, "transport_planner")
- graph.add_edge("transport_planner", "hotel_planner")
- graph.add_edge("hotel_planner", "final_planner")
- graph.add_edge("final_planner", END)
- app = graph.compile()
- result = await app.ainvoke(
- {
- "user_request": (
- "我想 2026-08-15 从成都去重庆玩 3 天 2 晚,"
- "预算控制在 1500 元以内,帮我安排交通和住宿。"
- )
- }
- )
- print("\n========== LangGraph 最终结果 ==========\n")
- print(result["final_answer"])
- if __name__ == "__main__":
- asyncio.run(main())
|