{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "36449e38",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "✅ 数据库 analytics_demo 已创建\n"
     ]
    }
   ],
   "source": [
    "# 老师，作业在最后一个cell\n",
    "\n",
    "\n",
    "# NL2SQL 查询 Agent\n",
    "\n",
    "import mysql.connector\n",
    "\n",
    "conn = mysql.connector.connect(\n",
    "    host=\"127.0.0.1\",\n",
    "    port=3306,\n",
    "    user=\"root\",\n",
    "    password=\"123456\",\n",
    ")\n",
    "cursor = conn.cursor()\n",
    "cursor.execute(\"CREATE DATABASE IF NOT EXISTS analytics_demo\")\n",
    "cursor.close()\n",
    "conn.close()\n",
    "print(\"✅ 数据库 analytics_demo 已创建\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "e9d981f5",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "✅ 数据库初始化完成\n",
      "   - employees 表：5 条员工记录\n",
      "   - products  表：4 条产品记录\n",
      "   - orders    表：5 条订单记录\n"
     ]
    }
   ],
   "source": [
    "import os\n",
    "import mysql.connector\n",
    "\n",
    "conn = mysql.connector.connect(\n",
    "    host=\"127.0.0.1\",\n",
    "    port=3306,\n",
    "    user=\"root\",\n",
    "    password=\"123456\",\n",
    "    database=\"analytics_demo\",\n",
    ")\n",
    "\n",
    "cursor = conn.cursor()\n",
    "\n",
    "cursor.execute(\"\"\"\n",
    "CREATE TABLE IF NOT EXISTS employees (\n",
    "    id          INT PRIMARY KEY AUTO_INCREMENT,  -- 自增主键\n",
    "    name        VARCHAR(50)  NOT NULL,           -- 姓名\n",
    "    department  VARCHAR(50)  NOT NULL,           -- 所属部门\n",
    "    salary      DECIMAL(10,2) NOT NULL,          -- 月薪（精确到分）\n",
    "    hire_date   DATE                             -- 入职日期\n",
    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;\n",
    "\"\"\")\n",
    "\n",
    "cursor.execute(\"\"\"\n",
    "CREATE TABLE IF NOT EXISTS products (\n",
    "    id           INT PRIMARY KEY AUTO_INCREMENT,  -- 自增主键\n",
    "    product_name VARCHAR(100) NOT NULL,           -- 商品名称\n",
    "    category     VARCHAR(50)  NOT NULL,           -- 商品分类\n",
    "    price        DECIMAL(10,2) NOT NULL,          -- 单价\n",
    "    stock        INT DEFAULT 0                    -- 当前库存量\n",
    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;\n",
    "\"\"\")\n",
    "\n",
    "cursor.execute(\"\"\"\n",
    "CREATE TABLE IF NOT EXISTS orders (\n",
    "    id           INT PRIMARY KEY AUTO_INCREMENT,  -- 自增主键\n",
    "    employee_id  INT NOT NULL,                    -- 下单员工（外键）\n",
    "    product_id   INT NOT NULL,                    -- 购买商品（外键）\n",
    "    quantity     INT NOT NULL,                    -- 购买数量\n",
    "    order_date   DATE NOT NULL,                   -- 下单日期\n",
    "    FOREIGN KEY (employee_id) REFERENCES employees(id),   -- 关联员工表\n",
    "    FOREIGN KEY (product_id)  REFERENCES products(id)     -- 关联产品表\n",
    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;\n",
    "\"\"\")\n",
    "\n",
    "employees_data = [\n",
    "    (1, \"张三\", \"技术部\",   20000.00, \"2023-01-15\"),\n",
    "    (2, \"李四\", \"销售部\",   11000.00, \"2023-02-20\"),\n",
    "    (3, \"王五\", \"技术部\",   16000.00, \"2022-11-10\"),\n",
    "    (4, \"赵六\", \"人力资源\", 5000.00, \"2023-03-01\"),\n",
    "    (5, \"钱七\", \"销售部\",   17000.00, \"2022-12-05\"),\n",
    "]\n",
    "\n",
    "products_data = [\n",
    "    (1, \"笔记本电脑\", \"电子产品\", 6999.00, 500),\n",
    "    (2, \"机械键盘\",   \"电子产品\", 399.00,  1000),\n",
    "    (3, \"办公椅\",     \"办公用品\", 499.00,  300),\n",
    "    (4, \"显示器\",     \"电子产品\", 1200.00, 400),\n",
    "]\n",
    "\n",
    "orders_data = [\n",
    "    (1, 1, 1, 2, \"2024-01-15\"),  # 张三买了 2 台笔记本\n",
    "    (2, 2, 2, 15, \"2024-01-16\"),  # 李四买了 15 个键盘\n",
    "    (3, 3, 1, 10, \"2024-01-17\"),  # 王五买了 10 台笔记本\n",
    "    (4, 5, 3, 6, \"2024-01-18\"),  # 钱七买了 6 把办公椅\n",
    "    (5, 2, 4, 5, \"2024-01-19\"),  # 李四买了 5 台显示器\n",
    "]\n",
    "\n",
    "cursor.executemany(\"INSERT IGNORE INTO employees VALUES (%s,%s,%s,%s,%s)\", employees_data)\n",
    "cursor.executemany(\"INSERT IGNORE INTO products  VALUES (%s,%s,%s,%s,%s)\", products_data)\n",
    "cursor.executemany(\"INSERT IGNORE INTO orders     VALUES (%s,%s,%s,%s,%s)\", orders_data)\n",
    "\n",
    "conn.commit()\n",
    "conn.close()\n",
    "\n",
    "print(\"✅ 数据库初始化完成\")\n",
    "print(\"   - employees 表：5 条员工记录\")\n",
    "print(\"   - products  表：4 条产品记录\")\n",
    "print(\"   - orders    表：5 条订单记录\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "0094720d",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "数据库连接成功\n",
      "   可用表：['employees', 'orders', 'products']\n",
      "\n",
      "SQL 工具包已加载（4 个工具）：\n",
      "   - sql_db_query\n",
      "   - sql_db_schema\n",
      "   - sql_db_list_tables\n",
      "   - sql_db_query_checker\n",
      "\n",
      "NL2SQL Agent 创建完成，可以开始提问了！\n"
     ]
    }
   ],
   "source": [
    "from langchain_community.utilities import SQLDatabase\n",
    "from langchain_community.agent_toolkits import SQLDatabaseToolkit\n",
    "from langchain_openai import ChatOpenAI\n",
    "from langchain.agents import create_agent\n",
    "from dotenv import load_dotenv\n",
    "\n",
    "load_dotenv()\n",
    "api_key= os.getenv('DS_API_KEY')\n",
    "base_url= os.getenv('DS_BASE_URL')\n",
    "\n",
    "mysql_url = \"mysql+pymysql://root:123456@localhost:3306/analytics_demo\"\n",
    "db = SQLDatabase.from_uri(mysql_url) #连接数据库\n",
    "print(f\"数据库连接成功\")\n",
    "print(f\"   可用表：{db.get_usable_table_names()}\")\n",
    "\n",
    "# 初始化大模型\n",
    "llm = ChatOpenAI(\n",
    "    model_name=\"deepseek-v4-pro\",              # DeepSeek 的对话模型\n",
    "    api_key=api_key,                    # 在 platform.deepseek.com 获取\n",
    "    base_url=base_url,                  # DeepSeek API 地址\n",
    "    temperature=0\n",
    ")\n",
    "\n",
    "toolkit = SQLDatabaseToolkit(db=db, llm=llm)\n",
    "tools = toolkit.get_tools()\n",
    "print(f\"\\nSQL 工具包已加载（{len(tools)} 个工具）：\")\n",
    "for t in tools:\n",
    "    print(f\"   - {t.name}\")\n",
    "\n",
    "SQL_AGENT_PROMPT = \"\"\"你是一名专业的 SQL 数据分析师。\n",
    "\n",
    "## 工作流程\n",
    "1. 先用 sql_db_list_tables 查看数据库中有哪些表\n",
    "2. 用 sql_db_schema 获取相关表的字段结构和类型\n",
    "3. 生成 SQL 之前，用 sql_db_query_checker 检查语法\n",
    "4. 确认无误后，用 sql_db_query 执行查询\n",
    "5. 用中文总结查询结果，给出简洁的业务洞察\n",
    "\n",
    "## 约束\n",
    "- 只使用数据库中实际存在的表和字段，不要凭空编造\n",
    "- 单次查询结果限制在 50 条以内\n",
    "- 如果查询出错，分析错误原因后重新生成 SQL\n",
    "- 回答要简洁专业，不要啰嗦\n",
    "\"\"\"\n",
    "\n",
    "sql_agent = create_agent(\n",
    "    model=llm,\n",
    "    tools=tools,\n",
    "    system_prompt=SQL_AGENT_PROMPT\n",
    ")\n",
    "print(\"\\nNL2SQL Agent 创建完成，可以开始提问了！\")\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "32a3e658",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "回答：查询结果如下：\n",
      "\n",
      "---\n",
      "\n",
      "## 📊 公司员工统计\n",
      "\n",
      "| 部门 | 人数 |\n",
      "|------|------|\n",
      "| 技术部 | 2 人 |\n",
      "| 销售部 | 2 人 |\n",
      "| 人力资源 | 1 人 |\n",
      "\n",
      "> **公司共计 5 名员工**，分布在 3 个部门。其中技术部和销售部各占 2 人，人力资源部有 1 人。整体来看团队规模较小，结构相对精简。\n",
      "回答：## 查询结果\n",
      "\n",
      "技术部共有 **2 名员工**，按薪资从高到低排列：\n",
      "\n",
      "| 排名 | 姓名 | 月薪（元） |\n",
      "|------|------|------------|\n",
      "| 1    | 张三 | 20,000.00  |\n",
      "| 2    | 王五 | 16,000.00  |\n",
      "\n",
      "**业务洞察**：技术部目前仅两人，张三薪资领先王五约 25%（高出 4,000 元），整体薪资水平较高，符合技术岗位的市场定位。\n",
      "回答：## 查询结果\n",
      "\n",
      "| 指标 | 数值 |\n",
      "|------|------|\n",
      "| **订单数量** | 3 单 |\n",
      "| **订单总金额** | ¥14,979.00 |\n",
      "\n",
      "---\n",
      "\n",
      "### 📊 业务洞察\n",
      "\n",
      "销售部员工共产生 **3 笔订单**，累计金额 **¥14,979.00**。目前数据库中仅有一位销售部员工（李四），平均每单约 ¥4,993，客单价处于中等水平。如果销售团队规模扩大，建议关注人均订单量和客单价的增长趋势。\n"
     ]
    }
   ],
   "source": [
    "# 执行\n",
    "response = sql_agent.invoke({\n",
    "    \"messages\": [{\"role\": \"user\", \"content\": \"公司一共有多少名员工？分别在哪个部门？\"}]\n",
    "})\n",
    "final_msg = response[\"messages\"][-1]\n",
    "print(f\"回答：{final_msg.content}\")\n",
    "\n",
    "response = sql_agent.invoke({\n",
    "    \"messages\": [{\"role\": \"user\", \"content\": \"列出技术部所有员工的姓名和薪资，按薪资从高到低排序\"}]\n",
    "})\n",
    "final_msg = response[\"messages\"][-1]\n",
    "print(f\"回答：{final_msg.content}\")\n",
    "\n",
    "response = sql_agent.invoke({\n",
    "    \"messages\": [{\"role\": \"user\", \"content\": \"销售部的员工总共下了多少订单？订单总金额是多少？\"}]\n",
    "})\n",
    "final_msg = response[\"messages\"][-1]\n",
    "print(f\"回答：{final_msg.content}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "id": "c7370f7c",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 数据可视化 Agent\n",
    "\n",
    "import pandas as pd\n",
    "import matplotlib.pyplot as plt\n",
    "import seaborn as sns\n",
    "import numpy as np\n",
    "\n",
    "plt.rcParams[\"font.sans-serif\"] = [\"SimHei\", \"PingFang SC\", \"DejaVu Sans\"]\n",
    "plt.rcParams[\"axes.unicode_minus\"] = False  # 解决负号显示为方块的问题\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "id": "264b4f91",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "✅ 数据加载完成\n",
      "   员工表：5 行 × 5 列\n",
      "   产品表：4 行 × 5 列\n",
      "   订单表：5 行 × 5 列\n",
      "\n",
      "📋 员工表示例：\n",
      " id name department  salary  hire_date\n",
      "  1   张三        技术部 20000.0 2023-01-15\n",
      "  2   李四        销售部 11000.0 2023-02-20\n",
      "  3   王五        技术部 16000.0 2022-11-10\n"
     ]
    }
   ],
   "source": [
    "from sqlalchemy import create_engine\n",
    "\n",
    "engine = create_engine(mysql_url)\n",
    "\n",
    "employees_df = pd.read_sql(\"SELECT * FROM employees\", engine)\n",
    "products_df  = pd.read_sql(\"SELECT * FROM products\", engine)\n",
    "orders_df    = pd.read_sql(\"SELECT * FROM orders\", engine)\n",
    "\n",
    "print(\"✅ 数据加载完成\")\n",
    "print(f\"   员工表：{len(employees_df)} 行 × {len(employees_df.columns)} 列\")\n",
    "print(f\"   产品表：{len(products_df)} 行 × {len(products_df.columns)} 列\")\n",
    "print(f\"   订单表：{len(orders_df)} 行 × {len(orders_df.columns)} 列\")\n",
    "print(f\"\\n📋 员工表示例：\")\n",
    "print(employees_df.head(3).to_string(index=False))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "id": "db0930e1",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "✅ Python 代码执行沙箱创建成功\n"
     ]
    }
   ],
   "source": [
    "# 构建代码执行沙箱\n",
    "\n",
    "import traceback\n",
    "from io import StringIO\n",
    "from contextlib import redirect_stdout\n",
    "from langchain.tools import tool\n",
    "\n",
    "SANDBOX_GLOBALS = {\n",
    "    # 数据：Agent 可以分析这三张表\n",
    "    \"employees_df\": employees_df,\n",
    "    \"products_df\":  products_df,\n",
    "    \"orders_df\":    orders_df,\n",
    "    # 工具库：Agent 可以用这些库做分析和画图\n",
    "    \"pd\":  pd,       # pandas — 数据处理\n",
    "    \"plt\": plt,      # matplotlib — 基础绑图\n",
    "    \"sns\": sns,      # seaborn — 统计可视化\n",
    "    \"np\":  np,       # numpy — 数值计算\n",
    "}\n",
    "\n",
    "@tool\n",
    "def execute_python_code(code:str) -> str:\n",
    "    \"\"\"\n",
    "    执行 Python 代码进行数据分析和可视化。\n",
    "\n",
    "    可用变量：\n",
    "      - employees_df: 员工表 DataFrame（字段：id, name, department, salary, hire_date）\n",
    "      - products_df:  产品表 DataFrame（字段：id, product_name, category, price, stock）\n",
    "      - orders_df:    订单表 DataFrame（字段：id, employee_id, product_id, quantity, order_date）\n",
    "      - pd:   pandas 库\n",
    "      - plt:  matplotlib.pyplot\n",
    "      - sns:  seaborn\n",
    "      - np:   numpy\n",
    "\n",
    "    使用示例：\n",
    "      # 统计各部门平均薪资\n",
    "      result = employees_df.groupby('department')['salary'].mean()\n",
    "      print(result)\n",
    "\n",
    "      # 画柱状图\n",
    "      plt.figure(figsize=(10, 6))\n",
    "      employees_df.groupby('department')['salary'].mean().plot(kind='bar')\n",
    "      plt.title('各部门平均薪资')\n",
    "      plt.show()\n",
    "    \"\"\"\n",
    "    exec_globals = dict(SANDBOX_GLOBALS)\n",
    "    exec_locals = {}\n",
    "    output_buffer = StringIO()\n",
    "\n",
    "    try:\n",
    "        with redirect_stdout(output_buffer):\n",
    "            exec(code, exec_globals, exec_locals)\n",
    "\n",
    "        result = output_buffer.getvalue()\n",
    "\n",
    "        # 如果代码没有 print 任何东西，给个默认提示\n",
    "        if not result.strip():\n",
    "            result = \"✅ 代码执行成功（无文本输出，可能已生成图表）\"\n",
    "\n",
    "        return f\"执行成功：\\n{result}\"\n",
    "\n",
    "    except Exception as e:\n",
    "        error_detail = traceback.format_exc()\n",
    "        return f\"❌ 执行出错：{e}\\n\\n{error_detail}\"\n",
    "    \n",
    "print(\"✅ Python 代码执行沙箱创建成功\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "id": "82985918",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "数据可视化 Agent 创建完成\n"
     ]
    }
   ],
   "source": [
    "# 创建可视化agent\n",
    "\n",
    "from langchain.agents import create_agent\n",
    "VISUALIZATION_PROMPT = \"\"\"你是一名资深数据分析师，精通 Python、Pandas 和 Matplotlib 数据可视化。\n",
    "\n",
    "## 可用数据\n",
    "1. employees_df — 员工表（字段：id, name, department, salary, hire_date）\n",
    "2. products_df  — 产品表（字段：id, product_name, category, price, stock）\n",
    "3. orders_df    — 订单表（字段：id, employee_id, product_id, quantity, order_date）\n",
    "\n",
    "## 工作流程\n",
    "1. 理解用户的分析需求\n",
    "2. 用 execute_python_code 工具编写并执行 Python 代码\n",
    "3. 先做数据探索（head、describe、info），再做深入分析\n",
    "4. 用中文解释分析结果，给出业务洞察\n",
    "\n",
    "## 代码规范\n",
    "- 绑图前设置中文字体：plt.rcParams['font.sans-serif'] = ['SimHei', 'PingFang SC', 'DejaVu Sans']\n",
    "- 设置 plt.rcParams['axes.unicode_minus'] = False\n",
    "- 图表尺寸统一用 plt.figure(figsize=(10, 6))\n",
    "- 必须添加标题、坐标轴标签，让图表自解释\n",
    "- 用 print() 输出关键统计量，不要只画图不说话\n",
    "- 图表标题用英文（避免渲染问题），但用中文向用户解释结果\n",
    "\n",
    "## 注意事项\n",
    "- 每次只执行一段完整的代码，不要拆成多段\n",
    "- 先探索数据结构，再做分析——不要上来就画图\n",
    "- 结果要有业务洞察，不只是\"最大值是 XXX\"\n",
    "\"\"\"\n",
    "\n",
    "viz_tools = [execute_python_code]\n",
    "visualization_agent = create_agent(\n",
    "    model=llm,\n",
    "    tools=viz_tools,\n",
    "    system_prompt=VISUALIZATION_PROMPT,\n",
    ")\n",
    "print(\"数据可视化 Agent 创建完成\")\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "id": "70299507",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "findfont: Failed to find font weight bold, now using 400.\n",
      "findfont: Failed to find font weight bold, now using 400.\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 116608 characters]",
      "text/plain": [
       "<Figure size 1600x1200 with 4 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "分析结果：\n",
      "---\n",
      "\n",
      "## 📊 员工薪资分析报告\n",
      "\n",
      "### 一、数据概览\n",
      "\n",
      "当前数据集共 **5 名员工**，分布在 3 个部门（技术部、销售部、人力资源）。\n",
      "\n",
      "---\n",
      "\n",
      "### 二、核心统计量\n",
      "\n",
      "| 指标 | 数值 |\n",
      "|------|------|\n",
      "| **均值** | ¥13,800 |\n",
      "| **中位数** | ¥16,000 |\n",
      "| **最小值** | ¥5,000（赵六，人力资源） |\n",
      "| **最大值** | ¥20,000（张三，技术部） |\n",
      "| **标准差** | ¥5,891 |\n",
      "| **Q1 / Q3** | ¥11,000 / ¥17,000 |\n",
      "| **偏度** | -0.86 |\n",
      "\n",
      "---\n",
      "\n",
      "### 三、关键洞察\n",
      "\n",
      "#### 1️⃣ 薪资分布呈「左偏」形态\n",
      "\n",
      "中位数（¥16,000）**高于**均值（¥13,800），偏度为 **-0.86**。这说明少数低薪员工（赵六 ¥5,000）大幅拉低了整体均值，而大多数员工集中在较高薪资区间。从业务角度看，**人力资源部门的赵六薪资明显偏低**，是拉低整体均值的关键因素。\n",
      "\n",
      "#### 2️⃣ 部门间薪资差距悬殊\n",
      "\n",
      "| 部门 | 人数 | 均值 | 中位数 |\n",
      "|------|------|------|--------|\n",
      "| 技术部 | 2 | ¥18,000 | ¥18,000 |\n",
      "| 销售部 | 2 | ¥14,000 | ¥14,000 |\n",
      "| 人力资源 | 1 | ¥5,000 | ¥5,000 |\n",
      "\n",
      "- **技术部**薪资最高，平均 ¥18,000，且两人薪资接近（¥16K / ¥20K），内部公平性较好。\n",
      "- **销售部**居中，平均 ¥14,000，两人差距 ¥6,000，可能存在绩效差异。\n",
      "- **人力资源部**仅 1 人，薪资 ¥5,000，与技术部最高差 **3 倍**，部门间差距较大。\n",
      "\n",
      "#### 3️⃣ 离散程度分析\n",
      "\n",
      "标准差 ¥5,891，变异系数（CV）= 42.7%。这个值偏高，说明员工之间薪资**分化较明显**——并非均匀分布，而是存在明显的层级差距。\n",
      "\n",
      "#### 4️⃣ 没有极端异常值\n",
      "\n",
      "IQR = ¥6,000，箱线图显示无离群点，所有数据都在合理范围内。最高 ¥20,000 和最低 ¥5,000 虽然差距大，但在统计上不算异常。\n",
      "\n",
      "---\n",
      "\n",
      "### 四、建议\n",
      "\n",
      "> - **关注人力资源岗位**：¥5,000 的薪资是否与市场水平匹配？如果该岗位职责不小，可能需要调薪以提升留存率。\n",
      "> - **建立薪资带宽**：当前 5 人薪资离散度较大（CV 42.7%），建议按岗位级别建立薪资带宽，避免新老员工倒挂。\n",
      "> - **样本量有限**：目前仅 5 条数据，统计结论的参考意义有限，建议后续扩充数据后再做更稳健的分析。\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "findfont: Failed to find font weight bold, now using 400.\n",
      "findfont: Failed to find font weight bold, now using 400.\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 11228 characters]",
      "text/plain": [
       "<Figure size 1200x700 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "[encoded data, 11228 characters]",
      "text/plain": [
       "<Figure size 1200x700 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "[encoded data, 41948 characters]",
      "text/plain": [
       "<Figure size 1000x600 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "分析结果：\n",
      "---\n",
      "\n",
      "## 📊 分析结果总结\n",
      "\n",
      "### 数据概览\n",
      "\n",
      "公司共有 **5 名员工**，分布在 **3 个部门**。数据量虽小，但部门间薪资差异已经非常明显。\n",
      "\n",
      "### 各部门平均薪资（降序）\n",
      "\n",
      "| 部门 | 平均薪资 | 人数 |\n",
      "|------|---------|------|\n",
      "| 🔵 技术部 | ¥18,000 | 2人 |\n",
      "| 🟢 销售部 | ¥14,000 | 2人 |\n",
      "| 🔴 人力资源 | ¥5,000 | 1人 |\n",
      "\n",
      "- **公司整体均值**：¥13,800（图中红色虚线）\n",
      "- **极差**：¥13,000（最高与最低相差近 3.6 倍）\n",
      "\n",
      "### 💡 业务洞察\n",
      "\n",
      "1. **技术部薪资领跑**：以 ¥18,000 位居榜首，高出公司均值 **30%**，符合技术岗位市场溢价规律。\n",
      "2. **人力资源部明显偏低**：仅 ¥5,000，不足公司均值的一半，且仅 1 人，可能存在岗位层级偏低或数据样本不足的问题，建议进一步核实该岗位的职级定位。\n",
      "3. **销售部处于中等水平**：¥14,000 与公司均值基本持平，薪酬结构合理。\n",
      "4. **样本量提醒**：当前仅 5 条数据，统计结论仅供参考。随着团队扩张，部门间的对比会更具有参考价值。\n",
      "\n",
      "> 柱状图中红色虚线代表公司整体平均薪资线，高于该线的部门属于「高薪部门」，低于该线的则有薪酬优化空间。\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "findfont: Failed to find font weight bold, now using 400.\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 25768 characters]",
      "text/plain": [
       "<Figure size 1000x600 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "分析结果：\n",
      "## 📊 分析结果\n",
      "\n",
      "### 数据概览\n",
      "\n",
      "产品表共 **4 条记录**，涵盖 **2 个产品类别**：\n",
      "\n",
      "| 类别 | 产品数 | 库存总量 |\n",
      "|------|--------|----------|\n",
      "| 电子产品 | 3（笔记本电脑、机械键盘、显示器） | **1,900** 件 |\n",
      "| 办公用品 | 1（办公椅） | **300** 件 |\n",
      "\n",
      "---\n",
      "\n",
      "### 🔍 业务洞察\n",
      "\n",
      "1. **库存严重向电子产品倾斜**：电子产品库存总量（1,900 件）是办公用品（300 件）的 **6.3 倍**。这一方面是因为电子产品有 3 款 SKU，办公用品仅有 1 款，但也反映出公司在品类布局上高度聚焦 3C 数码。\n",
      "\n",
      "2. **办公用品种类过于单薄**：仅有 1 款产品（办公椅），且库存只有 300 件。如果这是主营品类之一，建议扩充 SKU 丰富度，否则该品类对整体业绩贡献将非常有限。\n",
      "\n",
      "3. **单品库存健康度需关注**：机械键盘库存 1,000 件，远超其他单品。需结合订单流转速度判断是否存在滞销风险，避免资金被库存占用。\n",
      "\n",
      "4. **数据量偏小**：当前产品表仅有 4 条记录，以上分析仅供参考。若这是真实业务场景的完整数据，建议尽快扩充产品线；若只是示例数据，分析框架已就绪，接入完整数据后可直接复用。\n"
     ]
    }
   ],
   "source": [
    "import warnings\n",
    "warnings.filterwarnings(\"ignore\")  # 抑制 matplotlib 的字体警告\n",
    "\n",
    "response = visualization_agent.invoke({\n",
    "    \"messages\": [{\"role\": \"user\", \"content\": \"分析一下员工薪资的分布情况，包括均值、中位数、最大最小值等统计量\"}]\n",
    "})\n",
    "final_msg = response[\"messages\"][-1]\n",
    "print(f\"\\n分析结果：\\n{final_msg.content}\")\n",
    "\n",
    "response = visualization_agent.invoke({\n",
    "    \"messages\": [{\"role\": \"user\", \"content\": \"画一个柱状图，展示各部门的平均薪资对比\"}]\n",
    "})\n",
    "final_msg = response[\"messages\"][-1]\n",
    "print(f\"\\n分析结果：\\n{final_msg.content}\")\n",
    "\n",
    "response = visualization_agent.invoke({\n",
    "    \"messages\": [{\"role\": \"user\", \"content\": \"计算每个产品类别的库存总量，用水平条形图展示，并标注具体数值\"}]\n",
    "})\n",
    "final_msg = response[\"messages\"][-1]\n",
    "print(f\"\\n分析结果：\\n{final_msg.content}\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "id": "94d45a94",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "findfont: Failed to find font weight bold, now using 400.\n",
      "findfont: Failed to find font weight bold, now using 400.\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 54008 characters]",
      "text/plain": [
       "<Figure size 1200x600 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "分析结果：\n",
      "从分析结果来看，公司各部门薪资情况如下：\n",
      "\n",
      "---\n",
      "\n",
      "### 📊 各部门平均薪资\n",
      "\n",
      "| 排名 | 部门 | 平均薪资 |\n",
      "|:---:|------|--------:|\n",
      "| 🥇 | **技术部** | ¥18,000 |\n",
      "| 🥈 | **销售部** | ¥14,000 |\n",
      "| 🥉 | **人力资源** | ¥5,000 |\n",
      "\n",
      "- 公司整体平均薪资为 **¥13,800**\n",
      "\n",
      "---\n",
      "\n",
      "### 💡 业务洞察\n",
      "\n",
      "1. **技术部遥遥领先**：平均薪资 ¥18,000，是人力资源部的 **3.6 倍**，比公司整体水平高出 **30%+**，反映了公司对技术人才的重视和市场竞争程度。\n",
      "\n",
      "2. **人力资源部薪资偏低**：仅 ¥5,000，远低于整体平均线（差距 ¥8,800），可能需要评估是否存在人才流失风险。\n",
      "\n",
      "3. **销售部居中**：¥14,000 接近整体均值，属于合理区间。如果销售团队业绩贡献大，可考虑适当提高激励。\n",
      "\n",
      "柱状图中红色虚线为整体平均薪资 ¥13,800，可以直观看到仅技术部高于均值，销售部和人力资源部均低于均值，薪资结构存在一定两极分化。\n"
     ]
    }
   ],
   "source": [
    "# 把 SQL 工具和 Python 代码执行工具都挂到同一个 Agent 上，让 LLM 自己决定调用哪些工具\n",
    "\n",
    "all_tools = tools + [execute_python_code]\n",
    "\n",
    "UNIFIED_PROMPT = \"\"\"你是一名全能数据分析师，既精通 SQL 查询，也精通 Python 数据分析与可视化。\n",
    "\n",
    "  ## 你的工具\n",
    "  - sql_db_list_tables / sql_db_schema：侦察数据库结构\n",
    "  - sql_db_query_checker：检查 SQL 语法\n",
    "  - sql_db_query：执行 SQL 查询，返回结构化数据\n",
    "  - execute_python_code：执行 Python 代码，可访问 employees_df、products_df、orders_df 三张表，以及 pd、plt、sns、np\n",
    "\n",
    "  ## 决策规则\n",
    "  1. 需要从数据库查原始数据 → 用 SQL 工具\n",
    "  2. 需要统计分析、画图表 → 用 execute_python_code\n",
    "  3. 需要先查数据再画图 → 先用 SQL 工具，再用 execute_python_code\n",
    "  4. 数据已经在 DataFrame 里（如统计各部门人数）→ 直接用 execute_python_code，不必重复查 SQL\n",
    "\n",
    "  ## 注意事项\n",
    "  - 查询结果限制在 50 条以内\n",
    "  - 画图前设置中文字体\n",
    "  - 用中文给出业务洞察\n",
    "  \"\"\"\n",
    "\n",
    "unified_agent = create_agent(\n",
    "    model=llm,\n",
    "    tools=all_tools,\n",
    "    system_prompt=UNIFIED_PROMPT\n",
    ")\n",
    "\n",
    "response = unified_agent.invoke({\n",
    "    \"messages\": [{\"role\": \"user\", \"content\": \"查询每个部门的平均薪资，然后画一个柱状图展示\"}]\n",
    "})\n",
    "final_msg = response[\"messages\"][-1]\n",
    "print(f\"\\n分析结果：\\n{final_msg.content}\")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "python_stydy (3.11.9)",
   "language": "python",
   "name": "python3"
  },
  "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.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
