{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "b3352b1f",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 安装依赖:uv pip install langchain_mcp_adapters langchain-deepseek -i https://pypi.tuna.tsinghua.edu.cn/simple\n",
    "\n",
    "from langchain_mcp_adapters.client import MultiServerMCPClient\n",
    "\n",
    "# MultiServerMCPClient 可以同时挂多个 MCP Server，给每个 Server 起个名字即可\n",
    "mcp_client = MultiServerMCPClient({\n",
    "    \"amap\": {\n",
    "      \"transport\": \"sse\",\n",
    "      \"url\": \"https://mcp.api-inference.modelscope.net/6ae8935632f641/sse\"\n",
    "    },\n",
    "    \"mcp_tools\": {\n",
    "        \"url\": \"http://localhost:8000/sse\",   # 指向上面启动的 FastMCP 服务\n",
    "        \"transport\": \"sse\",\n",
    "    },\n",
    "  })"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "5152d261",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "  + Exception Group Traceback (most recent call last):\n",
      "  |   File \"d:\\code\\shuheAI\\.venv\\Lib\\site-packages\\IPython\\core\\interactiveshell.py\", line 3746, in run_code\n",
      "  |     await eval(code_obj, self.user_global_ns, self.user_ns)\n",
      "  |   File \"C:\\Users\\29448\\AppData\\Local\\Temp\\ipykernel_56308\\1519922208.py\", line 2, in <module>\n",
      "  |     tools = await mcp_client.get_tools()\n",
      "  |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
      "  |   File \"d:\\code\\shuheAI\\.venv\\Lib\\site-packages\\langchain_mcp_adapters\\client.py\", line 213, in get_tools\n",
      "  |     tools_list = await asyncio.gather(*load_mcp_tool_tasks)\n",
      "  |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
      "  |   File \"d:\\code\\shuheAI\\.venv\\Lib\\site-packages\\langchain_mcp_adapters\\tools.py\", line 590, in load_mcp_tools\n",
      "  |     async with create_session(\n",
      "  |   File \"C:\\Users\\29448\\AppData\\Roaming\\uv\\python\\cpython-3.11.14-windows-x86_64-none\\Lib\\contextlib.py\", line 210, in __aenter__\n",
      "  |     return await anext(self.gen)\n",
      "  |            ^^^^^^^^^^^^^^^^^^^^^\n",
      "  |   File \"d:\\code\\shuheAI\\.venv\\Lib\\site-packages\\langchain_mcp_adapters\\sessions.py\", line 449, in create_session\n",
      "  |     async with _create_sse_session(**params) as session:\n",
      "  |   File \"C:\\Users\\29448\\AppData\\Roaming\\uv\\python\\cpython-3.11.14-windows-x86_64-none\\Lib\\contextlib.py\", line 210, in __aenter__\n",
      "  |     return await anext(self.gen)\n",
      "  |            ^^^^^^^^^^^^^^^^^^^^^\n",
      "  |   File \"d:\\code\\shuheAI\\.venv\\Lib\\site-packages\\langchain_mcp_adapters\\sessions.py\", line 304, in _create_sse_session\n",
      "  |     async with (\n",
      "  |   File \"C:\\Users\\29448\\AppData\\Roaming\\uv\\python\\cpython-3.11.14-windows-x86_64-none\\Lib\\contextlib.py\", line 210, in __aenter__\n",
      "  |     return await anext(self.gen)\n",
      "  |            ^^^^^^^^^^^^^^^^^^^^^\n",
      "  |   File \"d:\\code\\shuheAI\\.venv\\Lib\\site-packages\\mcp\\client\\sse.py\", line 62, in sse_client\n",
      "  |     async with anyio.create_task_group() as tg:\n",
      "  |   File \"d:\\code\\shuheAI\\.venv\\Lib\\site-packages\\anyio\\_backends\\_asyncio.py\", line 815, in __aexit__\n",
      "  |     raise BaseExceptionGroup(\n",
      "  | ExceptionGroup: unhandled errors in a TaskGroup (1 sub-exception)\n",
      "  +-+---------------- 1 ----------------\n",
      "    | Traceback (most recent call last):\n",
      "    |   File \"d:\\code\\shuheAI\\.venv\\Lib\\site-packages\\mcp\\client\\sse.py\", line 73, in sse_client\n",
      "    |     event_source.response.raise_for_status()\n",
      "    |   File \"d:\\code\\shuheAI\\.venv\\Lib\\site-packages\\httpx\\_models.py\", line 829, in raise_for_status\n",
      "    |     raise HTTPStatusError(message, request=request, response=self)\n",
      "    | httpx.HTTPStatusError: Server error '502 Bad Gateway' for url 'http://localhost:8000/sse'\n",
      "    | For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/502\n",
      "    +------------------------------------\n"
     ]
    }
   ],
   "source": [
    "#获取工具\n",
    "tools = await mcp_client.get_tools()\n",
    "print(f\"当前可用工具：{[t.name for t in tools]}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "97bb3998",
   "metadata": {},
   "outputs": [
    {
     "ename": "OpenAIError",
     "evalue": "Missing credentials. Please pass an `api_key`, `workload_identity`, `admin_api_key`, or set the `OPENAI_API_KEY` or `OPENAI_ADMIN_KEY` environment variable.",
     "output_type": "error",
     "traceback": [
      "\u001b[31m---------------------------------------------------------------------------\u001b[39m",
      "\u001b[31mOpenAIError\u001b[39m                               Traceback (most recent call last)",
      "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[3]\u001b[39m\u001b[32m, line 12\u001b[39m\n\u001b[32m      8\u001b[39m \n\u001b[32m      9\u001b[39m api_key = os.getenv(\u001b[33m'OPENAI_API_KEY'\u001b[39m)\n\u001b[32m     10\u001b[39m \n\u001b[32m     11\u001b[39m \u001b[38;5;66;03m# 使用 DeepSeek 作为底座模型（国内访问快、性价比高）\u001b[39;00m\n\u001b[32m---> \u001b[39m\u001b[32m12\u001b[39m llm = ChatDeepSeek(\n\u001b[32m     13\u001b[39m     model=\u001b[33m\"deepseek-chat\"\u001b[39m,\n\u001b[32m     14\u001b[39m     api_key=api_key,  \u001b[38;5;66;03m# 替换为你自己的 API Key\u001b[39;00m\n\u001b[32m     15\u001b[39m )\n",
      "\u001b[36mFile \u001b[39m\u001b[32md:\\code\\shuheAI\\.venv\\Lib\\site-packages\\langchain_core\\load\\serializable.py:118\u001b[39m, in \u001b[36mSerializable.__init__\u001b[39m\u001b[34m(self, *args, **kwargs)\u001b[39m\n\u001b[32m    116\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34m__init__\u001b[39m(\u001b[38;5;28mself\u001b[39m, *args: Any, **kwargs: Any) -> \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[32m    117\u001b[39m \u001b[38;5;250m    \u001b[39m\u001b[33;03m\"\"\"\"\"\"\u001b[39;00m  \u001b[38;5;66;03m# noqa: D419  # Intentional blank docstring\u001b[39;00m\n\u001b[32m--> \u001b[39m\u001b[32m118\u001b[39m     \u001b[30;43msuper\u001b[39;49m\u001b[30;43m(\u001b[39;49m\u001b[30;43m)\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43m__init__\u001b[39;49m\u001b[30;43m(\u001b[39;49m\u001b[30;43m*\u001b[39;49m\u001b[30;43margs\u001b[39;49m\u001b[30;43m,\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43m*\u001b[39;49m\u001b[30;43m*\u001b[39;49m\u001b[30;43mkwargs\u001b[39;49m\u001b[30;43m)\u001b[39;49m\n",
      "    \u001b[31m[... skipping hidden 1 frame]\u001b[39m\n",
      "\u001b[36mFile \u001b[39m\u001b[32md:\\code\\shuheAI\\.venv\\Lib\\site-packages\\langchain_deepseek\\chat_models.py:250\u001b[39m, in \u001b[36mChatDeepSeek.validate_environment\u001b[39m\u001b[34m(self)\u001b[39m\n\u001b[32m    248\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m (\u001b[38;5;28mself\u001b[39m.client \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m):\n\u001b[32m    249\u001b[39m     sync_specific: \u001b[38;5;28mdict\u001b[39m = {\u001b[33m\"\u001b[39m\u001b[33mhttp_client\u001b[39m\u001b[33m\"\u001b[39m: \u001b[38;5;28mself\u001b[39m.http_client}\n\u001b[32m--> \u001b[39m\u001b[32m250\u001b[39m     \u001b[38;5;28mself\u001b[39m.root_client = \u001b[30;43mopenai\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43mOpenAI\u001b[39;49m\u001b[30;43m(\u001b[39;49m\u001b[30;43m*\u001b[39;49m\u001b[30;43m*\u001b[39;49m\u001b[30;43mclient_params\u001b[39;49m\u001b[30;43m,\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43m*\u001b[39;49m\u001b[30;43m*\u001b[39;49m\u001b[30;43msync_specific\u001b[39;49m\u001b[30;43m)\u001b[39;49m\n\u001b[32m    251\u001b[39m     \u001b[38;5;28mself\u001b[39m.client = \u001b[38;5;28mself\u001b[39m.root_client.chat.completions\n\u001b[32m    252\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m (\u001b[38;5;28mself\u001b[39m.async_client \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m):\n",
      "\u001b[36mFile \u001b[39m\u001b[32md:\\code\\shuheAI\\.venv\\Lib\\site-packages\\openai\\_client.py:230\u001b[39m, in \u001b[36mOpenAI.__init__\u001b[39m\u001b[34m(self, api_key, admin_api_key, workload_identity, organization, project, webhook_secret, provider, base_url, websocket_base_url, timeout, max_retries, default_headers, default_query, http_client, _strict_response_validation, _enforce_credentials)\u001b[39m\n\u001b[32m    220\u001b[39m \u001b[38;5;28mself\u001b[39m.admin_api_key = admin_api_key \u001b[38;5;28;01mif\u001b[39;00m provider_runtime \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;28;01melse\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[32m    222\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m (\n\u001b[32m    223\u001b[39m     provider_runtime \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[32m    224\u001b[39m     \u001b[38;5;129;01mand\u001b[39;00m _enforce_credentials\n\u001b[32m   (...)\u001b[39m\u001b[32m    228\u001b[39m     \u001b[38;5;129;01mand\u001b[39;00m \u001b[38;5;28mself\u001b[39m.admin_api_key \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[32m    229\u001b[39m ):\n\u001b[32m--> \u001b[39m\u001b[32m230\u001b[39m     \u001b[38;5;28;01mraise\u001b[39;00m OpenAIError(\n\u001b[32m    231\u001b[39m         \u001b[33m\"\u001b[39m\u001b[33mMissing credentials. Please pass an `api_key`, `workload_identity`, `admin_api_key`, or set the `OPENAI_API_KEY` or `OPENAI_ADMIN_KEY` environment variable.\u001b[39m\u001b[33m\"\u001b[39m\n\u001b[32m    232\u001b[39m     )\n\u001b[32m    234\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m organization \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;129;01mand\u001b[39;00m provider_runtime \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[32m    235\u001b[39m     organization = os.environ.get(\u001b[33m\"\u001b[39m\u001b[33mOPENAI_ORG_ID\u001b[39m\u001b[33m\"\u001b[39m)\n",
      "\u001b[31mOpenAIError\u001b[39m: Missing credentials. Please pass an `api_key`, `workload_identity`, `admin_api_key`, or set the `OPENAI_API_KEY` or `OPENAI_ADMIN_KEY` environment variable."
     ]
    }
   ],
   "source": [
    "from langchain_deepseek import ChatDeepSeek\n",
    "from langchain.agents import create_agent\n",
    "from langchain_core.prompts import ChatPromptTemplate\n",
    "import os\n",
    "from dotenv import load_dotenv\n",
    "\n",
    "load_dotenv()\n",
    "\n",
    "api_key = os.getenv('OPENAI_API_KEY')\n",
    "\n",
    "# 使用 DeepSeek 作为底座模型（国内访问快、性价比高）\n",
    "llm = ChatDeepSeek(\n",
    "    model=\"deepseek-chat\",\n",
    "    api_key=api_key,  # 替换为你自己的 API Key\n",
    ")\n",
    "\n",
    "async def ask_agent(question: str) -> str:\n",
    "    \"\"\"\n",
    "    核心调用函数：从 MCP Server 拉取工具 → 注入 Agent → 执行对话。\n",
    "\n",
    "    每次调用都会重新拉取工具列表，保证服务端的工具变更能实时生效。\n",
    "    \"\"\"\n",
    "    # 第一步：向所有已配置的 MCP Server 拉取可用工具\n",
    "    tools = await mcp_client.get_tools()\n",
    "\n",
    "    # 第二步：用工具 + 模型 + 提示词组装 Agent\n",
    "    agent = create_agent(\n",
    "        model=llm,\n",
    "        tools=tools,\n",
    "        system_prompt=\"你是一个出行助手，可以使用地图工具帮用户查天气、规划路线。回答要简洁，不要编造数据。\",\n",
    "    )\n",
    "\n",
    "    # 第三步：执行对话，让 LLM 自行判断是否需要调用工具\n",
    "    result = await agent.ainvoke({\n",
    "        \"messages\": [{\"role\": \"user\", \"content\": question}]\n",
    "    })\n",
    "\n",
    "    # 第四步：返回最后一条消息（即 LLM 的最终回复）\n",
    "    return result[\"messages\"][-1].content"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0a3a305f",
   "metadata": {},
   "outputs": [],
   "source": [
    "import asyncio\n",
    "\n",
    "async def main():\n",
    "    # 查天气——LLM 会自动调用 amap 的天气工具\n",
    "    # weather = await ask_agent(\"成都今天天气怎么样？\")\n",
    "    # print(f\"天气查询结果：{weather}\\n\")\n",
    "\n",
    "    # # 规划路线——不同的问题，LLM 会选择不同的工具\n",
    "    # route = await ask_agent(\"帮我规划一条从春熙路到双流机场的地铁换乘路线\")\n",
    "    # print(f\"路线规划结果：{route}\")\n",
    "\n",
    "    route = await ask_agent(\"帮助我计算 bmi，我的体重是 100kg，身高 192cm\")\n",
    "    print(f\"路线规划结果：{route}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a448e38a",
   "metadata": {},
   "outputs": [],
   "source": [
    "await main()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3.11 (shuheAI)",
   "language": "python",
   "name": "shuheai"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.14"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
