Metadata-Version: 2.5
Name: haoke-mcp-server
Version: 0.1.0
Summary: 好课在线 后端 OpenAPI 的 MCP Server 示例实现
Requires-Python: >=3.11
Requires-Dist: httpx>=0.27.0
Requires-Dist: markdown>=3.5.0
Requires-Dist: mcp<2,>=1.27.0
Requires-Dist: pycryptodome>=3.20.0
Requires-Dist: pyyaml>=6.0.0
Provides-Extra: dev
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
Requires-Dist: pytest>=8.0.0; extra == 'dev'
Provides-Extra: screenshot
Requires-Dist: playwright>=1.40.0; extra == 'screenshot'
Description-Content-Type: text/markdown

## haoke-mcp-server（Python MCP Server 示例）

本项目提供一个最小可运行的 Python MCP Server，把“好课在线”的选定后端接口封装为 MCP Tool。

当前白名单示例（见 `tool_whitelist.json`）覆盖教学与资源模块若干接口；生成器会遍历 `api/` 目录下全部 `*.json` 接口文档。

### 运行前准备（配置来源）

**stdio 模式**（默认；或显式 `HAOKE_MCP_TRANSPORT=stdio`）：使用进程环境变量

- `SERVER_API_URL`: 后端基地址，例如 `http://172.31.16.31:8089`
- `LOGIN_TOKEN`: 裸 token 值（不包含 `Bearer ` 前缀）
- `DOCUMENT_CONVERT_TOKEN`（可选，仅 `convert_office_to_pdf` 需要）：文档转 PDF 服务鉴权 token
- `DOCUMENT_CONVERT_BASE_URL`（可选）：文档转 PDF 服务基址，默认 `https://bots.wisedu.com`
- `LOG_LEVEL`（可选）: 日志级别，默认 `INFO`；支持 `DEBUG`/`INFO`/`WARNING`/`ERROR`/`CRITICAL`
- `LOG_DIR`（可选）: 日志文件目录；设置后同时写入文件（目录不存在时自动创建）
- `LOG_FILE`（可选）: 日志文件名，默认 `haoke-mcp-server.log`（仅 `LOG_DIR` 设置时生效）

**streamable-http 模式**（默认）：优先从每个 HTTP 请求头读取

| 请求头 | 等价配置 | 说明 |
|--------|----------|------|
| `Origin` | `SERVER_API_URL` | 后端基地址，如 `http://brains.v2.zhituxueyuan.cn` |
| `Authorization` | `LOGIN_TOKEN` | 支持 `Bearer <token>`，内部按裸 token 处理 |

请求头缺失时回退到上述环境变量；调用后端时统一注入 `Authorization: Bearer <token>`。

### 任务预览地址（`previewUrl`）

12 个创建类 Tool，以及 `update_task_file_detail`（更新视频/文档任务）、`update_scene_task_detail`（更新场景模拟任务）、`update_richtext_task_detail`（更新 AI 互动图文任务）、`update_question_task_detail`（更新 AI 辩论/讨论任务）、`update_writing_task_detail`（更新作文写作任务）、`update_pbl_task_detail`（更新 PBL 任务）、`update_individual_exercise_task_detail`（更新个性化练习任务）、`update_experiment_task_detail`（更新线下实验任务）、`update_exercise_task_detail`（更新常规练习任务）、`update_evaluate_exercise_task_detail`（更新自评/互评练习任务）、`update_code_task_detail`（更新代码训练任务）在成功返回时，会在 `data` 中附加 `previewUrl`、`taskName`、`taskType`，供前端或 Agent 直接打开学生端任务预览页并识别任务。

- **基址来源**：与后端 API 相同——`streamable-http` 模式优先取请求头 `Origin`，否则使用环境变量 `SERVER_API_URL`；`stdio` 模式使用 `SERVER_API_URL`。
- **路径格式**：`{基址}/student/course/0/studyTask/{taskId}/{页面路径}?taskId={taskId}&instanceId={instanceId}`（文档任务为 `.../doc?code&taskId=...&instanceId=...`）。
- **taskName**：创建入参中的任务名称（如 `taskName`、`title`、`name`）。
- **taskType**：好课标准任务类型码（如视频 10、文档 20、辩论 30、场景 40、常规练习 50、自互评 51、个性化 60、实验 70、写作 80、代码 90、图文 130、PBL 140）。
- **无法生成 previewUrl 时**：缺少 `taskId` 或未配置基址时，不附加 `previewUrl`；`taskName`/`taskType` 仍会写入（若调用方传入）。
- **按需生成**：实施专家 Profile 提供 Tool `generate_preview_link`，可按任务/公告/资源/试卷/章节/题库/话题类型生成预览地址（见下文）。

### 安装依赖
在仓库根目录执行：

```bash
python -m pip install -e .
```

### 启动（按专家 Profile + 默认 stdio，BREAKING）

**BREAKING：**

1. **不再提供单体全量 MCP Server**（无 `all` / `legacy`），必须指定五个 profile 之一。
2. **默认传输为 `stdio`**（未设置 `HAOKE_MCP_TRANSPORT` 时）。若仍要用 HTTP，须显式设置 `HAOKE_MCP_TRANSPORT=streamable-http`。

| Profile | 角色 | Console script | HTTP 可选端口 |
|---------|------|----------------|---------------|
| `design` | 智慧课程设计专家 | `haoke-mcp-design` | 9001 |
| `develop` | 智慧课程开发专家 | `haoke-mcp-develop` | 9002 |
| `implement` | 智慧课程实施专家 | `haoke-mcp-implement` | 9003 |
| `evaluate` | 智慧课程评价专家 | `haoke-mcp-evaluate` | 9004 |
| `common` | 跨专家基础能力 | `haoke-mcp-common` | 9005 |

归属名单：`src/haoke_mcp_server/profiles/`（`index.yaml` + 各 profile 文件，互不合并）。

重叠域摘要：任务统计 / 掌握度 → `evaluate`；任务批阅 / 班级 / 题目上传 / 话题公告 → `implement`；批量推送 / AI 出题 → `develop`；量规 / 图谱 → `design`；健康检查 / 转换 / 租户字典 → `common`。

专家 Server **默认不含** `common`；需要转换或健康检查时请在客户端**额外挂载** `common`。

#### 推荐：stdio + 客户端同时挂载多个 Server

由 Cursor 等宿主按配置**分别拉起**多个 stdio 子进程（一进程一 profile）。鉴权使用环境变量 `SERVER_API_URL` / `LOGIN_TOKEN`（stdio 无请求头）。

完整示例：[examples/mcp-client-multi-server.json](examples/mcp-client-multi-server.json)。

开发专家 + 通用基础（常用）：

```json
{
  "mcpServers": {
    "haoke-develop": {
      "command": "python",
      "args": ["-m", "haoke_mcp_server.server", "--profile", "develop"],
      "env": {
        "HAOKE_MCP_TRANSPORT": "stdio",
        "SERVER_API_URL": "http://brains.v2.zhituxueyuan.cn",
        "LOGIN_TOKEN": "<你的裸token>"
      }
    },
    "haoke-common": {
      "command": "python",
      "args": ["-m", "haoke_mcp_server.server", "--profile", "common"],
      "env": {
        "HAOKE_MCP_TRANSPORT": "stdio",
        "SERVER_API_URL": "http://brains.v2.zhituxueyuan.cn",
        "LOGIN_TOKEN": "<你的裸token>"
      }
    }
  }
}
```

也可使用已安装的 console script 作为 `command`（如 `haoke-mcp-develop`）。日志走 **stderr** / `LOG_DIR`，勿往 stdout 打印，以免破坏 MCP 协议。

本地单进程调试：

```bash
haoke-mcp-develop
# 或
python -m haoke_mcp_server.server --profile develop
```

#### 可选附录：streamable-http 多端口

仅当无法使用 stdio 宿主时：

```bash
set HAOKE_MCP_TRANSPORT=streamable-http
haoke-mcp-server --profile develop
```

HTTP 模式下客户端可传请求头 `Origin` / `Authorization`（缺省回退环境变量）。多端口后台脚本（可选）：

```powershell
.\scripts\start_mcp_profiles.ps1 -Profiles develop,common
```

```bash
PROFILES=develop,common ./scripts/start_mcp_profiles.sh
```

脚本会强制 `HAOKE_MCP_TRANSPORT=streamable-http`。日常请优先 stdio 多 Server 配置。

### Linux 服务器部署发布步骤（streamable-http）

以下以 Ubuntu 22.04 为例，目录使用 `/opt/haoke-mcp-server`。

1. 准备环境

```bash
sudo apt update
sudo apt install -y python3 python3-venv python3-pip git nginx
```

2. 拉取代码

```bash
sudo mkdir -p /opt
cd /opt
sudo git clone <你的仓库地址> haoke-mcp-server
sudo chown -R $USER:$USER /opt/haoke-mcp-server
cd /opt/haoke-mcp-server
```

3. 创建虚拟环境并安装

```bash
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install -e .
```

4. 配置运行环境变量

```bash
cat > /opt/haoke-mcp-server/.env <<'EOF'
SERVER_API_URL=http://你的后端:端口
LOGIN_TOKEN=你的裸token
EOF
```

5. 创建 systemd 服务

```bash
sudo tee /etc/systemd/system/haoke-mcp-server.service >/dev/null <<'EOF'
[Unit]
Description=haoke mcp server (streamable-http)
After=network.target

[Service]
Type=simple
User=root
WorkingDirectory=/opt/haoke-mcp-server
EnvironmentFile=/opt/haoke-mcp-server/.env
ExecStart=/opt/haoke-mcp-server/.venv/bin/python -m haoke_mcp_server.server
Restart=always
RestartSec=3

[Install]
WantedBy=multi-user.target
EOF
```

6. 启动并设置开机自启

```bash
sudo systemctl daemon-reload
sudo systemctl enable haoke-mcp-server
sudo systemctl restart haoke-mcp-server
sudo systemctl status haoke-mcp-server --no-pager
```

7. 查看日志

```bash
sudo journalctl -u haoke-mcp-server -f
```

8. （可选）Nginx 反向代理

```bash
sudo tee /etc/nginx/conf.d/haoke-mcp-server.conf >/dev/null <<'EOF'
server {
    listen 80;
    server_name _;

    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
EOF
sudo nginx -t
sudo systemctl reload nginx
```

说明：
- `proxy_pass` 端口请按服务实际监听端口调整。
- 若要对外发布，建议再加 HTTPS（如 certbot）。

### Windows 服务器部署发布步骤（streamable-http）

以下以 Windows Server 2019/2022 + PowerShell 为例，目录使用 `D:\services\haoke-mcp-server`。

1. 准备环境

- 安装 Git（可选，用于拉代码）
- 安装 Python 3.11+（安装时勾选 `Add python.exe to PATH`）

PowerShell 验证：

```powershell
python --version
pip --version
```

2. 拉取代码

```powershell
New-Item -ItemType Directory -Path "D:\services" -Force | Out-Null
Set-Location "D:\services"
git clone <你的仓库地址> haoke-mcp-server
Set-Location "D:\services\haoke-mcp-server"
```

3. 创建虚拟环境并安装依赖

```powershell
python -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install --upgrade pip
python -m pip install -e .
```

4. 配置运行环境变量（推荐进程级配置文件）

在项目根目录创建 `run.ps1`（用于统一启动）：

```powershell
$env:SERVER_API_URL = "http://你的后端:端口"
$env:LOGIN_TOKEN = "你的裸token"

Set-Location "D:\services\haoke-mcp-server"
& "D:\services\haoke-mcp-server\.venv\Scripts\python.exe" -m haoke_mcp_server.server
```

手工启动验证：

```powershell
powershell -ExecutionPolicy Bypass -File .\run.ps1
```

5. 注册为 Windows 服务（推荐使用 NSSM）

下载并解压 [NSSM](https://nssm.cc/download)，假设路径为 `D:\tools\nssm\nssm.exe`。

```powershell
D:\tools\nssm\nssm.exe install haoke-mcp-server "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" "-ExecutionPolicy Bypass -File D:\services\haoke-mcp-server\run.ps1"
D:\tools\nssm\nssm.exe set haoke-mcp-server AppDirectory "D:\services\haoke-mcp-server"
D:\tools\nssm\nssm.exe set haoke-mcp-server Start SERVICE_AUTO_START
D:\tools\nssm\nssm.exe set haoke-mcp-server AppStdout "D:\services\haoke-mcp-server\logs\service.out.log"
D:\tools\nssm\nssm.exe set haoke-mcp-server AppStderr "D:\services\haoke-mcp-server\logs\service.err.log"
```

启动服务：

```powershell
New-Item -ItemType Directory -Path "D:\services\haoke-mcp-server\logs" -Force | Out-Null
sc.exe start haoke-mcp-server
sc.exe query haoke-mcp-server
```

6. 防火墙放行（按实际端口）

```powershell
New-NetFirewallRule -DisplayName "haoke-mcp-server-8000" -Direction Inbound -Protocol TCP -LocalPort 8000 -Action Allow
```

7. 发布与回滚建议

- 发布：`git pull` -> 激活 `.venv` -> `pip install -e .` -> `sc.exe stop/start haoke-mcp-server`
- 回滚：切回上一个稳定 commit/tag 后重复重启服务

8. 常用排障命令

```powershell
sc.exe query haoke-mcp-server
Get-Content "D:\services\haoke-mcp-server\logs\service.err.log" -Tail 200
Get-Content "D:\services\haoke-mcp-server\logs\service.out.log" -Tail 200
```

说明：
- 若服务器启用执行策略限制，使用 `-ExecutionPolicy Bypass` 启动脚本。
- 如需反向代理和 HTTPS，建议在前置网关（IIS/Nginx/SLB）统一处理。

### Tool：最小联通性验证

Tool 名称：`haoke_health_check`

行为：
- 固定调用 `POST /api/core/tenant/queryCurrentTenant`
- 用于快速验证 `SERVER_API_URL`、`LOGIN_TOKEN` 与网络可达性

### Tool：AI 互动任务（视频 / 文档）

#### copy_task

- 名称：`copy_task`
- 说明：按源任务另存为新任务；成功后查询新任务并尽量返回学生端 `previewUrl`（规则与任务预览一致）
- Profile：`develop`

输入参数：

- `fromId`（int，必填）：源任务标识
- `copyUnitFlag`（integer/int32，可选）：是否复制章节目录和栏目；0：否；1：是。未传或为 `null` 时省略

示例：

```json
{
  "tool": "copy_task",
  "arguments": {
    "fromId": 10001
  }
}
```

成功响应示例：

```json
{
  "code": 200,
  "message": "复制任务成功！",
  "data": {
    "taskId": 10088,
    "taskName": "常规练习（副本）",
    "taskType": 50,
    "previewUrl": "http://brains.v2.zhituxueyuan.cn/student/course/0/studyTask/10088/practiceHome?taskId=10088&instanceId=5442"
  }
}
```

说明：`instanceId` 取自 `queryTask`；无有效 `instanceId` 或基址缺失时仍返回新 `taskId`，不伪造 `previewUrl`。

后端链路：`POST /api/teach/task/saveAsNewTask` → `POST /api/teach/task/queryTask`

#### create_video_task

- 名称：`create_video_task`
- 说明：创建 AI 互动视频任务（包含摘要/大纲、开放讨论与测验生成）

输入参数（全部通过 tool arguments 传入）：

- `instanceId`（int，必填）：课程实例标识
- `fileId`（int，必填）：视频资源标识 fileId
- `taskName`（string，必填）：任务名称
- `unitId`（int，可选，默认 0）：任务归属的课程目录节标识
- `customColumnId`（integer/int64，可选）：自定义栏目标识；未传或为 `null` 时省略
- `openDiscussion`（object，可选）：
  - `discussionNumber`（int，可选，默认 1）：生成开放讨论数量
  - `requireFlag`（int，可选，默认 0）：是否必须参与 0/1
- `quizInfo`（array，可选）：按题型生成题目
  - 每个元素包含：
    - `quizType`（string）：题型，支持「单选题 / 多选题 / 判断题 / 简答题 / 填空题」
    - `quizNumber`（int，可选，默认 1）：生成题目数量
    - `requireFlag`（int，可选，默认 0）：是否必须参与 0/1
- `quizIdInfo`（array，可选）：从题库中直接挂载指定题目
  - 每个元素包含：
    - `quizId`（int）：题库中题目 Id
    - `requireFlag`（int，可选，默认 0）：是否必须参与 0/1

示例（贴近 `course_mcpserver/mcp_server.json`）：

```json
{
  "tool": "create_video_task",
  "arguments": {
    "instanceId": 715,
    "fileId": 3241,
    "unitId": 5034,
    "taskName": "理解从无向图到CRF练习",
    "openDiscussion": { "discussionNumber": 1, "requireFlag": 1 },
    "quizInfo": [
      { "quizNumber": 2, "quizType": "单选题", "requireFlag": 1 },
      { "quizNumber": 2, "quizType": "判断题", "requireFlag": 1 }
    ],
    "quizIdInfo": [
      { "quizId": 1000, "requireFlag": 1 },
      { "quizId": 1001, "requireFlag": 1 }
    ]
  }
}
```

#### create_doc_task

- 名称：`create_doc_task`
- 说明：创建 AI 互动文档任务（包含摘要/分段、开放讨论与测验生成）
- 输入参数与 `create_video_task` 一致，`fileId` 为文档资源标识；支持可选 `customColumnId`（integer/int64，自定义栏目标识），未传或为 `null` 时省略

#### query_task_file_detail

- 名称：`query_task_file_detail`
- 说明：按任务标识查询视频/文档任务详情；成功时对 `data` 精简字段，仅保留摘要、大纲、分段、讨论题、测验题及关联资源文件等

输入参数：

- `taskId`（int，必填）：任务标识

示例：

```json
{
  "tool": "query_task_file_detail",
  "arguments": {
    "taskId": 10001
  }
}
```

成功返回示例（`data` 已优化）：

```json
{
  "code": 200,
  "message": "查询任务详情成功！",
  "data": {
    "taskId": 10001,
    "instanceId": 5442,
    "title": "第一章 视频任务",
    "summary": "本节介绍...",
    "summaryFlag": 1,
    "outline": "1. 概述\n2. 示例",
    "outlineFlag": 1,
    "paragraphFlag": 1,
    "teachTaskFileParagraphsItemList": [
      {"paragraphId": 1, "content": "第一段内容"}
    ],
    "teachTaskFileDiscussionItemList": [
      {"discussionId": 10, "question": "开放讨论问题", "requireFlag": 1}
    ],
    "teachTaskFileQuizItemList": [
      {
        "rpId": 20,
        "quizId": 12345,
        "requireFlag": 1,
        "taskQuizItem": {
          "quizId": 12345,
          "quizTypeName": "单选题",
          "content": "题干内容",
          "resQuizOptionList": [{"optionId": 1}],
          "difficulty": 2,
          "resQuizPointList": [{"pointId": 101}],
          "solution": "解析"
        }
      }
    ],
    "resourceFileItem": {
      "fileId": 5001,
      "fileName": "lesson.mp4",
      "flvUrl": "https://example.com/video.flv"
    }
  }
}
```

后端：`POST /api/teach/task/queryTaskFileById`

#### update_task_file_detail

- 名称：`update_task_file_detail`
- 说明：编辑更新视频/文档任务。提交前先查询任务详情：`title`/`summary`/`outline` 及三个列表为空时从查询结果回填；`teachTaskFileParagraphsItemList`/`teachTaskFileDiscussionItemList`/`teachTaskFileQuizItemList` 非空时与查询结果取合集；`delParagraphIds`/`delDiscussionIds`/`delRpIds` 非空时从合并结果中去除对应项。提交前自动为三个列表项注入 `taskId`，并对入参明文 `summary`、`outline`、分段 `content`、讨论 `question` 做 AES 加密（查询回填的密文不再加密）。**更新成功时**在 `data` 中附加 `previewUrl`、`taskName`、`taskType`（规则与创建类视频/文档任务一致；按详情 `fileType` 区分视频 `10` / 文档 `20`）

输入参数：

- `taskId`（int，必填）：任务标识
- `instanceId`（int，必填）：课程实例标识
- `title`（string，可选）：任务标题；为空时从查询结果回填
- `summary`（string，可选）：任务摘要（明文，提交前自动加密）；为空时从查询结果回填（密文不再加密）
- `summaryFlag`（int，可选）：摘要是否对学生显示（1 显示，0 不显示）；未传时从查询结果回填
- `outline`（string，可选）：任务大纲（明文，提交前自动加密）；为空时从查询结果回填
- `outlineFlag`（int，可选）：大纲是否对学生显示；未传时从查询结果回填
- `paragraphFlag`（int，可选）：分段是否对学生显示；未传时从查询结果回填
- `teachTaskFileParagraphsItemList`（array，可选）：分段列表，每项含 `paragraphId`、`content`；非空时与查询结果取合集。**新增分段**可将 `paragraphId` 置空或 `0`
- `teachTaskFileDiscussionItemList`（array，可选）：讨论列表，每项含 `discussionId`、`question`、`requireFlag`；非空时与查询结果取合集。**新增讨论**可将 `discussionId` 置空或 `0`
- `teachTaskFileQuizItemList`（array，可选）：测验列表，每项含 `rpId`、`quizId`、`requireFlag`；非空时与查询结果取合集。**新增题目**可将 `rpId` 置空或 `0`
- `delDiscussionIds`（int[]，可选）：待删除讨论题 ID；非空时从合并结果中去除
- `delRpIds`（int[]，可选）：待删除测验关联 ID；非空时从合并结果中去除
- `delParagraphIds`（int[]，可选）：待删除分段 ID；非空时从合并结果中去除

示例：

```json
{
  "tool": "update_task_file_detail",
  "arguments": {
    "taskId": 10001,
    "instanceId": 5442,
    "title": "第一章 视频任务（修订）",
    "summary": "本节介绍修订后的内容",
    "summaryFlag": 1,
    "outline": "1. 概述\n2. 示例",
    "outlineFlag": 1,
    "paragraphFlag": 1,
    "teachTaskFileParagraphsItemList": [
      {"paragraphId": 1, "content": "第一段修订内容"},
      {"paragraphId": 0, "content": "新增分段"}
    ],
    "teachTaskFileDiscussionItemList": [
      {"discussionId": 10, "question": "修订后的讨论问题", "requireFlag": 1},
      {"discussionId": 0, "question": "新增讨论", "requireFlag": 0}
    ],
    "teachTaskFileQuizItemList": [
      {"rpId": 20, "quizId": 12345, "requireFlag": 1},
      {"rpId": 0, "quizId": 99999, "requireFlag": 0}
    ],
    "delParagraphIds": [],
    "delDiscussionIds": [],
    "delRpIds": []
  }
}
```

成功返回示例：

```json
{
  "code": 200,
  "message": "更新视频/文档任务成功！",
  "data": {
    "taskId": 10001,
    "taskName": "第一章 视频任务（修订）",
    "taskType": 10,
    "previewUrl": "http://brains.v2.zhituxueyuan.cn/student/course/0/studyTask/10001/video?taskId=10001&instanceId=5442"
  }
}
```

说明：`previewUrl` 基址与创建类 Tool 相同（请求头 `Origin` 或 `SERVER_API_URL`）；文档任务路径含 `/doc?code&taskId=`，`taskType` 为 `20`。更新失败时不附加预览字段。

后端：`POST /api/teach/task/updateTaskFile`

#### create_richtext_task

- 名称：`create_richtext_task`
- 说明：统一生成 AI 互动图文任务，支持“根据主题”与“根据参考资源”两种模式（富文本正文 + 题目，可选挂载题库题目）

输入参数：

- `instanceId`（int，必填）：课程实例标识
- `theme`（string，可选）：主题内容（主题模式必填，资源模式留空）
- `fileIdList`（array[int]，可选）：选择的资源列表（资源模式必填，主题模式留空）
- `abilityIdList`（array[int]，可选）：任务关联能力；显式空列表也透传，未传时省略
- `customColumnId`（integer/int64，可选）：自定义栏目标识；支持 0，未传时省略
- `unitId`（int，可选，默认 0）：任务归属的课程目录节标识
- `pointIdList`（array[int]，可选）：关联知识点标识列表
- `contextVos`（array，可选）：知识库回答范围设置
- `recallVo`（object，可选）：召回参数（`maxRecallNum`、`minMatch`、`showSourceFlag`）
- `quizIdInfo`（array，可选）：从题库挂载题目，元素含 `quizId`、`requireFlag`

模式约束（二选一）：

- 主题模式：`theme` 非空 且 `fileIdList` 为空
- 资源模式：`fileIdList` 非空 且 `theme` 为空

示例（主题模式）：

```json
{
  "tool": "create_richtext_task",
  "arguments": {
    "instanceId": 378,
    "theme": "NLP基本概念",
    "fileIdList": [],
    "unitId": 5689,
    "pointIdList": [1000006425],
    "quizIdInfo": [{ "quizId": 1000, "requireFlag": 1 }]
  }
}
```

示例（参考资源模式）：

```json
{
  "tool": "create_richtext_task",
  "arguments": {
    "instanceId": 378,
    "theme": "",
    "fileIdList": [100005176, 100005177],
    "unitId": 5689
  }
}
```

#### create_ai_rich_text_task

- 名称：`create_ai_rich_text_task`
- 说明：通过标题和内容直接创建 AI 互动图文（不走 AI 生成；与 `create_richtext_task` 区分）

输入参数：

- `instanceId`（int，必填）：课程实例标识
- `taskName`（string，必填）：任务名称
- `itemList`（array[object]，必填）：图文条目列表，每项结构：
  ```
  {
    "textItemType": 10|20,
    "requireFlag": 0|1,          // 可选，默认 0
    "displayOrder": 0,           // 可选，未传按列表下标
    "content": "...",            // textItemType=10 必填：Markdown 或 HTML 明文
    "quizInfo": { "quizId": 1 }  // textItemType=20 必填
  }
  ```
  - `textItemType=10`：`content` 若判定为 Markdown 则先转 HTML，再 AES 加密（已是 HTML 则不二次转换）
  - `textItemType=20`：`quizInfo.quizId` 必填
- `pointIdList`（array[int]，可选）：关联知识点标识列表
- `abilityIdList`（array[int]，可选）：任务关联能力；显式空列表也透传，未传时省略
- `customColumnId`（integer/int64，可选）：自定义栏目标识；支持 0，未传时省略

示例：

```json
{
  "tool": "create_ai_rich_text_task",
  "arguments": {
    "instanceId": 378,
    "taskName": "NLP 互动图文",
    "pointIdList": [1000006425],
    "itemList": [
      {
        "textItemType": 10,
        "requireFlag": 0,
        "displayOrder": 0,
        "content": "# 标题\n\n这是 Markdown 正文"
      },
      {
        "textItemType": 20,
        "requireFlag": 1,
        "displayOrder": 1,
        "quizInfo": {"quizId": 1000}
      }
    ]
  }
}
```

后端：`POST /api/teach/task/richtext/saveRichTextTask`（新增 `taskId=0`）

#### query_richtext_task_detail

- 名称：`query_richtext_task_detail`
- 说明：查询 AI 互动图文任务详情；成功时对 `data`（含 `itemList`/`quizInfo`）做白名单字段精简

输入参数：

- `taskId`（int，必填）：任务标识
- `instanceId`（int，必填）：课程实例标识

示例：

```json
{
  "tool": "query_richtext_task_detail",
  "arguments": {
    "taskId": 10001,
    "instanceId": 5442
  }
}
```

成功返回示例（已优化）：

```json
{
  "code": 200,
  "message": "查询AI互动图文任务详情成功！",
  "data": {
    "taskId": 10001,
    "instanceId": 5442,
    "taskName": "NLP基本概念",
    "pointIdList": [123, 456],
    "itemList": [
      {
        "textItemId": 1,
        "textItemType": 10,
        "content": "图文内容……"
      },
      {
        "textItemId": 2,
        "textItemType": 20,
        "content": "",
        "quizInfo": {
          "quizId": 1000,
          "quizTypeName": "单选题",
          "content": "题干",
          "difficulty": 2,
          "resQuizOptionList": [{"content": "选项A", "answerFlag": 1}],
          "resQuizPointList": [{"pointId": 123}],
          "solution": "解析"
        }
      }
    ]
  }
}
```

后端链路：`POST /api/teach/task/richtext/queryRichTextTaskByTaskId`

#### update_richtext_task_detail

- 名称：`update_richtext_task_detail`
- 说明：编辑更新 AI 互动图文任务；提交前先查询详情回填，再调用保存接口。**更新成功时**在 `data` 中附加 `previewUrl`、`taskName`、`taskType`（规则与 `create_richtext_task` 一致，`taskType=130`，路径含 `/imageText`）

输入参数：

- `taskId`（int，必填）：任务标识
- `instanceId`（int，必填）：课程实例标识
- `itemList`（array[object]，必填）：图文条目列表（整表提交，不与查询结果合并），每项结构：
  ```
  {
    "textItemId": 1,                 // 可选：条目标识
    "textItemType": 10|20,
    "requireFlag": 0|1,              // 可选，默认 0
    "displayOrder": 0,               // 可选，未传按列表下标
    "content": "...",                // textItemType=10 必填：Markdown 或 HTML 明文
    "quizInfo": { "quizId": 1 }      // textItemType=20 必填
  }
  ```
- `taskName`（string，可选）：任务名称（为空时从查询结果回填）
- `pointIdList`（array[int]，可选）：新增知识点标识（与查询结果取合集）
- `abilityIdList`（array[int]，可选）：任务关联能力；显式列表替换，空列表清空，未传时保留原值
- `customColumnId`（integer/int64，可选）：自定义栏目标识；显式传入覆盖（包括 0），未传时保留原栏目
- `delPointIdList`（array[int]，可选）：需解除关联的知识点标识

回填与合并规则：

1. 先调用 `queryRichTextTaskByTaskId`；失败则中止，不保存
2. `taskName` 为空时从查询结果回填；合并后仍为空则参数错误
3. `pointIdList` 以查询结果为基线；入参非空时取合集；`delPointIdList` 非空时从结果中去除
   可选 `abilityIdList`（array[int]）显式传入替换原关联，空列表清空能力，未传时保留原值；可选 `customColumnId`（integer/int64）显式传入覆盖（包括 0），未传时保留原栏目。原值缺失/null 时省略对应字段。
4. 始终回填 `contextVos`、`recallVo`、`unitId`（不暴露为 Tool 入参）
5. `textItemType=10`：Markdown→HTML（已是 HTML 不二次转换）后 AES 加密再提交；`requireFlag` 默认 0，`displayOrder` 未传按下标

示例：

```json
{
  "tool": "update_richtext_task_detail",
  "arguments": {
    "taskId": 10001,
    "instanceId": 5442,
    "taskName": "更新后的图文任务",
    "itemList": [
      {
        "textItemId": 1,
        "textItemType": 10,
        "requireFlag": 0,
        "displayOrder": 0,
        "content": "# 更新后的标题\n\nMarkdown 正文"
      },
      {
        "textItemId": 2,
        "textItemType": 20,
        "requireFlag": 1,
        "displayOrder": 1,
        "quizInfo": { "quizId": 1000 }
      }
    ],
    "pointIdList": [789],
    "delPointIdList": [123]
  }
}
```

成功响应示例：

```json
{
  "code": 200,
  "message": "更新AI互动图文任务成功！",
  "data": {
    "taskId": 10001,
    "taskName": "更新后的图文任务",
    "taskType": 130,
    "previewUrl": "http://brains.v2.zhituxueyuan.cn/student/course/0/studyTask/10001/imageText?taskId=10001&instanceId=5442"
  }
}
```

说明：`previewUrl` 基址与创建类 Tool 相同（请求头 `Origin` 或 `SERVER_API_URL`）；更新失败时不附加预览字段。

后端链路：`POST /api/teach/task/richtext/queryRichTextTaskByTaskId` → `POST /api/teach/task/richtext/saveRichTextTask`

#### update_survey_publish_settings

修改既有调查问卷发布设置，归属 `evaluate`，调用 `POST /api/survey/manager/updatePublishSettings`。

- 仅 `publishId`（正整数 int64 发布记录标识，区别于 `surveyId`）必填。可选 `anonymousFlag`（0 实名/1 匿名）、`specifiedRespondentType`（1 学生/2 教师）、`timeLimitFlag`（0 不限/1 限时）、`beginTime`、`endTime`、`userIds`、`delUserIds`。省略/null 不提交、不修改原值，显式 0 保留；允许只传 `publishId`。
- 固定 `respondentType=1`，仅用于原范围为指定人员的记录。发布后范围与身份不可切换，开始时间只允许回传原值；后端业务拒绝原样返回。
- 提供 `userIds` 或 `delUserIds`（可为 `[]`）时，先调用 `POST /api/survey/manager/querySurveyPublish` 获取 `data.publish.respondents`，移除 `delUserIds` 中的旧用户，再合并 `userIds`，按首次出现顺序去重后提交完整 `respondents: [{"userId": ...}]`。同一 ID 先删后加会重新加入。两项均省略/null 时不查询人员、不提交 respondents；合并结果不能为空。所有用户标识必须为正整数 int64。
- `timeLimitFlag=1` 时起止时间均必填，严格使用 `YYYY-MM-DD HH:mm:ss` 格式的有效日期，开始须早于截止。`timeLimitFlag=0` 时两个时间都从请求省略，传入字符串也不生效。
- 未传 `timeLimitFlag` 时，可单独提交有效 `endTime`；显式时间仍需严格日期格式，空字符串不是清空指令。若两个时间均提供，则检查顺序；不查询原值、不转换时区、不比较当前时间。
- 鉴权及 `requestId` 由共享层注入；保留完整后端响应，允许成功空数据，不构造预览链接或重新发布。

完整设置示例（开始时间和身份应与原发布记录一致）：

```json
{
  "tool": "update_survey_publish_settings",
  "arguments": {
    "publishId": 63,
    "anonymousFlag": 1,
    "specifiedRespondentType": 1,
    "timeLimitFlag": 1,
    "beginTime": "2026-10-25 00:00:00",
    "endTime": "2026-10-31 00:00:00",
    "userIds": [34032, 34139, 48666, 48667, 48668, 48669]
  }
}
```

仅修改为实名：

```json
{"tool": "update_survey_publish_settings", "arguments": {"publishId": 63, "anonymousFlag": 0}}
```

仅延长截止时间，保留原限时设置与开始时间：

```json
{"tool": "update_survey_publish_settings", "arguments": {"publishId": 63, "endTime": "2026-11-01 00:00:00"}}
```

移除指定用户并追加新用户，其余旧人员保留：

```json
{"tool": "update_survey_publish_settings", "arguments": {"publishId": 63, "delUserIds": [34032], "userIds": [50001]}}
```

#### publish_survey

发布调查问卷给指定用户，归属 `evaluate`，调用 `POST /api/survey/manager/saveSurveyPublish`。

- 必填 `surveyId`（正整数 int64）、`anonymousFlag`（0 实名/1 匿名）、`specifiedRespondentType`（1 学生/2 教师）、`timeLimitFlag`（0 不限时/1 限时）、`userIds`（非空正整数 int64 数组）。开关和身份必须显式提供。
- 服务固定 `respondentType=1`，将 `userIds` 按原顺序映射为 `respondents: [{"userId": ...}]`，保留重复项，不自动查找或追加人员。后端请求不包含 `userIds`。
- `timeLimitFlag=1` 时 `beginTime`、`endTime` 必填，严格使用 `YYYY-MM-DD HH:mm:ss` 格式的有效日期，开始须早于截止。时间原样发送，不转换时区，不要求晚于当前时间。
- `timeLimitFlag=0` 时无需时间参数；即使提供字符串也忽略并从后端请求中省略。两个字段仍只允许字符串/null。
- 共享客户端注入鉴权和 `requestId`。完整保留后端响应及 `data.publishId`，成功空数据原样返回；不添加预览链接或业务自动重试。

限时发布给六名学生：

```json
{
  "tool": "publish_survey",
  "arguments": {
    "surveyId": 73,
    "anonymousFlag": 1,
    "specifiedRespondentType": 1,
    "timeLimitFlag": 1,
    "beginTime": "2026-10-25 00:00:00",
    "endTime": "2026-10-31 00:00:00",
    "userIds": [34032, 34139, 48666, 48668, 48667, 48669]
  }
}
```

不限时实名发布给教师：

```json
{
  "tool": "publish_survey",
  "arguments": {
    "surveyId": 73,
    "anonymousFlag": 0,
    "specifiedRespondentType": 2,
    "timeLimitFlag": 0,
    "userIds": [34032]
  }
}
```

#### save_survey

创建/完整编辑问卷调查，归属 `evaluate`，调用 `POST /api/survey/manager/saveSurvey`。

- `surveyName` 必填非空；`surveyId` 省略/null 为创建，编辑必须提供正整数 int64 标识。`description` 可选，省略/null 不发送，空字符串保留。
- `questions` 独立题目、`sections` 段落至少一个列表非空，可同时提供。**编辑必须提交完整内容，省略/null 的列表按 `[]` 发送，不保留旧内容。**
- 段落包含 `sectionTitle`、`displayOrder`、`questions`，可选 `sectionDesc`。题目必含 `questionType`、非空 `content`、`displayOrder`、`isRequired`，支持 10 单选、11 评分单选、20 多选、21 评分多选、30 量表、31 矩阵量表、40 单项填空、50 简答；不要求 `questionTypeCode`。
- 10/11/20/21/30/31 需非空 `options`，31 还需非空 `matrixRows`。选项含 `content`、`displayOrder`，可选 `score`（默认 0）、`answerFlag`（0/1，默认 0）；矩阵行含非空 `rowTitle`、`displayOrder`。非矩阵题无矩阵行，40/50 无选项。
- `scaleLevel` 对 30/31 必填且为 2～10；`scaleShowAll` 为 0 只首尾/1 全部，默认 0。`scaleTemplate=0/null` 表示无预设；量表可用 1 满意度、2 认同度、3 重要度、4 符合度、5 可能性。非量表默认 0，量表默认 null，显式 0/null 保留；非量表的其他量表配置为 0。
- `maxScore`、选项 `score` 传实际分×100 后的存储整数，默认 0，例如 1.25 分传 125；不重复放大、不计算总分或矩阵行乘积。10/20/40/50 满分为 0，10/20 选项分值为 0，评分题和量表允许非零分值。排序、正文和换行保留。
- 服务固定顶层 `useType=2`、`surveyType=2` 并注入 `requestId`；名称和说明位于顶层，`survey` 内仅含题目与段落。成功保留后端结果并附加 `data.previewUrl={base}/teacher/survey/manage/list`。

创建独立题目问卷：

```json
{
  "tool": "save_survey",
  "arguments": {
    "surveyName": "课程反馈",
    "questions": [{"questionType": 50, "content": "你的建议？", "displayOrder": 0, "isRequired": 0}]
  }
}
```

完整编辑为段落问卷：

```json
{
  "tool": "save_survey",
  "arguments": {
    "surveyId": 73,
    "surveyName": "课程反馈",
    "description": "请填写建议",
    "questions": [],
    "sections": [{
      "sectionTitle": "反馈建议",
      "displayOrder": 0,
      "questions": [{"questionType": 50, "content": "你的建议？", "displayOrder": 0, "isRequired": 1, "scaleTemplate": 0}]
    }]
  }
}
```

#### save_survey_template

创建/完整编辑问卷调查模板，归属 `evaluate`，调用 `POST /api/survey/manager/saveSurveyTemplate`。

- `templateName` 必填且非空；`templateId` 省略或 null 为创建，编辑必须传入正整数模板 ID。
- `description` 可选。独立题目 `questions` 和段落 `sections` 至少一个列表非空，可同时提供；省略/null 的列表按 `[]` 提交。**编辑需提交完整内容，省略列表不会保留旧内容。**
- 段落包含 `sectionTitle`、`displayOrder`、`questions`，可选 `sectionDesc`。题目包含 `questionType`、`content`、`displayOrder`、`isRequired`，可选分值、量表、选项及矩阵行字段；不要求 `questionTypeCode`。
- 题型：10 单选、11 评分单选、20 多选、21 评分多选、30 量表、31 矩阵量表、40 单项填空、50 简答。10/11/20/21/30/31 需非空 `options`；31 还需非空 `matrixRows`；40/50 无选项，其他题型无矩阵行。
- `options` 元素含 `content`、`displayOrder`、`score`（默认 0）、`answerFlag`（0/1，默认 0）；矩阵行含 `rowTitle`、`displayOrder`。排序从 0 开始，按输入保留；`isRequired` 为 0/1。
- 30/31 必须提供 `scaleLevel`（2～10），`scaleShowAll` 为 0 只首尾/1 全部（默认 0），`scaleTemplate` 为 null（默认）或 1 满意度、2 认同度、3 重要度、4 符合度、5 可能性。其他题型使用 0/0/null 的默认量表配置。
- `maxScore`、选项 `score` 传**实际分 × 100 后的存储整数**，例如实际 1.25 分传 125；不会再次乘以 100 或自动计算满分。10/20/40/50 的满分为 0，10/20 选项分值为 0；评分题和量表可带分值。
- `useType=2`、`surveyType=2` 和 `requestId` 由服务处理。成功保留后端数据并增加 `data.previewUrl={base}/teacher/survey/manage/templete`，指向模板列表。

创建独立题目模板：

```json
{
  "tool": "save_survey_template",
  "arguments": {
    "templateName": "课程反馈",
    "questions": [{"questionType": 50, "content": "你的建议是什么？", "displayOrder": 0, "isRequired": 0}]
  }
}
```

完整编辑为段落模板（独立题目列表明确清空）：

```json
{
  "tool": "save_survey_template",
  "arguments": {
    "templateId": 22,
    "templateName": "课程反馈",
    "description": "请提交本次课程建议",
    "questions": [],
    "sections": [{
      "sectionTitle": "开放反馈",
      "displayOrder": 0,
      "questions": [{"questionType": 50, "content": "你的建议是什么？", "displayOrder": 0, "isRequired": 1}]
    }]
  }
}
```

#### delete_my_survey_templates

删除我的问卷调查模板，归属 `evaluate`，调用 `POST /api/survey/manager/deleteSurveyTemplate`。

- `templateIds`：必填非空数组，元素为正整数模板 ID（integer/int64）；单条传一个元素，多个元素一次批量提交。
- 完整校验后按首次出现顺序去重，原始输入保持不变；空数组、null、非数组或任一非法元素都会阻断整个请求。
- 仅提交明确指定的模板 ID，不自动查询或追加对象；模板归属、权限和可删除条件由后端判断。
- 沿用当前教师鉴权及 requestId，不额外传 surveyIds、surveyType、useType 或用户标识。
- 保留后端 code、message 和 data；成功 data 缺失或为 null 均正常返回，不编造删除数量。不额外实现逐条删除或业务循环重试。

```json
{
  "tool": "delete_my_survey_templates",
  "arguments": {"templateIds": [101, 102]}
}
```

#### delete_my_surveys

删除我的问卷调查，归属 `evaluate`，调用 `POST /api/survey/manager/deleteSurvey`。

- `surveyIds`：必填非空数组，元素为正整数问卷 ID（integer/int64）；单条删除传一个元素，多个元素一次批量提交。
- 完整校验数组后按首次出现顺序去重，保留原始输入。缺失、null、空数组、非数组或含非法元素均拒绝，不执行部分删除。
- 仅删除显式指定的 ID，不自动查询、推断或追加删除对象；归属、权限和可删除状态由后端校验。
- 沿用当前教师鉴权及 requestId 注入，不额外提交 surveyType、useType 或用户标识。
- 保留后端 code、message 和 data；成功 data 缺失或为 null 均正常返回，不虚构删除数量。网络、HTTP、鉴权及格式错误沿用现有错误处理。

```json
{
  "tool": "delete_my_surveys",
  "arguments": {"surveyIds": [101, 102]}
}
```

#### query_my_survey_template_list

我的问卷调查模板分页列表，归属 `evaluate`，调用 `POST /api/survey/manager/listSurveyTemplate`。

- `templateName`：可选字符串，模板名称模糊匹配；由后端执行匹配，不添加通配符。
- `templateSourceType`：可选整数，1 来自学校，2 我创建的；未传或 null 时不限制来源。
- `page`：可选对象，默认 `{pageNo: 1, pageSize: 10}`。未传、null、空对象或省略子字段时补齐对应默认值；页码和每页数量须为正整数。

内部固定 `surveyType=2`、`useType=2`，使用当前教师鉴权。未传或 null 的筛选不发送，空名称字符串原值透传；响应保留完整分页 data 和后端 code/message，空列表正常返回，不自动翻页。

```json
{
  "tool": "query_my_survey_template_list",
  "arguments": {
    "templateName": "教学",
    "templateSourceType": 1,
    "page": {"pageNo": 1, "pageSize": 10}
  }
}
```

#### list_survey_question_answer

查看问卷调查文本题详细作答，归属 `evaluate`，调用 `POST /api/survey/manager/listSurveyQuestionAnswer`。

- `questionId`、`publishId` 必填，为正整数 int64，分别表示题目与发布记录标识；`searchKeyword` 为可选严格字符串，省略或 null 时不发送，显式空串、空白及特殊字符原样传递。
- 完整返回后端 `data.rowList` 及 `pageNo`、`pageSize`、`pageCount`、`totalRowCount`、`startIndex`、`endIndex` 等分页信息。使用后端默认页，不发送 `page` 或自动翻页，当前结果可能只包含部分作答。
- 保留答案文本、原顺序、重复答案、人员/组织字段、未知字段和空值；匿名字段按后端原状返回，不补齐身份。题型支持和关键词匹配规则由后端决定。
- 空结果及业务错误原样返回，鉴权和 `requestId` 由共享层处理。

```json
{
  "tool": "list_survey_question_answer",
  "arguments": {"questionId": 8, "publishId": 63, "searchKeyword": "建议"}
}
```

#### query_survey_question_statistics

查询问卷调查答题情况统计，归属 `evaluate`，调用 `POST /api/survey/manager/querySurveyQuestionStatistics`。

- 唯一入参 `publishId` 必填，为正整数 int64 的发布记录标识。
- 完整返回后端对象，`data.statistics` 包含问卷及发布标识、匿名标志、`questionStats` 题目统计和 `sectionStats` 段落统计。保留题目与段落的 `parentSectionId`/`questionIds` 关联及各层 `matrixRowStats`、`optionStats`。
- `answerCount` 与 `responseCount` 分别保留；均分、分数和比例保持后端原值（如 1.25、125、500、0.125），不推断单位、不缩放或舍入。数组顺序、正文换行、未知字段、null 和空数组保持原值。
- 业务错误及空统计原样返回；鉴权和 `requestId` 由共享层处理。

```json
{
  "tool": "query_survey_question_statistics",
  "arguments": {"publishId": 63}
}
```

#### query_survey_statistics

查询问卷调查提交情况和评分分段统计，归属 `evaluate`，调用 `POST /api/survey/manager/querySurveyStatistics`。

- 唯一入参 `publishId` 必填，为正整数 int64 的发布记录标识。
- 完整返回后端对象，统计保持在 `data.statistics` 内，包括 `publish` 发布信息、`overall` 应提交数/已提交数/提交率、`scoreSegments` 评分分段，以及后端附带的 `questionStats` 和 `targets`。
- 存储评分值保持原值（如 `avgTotalScore=8125`）；分段 `label` 已是实际分值区间，比例也保持原值（如 `rate=0.125`），均不换算、不舍入、不重新计算。保留未知字段、时间、null、零值和空数组。
- 后端业务错误及空结果原样返回；共享层处理鉴权和 `requestId`。

```json
{
  "tool": "query_survey_statistics",
  "arguments": {"publishId": 63}
}
```

#### query_survey_detail

查询完整调查问卷详情，归属 `evaluate`，调用 `POST /api/survey/manager/querySurvey`。

- 唯一入参 `surveyId` 必填，必须为正整数 int64。
- 原样返回后端 `code`、`message`、`data` 及额外字段；问卷详情保持在 `data.survey` 内，包括基础属性、发布状态、独立 `questions`、段落 `sections` 及题目中的选项、矩阵行和标识。
- `totalScore`、`maxScore`、选项 `score` 保持后端存储单位，不除以 100（例如 125 仍返回 125）。正文、排序、时间、null 和空数组也保持原值，不过滤未知字段或增加预览链接。
- 后端成功空数据及业务错误原样返回；查询不调用保存、发布、删除或其他详情接口，鉴权和 `requestId` 由共享层处理。

```json
{
  "tool": "query_survey_detail",
  "arguments": {"surveyId": 73}
}
```

#### list_survey_respondent_teacher

查询指定问卷的可选答题教师，归属 `evaluate`，调用 `POST /api/survey/manager/listSurveyRespondentTeacher`。

- `surveyId`：必填问卷标识，严格正整数 int64。
- `departmentId`、`majorId`、`classId`：可选学院、专业、教学班标识，严格整数 int64，独立或组合传递，业务有效性由后端判断。
- `searchKeyword`：可选姓名或学号/工号关键词，查询对象为教师；保留前导零、空串和首尾空格。
- 可选字段省略或显式 `null` 时不发送。按用户契约支持 `majorId`；本地接口快照暂未列出该字段，实际专业筛选效果需后端联调确认。
- 完整返回 `data.teachers`，保留 `userId`、`realName`、`teacherNo`、`userCode`、学院、`majorName`、教学班/课程实例名称及扩展字段；专业名称空串、编号前导零和逗号连接名称保持原样，不本地过滤、去重或分页。
- 发布问卷时使用返回的 **`userId`** 填入 `respondents.userId`，不能以教工号替代；本工具只查询候选教师。

最小、独立专业、姓名/工号及组合调用示例：

```json
{"tool":"list_survey_respondent_teacher","arguments":{"surveyId":73}}
```

```json
{"tool":"list_survey_respondent_teacher","arguments":{"surveyId":73,"majorId":34}}
```

```json
{"tool":"list_survey_respondent_teacher","arguments":{"surveyId":73,"searchKeyword":"李老师"}}
```

```json
{"tool":"list_survey_respondent_teacher","arguments":{"surveyId":73,"departmentId":12,"majorId":34,"searchKeyword":"0012","classId":56}}
```

#### list_survey_respondent_student

查询指定问卷的可选答题学生，归属 `evaluate`，调用 `POST /api/survey/manager/listSurveyRespondentStudent`。

- `surveyId`：必填问卷标识，严格正整数 int64。
- `departmentId`、`majorId`：可选学院、专业标识，严格整数 int64。
- `searchKeyword`：可选学生姓名或学号/工号关键词，保留前导零、空字符串和首尾空格。
- `classId`：可选教学班标识；`instanceId`：可选课程实例标识；`administrativeClassId`：可选行政班 ID，三者独立传递，均为严格整数 int64。
- 六项筛选可独立或组合使用，省略或显式 `null` 时不发送对应字段，业务有效性和候选范围由后端判断。
- 完整返回 `data.students`，保留 `userId`、`realName`、`userCode`、`studentNo`、学院/专业、教学班/课程实例/行政班名称及扩展字段。学号原值和逗号连接名称保持原样，不本地筛选、去重或自动分页。
- 后续发布问卷时，选择返回的 **`userId`** 填入 `respondents.userId`，不能以 `userCode` 或 `studentNo` 替代；本工具只查询候选学生。

最小、独立筛选及全部字段调用示例：

```json
{"tool":"list_survey_respondent_student","arguments":{"surveyId":73}}
```

```json
{"tool":"list_survey_respondent_student","arguments":{"surveyId":73,"majorId":34}}
```

```json
{"tool":"list_survey_respondent_student","arguments":{"surveyId":73,"administrativeClassId":90}}
```

```json
{"tool":"list_survey_respondent_student","arguments":{"surveyId":73,"departmentId":12,"majorId":34,"searchKeyword":"0012","classId":56,"instanceId":78,"administrativeClassId":90}}
```

#### list_survey_admin_class

查询当前鉴权租户的行政班列表，归属 `common`，调用 `POST /api/survey/manager/listSurveyAdminClass`。

- `departmentId`：可选学院标识，严格整数 int64。
- `majorId`：可选专业标识，严格整数 int64，可独立传入，无需同时提供学院。
- `searchKeyword`：可选行政班名称或编号关键词，保留编号前导零、空字符串和首尾空格。
- 三参数均可独立或组合使用；省略或显式 `null` 不发送对应筛选字段，标识的业务有效性由后端判断。
- 完整返回后端 `data.classes`，保留 `classId`、`className`、`classNo`、`classGrade`、`startYear`、`departmentId`、`majorId` 及扩展字段，不本地过滤、去重或自动分页。

无筛选、仅专业、名称/编号及组合调用示例：

```json
{"tool":"list_survey_admin_class","arguments":{}}
```

```json
{"tool":"list_survey_admin_class","arguments":{"majorId":34}}
```

```json
{"tool":"list_survey_admin_class","arguments":{"searchKeyword":"软件一班"}}
```

```json
{"tool":"list_survey_admin_class","arguments":{"searchKeyword":"0012"}}
```

```json
{"tool":"list_survey_admin_class","arguments":{"departmentId":12,"majorId":34,"searchKeyword":"0012"}}
```

#### list_survey_course_instance

查询当前鉴权租户的课程实例列表，归属 `common`，调用 `POST /api/survey/manager/listSurveyCourseInstance`。

- `departmentId`：可选学院标识，严格整数 int64，业务有效性由后端判断。
- `searchKeyword`：可选课程名称关键词，原样保留空字符串和首尾空格。
- 两参数省略或显式 `null` 均不发送对应筛选字段，无参时查询当前租户范围。
- 完整返回后端 `data.instances`，保留 `courseId`、`courseName`、`instanceId`、`instanceName` 及扩展字段；不本地过滤、去重或自动分页。

无筛选、单项筛选和组合筛选示例：

```json
{"tool":"list_survey_course_instance","arguments":{}}
```

```json
{"tool":"list_survey_course_instance","arguments":{"departmentId":12}}
```

```json
{"tool":"list_survey_course_instance","arguments":{"searchKeyword":"机器学习"}}
```

```json
{"tool":"list_survey_course_instance","arguments":{"departmentId":12,"searchKeyword":"机器学习"}}
```

#### list_teach_class

查询当前教师的教学班列表，归属 `common`，调用 `POST /api/teach/class/listTeachClass`。

- 唯一必填 `instanceId` 为正整数int64开课标识。`hasFinish` 为可选严格整数：0不包含已结课、1包含、2仅包含，省略或null均默认0，显式0保留。
- 单次查询，仅发送 `instanceId` 和 `hasFinish`；不额外发送任务、考试、班级、分页或教师标识。教师范围及结课筛选由后端决定，鉴权和 `requestId` 由共享层处理。
- 完整返回 `data.teachClassList`、`children` 分班、结课及父班信息、统计和未知字段，保留原序与null，不本地筛选、去重或展平。空结果及业务错误原样返回。

默认排除已结课：

```json
{"tool":"list_teach_class","arguments":{"instanceId":5442}}
```

包含已结课或仅查询已结课：

```json
{"tool":"list_teach_class","arguments":{"instanceId":5442,"hasFinish":1}}
```

```json
{"tool":"list_teach_class","arguments":{"instanceId":5442,"hasFinish":2}}
```

#### list_major

查询专业信息列表，归属 `common`，调用 `POST /api/teach/instance/listMajor`。

- 唯一必填入参 `departmentId` 为严格整数int64：-1不限、0全部、正整数为指定学院。`searchText` 为可选专业名称/专业代码字符串，省略/null不发送，空串、空白和代码前导零原样保留。
- `page` 省略/null/{}时默认 `{"pageNo":1,"pageSize":1000}`；只传一个子字段则另一个补默认。两项均须为正整数int32，显式null、0、负数及错误类型拒绝，不自动裁剪页大小。
- 单次查询，不自动翻页。完整保留后端 `code`、`message`、`data` 和额外字段，包括专业/学院信息及关联课程、开课；不擅自将数组包装成分页对象，不补造总记录数。业务错误和成功空结果原样返回。
- 不额外发送classId、webSearchFlag或instanceId；鉴权和 `requestId` 由共享层处理。

默认分页：

```json
{"tool":"list_major","arguments":{"departmentId":12}}
```

专业名称/代码检索并指定分页：

```json
{"tool":"list_major","arguments":{"departmentId":12,"searchText":"0012","page":{"pageNo":2,"pageSize":20}}}
```

#### query_my_survey_list

我的问卷调查分页列表，归属 `evaluate`，调用 `POST /api/survey/manager/listSurvey`。

- `survyName`：可选字符串，问卷名称模糊匹配，映射到后端 `surveyName`。
- `publishStatus`：可选整数，0 未发布、1 已发布。
- `surveyStsaus`：可选整数，0 未开始、1 进行中、2 已结束，映射到后端 `surveyStatus`。
- `page`：可选分页对象；未传、null 或空对象时为 `{pageNo: 1, pageSize: 10}`，省略其中某个字段时分别补齐默认值。两项均须为正整数。

内部固定 `surveyType=2`（调查）、`useType=2`（教师端），不作为工具入参。查询使用当前登录教师鉴权；未传或 null 的筛选不发送，显式状态 0 正确保留。响应保留后端 code、message 和完整分页 data，空列表也正常返回，不自动翻页。

```json
{
  "tool": "query_my_survey_list",
  "arguments": {
    "survyName": "教学",
    "publishStatus": 0,
    "surveyStsaus": 0,
    "page": {"pageNo": 1, "pageSize": 10}
  }
}
```

#### create_question_task

新增可选 `voiceId`（integer/int64，角色音色）。未传或为 `null` 时，以当前 `instanceId` 调用 `/api/teach/bot/queryDefaultAiVoice`，取 `data.voList[0].voiceId` 保存；显式传值不查询。默认列表为空、首项缺少有效音色或查询失败时停止保存并报错。更新工具未传音色时也使用此默认规则。

- 名称：`create_question_task`
- 说明：创建一个 AI 辩论/讨论任务；`title`/`background`/`question` 未提供时由 AI 按顺序自动补全

输入参数：

- `instanceId`（int，必填）：课程实例标识（替代旧版全局 `INSTANCE_ID`）
- `title`（string，可选）：讨论任务标题
- `background`（string，可选）：讨论背景描述
- `question`（string，可选）：讨论具体内容
- `pointIdList`（array[int]，可选）：关联知识点标识列表
- `contextVos`（array，可选）：知识库回答范围设置
- `recallVo`（object，可选）：召回参数（`maxRecallNum`、`minMatch`、`showSourceFlag`）
- `unitId`（int，可选，默认 0）：任务归属的课程目录节标识

示例（完整输入，直接保存）：

```json
{
  "tool": "create_question_task",
  "arguments": {
    "instanceId": 5442,
    "title": "生活中接触到的NLP技术",
    "background": "讨论背景描述",
    "question": "讨论具体内容",
    "pointIdList": [123, 456],
    "unitId": 1024,
    "contextVos": [
      {
        "contentType": 10,
        "baseType": 10,
        "baseTypeLevel": 1,
        "repositoryType": 123,
        "useId": "91,93",
        "useName": "作业,练习"
      }
    ],
    "recallVo": {
      "maxRecallNum": 3,
      "minMatch": 0.7,
      "showSourceFlag": 0
    }
  }
}
```

示例（仅传 instanceId，由 AI 生成标题/背景/内容）：

```json
{
  "tool": "create_question_task",
  "arguments": {
    "instanceId": 5442,
    "unitId": 1024
  }
}
```

AI 生成链路：

- `POST /api/teach/ai/lesson/question/chat`
- `POST /api/teach/ai/lesson/openquestion/queryResult`
- `POST /api/teach/ai/lesson/openquestion/finishQuery`
- `POST /api/teach/task/newTaskOpenQuestion`

#### query_question_task_detail

- 名称：`query_question_task_detail`
- 说明：按任务标识查询 AI 辩论/讨论任务详情；成功时对 `data` 精简字段，保留标题、背景、讨论内容、知识点与知识库配置等，并在 `data` 顶层返回 `taskId`

输入参数：

- `taskId`（int，必填）：任务标识

示例：

```json
{
  "tool": "query_question_task_detail",
  "arguments": {
    "taskId": 10002
  }
}
```

成功返回示例（`data` 已优化）：

```json
{
  "code": 200,
  "message": "查询AI辩论任务详情成功！",
  "data": {
    "taskId": 10002,
    "taskOpenQuestionItem": {
      "taskId": 10002,
      "questionTaskId": 20002,
      "title": "生活中接触到的NLP技术",
      "background": "讨论背景描述",
      "question": "讨论具体内容",
      "aiFlag": 1,
      "roundNum": 3,
      "pasteFlag": 0
    },
    "pointIdList": [123, 456],
    "contextVos": [
      {
        "contentType": 10,
        "baseType": 10,
        "baseTypeLevel": 1,
        "repositoryType": 123,
        "useId": "91,93",
        "useName": "作业,练习"
      }
    ],
    "recallVo": {
      "maxRecallNum": 3,
      "minMatch": 0.7,
      "showSourceFlag": 0
    }
  }
}
```

后端：`POST /api/teach/task/queryTaskOpenQuestion`

#### update_question_task_detail

新增可选 `voiceId`（integer/int64，角色音色）。未传或为 `null` 时，以当前 `instanceId` 调用 `/api/teach/bot/queryDefaultAiVoice`，取 `data.voList[0].voiceId` 保存；显式传值不查询。默认列表为空、首项缺少有效音色或查询失败时停止保存并报错。更新工具未传音色时也使用此默认规则。

- 名称：`update_question_task_detail`
- 说明：编辑更新 AI 辩论/讨论任务。提交前先调用 `query_question_task_detail`：`title`/`background`/`question` 未传或为空时从查询结果回填；`pointIdList`/`abilityIdList` 非空时与查询结果取合集；`delPointIdList`/`delAbilityIdList` 非空时从合并结果中去除对应项；同时回填 `contextVos`、`recallVo` 及 `aiFlag`/`roundNum`/`pasteFlag`。**更新成功时**在 `data` 中附加 `previewUrl`、`taskName`、`taskType`（规则与 `create_question_task` 一致，`taskType=30`，路径含 `/aiTalk`）

输入参数：

- `taskId`（int，必填）：任务标识
- `instanceId`（int，必填）：课程实例标识
- `title`（string，可选）：讨论任务标题；未传时从查询结果回填
- `background`（string，可选）：讨论背景描述；未传时从查询结果回填
- `question`（string，可选）：讨论具体内容；未传时从查询结果回填
- `pointIdList`（int[]，可选）：关联知识点标识列表；非空时与查询结果取合集
- `abilityIdList`（int[]，可选）：关联能力标识列表；非空时与查询结果取合集
- `delPointIdList`（int[]，可选）：待移除的知识点标识列表；非空时从合并结果中去除
- `delAbilityIdList`（int[]，可选）：待移除的能力标识列表；非空时从合并结果中去除

示例（仅修改标题，其余字段自动回填）：

```json
{
  "tool": "update_question_task_detail",
  "arguments": {
    "taskId": 10002,
    "instanceId": 5442,
    "title": "生活中接触到的NLP技术（修订）"
  }
}
```

示例（完整覆盖）：

```json
{
  "tool": "update_question_task_detail",
  "arguments": {
    "taskId": 10002,
    "instanceId": 5442,
    "title": "生活中接触到的NLP技术（修订）",
    "background": "修订后的讨论背景",
    "question": "修订后的讨论内容",
    "pointIdList": [123, 456],
    "abilityIdList": [10, 20]
  }
}
```

成功返回示例：

```json
{
  "code": 200,
  "message": "更新AI辩论任务成功！",
  "data": {
    "taskId": 10002,
    "taskName": "生活中接触到的NLP技术（修订）",
    "taskType": 30,
    "previewUrl": "http://brains.v2.zhituxueyuan.cn/student/course/0/studyTask/10002/aiTalk?taskId=10002&instanceId=5442"
  }
}
```

说明：`previewUrl` 基址与创建类 Tool 相同（请求头 `Origin` 或 `SERVER_API_URL`）；更新失败时不附加预览字段。

后端：`POST /api/teach/task/updateTaskOpenQuestion`（提交前内部调用 `query_question_task_detail` 回填未传字段与配置）

#### create_pbl_task

- 名称：`create_pbl_task`
- 说明：创建 PBL 项目式教学任务，直接保存任务配置（无 AI 生成步骤）

输入参数：

- `instanceId`（int，必填）：课程实例标识
- `taskName`（string，必填）：任务名称
- `pblDescription`（string，必填）：任务说明
- `standard`（string，必填）：评分标准
- `referenceAnswer`（string，必填）：参考答案
- `groupDiscussFlag`（int，必填）：是否开启分组讨论 `0` / `1`
- `discussionTopic`（string，可选）：讨论主题（`groupDiscussFlag=1` 时必填）
- `discussionDescription`（string，可选）：讨论说明（`groupDiscussFlag=1` 时必填）
- `pointIdList`（array[int]，可选）：关联知识点标识列表
- `abilityIdList`（array[int]，可选）：任务关联能力；保留显式空列表，未传时省略
- `customColumnId`（integer/int64，可选）：自定义栏目标识；支持 0，未传时省略
- `replyType`（integer/int32，可选）：回复类型；10：立即回复；20：延迟回复；未传时省略
- `contextVos`（array，可选）：知识库回答范围设置
- `recallVo`（object，可选）：召回参数
- `unitId`（int，可选，默认 0）：目录节标识

示例：

```json
{
  "tool": "create_pbl_task",
  "arguments": {
    "instanceId": 5442,
    "taskName": "使用NLTK学习语言模型",
    "pblDescription": "使用 Python NLTK 学习语言模型的创建、训练和评估。",
    "standard": "1. 能定义语言模型；2. 能训练语言模型；3. 能评估语言模型。",
    "referenceAnswer": "使用 NLTK 实现语言模型并完成训练与评估。",
    "groupDiscussFlag": 0,
    "pointIdList": [123, 456],
    "unitId": 1024
  }
}
```

后端链路：

- `POST /api/teach/task/pbl/newTaskPbl`

#### query_pbl_task_detail

- 名称：`query_pbl_task_detail`
- 说明：查询 PBL 项目式教学任务详情；成功时对 `data` 做白名单字段精简

输入参数：

- `taskId`（int，必填）：任务标识

示例：

```json
{
  "tool": "query_pbl_task_detail",
  "arguments": {
    "taskId": 10001
  }
}
```

成功返回示例（已优化）：

```json
{
  "code": 200,
  "message": "查询PBL任务详情成功！",
  "data": {
    "taskId": 10001,
    "instanceId": 5442,
    "taskName": "使用NLTK学习语言模型",
    "description": "使用 Python NLTK 学习语言模型的创建、训练和评估。",
    "groupDiscussFlag": 0,
    "discussionTopic": "",
    "discussionDescription": "",
    "linkTaskId": 0,
    "standard": "1. 能定义语言模型；2. 能训练语言模型；3. 能评估语言模型。",
    "referenceAnswer": "使用 NLTK 实现语言模型并完成训练与评估。",
    "teachTaskPblFileList": [{"fileId": 1, "fileName": "参考资料.pdf"}],
    "pointIdList": [123, 456],
    "abilityIdList": [10]
  }
}
```

后端链路：`POST /api/teach/task/pbl/queryTaskPbl`

#### update_pbl_task_detail

- 名称：`update_pbl_task_detail`
- 说明：编辑更新 PBL 项目式教学任务；提交前先查询详情回填，再调用更新接口。**更新成功时**在 `data` 中附加 `previewUrl`、`taskName`、`taskType`（规则与 `create_pbl_task` 一致，`taskType=140`，路径含 `/pbl`；`instanceId` 取自最终提交值）

输入参数：

- `taskId`（int，必填）：任务标识
- `instanceId`（int，可选）：课程实例标识（为空时从查询结果回填）
- `taskName` / `description` / `standard` / `referenceAnswer`（string，可选）：为空时回填
- `groupDiscussFlag`（int，可选）：是否开启分组讨论 `0`/`1`（未传时回填；为 `1` 时讨论主题与说明不能为空）
- `discussionTopic` / `discussionDescription`（string，可选）：为空时回填
- `linkTaskId`（int，可选）：关联任务标识（未传时回填）
- `teachTaskPblFileList`（array，可选）：附件列表；新增项须含 `fileName`、`fileSize`、`fileTempPath`、`fileRelativePath`；为空时由查询结果映射回填 `fileId`/`fileName`
- `pointIdList` / `abilityIdList`（array[int]，可选）：与查询结果取合集
- `replyType`（integer/int32，可选）：回复类型；10：立即回复；20：延迟回复；显式传入覆盖，未传时保留查询原值，原值缺失/null 时省略
- `delPointIdList` / `delAbilityIdList`（array[int]，可选）：从合并结果中去除

回填与合并规则：

1. 先调用 `queryTaskPbl`；失败则中止，不更新
2. 文本为空时回填；`groupDiscussFlag`/`linkTaskId` 仅未传（`None`）时回填
3. `pointIdList`/`abilityIdList` 以查询为基线，合集后再按删除列表去除
4. `teachTaskPblFileList` 为空且查询非空时映射回填
5. 始终回填 `contextVos`、`recallVo`（及 `unitId` 若有）

示例：

```json
{
  "tool": "update_pbl_task_detail",
  "arguments": {
    "taskId": 10001,
    "taskName": "更新后的PBL任务",
    "groupDiscussFlag": 1,
    "discussionTopic": "讨论主题",
    "discussionDescription": "讨论说明",
    "standard": "更新后的评分标准",
    "referenceAnswer": "更新后的参考答案",
    "teachTaskPblFileList": [
      {
        "fileName": "新附件.pdf",
        "fileSize": 1024,
        "fileTempPath": "tmp/xxx.pdf",
        "fileRelativePath": "pbl/xxx.pdf"
      }
    ],
    "pointIdList": [789],
    "delPointIdList": [123]
  }
}
```

成功响应示例：

```json
{
  "code": 200,
  "message": "更新PBL任务成功！",
  "data": {
    "taskId": 10001,
    "taskName": "更新后的PBL任务",
    "taskType": 140,
    "previewUrl": "http://brains.v2.zhituxueyuan.cn/student/course/0/studyTask/10001/pbl?taskId=10001&instanceId=5442"
  }
}
```

说明：`previewUrl` 基址与创建类 Tool 相同；拼装所用 `instanceId` 为最终提交值（入参优先，否则查询回填）；无有效 `instanceId` 或更新失败时不附加 `previewUrl`。

后端链路：`POST /api/teach/task/pbl/queryTaskPbl` → `POST /api/teach/task/pbl/updateTaskPbl`

#### query_pbl_task_review_list

- 名称：`query_pbl_task_review_list`
- 说明：查询 PBL 任务批阅列表（按小组）；`groupId` 未传时默认 `0`，`page` 默认 `{pageNo:1, pageSize:50}`；成功返回时对 `rowList` 中字段名含 `score` 且非空的数值除以 100

输入参数：

- `taskId`（int，必填）：任务标识
- `classId`（int，必填）：班级标识
- `groupId`（int，可选）：分组标识；未传时默认 `0`
- `submitStatus`（int，可选）：提交状态筛选
- `appealStatus`（int，可选）：申诉状态筛选
- `searchText`（string，可选）：搜索关键字
- `page`（object，可选）：分页对象，含 `pageNo`、`pageSize`

示例：

```json
{
  "tool": "query_pbl_task_review_list",
  "arguments": {
    "taskId": 10001,
    "classId": 24135,
    "page": {"pageNo": 1, "pageSize": 50}
  }
}
```

后端：`POST /api/teach/task/pbl/listTaskGroupReview`

#### query_pbl_next_review_group

- 名称：`query_pbl_next_review_group`
- 说明：查询 PBL 任务下一个批阅小组；成功响应中含 `score` 的非空字段自动 ÷100

输入参数：

- `taskId`（int，必填）：任务标识
- `classId`（int，必填）：班级标识
- `taskGroupId`（int，必填）：当前批阅小组标识

示例：

```json
{
  "tool": "query_pbl_next_review_group",
  "arguments": {
    "taskId": 10001,
    "classId": 24135,
    "taskGroupId": 11
  }
}
```

后端：`POST /api/teach/task/pbl/queryNextReviewGroup`

#### query_pbl_group_submit_detail

- 名称：`query_pbl_group_submit_detail`
- 说明：查询小组 PBL 任务提交详情；成功返回时对响应中字段名含 `score` 且非空的数值递归除以 100

输入参数：

- `taskId`（int，必填）：任务标识
- `taskGroupId`（int，必填）：任务小组标识
- `instanceId`（int，必填）：课程实例标识

示例：

```json
{
  "tool": "query_pbl_group_submit_detail",
  "arguments": {
    "taskId": 10001,
    "taskGroupId": 11,
    "instanceId": 5442
  }
}
```

后端：`POST /api/teach/task/pbl/queryReviewDetail`

#### save_pbl_group_review

- 名称：`save_pbl_group_review`
- 说明：保存小组 PBL 任务批阅结果；入参 `score` 为可读分值，提交时自动 ×100；`markType` 未传或为空时默认 `30`

输入参数：

- `learnPblId`（int，必填）：学习 PBL 标识
- `taskId`（int，必填）：任务标识
- `taskGroupId`（int，必填）：任务小组标识
- `instanceId`（int，必填）：课程实例标识
- `score`（number，必填）：得分（可读分值，提交时自动 ×100）
- `comment`（string，必填）：批阅评语
- `markType`（int，可选）：批阅类型；未传或为空时默认 `30`

示例：

```json
{
  "tool": "save_pbl_group_review",
  "arguments": {
    "learnPblId": 21,
    "taskId": 10001,
    "taskGroupId": 11,
    "instanceId": 5442,
    "score": 85,
    "comment": "表现良好"
  }
}
```

后端：`POST /api/teach/task/pbl/saveGroupReview`

#### create_experiment_task

- 名称：`create_experiment_task`
- 说明：创建线下实验任务；可选上传附件，实验步骤包装为 `{content}` 后保存

输入参数：

- `instanceId`（int，必填）：课程实例标识
- `taskName`（string，必填）：任务名称
- `manualFlag`（int，必填）：是否包含实验手册 `0` / `1`
- `taskDescription`（string，可选）：任务说明
- `reportFlag`（int，可选，默认 0）：是否包含实验报告；为 `1` 时须传 `criterion`
- `criterion`（string，可选）：实验报告评分标准
- `experimentContent`（array[string]，可选）：实验步骤内容列表
- `aiFlag`（int，可选，默认 0）：是否开启 AI 助教
- `pointIdList`（array[int]，可选）：关联知识点标识列表
- `abilityIdList`（array[int]，可选）：任务关联能力，透传至 `newExperiment`；显式空列表也透传，未传时省略
- `customColumnId`（integer/int64，可选）：自定义栏目标识，支持 0；未传时省略
- `contextVos`（array，可选）：知识库回答范围设置
- `recallVo`（object，可选）：召回参数
- `uploadVoList`（array[string]，可选）：附件本地路径（最多 5 个，须为 MCP 服务端可读路径）
- `unitId`（int，可选，默认 0）：目录节标识

示例：

```json
{
  "tool": "create_experiment_task",
  "arguments": {
    "instanceId": 5442,
    "taskName": "Python文本处理实验",
    "manualFlag": 1,
    "reportFlag": 1,
    "taskDescription": "本实验演示 Python 文件操作与正则表达式基础用法。",
    "criterion": "实验报告需包含实验目的、步骤、代码实现、结果分析与总结。",
    "experimentContent": ["安装 Python 环境", "文本读写练习", "正则匹配练习"],
    "aiFlag": 1,
    "pointIdList": [123, 456],
    "unitId": 1024
  }
}
```

后端链路：

- `POST /api/resource/upload/file`（`uploadType=experimentAt`，可选附件）
- `POST /api/teach/task/experiment/newExperiment`（`reviewType=10`）

#### query_experiment_task_detail

- 名称：`query_experiment_task_detail`
- 说明：查询线下实验任务详情；成功时对 `data` 做白名单字段精简

输入参数：

- `taskId`（int，必填）：任务标识

示例：

```json
{
  "tool": "query_experiment_task_detail",
  "arguments": {
    "taskId": 10001
  }
}
```

成功返回示例（已优化）：

```json
{
  "code": 200,
  "message": "查询线下实验任务详情成功！",
  "data": {
    "taskId": 10001,
    "instanceId": 5442,
    "taskName": "Python文本处理实验",
    "description": "本实验演示 Python 文件操作与正则表达式基础用法。",
    "criterion": "实验报告需包含实验目的、步骤、代码实现、结果分析与总结。",
    "experimentContent": [{"content": "安装 Python 环境"}],
    "experimentFileList": [{"fileId": 1, "fileName": "手册.pdf"}],
    "aiFlag": 1,
    "manualFlag": 1,
    "reportFlag": 1,
    "pointIdList": [123, 456],
    "abilityIdList": [10]
  }
}
```

后端链路：`POST /api/teach/task/experiment/queryExperimentTask`

#### query_experiment_review_student_list

- 名称：`query_experiment_review_student_list`
- 说明：按任务与班级查询线下实验批阅学生列表；成功返回时对响应中字段名含 `score` 且非空的数值递归除以 100

输入参数：

- `taskId`（int，必填）：任务标识
- `classId`（int，必填）：班级标识
- `groupId`（int，可选）：分组标识；未传时默认 `0`
- `submitStatus`（int，可选）：提交状态筛选
- `markStatus`（int，可选）：批阅状态筛选
- `appealStatus`（int，可选）：申诉状态筛选
- `searchText`（string，可选）：搜索关键字
- `page`（object，可选）：分页，含 `pageNo`、`pageSize`；默认 `{pageNo: 1, pageSize: 50}`

示例：

```json
{
  "tool": "query_experiment_review_student_list",
  "arguments": {
    "taskId": 10001,
    "classId": 24135,
    "markStatus": 10,
    "page": {"pageNo": 1, "pageSize": 50}
  }
}
```

后端：`POST /api/teach/task/experiment/listReviewStudent`

#### query_experiment_class_stat

- 名称：`query_experiment_class_stat`
- 说明：查询线下实验完成率成绩分布统计

输入参数：

- `taskId`（int，必填）：任务标识
- `classId`（int，必填）：班级标识
- `status`（int，可选）：状态筛选，仅允许 `0` 或 `10`；未传时默认 `10`

示例：

```json
{
  "tool": "query_experiment_class_stat",
  "arguments": {
    "taskId": 10001,
    "classId": 24135,
    "status": 10
  }
}
```

后端：`POST /api/teach/task/experiment/queryClassStat`

#### query_experiment_student_submit_detail

- 名称：`query_experiment_student_submit_detail`
- 说明：按任务、班级、学生查询线下实验提交详情；成功返回时对响应中字段名含 `score` 且非空的数值递归除以 100

输入参数：

- `taskId`（int，必填）：任务标识
- `classId`（int，必填）：班级标识
- `userId`（int，必填）：学生用户标识

示例：

```json
{
  "tool": "query_experiment_student_submit_detail",
  "arguments": {
    "taskId": 10001,
    "classId": 24135,
    "userId": 1001
  }
}
```

后端：`POST /api/teach/task/experiment/queryStuExperiment`

#### save_experiment_student_review

- 名称：`save_experiment_student_review`
- 说明：保存学生线下实验批阅结果；入参 `score` 为可读分值，提交时自动 ×100；成功响应中含 `score` 的非空字段自动 ÷100

输入参数：

- `taskId`（int，必填）：任务标识
- `classId`（int，必填）：班级标识
- `userId`（int，必填）：学生用户标识
- `score`（number，必填）：得分（可读分值，提交时自动 ×100）
- `comment`（string，必填）：批阅评语
- `rejectReason`（string，可选）：申诉处理理由

示例：

```json
{
  "tool": "save_experiment_student_review",
  "arguments": {
    "taskId": 10001,
    "classId": 24135,
    "userId": 1001,
    "score": 85,
    "comment": "表现良好"
  }
}
```

后端：`POST /api/teach/task/experiment/updateExperimentStudentReview`

#### update_experiment_task_detail

- 名称：`update_experiment_task_detail`
- 说明：编辑更新线下实验任务；提交前先查询详情回填，再调用更新接口。**更新成功时**在 `data` 中附加 `previewUrl`、`taskName`、`taskType`（规则与 `create_experiment_task` 一致，`taskType=70`，路径含 `/experiment`；`instanceId` 取自查询回填）

输入参数：

- `taskId`（int，必填）：任务标识
- `taskName`（string，可选）：任务名称（为空时从查询结果回填）
- `description`（string，可选）：任务说明（为空时回填）
- `criterion`（string，可选）：实验报告评分标准（为空时回填；`reportFlag=1` 时不能为空）
- `experimentContent`（array，可选）：实验手册内容，项含 `detailId`、`content`（新增模块 `detailId=0`；为空时回填）
- `uploadVoList`（array，可选）：附件列表；新增项须含 `fileName`、`fileTempPath`；为空时由查询 `experimentFileList` 映射回填 `fileId`/`fileName`
- `aiFlag`（int，可选）：是否开启 AI 助教 `0`/`1`（未传时回填；显式 `0` 不会被覆盖）
- `manualFlag`（int，可选）：是否提交实验手册 `0`/`1`（未传时回填；为 `1` 时 `experimentContent` 不能为空）
- `reportFlag`（int，可选）：是否提交实验报告 `0`/`1`（未传时回填；为 `1` 时 `criterion` 不能为空）
- `pointIdList` / `abilityIdList`（array[int]，可选）：与查询结果取合集
- `customColumnId`（integer/int64，可选）：自定义栏目标识；传入时覆盖（包括 0），未传时保留原栏目，查询无栏目时省略
- `delPointIdList` / `delAbilityIdList`（array[int]，可选）：从合并结果中去除

回填与合并规则：

1. 先调用 `queryExperimentTask`；失败则中止，不更新
2. 文本/`experimentContent` 为空时回填；开关仅未传（`None`）时回填
3. `pointIdList`/`abilityIdList` 以查询为基线，合集后再按删除列表去除
4. `uploadVoList` 为空且查询 `experimentFileList` 非空时映射回填
5. 始终回填 `contextVos`、`recallVo`（及 `instanceId`/`unitId` 若有）

示例：

```json
{
  "tool": "update_experiment_task_detail",
  "arguments": {
    "taskId": 10001,
    "taskName": "更新后的实验任务",
    "manualFlag": 1,
    "reportFlag": 1,
    "criterion": "实验报告需包含目的、步骤与结果分析",
    "experimentContent": [
      { "detailId": 1, "content": "更新步骤" },
      { "detailId": 0, "content": "新增手册模块" }
    ],
    "uploadVoList": [
      { "fileName": "新附件.pdf", "fileTempPath": "tmp/xxx.pdf" }
    ],
    "pointIdList": [789],
    "delPointIdList": [123]
  }
}
```

成功响应示例：

```json
{
  "code": 200,
  "message": "更新线下实验任务成功！",
  "data": {
    "taskId": 10001,
    "taskName": "更新后的实验任务",
    "taskType": 70,
    "previewUrl": "http://brains.v2.zhituxueyuan.cn/student/course/0/studyTask/10001/experiment?taskId=10001&instanceId=5442"
  }
}
```

说明：`previewUrl` 基址与创建类 Tool 相同；拼装所用 `instanceId` 来自查询回填；查询无有效 `instanceId` 或更新失败时不附加 `previewUrl`。

后端链路：`POST /api/teach/task/experiment/queryExperimentTask` → `POST /api/teach/task/experiment/updateExperiment`

#### create_code_task

- 名称：`create_code_task`
- 说明：创建代码训练任务；根据 `taskDescription` 经 AI 生成完整任务（说明、预设代码、初始化脚本）后保存

输入参数：

- `instanceId`（int，必填）：课程实例标识
- `taskName`（string，必填）：代码训练任务名称
- `codeType`（int，必填）：实验类型 `10`(Linux) / `20`(C语言) / `30`(MySQL) / `40`(Python)
- `taskDescription`（string，可选）：实验任务说明（作为 AI 生成输入）
- `aiFlag`（int，可选，默认 0）：是否和 AI 助教讨论 `1` 是 / `0` 否
- `pointIdList`（array[int]，可选）：关联知识点标识列表
- `abilityIdList`（array[int]，可选）：任务关联能力；提供时原样提交（包括 `[]`），未传时省略
- `customColumnId`（integer/int64，可选）：自定义栏目标识；提供时原样提交（包括 `0`），未传时省略
- `contextVos`（array，可选）：知识库回答范围设置
- `recallVo`（object，可选）：召回参数（`maxRecallNum`、`minMatch`、`showSourceFlag`）
- `unitId`（int，可选，默认 0）：任务归属的课程目录节标识

示例：

```json
{
  "tool": "create_code_task",
  "arguments": {
    "abilityIdList": [10, 20],
    "customColumnId": 101,
    "instanceId": 5442,
    "taskName": "Python文本处理基础",
    "codeType": 40,
    "taskDescription": "开发简单的文本情感分析工具，掌握字符串操作与条件语句。",
    "aiFlag": 1,
    "pointIdList": [123, 456],
    "unitId": 1024,
    "contextVos": [
      {
        "contentType": 10,
        "baseType": 10,
        "repositoryType": 123,
        "useId": "91,93",
        "useName": "作业,练习"
      }
    ],
    "recallVo": {
      "maxRecallNum": 3,
      "minMatch": 0.7,
      "showSourceFlag": 0
    }
  }
}
```

后端链路：

- `POST /api/teach/ai/lesson/code/chat`（SSE）
- `POST /api/teach/ai/lesson/code/queryResult`
- `POST /api/teach/ai/lesson/code/finishQuery`
- `POST /api/teach/task/code/newTaskCode`（敏感字段 AES 加密）

#### query_code_task_detail

- 名称：`query_code_task_detail`
- 说明：查询代码训练任务详情；成功时对 `data` 做白名单字段精简

输入参数：

- `taskId`（int，必填）：任务标识

示例：

```json
{
  "tool": "query_code_task_detail",
  "arguments": {
    "taskId": 10001
  }
}
```

成功返回示例（已优化）：

```json
{
  "code": 200,
  "message": "查询代码训练任务详情成功！",
  "data": {
    "taskId": 10001,
    "taskName": "Python文本处理基础",
    "type": 40,
    "description": "开发简单的文本情感分析工具",
    "initScript": "print('hello')",
    "presetCode": "# TODO",
    "aiFlag": 1,
    "pointIdList": [123, 456],
    "abilityIdList": [10]
  }
}
```

后端链路：`POST /api/teach/task/code/queryTaskCode`

#### update_code_task_detail

- 名称：`update_code_task_detail`
- 说明：编辑更新代码训练任务；提交前先 `queryTaskCode` 回填空字段与 `contextVos`/`recallVo`。**更新成功时**在 `data` 中附加 `previewUrl`、`taskName`、`taskType`（规则与 `create_code_task` 一致，`taskType=90`，路径含 `/code`；`instanceId` 取自必填入参）

输入参数：

- `taskId`（int，必填）：任务标识
- `instanceId`（int，必填）：课程实例标识
- `type`（int，必填）：实验类型 `10`(Linux) / `20`(C语言) / `30`(MySQL) / `40`(Python)
- `taskName`（string，可选）：任务名称；空则从查询回填
- `description`（string，可选）：任务说明；空则从查询回填
- `presetCode`（string，可选）：预置代码；空则从查询回填
- `initScript`（string，可选）：初始化脚本；空则从查询回填
- `aiFlag`（int，可选）：是否和 AI 助教讨论；未传则从查询回填
- `pointIdList`（array[int]，可选）：关联知识点；非空时与查询结果取合集
- `abilityIdList`（array[int]，可选）：关联能力；非空时与查询结果取合集
- `customColumnId`（integer/int64，可选）：自定义栏目标识；传入时覆盖（包括 `0`），未传时保留查询结果，无原值则省略
- `delPointIdList`（array[int]，可选）：待删除知识点；非空时从合并结果中去除
- `delAbilityIdList`（array[int]，可选）：待删除能力；非空时从合并结果中去除

回填规则：

- `taskName`/`description`/`presetCode`/`initScript` 为空时从查询结果回填
- `contextVos`/`recallVo` 始终从查询结果回填（不暴露为 Tool 入参）
- `pointIdList`/`abilityIdList` 未传或为空时使用查询基线；非空时取合集；再应用删除列表
- `instanceId` 始终使用入参，写入更新请求体

示例（仅改名称与合集知识点）：

```json
{
  "tool": "update_code_task_detail",
  "arguments": {
    "customColumnId": 102,
    "taskId": 10001,
    "instanceId": 5442,
    "type": 40,
    "taskName": "Python文本处理进阶",
    "pointIdList": [789],
    "delPointIdList": [123]
  }
}
```

成功响应示例：

```json
{
  "code": 200,
  "message": "更新代码训练任务成功！",
  "data": {
    "taskId": 10001,
    "taskName": "Python文本处理进阶",
    "taskType": 90,
    "previewUrl": "http://brains.v2.zhituxueyuan.cn/student/course/0/studyTask/10001/code?taskId=10001&instanceId=5442"
  }
}
```

说明：`previewUrl` 基址与创建类 Tool 相同；拼装所用 `instanceId` 为必填入参；更新失败时不附加预览字段。

后端链路：`POST /api/teach/task/code/queryTaskCode` → `POST /api/teach/task/code/updateTaskCode`（敏感字段 AES 加密）

#### create_writing_task

- 名称：`create_writing_task`
- 说明：创建作文写作任务；`example` 未提供时由 AI 自动生成参考样卷

输入参数：

- `instanceId`（int，必填）：课程实例标识（替代旧版全局 `INSTANCE_ID`）
- `taskName`（string，必填）：作文写作任务名称
- `taskDescription`（string，必填）：作文写作任务说明
- `criterion`（string，必填）：AI 评分标准（建议不少于三条）
- `example`（string，可选）：AI 参考样卷（建议不少于 300 字；为空时由 AI 生成）
- `pointIdList`（array[int]，可选）：关联知识点标识列表（保存时透传至 `newTaskWriting.pointIdList`；同义别名 `pointIds`）
- `abilityIdList`（array[int]，可选）：任务关联能力；显式空列表也透传，未传时省略
- `customColumnId`（integer/int64，可选）：自定义栏目标识；支持 0，未传时省略
- `contextVos`（array，可选）：知识库回答范围设置
- `recallVo`（object，可选）：召回参数（`maxRecallNum`、`minMatch`、`showSourceFlag`）
- `unitId`（int，可选，默认 0）：任务归属的课程目录节标识

示例（完整输入，直接保存）：

```json
{
  "tool": "create_writing_task",
  "arguments": {
    "instanceId": 5442,
    "taskName": "我的暑假生活",
    "taskDescription": "请结合暑假经历写一篇记叙文",
    "criterion": "1. 结构完整\n2. 语言流畅\n3. 情感真挚",
    "example": "（此处为不少于300字的参考样卷全文）",
    "pointIdList": [123, 456],
    "unitId": 1024
  }
}
```

示例（省略 example，由 AI 生成范文后保存）：

```json
{
  "tool": "create_writing_task",
  "arguments": {
    "instanceId": 5442,
    "taskName": "我的暑假生活",
    "taskDescription": "请结合暑假经历写一篇记叙文",
    "criterion": "1. 结构完整\n2. 语言流畅\n3. 情感真挚"
  }
}
```

AI 生成链路（仅 `example` 为空时）：

- `POST /api/teach/task/writing/taskWritingChat`
- `POST /api/teach/ai/lesson/writing/queryResult`
- `POST /api/teach/ai/lesson/writing/finishQuery`
- `POST /api/teach/task/writing/newTaskWriting`

#### query_writing_task_detail

- 名称：`query_writing_task_detail`
- 说明：查询作文写作任务详情；成功时对 `data` 做白名单字段精简

输入参数：

- `taskId`（int，必填）：任务标识

示例：

```json
{
  "tool": "query_writing_task_detail",
  "arguments": {
    "taskId": 10001
  }
}
```

成功返回示例（已优化）：

```json
{
  "code": 200,
  "message": "查询作文写作任务详情成功！",
  "data": {
    "taskId": 10001,
    "taskName": "我的暑假生活",
    "description": "请结合暑假经历写一篇记叙文",
    "criterion": "1. 结构完整\n2. 语言流畅\n3. 情感真挚",
    "example": "参考样卷全文……",
    "pointIdList": [123, 456],
    "abilityIdList": [10]
  }
}
```

后端链路：`POST /api/teach/task/writing/queryTaskWritingByTaskId`

#### query_writing_review_student_list

- 名称：`query_writing_review_student_list`
- 说明：按任务与班级查询作文写作批阅学生列表；成功返回时对响应中字段名含 `score` 且非空的数值递归除以 100

输入参数：

- `taskId`（int，必填）：任务标识
- `classId`（int，必填）：班级标识
- `groupId`（int，可选）：分组标识；未传时默认 `0`
- `submitStatus`（int，可选）：提交状态筛选
- `markStatus`（int，可选）：批阅状态筛选
- `appealStatus`（int，可选）：申诉状态筛选
- `searchText`（string，可选）：搜索关键字
- `page`（object，可选）：分页，含 `pageNo`、`pageSize`；默认 `{pageNo: 1, pageSize: 50}`

示例：

```json
{
  "tool": "query_writing_review_student_list",
  "arguments": {
    "taskId": 10001,
    "classId": 24135,
    "markStatus": 10,
    "page": {"pageNo": 1, "pageSize": 50}
  }
}
```

后端：`POST /api/teach/task/writing/listReviewStudent`

#### query_writing_task_complete

- 名称：`query_writing_task_complete`
- 说明：查询作文写作完成率统计

输入参数：

- `taskId`（int，必填）：任务标识
- `classId`（int，必填）：班级标识
- `status`（int，可选）：状态筛选，仅允许 `0` 或 `10`；未传时默认 `10`

示例：

```json
{
  "tool": "query_writing_task_complete",
  "arguments": {
    "taskId": 10001,
    "classId": 24135,
    "status": 10
  }
}
```

后端：`POST /api/teach/task/writing/queryTaskWritingComplete`

#### query_writing_task_commit_cnt

- 名称：`query_writing_task_commit_cnt`
- 说明：查询作文写作提交次数分布统计

输入参数：

- `taskId`（int，必填）：任务标识
- `classId`（int，必填）：班级标识
- `status`（int，可选）：状态筛选，仅允许 `0` 或 `10`；未传时默认 `10`

示例：

```json
{
  "tool": "query_writing_task_commit_cnt",
  "arguments": {
    "taskId": 10001,
    "classId": 24135,
    "status": 10
  }
}
```

后端：`POST /api/teach/task/writing/queryTaskWritingCommitCnt`

#### query_writing_task_stat_score

- 名称：`query_writing_task_stat_score`
- 说明：查询作文写作学生成绩分布统计

输入参数：

- `taskId`（int，必填）：任务标识
- `classId`（int，必填）：班级标识
- `status`（int，可选）：状态筛选，仅允许 `0` 或 `10`；未传时默认 `10`

示例：

```json
{
  "tool": "query_writing_task_stat_score",
  "arguments": {
    "taskId": 10001,
    "classId": 24135,
    "status": 10
  }
}
```

后端：`POST /api/teach/task/writing/queryTaskWritingStatScore`

#### query_writing_student_submit_detail

- 名称：`query_writing_student_submit_detail`
- 说明：查询学生作文写作提交详情。先调 `queryReviewWrite` 取 `learnTaskWriteDetailRoundItems` 中的 `learnWriteDetailId`，再逐个调用 `queryReviewWriteDetail`，将各次 `learnTaskWriteDetailItem` 组合为 `data.learnTaskWriteDetailList`；成功返回时对字段名含 `score` 且非空的数值递归除以 100

输入参数：

- `taskId`（int，必填）：任务标识
- `teachClassId`（int，必填）：教学班级标识
- `userId`（int，必填）：学生用户标识

示例：

```json
{
  "tool": "query_writing_student_submit_detail",
  "arguments": {
    "taskId": 10001,
    "teachClassId": 24135,
    "userId": 1001
  }
}
```

后端链路：

- `POST /api/teach/task/writing/queryReviewWrite`
- `POST /api/teach/task/writing/queryReviewWriteDetail`（按写作会话 ID 逐个调用）

#### save_writing_student_review

- 名称：`save_writing_student_review`
- 说明：保存学生作文写作批阅结果；入参 `score` 为可读分值，提交时自动 ×100；成功响应中含 `score` 的非空字段自动 ÷100

输入参数：

- `learnWriteDetailId`（int，必填）：写作会话标识
- `taskId`（int，必填）：任务标识
- `teachClassId`（int，必填）：教学班级标识
- `userId`（int，必填）：学生用户标识
- `score`（number，必填）：得分（可读分值，提交时自动 ×100）
- `comment`（string，必填）：批阅评语
- `appealId`（int，可选）：学生 AI 申诉标识
- `rejectReason`（string，可选）：申诉处理理由

示例：

```json
{
  "tool": "save_writing_student_review",
  "arguments": {
    "learnWriteDetailId": 11,
    "taskId": 10001,
    "teachClassId": 24135,
    "userId": 1001,
    "score": 85,
    "comment": "表现良好"
  }
}
```

后端：`POST /api/teach/task/writing/updateWritingReview`

#### update_writing_task_detail

- 名称：`update_writing_task_detail`
- 说明：编辑更新作文写作任务；提交前先 `queryTaskWritingByTaskId` 回填空字段与 `contextVos`/`recallVo`。**更新成功时**在 `data` 中附加 `previewUrl`、`taskName`、`taskType`（规则与 `create_writing_task` 一致，`taskType=80`，路径含 `/writing`；`instanceId` 取自查询回填）

输入参数：

- `taskId`（int，必填）：任务标识
- `taskName`（string，可选）：任务名称；空则从查询回填
- `description`（string，可选）：任务说明；空则从查询回填
- `criterion`（string，可选）：AI 评分标准；空则从查询回填
- `example`（string，可选）：参考样卷；空则从查询回填
- `pointIdList`（array[int]，可选）：关联知识点；非空时与查询结果取合集
- `abilityIdList`（array[int]，可选）：任务关联能力；非空时与查询结果取合集，未传或空列表保留原值；移除能力使用 `delAbilityIdList`
- `delPointIdList`（array[int]，可选）：待删除知识点；非空时从合并结果中去除
- `delAbilityIdList`（array[int]，可选）：待删除能力；非空时从合并结果中去除

回填规则：

- `taskName`/`description`/`criterion`/`example` 为空时从查询结果回填
- `contextVos`/`recallVo` 始终从查询结果回填（不暴露为 Tool 入参）
- `pointIdList`/`abilityIdList` 未传或为空时使用查询基线；非空时取合集；再应用删除列表
- 若查询结果含 `instanceId`/`unitId`，一并回填写入更新请求体

示例（仅改名称与合集知识点）：

```json
{
  "tool": "update_writing_task_detail",
  "arguments": {
    "taskId": 10001,
    "taskName": "我的暑假生活（修订）",
    "pointIdList": [789],
    "delPointIdList": [123]
  }
}
```

成功响应示例：

```json
{
  "code": 200,
  "message": "更新作文写作任务成功！",
  "data": {
    "taskId": 10001,
    "taskName": "我的暑假生活（修订）",
    "taskType": 80,
    "previewUrl": "http://brains.v2.zhituxueyuan.cn/student/course/0/studyTask/10001/writing?taskId=10001&instanceId=5442"
  }
}
```

说明：`previewUrl` 基址与创建类 Tool 相同；拼装所用 `instanceId` 来自查询回填；查询无有效 `instanceId` 或更新失败时不附加 `previewUrl`。

后端链路：`POST /api/teach/task/writing/queryTaskWritingByTaskId` → `POST /api/teach/task/writing/updateTaskWriting`

#### query_exercise_task_detail

- 名称：`query_exercise_task_detail`
- 说明：查询常规练习任务详情；内部分别调用 `queryConfig` 与 `listQuiz` 并组合响应，仅返回 `taskId`、`taskName`、`description`、`pointIdList`、`abilityIdList`、`quizList`；`quizList` 中 `score` 为实际分值（后端值 ÷ 100）

输入参数：

- `taskId`（int，必填）：任务标识

示例：

```json
{
  "tool": "query_exercise_task_detail",
  "arguments": {
    "taskId": 10001
  }
}
```

成功返回示例：

```json
{
  "code": 200,
  "message": "查询常规练习任务详情成功！",
  "data": {
    "taskId": 10001,
    "taskName": "NPL基本概念",
    "description": "本练习旨在测试学生对NPL基本概念的理解程度。",
    "pointIdList": [1000187401, 1000187400],
    "abilityIdList": [101, 102],
    "quizList": [
      {
        "quizId": 100018815,
        "quizType": 10,
        "content": "题干内容",
        "solution": "解析",
        "optionList": [{"content": "选项A", "answerFlag": 1}],
        "pointName": "NLP基础",
        "score": 3
      }
    ]
  }
}
```

后端链路：

- `POST /api/teach/task/exercise/queryConfig`
- `POST /api/teach/task/exercise/listQuiz`

#### update_exercise_task_detail

- 名称：`update_exercise_task_detail`
- 说明：编辑更新常规练习任务。提交前先调用 `query_exercise_task_detail` 回填未传字段；`pointIdList`/`abilityIdList` 非空时取合集，`delPointIdList`/`delAbilityIdList` 非空时去除对应项。先调用 `saveAttr`（`materialFlag=0`）；`quizList` 非空时再经 `checkQuizAdd` 检测，仅对顶层题目调用 `queryQuiz` 组织 `typeList`（不遍历 `subList`），最后调用 `saveQuiz`。**更新成功时**在 `data` 中附加 `previewUrl`、`taskName`、`taskType`（规则与创建常规练习一致，`taskType=50`，路径含 `/practiceHome`；`instanceId` 入参优先，否则从 `queryConfig` 回填）

输入参数：

- `taskId`（int，必填）：任务标识
- `instanceId`（int，可选）：课程实例标识；为空时从 `queryConfig` 回填，用于拼装预览地址
- `taskName`（string，可选）：练习名称；为空时从查询结果回填
- `description`（string，可选）：练习说明；为空时从查询结果回填
- `pointIdList`（int[]，可选）：关联知识点；非空时与查询结果取合集
- `abilityIdList`（int[]，可选）：关联能力点；非空时与查询结果取合集
- `customColumnId`（integer/int64，可选）：自定义栏目标识；传入时覆盖（包括 0），未传时保留原栏目，查询无栏目时省略
- `quizList`（array，可选）：题目列表，每项含 `quizId`、`score`；可选 `subList`、`fillList`（`fillList` 项为 `{responseId, score}`）
- `continueEndTime`（string，可选）：重做截止时间（ISO 8601）；`checkQuizAdd` 返回 `submitCount>0` 且修改题目时必填
- `delPointIdList`（int[]，可选）：待移除的知识点 ID
- `delAbilityIdList`（int[]，可选）：待移除的能力点 ID

示例（仅更新名称与说明）：

```json
{
  "tool": "update_exercise_task_detail",
  "arguments": {
    "taskId": 10001,
    "instanceId": 5442,
    "taskName": "NPL基本概念（修订）"
  }
}
```

示例（更新题目，已有人作答需设置重做截止时间）：

```json
{
  "tool": "update_exercise_task_detail",
  "arguments": {
    "taskId": 10001,
    "quizList": [
      {"quizId": 100018815, "score": 30},
      {"quizId": 100018898, "score": 40, "fillList": [{"responseId": 100002847, "score": 20}]}
    ],
    "continueEndTime": "2026-12-31T23:59:59"
  }
}
```

成功响应示例：

```json
{
  "code": 200,
  "message": "更新常规练习任务成功！",
  "data": {
    "taskId": 10001,
    "taskName": "NPL基本概念（修订）",
    "taskType": 50,
    "previewUrl": "http://brains.v2.zhituxueyuan.cn/student/course/0/studyTask/10001/practiceHome?taskId=10001&instanceId=5442"
  }
}
```

说明：`previewUrl` 基址与创建类 Tool 相同；拼装所用 `instanceId` 入参优先，否则来自 `queryConfig`；无有效 `instanceId` 或更新失败时不附加 `previewUrl`。常规练习 `taskType` 为 `50`（勿与个性化练习 `60` 混淆）。

后端链路：

- `POST /api/teach/task/exercise/queryConfig` + `listQuiz`（查询回填）
- `POST /api/teach/task/exercise/saveAttr`
- `POST /api/teach/task/exercise/checkQuizAdd`（`quizList` 非空时）
- `POST /api/resource/quiz/queryQuiz`（组织 `typeList`，仅顶层题目）
- `POST /api/teach/task/exercise/saveQuiz`（`quizList` 非空时）

#### query_exercise_review_student_list

- 名称：`query_exercise_review_student_list`
- 说明：按学生查询常规练习 / 自互评练习任务批阅列表；成功时 `data` 仅保留精简后的 `rowList`

输入参数：

- `taskId`（int，必填）：任务标识
- `classId`（int，必填）：班级标识
- `groupId`（int，可选）：分组标识；未传时默认 `0`
- `submitStatus`（int，可选）：提交状态筛选
- `markStatus`（int，可选）：批阅状态筛选
- `appealStatus`（int，可选）：申诉状态筛选
- `searchText`（string，可选）：搜索关键字
- `page`（object，可选）：分页，含 `pageNo`、`pageSize`；默认 `{pageNo: 1, pageSize: 20}`

响应优化（`code=200` 时 `data` 仅含 `rowList`，每行白名单字段）：

- `userId`、`studentNo`、`realName`、`reviewType`、`submitFlag`、`markStatus`
- `aiScore`、`score`、`evaluateScore`、`appealStatus`、`evaluateType`、`evaluateStatus`
- `appealCount`、`appealAllCount`、`submitTimes`、`avgUseTime`

示例：

```json
{
  "tool": "query_exercise_review_student_list",
  "arguments": {
    "taskId": 10001,
    "classId": 24135,
    "markStatus": 10,
    "page": {"pageNo": 1, "pageSize": 20}
  }
}
```

后端：`POST /api/teach/task/exercise/listExerciseReviewStudent`

#### query_exercise_next_review_student

- 名称：`query_exercise_next_review_student`
- 说明：查询常规练习 / 自互评练习下一个批阅学生；请求体 `groupId` 固定为 `0`；成功响应中含 `score` 的非空字段自动 ÷100

输入参数：

- `taskId`（int，必填）：任务标识
- `classId`（int，必填）：班级标识
- `submitId`（int，必填）：当前提交标识
- `appealFlag`（int，必填）：申诉标记，仅允许 `0` 或 `1`
- `reviewType`（int，可选）：批阅类型

示例：

```json
{
  "tool": "query_exercise_next_review_student",
  "arguments": {
    "taskId": 10001,
    "classId": 24135,
    "submitId": 101,
    "appealFlag": 0,
    "reviewType": 10
  }
}
```

后端：`POST /api/teach/task/exercise/queryNextReviewStudent`

#### query_exercise_stat_summary

- 名称：`query_exercise_stat_summary`
- 说明：查询常规练习 / 自互评练习统计总览；成功响应中字段名以 `score` 结尾的非空数值自动 ÷100

输入参数：

- `taskId`（int，必填）：任务标识
- `classId`（int，必填）：班级标识

示例：

```json
{
  "tool": "query_exercise_stat_summary",
  "arguments": {
    "taskId": 10001,
    "classId": 24135
  }
}
```

后端：`POST /api/teach/task/exercise/queryStatSummary`

#### query_exercise_quiz_analysis

- 名称：`query_exercise_quiz_analysis`
- 说明：分页查询常规练习 / 自互评练习题目分析；成功响应中字段名以 `score` 结尾的非空数值自动 ÷100

输入参数：

- `taskId`（int，必填）：任务标识
- `classId`（int，必填）：班级标识
- `page`（object，可选）：分页，含 `pageNo`、`pageSize`；默认 `{pageNo: 1, pageSize: 10}`

示例：

```json
{
  "tool": "query_exercise_quiz_analysis",
  "arguments": {
    "taskId": 10001,
    "classId": 24135,
    "page": {"pageNo": 1, "pageSize": 10}
  }
}
```

后端：`POST /api/teach/task/exercise/queryQuizAnalysisByPage`

#### query_exercise_quiz_analysis_detail

- 名称：`query_exercise_quiz_analysis_detail`
- 说明：查询常规练习 / 自互评练习单题分析；成功响应中字段名以 `score` 结尾的非空数值自动 ÷100

输入参数：

- `taskId`（int，必填）：任务标识
- `classId`（int，必填）：班级标识
- `quizId`（int，必填）：题目标识

示例：

```json
{
  "tool": "query_exercise_quiz_analysis_detail",
  "arguments": {
    "taskId": 10001,
    "classId": 24135,
    "quizId": 9001
  }
}
```

后端：`POST /api/teach/task/exercise/queryQuizAnalysis`

#### query_task_next_review_student

- 名称：`query_task_next_review_student`
- 说明：查询场景模拟 / 线下实验 / 作文写作下一个批阅学生；先按 `taskId` 调用 `queryTask` 获取 `taskType` 再分支；请求体 `groupId` 固定为 `0`；成功响应中含 `score` 的非空字段自动 ÷100
- `taskType=40` → `/api/teach/task/scene/queryNextReviewStudent`
- `taskType=70` → `/api/teach/task/experiment/queryNextReviewStudent`
- `taskType=80` → `/api/teach/task/writing/queryNextReviewStudent`

输入参数：

- `taskId`（int，必填）：任务标识
- `classId`（int，必填）：班级标识
- `userId`（int，必填）：当前批阅用户标识
- `reviewType`（int，可选）：批阅类型

示例：

```json
{
  "tool": "query_task_next_review_student",
  "arguments": {
    "taskId": 10001,
    "classId": 24135,
    "userId": 1001,
    "reviewType": 10
  }
}
```

后端编排：`POST /api/teach/task/queryTask` → 按 `taskType` 调用对应 `queryNextReviewStudent`

#### query_exercise_review_quiz_list

- 名称：`query_exercise_review_quiz_list`
- 说明：按题目查询常规练习 / 自互评练习任务批阅列表；成功返回时对响应中字段名含 `score` 且非空的数值递归除以 100

输入参数：

- `taskId`（int，必填）：任务标识
- `classId`（int，必填）：班级标识
- `groupId`（int，可选）：分组标识；未传时默认 `0`
- `markStatus`（int，可选）：批阅状态筛选
- `appealStatus`（int，可选）：申诉状态筛选

示例：

```json
{
  "tool": "query_exercise_review_quiz_list",
  "arguments": {
    "taskId": 10001,
    "classId": 24135,
    "markStatus": 10
  }
}
```

后端：`POST /api/teach/task/exercise/listExerciseReviewQuiz`

#### query_exercise_student_submit_detail

- 名称：`query_exercise_student_submit_detail`
- 说明：查询学生常规练习 / 自互评练习提交详情。先查 `queryStudentSubmit` 取首个有效 `submitId`，再查 `queryTask` 取 `taskType`：`50` 走 `queryStudentReview`，`51` 走 `queryEvaluateStudent`；成功时 `data` 仅保留 `typeList` 与精简后的 `quizList`，并对字段名含 `score` 且非空的数值递归除以 100

输入参数：

- `taskId`（int，必填）：任务标识
- `teachClassId`（int，必填）：教学班级标识
- `userId`（int，必填）：学生用户标识
- `appealFlag`（int，可选）：申诉标记；未传时默认 `0`

响应优化（`code=200`）：

- `data.typeList`：透传后端列表（非 list 时为 `[]`）
- `data.quizList` 每项白名单：`quizId`、`quizScore`（÷100）、`quizTypeId`、`quizTypeName`、`content`、`solution`、`difficulty`、`pointName`、`quizAnswer`、`userAnswer`、`userAnswerMeta`、`score`（÷100）、`resQuizOptionList`、`aiMarkStatus`、`aiScore`、`aiComment`

示例：

```json
{
  "tool": "query_exercise_student_submit_detail",
  "arguments": {
    "taskId": 10001,
    "teachClassId": 24135,
    "userId": 30001
  }
}
```

后端链路：

- `POST /api/teach/task/exercise/queryStudentSubmit`
- `POST /api/teach/task/queryTask`
- `POST /api/teach/task/exercise/queryStudentReview`（`taskType=50`）
- `POST /api/teach/task/exercise/queryEvaluateStudent`（`taskType=51`）

#### save_exercise_student_review

- 名称：`save_exercise_student_review`
- 说明：保存学生常规练习 / 自互评练习某题的批阅结果；入参 `score` 为可读分值，提交前自动 ×100（与查询详情 ÷100 对称）

输入参数：

- `submitId`（int，必填）：提交标识
- `quizId`（int，必填）：题目标识
- `aiFlag`（int，必填）：AI 批阅标记（可为 `0`）
- `teacherFlag`（int，必填）：教师批阅标记（可为 `0`）
- `score`（number，必填）：得分（如 `85`，请求体写入 `8500`）
- `comment`（string，必填）：批阅评语
- `rejectReason`（string，可选）：驳回原因

示例：

```json
{
  "tool": "save_exercise_student_review",
  "arguments": {
    "submitId": 101,
    "quizId": 11,
    "aiFlag": 0,
    "teacherFlag": 1,
    "score": 85,
    "comment": "回答基本正确，表述可再完善"
  }
}
```

后端：`POST /api/teach/task/exercise/saveStudentReview`

#### save_common_exercise_task

- 名称：`save_common_exercise_task`
- 说明：创建或保存常规练习任务；根据 `quizList` 自动汇总各题型的 `typeList` 计分配置

输入参数：

- `instanceId`（int，必填）：课程实例标识（替代旧版全局 `INSTANCE_ID`）
- `taskName`（string，必填）：常规练习任务名称
- `quizList`（array，必填）：题目信息列表，每项含 `quizId`、`quizTypeId`、`score`；填空题可加 `fillList`（`responseId`、`score`）
- `taskDescription`（string，可选）：任务说明
- `pointIdList`（array[int]，可选）：关联知识点标识列表（透传至 `newExercise.pointIdList`）
- `abilityIdList`（array[int]，可选）：任务关联能力，透传至 `newExercise.abilityIdList`；显式空列表也透传
- `customColumnId`（integer/int64，可选）：自定义栏目标识，透传至 `newExercise.customColumnId`；支持 0，未传时省略
- `unitId`（int，可选，默认 0）：任务归属的课程目录节标识

示例：

```json
{
  "tool": "save_common_exercise_task",
  "arguments": {
    "instanceId": 5442,
    "taskName": "NPL基本概念",
    "taskDescription": "本练习旨在测试学生对NPL基本概念的理解程度。",
    "pointIdList": [1000187401, 1000187400],
    "quizList": [
      { "quizId": 100018815, "quizTypeId": 2, "score": 30 },
      { "quizId": 100018816, "quizTypeId": 2, "score": 30 },
      {
        "quizId": 100018898,
        "quizTypeId": 4,
        "score": 40,
        "fillList": [
          { "responseId": 100002847, "score": 20 },
          { "responseId": 100002848, "score": 20 }
        ]
      }
    ],
    "unitId": 1024
  }
}
```

后端链路：

- `POST /api/resource/quiz/listQuizType`（构建 `typeList`）
- `POST /api/teach/task/exercise/newExercise`

#### query_evaluate_exercise_task_detail

- 名称：`query_evaluate_exercise_task_detail`
- 说明：查询自评/互评练习任务详情；内部分别调用 `queryEvaluateConfig` 与 `listQuiz` 并组合响应，仅返回 `taskId`、`taskName`、`description`、`pointIdList`、`abilityIdList`、`quizList`；`quizList` 中 `score` 为实际分值（后端值 ÷ 100）

输入参数：

- `taskId`（int，必填）：任务标识

示例：

```json
{
  "tool": "query_evaluate_exercise_task_detail",
  "arguments": {
    "taskId": 10001
  }
}
```

成功返回示例：

```json
{
  "code": 200,
  "message": "查询自评/互评练习任务详情成功！",
  "data": {
    "taskId": 10001,
    "taskName": "自然语言处理自评练习",
    "description": "本练习用于自评与互评",
    "pointIdList": [1000187401],
    "abilityIdList": [101],
    "quizList": [
      {
        "quizId": 100018815,
        "quizType": 10,
        "content": "题干内容",
        "solution": "解析",
        "optionList": [{"content": "选项A", "answerFlag": 1}],
        "pointName": "NLP基础",
        "score": 3
      }
    ]
  }
}
```

后端链路：

- `POST /api/teach/task/exercise/queryEvaluateConfig`
- `POST /api/teach/task/exercise/listQuiz`

#### update_evaluate_exercise_task_detail

- 名称：`update_evaluate_exercise_task_detail`
- 说明：编辑更新自评/互评练习任务。提交前先调用 `query_evaluate_exercise_task_detail` 回填未传字段；`pointIdList`/`abilityIdList` 非空时取合集，`delPointIdList`/`delAbilityIdList` 非空时去除对应项。先调用 `saveEvaluate`（`materialFlag=0`）；`quizList` 非空时再经 `checkQuizAdd` 检测，仅对顶层题目调用 `queryQuiz` 组织 `typeList`（不遍历 `subList`），顶层题目 `quizType` 必须为 50，最后调用 `saveQuiz`。**更新成功时**在 `data` 中附加 `previewUrl`、`taskName`、`taskType`（规则与 `create_evaluate_exercise` 一致，`taskType=51`，路径含 `/evaluation/home`；`instanceId` 入参优先，否则从 `queryEvaluateConfig` 回填）

输入参数：

- `taskId`（int，必填）：任务标识
- `instanceId`（int，可选）：课程实例标识；为空时从 `queryEvaluateConfig` 回填，用于拼装预览地址
- `taskName`（string，可选）：任务名称；为空时从查询结果回填
- `description`（string，可选）：任务说明；为空时从查询结果回填
- `pointIdList`（int[]，可选）：关联知识点；非空时与查询结果取合集
- `abilityIdList`（int[]，可选）：关联能力点；非空时与查询结果取合集
- `customColumnId`（integer/int64，可选）：自定义栏目标识；传入时覆盖（包括 `0`），未传时保留原栏目，无原值则省略
- `quizList`（array，可选）：题目列表，每项含 `quizId`、`score`；可选 `subList`、`fillList`（`fillList` 项为 `{responseId, score}`）；顶层题目 `quizType` 须为 50
- `continueEndTime`（string，可选）：重做截止时间（ISO 8601）；`checkQuizAdd` 返回 `submitCount>0` 且修改题目时必填
- `delPointIdList`（int[]，可选）：待移除的知识点 ID
- `delAbilityIdList`（int[]，可选）：待移除的能力点 ID

示例（仅更新名称与说明）：

```json
{
  "tool": "update_evaluate_exercise_task_detail",
  "arguments": {
    "customColumnId": 102,
    "taskId": 10001,
    "instanceId": 5442,
    "taskName": "自然语言处理自评练习（修订）"
  }
}
```

示例（更新题目，已有人作答需设置重做截止时间）：

```json
{
  "tool": "update_evaluate_exercise_task_detail",
  "arguments": {
    "taskId": 10001,
    "quizList": [
      {"quizId": 100018815, "score": 30},
      {"quizId": 100018898, "score": 40, "fillList": [{"responseId": 100002847, "score": 20}]}
    ],
    "continueEndTime": "2026-12-31T23:59:59"
  }
}
```

成功响应示例：

```json
{
  "code": 200,
  "message": "更新自评/互评练习任务成功！",
  "data": {
    "taskId": 10001,
    "taskName": "自然语言处理自评练习（修订）",
    "taskType": 51,
    "previewUrl": "http://brains.v2.zhituxueyuan.cn/student/course/0/studyTask/10001/evaluation/home?taskId=10001&instanceId=5442"
  }
}
```

说明：`previewUrl` 基址与创建类 Tool 相同；拼装所用 `instanceId` 入参优先，否则来自 `queryEvaluateConfig`；无有效 `instanceId` 或更新失败时不附加 `previewUrl`。自评/互评 `taskType` 为 `51`（勿与常规练习 `50` 或个性化 `60` 混淆）。

后端链路：

- `POST /api/teach/task/exercise/queryEvaluateConfig` + `listQuiz`（查询回填）
- `POST /api/teach/task/exercise/saveEvaluate`
- `POST /api/teach/task/exercise/checkQuizAdd`（`quizList` 非空时）
- `POST /api/resource/quiz/queryQuiz`（组织 `typeList`，仅顶层题目，`quizType=50`）
- `POST /api/teach/task/exercise/saveQuiz`（`quizList` 非空时）

#### create_evaluate_exercise

- 名称：`create_evaluate_exercise`
- 说明：创建自评/互评练习任务；`typeList` 自动含 `displayOrder`；评价方式等由后端默认处理

输入参数：

- `instanceId`（int，必填）：课程实例标识
- `taskName`（string，必填）：任务名称
- `quizList`（array，必填）：试题列表（`quizId`、`quizTypeId`、`score`；可选 `parentQuizId` 默认 -1、`fillList`）；每题的 `quizTypeId` 对应题型元数据中 `quizType` 必须为 `50`（简答题），否则拒绝整个创建请求；不自动改写题型。
- `taskDescription`（string，可选）：任务说明
- `pointIdList`（array[int]，可选）：关联知识点标识列表（透传至 `newEvaluate.pointIdList`）
- `abilityIdList`（array[int]，可选）：任务关联能力；传入时原样提交（包括 `[]`），未传时省略
- `customColumnId`（integer/int64，可选）：自定义栏目标识；传入时原样提交（包括 `0`），未传时省略
- `unitId`（int，可选，默认 0）：目录节标识

示例：

```json
{
  "tool": "create_evaluate_exercise",
  "arguments": {
    "abilityIdList": [10, 20],
    "customColumnId": 101,
    "instanceId": 5442,
    "taskName": "自然语言处理自评练习",
    "taskDescription": "任务说明",
    "pointIdList": [1000187401, 1000187400],
    "quizList": [
      { "quizId": 100018815, "parentQuizId": -1, "quizTypeId": 2, "score": 30 },
      {
        "quizId": 100018898,
        "parentQuizId": -1,
        "quizTypeId": 2,
        "score": 40
      }
    ],
    "unitId": 1024
  }
}
```

后端链路：

- `POST /api/resource/quiz/listQuizType`
- `POST /api/teach/task/exercise/newEvaluate`

#### query_individual_exercise_task_detail

- 名称：`query_individual_exercise_task_detail`
- 说明：查询个性化练习任务详情；调用 `queryIndividual` 并对 `data` 精简，仅返回 `taskId`、`taskName`、`description`、`pointIdList`、`abilityIdList`、`directoryIds`、`pointIds`、`quizNum`、`quizSourceType`、`quizSetType`、`quizTypeIds`、`quizTypeNames`、`individualQuizSet`；`individualQuizSet` 每项仅保留 `relativeId`、`relativeName`、`relativeType`、`datas`

输入参数：

- `taskId`（int，必填）：任务标识

示例：

```json
{
  "tool": "query_individual_exercise_task_detail",
  "arguments": {
    "taskId": 10001
  }
}
```

成功返回示例：

```json
{
  "code": 200,
  "message": "查询个性化练习任务详情成功！",
  "data": {
    "taskId": 10001,
    "taskName": "自然语言处理练习",
    "description": "本练习用于个性化巩固",
    "pointIdList": [1000006438],
    "abilityIdList": [101],
    "directoryIds": [],
    "pointIds": [1000006438, 1000006439],
    "quizNum": 10,
    "quizSourceType": 20,
    "quizSetType": 10,
    "quizTypeIds": [1, 2],
    "quizTypeNames": ["单选题", "多选题"],
    "individualQuizSet": [
      {
        "relativeId": 1000006438,
        "relativeName": "NLP基础",
        "relativeType": 20,
        "datas": [{"quizTypeId": 1, "quizNum": 5}]
      }
    ]
  }
}
```

后端：`POST /api/teach/task/exercise/queryIndividual`

#### update_individual_exercise_task_detail

- 名称：`update_individual_exercise_task_detail`
- 说明：编辑更新个性化练习任务。提交前调用 `queryIndividual` 回填空的 `taskName`/`description`，并回填 `pointIdList`/`abilityIdList`/`reviewType`/`scoreType`；用 `instanceId` 调用 `listPoint`/`listQuizType` 解析 `pointNames`/`quizTypeNames`；最后调用 `saveIndividual`。仅支持按目录出题，`directoryIds` 必填且不能为空，提交时固定 `quizSourceType=10`；`quizSetType=20` 时 `datas` 必填，提交时每项补 `quizTypeFlag=1`。**更新成功时**在 `data` 中附加 `previewUrl`、`taskName`、`taskType`（规则与 `create_personal_exercise` 一致，`taskType=60`，路径含 `/practiceHome`）

输入参数：

- `taskId`（int，必填）：任务标识
- `instanceId`（int，必填）：课程实例标识
- `quizNum`（int，必填）：题目数量
- `quizSetType`（int，必填）：出题方式 `10` 随机 / `20` 指定目录或知识点
- `directoryIds`（int[]，必填且非空）：目录标识；保存时固定 `quizSourceType=10`
- `taskName`（string，可选）：任务名称；为空时从查询回填
- `description`（string，可选）：任务说明；为空时从查询回填
- `pointIds`（int[]，可选）：用于进一步限定目录内知识点
- `quizTypeIds`（int[]，可选）：题型标识列表
- `abilityIdList`（array[int]，可选）：任务关联能力；传入时替换原关联，`[]` 清空，未传时保留查询结果
- `customColumnId`（integer/int64，可选）：自定义栏目标识；传入时覆盖（包括 `0`），未传时保留原栏目，无原值则省略
- `datas`（array，可选）：指定出题明细；`quizSetType=20` 时必填。每项含 `relativeId`、`relativeName`、`relativeType`、`quizNum`、`datas`（内层每项含 `quizTypeId`、`quizTypeName`、`quizNum`）

示例：

```json
{
  "tool": "update_individual_exercise_task_detail",
  "arguments": {
    "abilityIdList": [10, 20],
    "customColumnId": 102,
    "taskId": 10001,
    "instanceId": 5442,
    "quizNum": 10,
    "quizSetType": 20,
    "directoryIds": [2001],
    "pointIds": [1000006438],
    "quizTypeIds": [1, 2],
    "datas": [
      {
        "relativeId": 1000006438,
        "relativeName": "NLP基础",
        "relativeType": 20,
        "quizNum": 10,
        "datas": [
          {"quizTypeId": 1, "quizTypeName": "单选题", "quizNum": 5},
          {"quizTypeId": 2, "quizTypeName": "多选题", "quizNum": 5}
        ]
      }
    ]
  }
}
```

成功响应示例：

```json
{
  "code": 200,
  "message": "更新个性化练习任务成功！",
  "data": {
    "taskId": 10001,
    "taskName": "原个性化练习",
    "taskType": 60,
    "previewUrl": "http://brains.v2.zhituxueyuan.cn/student/course/0/studyTask/10001/practiceHome?taskId=10001&instanceId=5442"
  }
}
```

说明：`previewUrl` 基址与创建类 Tool 相同；更新失败时不附加预览字段。

后端链路：

- `POST /api/teach/task/exercise/queryIndividual`（回填）
- `POST /api/teach/point/listPoint`（解析 `pointNames`）
- `POST /api/resource/quiz/listQuizType`（解析 `quizTypeNames`）
- `POST /api/teach/task/exercise/saveIndividual`

#### create_personal_exercise

- 名称：`create_personal_exercise`
- 说明：创建个性化练习任务；仅支持按目录出题，提交时固定 `quizSourceType=10`；按需校验知识点并补全名称，按题型/用途 ID 补全名称；提交前调用 `queryIndividualQuizNum` 校验 `quizNum` 不超过可关联题量

输入参数：

- `instanceId`（int，必填）：课程实例标识
- `taskName`（string，必填）：任务名称
- `quizNum`（int，必填）：生成的题目数量（须 ≤ `queryIndividualQuizNum` 返回的 `data.quizNum`）
- `directoryIds`（array[int]，必填且非空）：目录标识；保存时固定 `quizSourceType=10`
- `pointIds`（array[int]，可选）：用于进一步限定目录内知识点
- `taskDescription`（string，可选）：任务说明
- `quizTypeIds`（array[int]，可选）：题型标识列表
- `useIds`（array[int]，可选）：题目用途标识列表
- `abilityIdList`（array[int]，可选）：任务关联能力；传入时原样提交（包括 `[]`），未传时省略
- `customColumnId`（integer/int64，可选）：自定义栏目标识；传入时原样提交（包括 `0`），未传时省略
- `unitId`（int，可选，默认 0）：目录节标识

示例：

```json
{
  "tool": "create_personal_exercise",
  "arguments": {
    "abilityIdList": [10, 20],
    "customColumnId": 101,
    "instanceId": 5442,
    "taskName": "自然语言处理练习",
    "taskDescription": "说明",
    "directoryIds": [2001],
    "pointIds": [1000006438, 1000006439],
    "quizTypeIds": [1, 2, 3],
    "useIds": [91, 93],
    "quizNum": 10,
    "unitId": 1024
  }
}
```

后端链路：

- `POST /api/teach/point/queryPoint`（逐 ID 校验知识点）
- `POST /api/resource/quiz/listQuizType`（补全题型名称）
- `POST /api/base/code/listCodeByType`（`quizUse`，补全用途名称）
- `POST /api/teach/task/exercise/queryIndividualQuizNum`（题量预检）
- `POST /api/teach/task/exercise/newIndividual`

输入参数与 `create_video_task` 基本一致，差异在于：

- `fileId`：为文档资源标识
- AI 生成链路会使用文档接口组合：
  - `POST /api/teach/ai/lesson/doc/chat`
  - `POST /api/teach/ai/lesson/document/queryResult`
  - `POST /api/teach/ai/lesson/document/finishQuery`

#### 场景模拟任务工具组

推荐使用流程：
1. `list_scene_templates` → 列出场景类型，选择 `templateId`
2. `get_scene_template_detail` → 获取模板 roles（`studentFlag=0` 为 AI 角色，`studentFlag=1` 为学生角色）
3. `list_ai_voices`（可选）→ 查看可用声音；创建时 `voiceId` 可省略，将自动使用列表第一项
4. `create_scene_task` → 填写 `standard`（学生角色评分标准）及 AI 角色覆盖，创建任务

##### list_scene_templates

- 名称：`list_scene_templates`
- 说明：查询场景模拟类型列表

输入参数：`instanceId`（必填）、`pageNo`（默认1）、`pageSize`（默认50）

##### get_scene_template_detail

- 名称：`get_scene_template_detail`
- 说明：查询指定场景模板详情，返回 `roles` 列表（含 `id`、`name`、`studentFlag`、`introduction`、`voiceId`、`standard` 等）

输入参数：`instanceId`（必填）、`templateId`（必填）

##### list_ai_voices

- 名称：`list_ai_voices`
- 说明：查询课程实例默认 AI 声音列表（`voiceId`、`toneId`、`toneName`）

输入参数：`instanceId`（必填）

后端：`POST /api/teach/bot/queryDefaultAiVoice`

##### create_scene_task

- 名称：`create_scene_task`
- 说明：创建场景模拟任务，内部自动获取模板 roles 并合并用户角色覆盖

输入参数：

- `instanceId`（int，必填）：课程实例标识
- `templateId`（int，必填）：场景模板标识
- `name`（string，必填）：场景任务名称
- `description`（string，必填）：场景描述
- `standard`（string，必填）：评分标准，写入模板中 `studentFlag=1` 学生角色的 `standard` 字段
- `roles`（array，可选）：AI 角色覆盖列表，仅提供 `studentFlag=0` 角色，每项包含 `id`/`roleName`/`introduction`/`voiceId`/`changeAvatar`（`voiceId` 可省略，省略时自动取系统内置声音列表第一项）
- `welcome`（string，可选）：角色开场白，写入第一个 AI 角色
- `changeBackgroundPath`（string，可选）：场景背景图本地或网络路径；非空时上传后写入 `backgroundPath`
- `digitalHuman`（object，可选）：数字人角色配置，包含 `roleId` 及可选 `speakFilePath`、`speakMeta`、`listenFilePath`、`listenMeta`；素材支持本地/网络 `zip`、`webp`、`gif`，Meta 为 JSON 字符串
  - 非空时提交 `imageFlag=1`，未传时提交 `imageFlag=0`
- `pointIds`（array[int]，可选）：关联知识点
- `abilityIdList`（array[int]，可选）：任务关联能力；传入时原样提交，`[]` 表示不关联能力，未传时省略该字段
- `customColumnId`（integer/int64，可选）：自定义栏目标识；显式 `0` 同样透传，未传时省略
- `unitId`（int，可选，默认0）：目录节标识

示例：

```json
{
  "tool": "create_scene_task",
  "arguments": {
    "abilityIdList": [10, 20],
    "customColumnId": 101,
    "instanceId": 5442,
    "templateId": 1001,
    "name": "医患沟通场景模拟",
    "description": "模拟医院问诊场景，训练沟通能力",
    "standard": "沟通态度、问诊完整性、共情表达、处置建议合理性",
    "welcome": "您好，我是今天的主治医生，请问您哪里不舒服？",
    "roles": [
      {
        "id": 2001,
        "roleName": "主治医生",
        "introduction": "经验丰富的主治医生，擅长与患者沟通",
        "voiceId": 5,
        "changeAvatar": "D:\\images\\doctor-avatar.png"
      }
    ],
    "digitalHuman": {
      "roleId": 2001,
      "speakFilePath": "https://example.com/doctor-speak.webp",
      "speakMeta": "{\"width\":352,\"height\":286,\"top\":20,\"left\":20,\"app\":{\"width\":305,\"height\":248,\"top\":137,\"left\":22}}",
      "listenFilePath": "D:\\images\\doctor-listen.gif",
      "listenMeta": "{\"width\":352,\"height\":286,\"top\":20,\"left\":20}"
    },
    "changeBackgroundPath": "D:\\images\\scene-background.png",
    "pointIds": [123],
    "unitId": 1024
  }
}
```

后端链路：

- `POST /api/teach/scene/templateDetail`（自动获取模板 roles）
- `POST /api/resource/upload/file`（可选：`changeBackgroundPath` → `uploadType=motto`、`moveFlag=1`；角色 `changeAvatar` → `uploadType=avatar`）
- `POST /api/resource/upload/botImage`（可选：上传数字人说话/倾听素材，以 `filePath`/`svgPath` 覆盖对应角色字段）
- `POST /api/teach/scene/newTask`

##### query_scene_task_detail

- 名称：`query_scene_task_detail`
- 说明：按 `taskId`/`instanceId` 查询场景模拟任务详情，并对 `data` 做白名单精简（含 `roles` 嵌套字段）

输入参数：

- `taskId`（int，必填）：任务标识
- `instanceId`（int，必填）：课程实例标识

响应优化（`code=200` 时 `data` 仅保留）：

- 顶层：`taskId`、`taskName`、`description`、`templateId`、`roles`、`backgroundPath`、`pointIdList`、`abilityIdList`
- `roles[]`：`taskSceneRoleId`、`name`、`roleName`、`introduction`、`welcome`、`standard`、`voiceId`
- 兼容：角色缺 `taskSceneRoleId` 时可用 `id`；缺 `taskName` 时可用 `name`；`roles` 非 list 时返回 `[]`

示例：

```json
{
  "tool": "query_scene_task_detail",
  "arguments": {
    "taskId": 10001,
    "instanceId": 5442
  }
}
```

后端：`POST /api/teach/scene/templateDetail`（请求体含 `taskId`、`instanceId`）

##### query_scene_review_student_list

- 名称：`query_scene_review_student_list`
- 说明：按任务与班级查询场景模拟批阅学生列表；成功返回时对响应中字段名含 `score` 且非空的数值递归除以 100

输入参数：

- `taskId`（int，必填）：任务标识
- `classId`（int，必填）：班级标识
- `groupId`（int，可选）：分组标识；未传时默认 `0`
- `submitStatus`（int，可选）：提交状态筛选
- `markStatus`（int，可选）：批阅状态筛选
- `appealStatus`（int，可选）：申诉状态筛选
- `searchText`（string，可选）：搜索关键字
- `page`（object，可选）：分页，含 `pageNo`、`pageSize`；默认 `{pageNo: 1, pageSize: 50}`

示例：

```json
{
  "tool": "query_scene_review_student_list",
  "arguments": {
    "taskId": 10001,
    "classId": 24135,
    "markStatus": 10,
    "page": {"pageNo": 1, "pageSize": 50}
  }
}
```

后端：`POST /api/teach/task/scene/listTaskSceneStudent`

##### query_scene_task_complete

- 名称：`query_scene_task_complete`
- 说明：查询场景模拟完成率统计

输入参数：

- `taskId`（int，必填）：任务标识
- `classId`（int，必填）：班级标识
- `status`（int，可选）：状态筛选，仅允许 `0` 或 `10`；未传时默认 `10`

示例：

```json
{
  "tool": "query_scene_task_complete",
  "arguments": {
    "taskId": 10001,
    "classId": 24135,
    "status": 10
  }
}
```

后端：`POST /api/teach/task/scene/queryTaskSceneComplete`

##### query_scene_task_commit_cnt

- 名称：`query_scene_task_commit_cnt`
- 说明：查询场景模拟提交次数分布统计

输入参数：

- `taskId`（int，必填）：任务标识
- `classId`（int，必填）：班级标识
- `status`（int，可选）：状态筛选，仅允许 `0` 或 `10`；未传时默认 `10`

示例：

```json
{
  "tool": "query_scene_task_commit_cnt",
  "arguments": {
    "taskId": 10001,
    "classId": 24135,
    "status": 10
  }
}
```

后端：`POST /api/teach/task/scene/queryTaskSceneCommitCnt`

##### query_scene_task_round_num_avg

- 名称：`query_scene_task_round_num_avg`
- 说明：查询场景模拟平均对话轮次分布统计

输入参数：

- `taskId`（int，必填）：任务标识
- `classId`（int，必填）：班级标识
- `status`（int，可选）：状态筛选，仅允许 `0` 或 `10`；未传时默认 `10`

示例：

```json
{
  "tool": "query_scene_task_round_num_avg",
  "arguments": {
    "taskId": 10001,
    "classId": 24135,
    "status": 10
  }
}
```

后端：`POST /api/teach/task/scene/queryTaskSceneRoundNumAvg`

##### query_scene_task_stat_score

- 名称：`query_scene_task_stat_score`
- 说明：查询场景模拟学生成绩分布统计

输入参数：

- `taskId`（int，必填）：任务标识
- `classId`（int，必填）：班级标识

示例：

```json
{
  "tool": "query_scene_task_stat_score",
  "arguments": {
    "taskId": 10001,
    "classId": 24135
  }
}
```

后端：`POST /api/teach/task/scene/queryTaskSceneStatScore`

##### query_scene_student_submit_detail

- 名称：`query_scene_student_submit_detail`
- 说明：查询学生场景模拟提交详情。先调 `queryTaskScene` 取 `sceneSessionItemList` 中的 `learnSceneSessionId`，再逐个调用 `queryTaskSceneSession`，将各次 `sceneSessionItem` 组合为 `data.sceneSessionList`；成功返回时对字段名含 `score` 且非空的数值递归除以 100

输入参数：

- `taskId`（int，必填）：任务标识
- `teachClassId`（int，必填）：教学班级标识
- `userId`（int，必填）：学生用户标识

示例：

```json
{
  "tool": "query_scene_student_submit_detail",
  "arguments": {
    "taskId": 10001,
    "teachClassId": 24135,
    "userId": 1001
  }
}
```

后端链路：

- `POST /api/teach/task/scene/queryTaskScene`
- `POST /api/teach/task/scene/queryTaskSceneSession`（按会话 ID 逐个调用）

##### save_scene_student_review

- 名称：`save_scene_student_review`
- 说明：保存学生场景模拟批阅结果；入参 `score` 为可读分值，提交时自动 ×100；成功响应中含 `score` 的非空字段自动 ÷100

输入参数：

- `learnSceneSessionId`（int，必填）：用户场景模拟会话标识
- `taskId`（int，必填）：任务标识
- `teachClassId`（int，必填）：教学班级标识
- `userId`（int，必填）：学生用户标识
- `score`（number，必填）：得分（可读分值，提交时自动 ×100）
- `comment`（string，必填）：批阅评语
- `appealId`（int，可选）：学生 AI 申诉标识
- `rejectReason`（string，可选）：申诉处理理由

示例：

```json
{
  "tool": "save_scene_student_review",
  "arguments": {
    "learnSceneSessionId": 11,
    "taskId": 10001,
    "teachClassId": 24135,
    "userId": 1001,
    "score": 85,
    "comment": "表现良好"
  }
}
```

后端：`POST /api/teach/task/scene/updateSceneReview`

##### update_scene_task_detail

- 名称：`update_scene_task_detail`
- 说明：编辑更新场景模拟任务。提交前先按 `taskId` 查询 `templateDetail`，回填空字段与 `contextVos`/`recallVo`，再调用 `newTask` 提交。**更新成功时**在 `data` 中附加 `previewUrl`、`taskName`、`taskType`（规则与 `create_scene_task` 一致，`taskType=40`，路径含 `/roleSimulation`）

输入参数：

- `taskId`（int，必填）：任务标识
- `instanceId`（int，必填）：课程实例标识
- `templateId`（int，必填）：场景模板标识
- `roles`（array，必填，可为 `[]`）：角色覆盖列表；以查询 `roles` 为基线，按 `taskSceneRoleId` 匹配后仅用非空字段覆盖
  - 每项：`taskSceneRoleId`、`name`、`roleName`、`introduction`、`welcome`、`standard`、`voiceId`、`changeAvatar`
  - 某项 `welcome` 非空时，对应角色提交体 `welcomeFlag` 固定为 `1`
- `taskName`（string，可选）：任务名称；为空时从查询回填（提交映射为 `name`）
- `description`（string，可选）：描述；为空时从查询回填
- `changeBackgroundPath`（string，可选）：场景背景图本地或网络路径；非空时上传后写入 `backgroundPath`
- `digitalHuman`（object，可选）：包含 `roleId` 及可选 `speakFilePath`、`speakMeta`、`listenFilePath`、`listenMeta`；素材支持本地/网络 `zip`、`webp`、`gif`，Meta 为 JSON 字符串
  - 非空时提交 `imageFlag=1`，未传时提交 `imageFlag=0`
- `pointIdList` / `abilityIdList`（array[int]，可选）：与查询结果取合集
- `customColumnId`（integer/int64，可选）：自定义栏目标识；传入时覆盖（包括 `0`），未传时保留查询结果，无查询值则省略
- `delPointIdList` / `delAbilityIdList`（array[int]，可选）：从合并结果中去除

回填与合并规则：

- `taskName`/`description` 为空 → 查询回填；`changeBackgroundPath` 非空 → 上传后写入 `backgroundPath`，否则查询回填；`contextVos`/`recallVo` 始终回填
- `roles=[]` → 不覆盖，提交查询角色列表；无法匹配的入参角色忽略（不新增）
- 入参 `roles[].welcome` 非空 → 覆盖 `welcome` 且该角色 `welcomeFlag=1`；空/未传 `welcome` 不改动查询侧 `welcomeFlag`
- 知识点/能力：查询基线 + 合集 + 删除

示例：

```json
{
  "tool": "update_scene_task_detail",
  "arguments": {
    "customColumnId": 102,
    "taskId": 10001,
    "instanceId": 5442,
    "templateId": 1001,
    "taskName": "医患沟通场景模拟（修订）",
    "roles": [
      {
        "taskSceneRoleId": 2001,
        "roleName": "主治医生",
        "welcome": "您好，请问哪里不舒服？"
      }
    ],
    "digitalHuman": {
      "roleId": 2001,
      "speakFilePath": "https://example.com/doctor-speak.webp",
      "speakMeta": "{\"width\":352,\"height\":286,\"top\":20,\"left\":20,\"app\":{\"width\":305,\"height\":248,\"top\":137,\"left\":22}}",
      "listenFilePath": "D:\\images\\doctor-listen.gif",
      "listenMeta": "{\"width\":352,\"height\":286,\"top\":20,\"left\":20}"
    },
    "pointIdList": [789],
    "delPointIdList": [123]
  }
}
```

成功响应示例：

```json
{
  "code": 200,
  "message": "更新场景模拟任务成功！",
  "data": {
    "taskId": 10001,
    "taskName": "医患沟通场景模拟（修订）",
    "taskType": 40,
    "previewUrl": "http://brains.v2.zhituxueyuan.cn/student/course/0/studyTask/10001/roleSimulation?taskId=10001&instanceId=5442"
  }
}
```

说明：`previewUrl` 基址与创建类 Tool 相同（请求头 `Origin` 或 `SERVER_API_URL`）；更新失败时不附加预览字段。

后端链路：

- `POST /api/teach/scene/templateDetail`（按 `taskId` 查询）
- `POST /api/teach/scene/newTask`（带既有 `taskId` 提交）

#### AI 智能体工具组

推荐使用流程：

1. `list_ai_tools` → 列出可添加智能体，选择 `botId` / `componentId`
2. `list_bot_types` → 列出工具箱类别，选择 `typeId`
3. `create_agent_bot` → 填写 `roleName` 等，添加智能体（获得 `instanceBotId`）
4. `update_bot_config` → 配置欢迎语、提示词与召回策略

##### list_ai_tools

- 名称：`list_ai_tools`
- 说明：查询可添加的 AI 智能体列表

输入参数：`instanceId`（必填）、`roleType`（可选，默认 `2`：`1` 学生、`2` 教师）、`searchText`（可选）

后端：`POST /api/teach/bot/listAITools`

##### list_bot_types

- 名称：`list_bot_types`
- 说明：查询 AI 工具箱类别列表

输入参数：`instanceId`（必填）、`pageNo`（默认1）、`pageSize`（默认50）、`userType`（默认0）、`searchText`（可选）

后端：`POST /api/teach/bot/type/listTypeByPage`

##### save_bot_type

- 名称：`save_bot_type`
- 说明：新增或编辑 AI 工具箱类别（`typeId=0` 新增，非 0 编辑）
- Profile：`design`

输入参数：

- `instanceId`（int，必填）：课程实例标识
- `typeName`（string，必填）：类别名称
- `userType`（int，必填）：用户类型
  - `0`：通用
  - `1`：学生
  - `2`：教师
- `typeId`（int，默认 `0`）：类别标识；新增传 `0`，修改传 `list_bot_types` 返回的 `typeId`

示例（新增）：

```json
{
  "tool": "save_bot_type",
  "arguments": {
    "instanceId": 5442,
    "typeName": "教学设计",
    "userType": 2,
    "typeId": 0
  }
}
```

示例（编辑）：

```json
{
  "tool": "save_bot_type",
  "arguments": {
    "instanceId": 5442,
    "typeName": "教学设计（修订）",
    "userType": 2,
    "typeId": 10
  }
}
```

后端：`POST /api/teach/bot/type/saveType`

##### create_agent_bot

- 名称：`create_agent_bot`
- 说明：添加 AI 智能体工具到开课

输入参数：

- `instanceId`（int，必填）：课程实例标识
- `roleName`（string，必填）：角色名称
- `botId`（string，必填）：智能体标识（来自 `list_ai_tools`）
- `typeId`（int，必填）：工具箱类别（来自 `list_bot_types`）
- `description`（string，可选）：角色描述
- `toolFlag`（int，可选，默认1）：同步 AI 工具箱状态，0-移除 1-同步
- `imagePath`（string，可选）：工具头像本地路径或 HTTP/HTTPS URL；支持 jpg、jpeg、png、bmp、svg、gif（扩展名大小写不敏感）

提供 `imagePath` 时，网络文件会先下载到临时文件，然后以 `uploadType=logo` 调用资源上传接口；创建请求会携带实际文件的 `fileName`、`fileSize`、`uploadType`，以及上传响应的 `fileTempPath`、`fileRelativePath`。

示例：

```json
{
  "tool": "create_agent_bot",
  "arguments": {
    "instanceId": 5442,
    "roleName": "备课小助手",
    "botId": "bot-001",
    "typeId": 10,
    "description": "辅助教师备课",
    "toolFlag": 1,
    "imagePath": "D:/images/agent-logo.png"
  }
}
```

后端链路：可选 `POST /api/resource/upload/file`（`uploadType=logo`）→ `POST /api/teach/bot/newAgentBot`

##### delete_ai_tool

- 名称：`delete_ai_tool`
- 说明：删除AI智能体
- Profile：`common`

输入参数（全部必填）：

- `instanceId`（int）：开课标识
- `instanceBotId`（int）：开课机器人标识

示例：

```json
{
  "tool": "delete_ai_tool",
  "arguments": {
    "instanceId": 5442,
    "instanceBotId": 1001
  }
}
```

后端：`POST /api/teach/bot/delAITool`

##### update_bot_config

- 名称：`update_bot_config`
- 说明：配置 AI 工具箱中已添加的智能体工具（欢迎语、提示词、召回）；具体可选参数可先通过 `list_ai_tools` 了解

输入参数：

- `instanceId`（int，必填）：课程实例标识
- `instanceBotId`（int，必填）：开课机器人标识
- `botId`（string，必填）：智能体标识（可参考 `list_ai_tools`）
- `componentId`（int，必填）：组件标识（可参考 `list_ai_tools`）
- `welcomeMessage`（string，必填）：欢迎词
- `prePrompt`（string，必填）：提示词
- `recallVo`（object，可选）：召回配置；未传默认 `{maxRecallNum: 3, minMatch: 0.7, showSourceFlag: 0}`

固定请求参数（MCP 自动写入）：`contextVos=[]`、`personContextVos=[]`、`suggestedAfterAnswer=0`

示例：

```json
{
  "tool": "update_bot_config",
  "arguments": {
    "instanceId": 5442,
    "instanceBotId": 90001,
    "botId": "bot-001",
    "componentId": 100,
    "welcomeMessage": "你好，我是备课小助手",
    "prePrompt": "你是一名课程助教，帮助教师备课。",
    "recallVo": {
      "maxRecallNum": 3,
      "minMatch": 0.7,
      "showSourceFlag": 0
    }
  }
}
```

后端：`POST /api/teach/bot/updateBotConfig`

后端：`POST /api/teach/task/pbl/newTaskPbl`

#### import_ability_graph_from_markdown

- 名称：`import_ability_graph_from_markdown`，归属 `design` Profile。
- 用途：通过本地或 HTTP(S) Markdown 文件导入课程能力图谱。
- 必填：`instanceId`（integer/int64，正整数）、`filePath`（非空字符串，文件后缀 `.md`，URL 可带查询参数，后缀大小写不敏感）。
- 可选：`overwrite`（integer/int32），0 只新增能力，1 覆盖相似能力，默认 0。

```json
{
  "tool": "import_ability_graph_from_markdown",
  "arguments": {
    "instanceId": 5442,
    "filePath": "D:/files/abilities.md",
    "overwrite": 0
  }
}
```

网络文件先下载，以 `uploadType=attachment` 上传到 `/api/resource/upload/file`。将返回的 `data.uploadVo.fileTempPath` 作为 `filePath`，连同 `instanceId`、`overwrite` 和固定 `templateType=ability` 提交到 `/api/resource/excel/importExcelData`。使用响应 `data.sessionId` 调用 `/api/resource/excel/queryImportTask` 查询状态。

首次立即查询，仅 `importStatus=4` 时等待 1 秒继续，最多查询 180 次。状态 1 返回成功；0 未导入、2 模板不正确、3 文件没有记录、5 图谱合并失败及未知状态返回错误。查询失败立即结束；超时保留会话及最后状态，不自动重提任务，后端可能仍在运行。

成功响应示例：

```json
{
  "code": 200,
  "message": "能力图谱导入完成",
  "data": {
    "sessionId": "import-session-id",
    "importStatus": 1,
    "previewUrl": "https://example.com/teacher/course/5442/column/ability-column/graph/ability"
  }
}
```

结果同时保留后端可用的统计和错误详情。预览地址沿用 Origin/SERVER_API_URL 基址，查询 `ability-graph` 栏目的 `uniqueId`；栏目查询失败或未匹配时回退为 `column`，保持导入成功。失败和超时不生成预览地址。下载不发送后端鉴权，网络临时文件在成功、失败、超时或取消时清理，本地源文件保留。

#### import_question_graph_from_markdown

- 名称：`import_question_graph_from_markdown`，归属 `design` Profile。
- 用途：使用 Markdown 文件导入课程问题图谱。
- 必填：`instanceId`（integer/int64，正整数）、`filePath`（本地文件路径或 HTTP(S) URL，后缀 `.md`，支持 URL 查询参数及大小写后缀）。
- 可选：`overwrite`（integer/int32），0 只新增，1 覆盖相似问题，默认 0。

```json
{
  "tool": "import_question_graph_from_markdown",
  "arguments": {
    "instanceId": 5442,
    "filePath": "D:/files/questions.md",
    "overwrite": 0
  }
}
```

流程：网络文件先下载；以 `uploadType=attachment` 上传到 `/api/resource/upload/file`；将 `data.uploadVo.fileTempPath` 作为 `filePath`，连同 `instanceId`、`overwrite` 和固定 `templateType=question` 提交到 `/api/resource/excel/importExcelData`；使用返回的 `data.sessionId` 查询 `/api/resource/excel/queryImportTask`。

仅 `importStatus=4` 继续轮询，间隔 1 秒，最多查询 180 次。状态 1 成功；0 未导入、2 模板不正确、3 文件没有记录、5 图谱合并失败及未知状态立即结束并返回错误。查询异常立即结束。超时仅结束等待，后端任务可能仍在运行，不自动重新提交。

成功响应示例：

```json
{
  "code": 200,
  "message": "问题图谱导入完成",
  "data": {
    "sessionId": "import-session-id",
    "importStatus": 1,
    "previewUrl": "https://example.com/teacher/course/5442/column/question-column/graph/question"
  }
}
```

`data` 同时保留后端可用的导入统计和错误详情。预览地址沿用请求头 `Origin` 或 `SERVER_API_URL` 基址，查询 `question-graph` 栏目的 `uniqueId`；查询失败或未匹配时回退为 `column`，不影响导入成功。失败或超时不生成预览地址。网络临时文件在流程退出时清理，本地源文件保留。

#### import_quiz

- 名称：`import_quiz`
- 说明：通过本地文件路径或 HTTP 地址上传 Word/Excel 试题文件并导入课程题库

输入参数：

- `instanceId`（int，必填）：课程实例标识
- `fileInfo`（array，必填）：文件列表，每项含：
  - `filePath`（string，必填）：本地路径或 HTTP(S) 地址
  - `fileName`（string，可选）：原文件名称
  - `typeName`（string，可选）：资源类型
- `directoryId`（int，可选）：导入目标目录，默认 `0`
- `screenShot`（int，可选）：是否截图题库页面，`0` 否 `1` 是，默认 `0`（需安装 Playwright，见 `screenshot_website`）

示例：

```json
{
  "tool": "import_quiz",
  "arguments": {
    "instanceId": 5442,
    "fileInfo": [
      {
        "filePath": "D:/quiz/import.docx",
        "fileName": "期中测验.docx"
      }
    ],
    "directoryId": 0,
    "screenShot": 0
  }
}
```

成功返回示例：

```json
{
  "code": 200,
  "message": "导入试题到题库完成！",
  "data": {
    "importedQuizIds": ["101", "102"],
    "importRow": 2,
    "successFileCount": 1,
    "failedFileCount": 0,
    "files": [
      {
        "filePath": "D:/quiz/import.docx",
        "success": true,
        "quizIds": ["101", "102"],
        "importRow": 2,
        "message": ""
      }
    ]
  }
}
```

后端链路：`POST /api/resource/upload/file` → `POST /api/resource/Word/importWord` 或 `POST /api/resource/excel/importExcelData` → 轮询 `POST /api/resource/excel/queryImportTask`

#### ai_new_quiz_by_res

- 名称：`ai_new_quiz_by_res`
- 说明：按知识点一次配置多种题型与数量 AI 自动生成试题并写入课程题库；各题型数量之和最多 50 道

输入参数：

- `instanceId`（int，必填）：课程实例标识
- `typeList`（array，必填）：题型配置列表，每项包含：
  - `quizType`（int，必填）：题型 `10` 单选、`20` 多选、`30` 判断、`40` 填空、`50` 简答
  - `quizNumber`（int，必填）：该题型生成数量（须大于 0）
  - `optionNumber`（int，可选）：选项数量，仅单选/多选有效；未设置时按 `4` 传给后端
- `point`（string，必填）：知识点名称（去空白后非空），映射为后端 `keyPoint`
- `level`（string，可选）：认知层级，默认「记忆」；可选：记忆、理解、运用、分析、评价、创造
- `directoryId`（int，可选）：出题目录，默认 `-1`
- `difficulty`（int，可选）：难易度 `0`/`3`/`5`/`7`/`10`，默认 `5`
- `screenShot`（int，可选）：是否截图题库页面，`0` 否 `1` 是，默认 `0`

约束：`typeList` 中所有 `quizNumber` 之和 ≤ `50`；`point` 不能为空或仅空白。

示例：

```json
{
  "tool": "ai_new_quiz_by_res",
  "arguments": {
    "instanceId": 5442,
    "typeList": [
      {"quizType": 10, "quizNumber": 3, "optionNumber": 4},
      {"quizType": 20, "quizNumber": 2},
      {"quizType": 40, "quizNumber": 1}
    ],
    "point": "自然语言处理",
    "level": "理解",
    "difficulty": 5,
    "screenShot": 0
  }
}
```

成功返回示例：

```json
{
  "code": 200,
  "message": "根据知识点生成试题成功！",
  "data": {
    "quizIds": [101, 102, 103],
    "previewUrl": "https://.../teacher/course/5442/column/{uniqueId}/resources/quizs"
  }
}
```

后端链路：`POST /api/resource/quiz/newQuizByAIMultiType` → 轮询 `POST /api/resource/quiz/queryAiNewQuizResult`（超时约 `总题数 × 120` 秒）→ `POST /api/resource/quiz/updateAiNewQuizFlag` → `queryInstanceInfo` 解析 `columnCode=quizs` 拼装 `data.previewUrl`

#### upload_html_file

- 名称：`upload_html_file`
- 说明：上传本地 HTML 文件，返回可访问的 `fileUrl`
- Profile：`common`

输入参数：

- `htmlPath`（string，必填）：本地 `.html` / `.htm` 文件路径

示例：

```json
{
  "tool": "upload_html_file",
  "arguments": {
    "htmlPath": "D:/tmp/page.html"
  }
}
```

成功返回示例：

```json
{
  "code": 200,
  "message": "上传 HTML 成功！",
  "data": {
    "fileUrl": "https://static.example.com/xxx/page.html"
  }
}
```

后端链路：`POST /api/resource/upload/file`（`uploadType=html`，`moveFlag=1`）→ 取 `data.uploadVo.fileUrl`

#### convert_office_to_pdf

- 名称：`convert_office_to_pdf`
- 说明：将本地 Word（`.doc` / `.docx`）经远程 Office 转换服务转为本地 PDF

输入参数：

- `filePath`（string，必填）：本地 `.doc` 或 `.docx` 绝对路径
- `outputDir`（string，可选）：PDF 输出目录；未传时写入系统临时目录下 `haoke-mcp-office-pdf`
- `pollInterval`（float，可选）：轮询间隔秒数，默认 `2`
- `maxWait`（float，可选）：最长等待秒数，默认 `180`
- `requestTimeout`（float，可选）：单次 HTTP 超时秒数，默认 `60`

环境变量（与好课 `LOGIN_TOKEN` 独立）：

- `DOCUMENT_CONVERT_TOKEN`（必填）：转换服务鉴权 token
- `DOCUMENT_CONVERT_BASE_URL`（可选）：默认 `https://bots.wisedu.com`

示例：

```json
{
  "tool": "convert_office_to_pdf",
  "arguments": {
    "filePath": "D:/docs/lecture.docx",
    "outputDir": "D:/tmp/pdfs"
  }
}
```

成功返回示例：

```json
{
  "code": 200,
  "message": "文档转换 PDF 成功",
  "data": {
    "sourcePath": "D:\\docs\\lecture.docx",
    "pdfPath": "D:\\tmp\\pdfs\\lecture-abcd1234.pdf",
    "taskId": "abcd1234-...."
  }
}
```

后端链路：`POST /api/conv/office/createTask` → 轮询 `POST /api/conv/office/queryTaskStatus`（`status=2` 成功）→ 下载 PDF（校验 `%PDF-`）

#### query_quiz_detail

- 名称：`query_quiz_detail`
- 说明：按题目标识查询题目详情；成功时对 `data` 精简字段，仅保留题干、目录、题型、选项、答案、知识点与用途等

输入参数：

- `quizId`（int，必填）：题目标识

示例：

```json
{
  "tool": "query_quiz_detail",
  "arguments": {
    "quizId": 12345
  }
}
```

成功返回示例（`data` 已优化）：

```json
{
  "code": 200,
  "message": "查询题目详情成功！",
  "data": {
    "content": "下列哪项属于 Python 的基本数据类型？",
    "directoryId": 100,
    "directoryName": "第一章",
    "instanceId": 5442,
    "quizTypeName": "单选题",
    "quizUseDTOList": [{"useId": 1, "useName": "练习"}],
    "solution": "解析内容...",
    "standard": "评分标准...",
    "resQuizOptionList": [{"optionId": 1, "optionContent": "int"}],
    "resQuizPointList": [{"pointId": 101, "pointName": "变量与类型"}],
    "resQuizResponseList": [{"responseContent": "A"}]
  }
}
```

后端：`POST /api/resource/quiz/queryQuiz`

#### update_quiz

- 名称：`update_quiz`
- 说明：编辑题目；先按 `quizId` 查询现有题目，**未传入的字段自动回填查询值**，再提交更新。提交前对 `content`、`solution`、`standard` 及选项/答案中的 `content` 做 AES 加密，并固定 `safeFlag=1`

输入参数：

- `quizId`（int，必填）：题目标识
- `content`（string，可选）：题干
- `directoryId`（int，可选）：目录标识
- `directoryName`（string，可选）：目录名称
- `instanceId`（int，可选）：课程实例标识
- `quizTypeId`（int，可选）：题目类型标识
- `quizUseDTOList`（array，可选）：题目用途列表
- `solution`（string，可选）：题目解析
- `standard`（string，可选）：评分标准
- `resQuizOptionList`（array，可选）：题目选项列表
- `resQuizPointList`（array，可选）：题目知识点列表
- `resQuizResponseList`（array，可选）：题目答案列表
- `difficulty`（int，可选）：难易度，仅允许 `0`、`3`、`5`、`7`、`10`

示例（仅修改题干与难度）：

```json
{
  "tool": "update_quiz",
  "arguments": {
    "quizId": 12345,
    "content": "更新后的题干内容",
    "difficulty": 5
  }
}
```

成功返回示例：

```json
{
  "code": 200,
  "message": "编辑题目成功！"
}
```

后端链路：`POST /api/resource/quiz/queryQuiz` → `POST /api/resource/quiz/updateQuiz`

#### create_point_by_str

- 名称：`create_point_by_str`
- 说明：根据 UTF-8 编码的知识图谱 outline 文本文件，解析并保存为课程**知识图谱**节点（非能力图谱/问题图谱）

输入参数：

- `instanceId`（int，必填）：课程实例标识
- `filePath`（string，必填）：本地路径或 HTTP(S) 地址，文件内容为 UTF-8 文本 outline
- `parentPointId`（int，可选）：父知识点 ID；未传时取 `listPoint` 首项

示例：

```json
{
  "tool": "create_point_by_str",
  "arguments": {
    "instanceId": 5442,
    "filePath": "http://static.example.com/course/kg-outline.txt",
    "parentPointId": 100
  }
}
```

成功返回示例：

```json
{
  "code": 200,
  "message": "生成知识图谱节点成功！",
  "data": {
    "previewUrl": "https://.../teacher/course/5442/column/{uniqueId}/graph"
  }
}
```

后端链路：`POST /api/teach/point/parseAIPointResult` → `POST /api/teach/point/savePointList` → `POST /api/teach/point/completionPointContents`（失败不阻断）→ `POST /api/teach/instance/queryInstanceInfo` 解析 `columnCode=knowledge-graph` 的 `uniqueId` 并拼装预览链接

#### add_knowledge_relation

- 名称：`add_knowledge_relation`
- 说明：增量添加知识图谱知识点关系定义。提交前自动调用 `listPointRelation`，将已有 `pointRelationItemList`（`relationId`/`relationName`/`description`/`color`/`relationType`）与本次新增项合并后全量保存；新增项 `relationType` 固定为 `0`

输入参数：

- `instanceId`（int，必填）：课程实例标识
- `savePointRelationVoList`（array，必填）：**本次新增**关系数据列表，元素字段：
  - `relationName`（string，必填）：关联关系名称
  - `description`（string，可选）：关联关系描述
  - `color`（string，可选）：关联关系颜色
  - `relationType`：由工具固定写入 `0`，调用方无需传或会被覆盖

示例：

```json
{
  "tool": "add_knowledge_relation",
  "arguments": {
    "instanceId": 5442,
    "savePointRelationVoList": [
      {
        "relationName": "包含",
        "description": "父知识点包含子知识点",
        "color": "#4A90E2"
      }
    ]
  }
}
```

后端：先 `POST /api/teach/kg/listPointRelation`，再 `POST /api/teach/kg/savePointRelation`

#### create_knowledge_graph_point

- 名称：`create_knowledge_graph_point`
- 说明：基于选中的知识点新增同级或子级知识点。先查询 `selectPointId` 对应详情以回填可选字段，按需解析知识类型与认知要求码表、关联文件和题目，最后调用 `newPoint`。

输入参数：

- `instanceId`、`selectPointId`、`pointName`、`description`、`type` 必填；`type=10` 表示同级，`type=20` 表示子级。查询到 `selectPointId` 对应知识点的 `parentId=-1` 时，最终提交的 `type` 固定为 `20`；其他节点保留传入的层级类型。
- `pointType`：事实性知识、概念性知识、程序性知识、元认知知识。
- `cognitionLevel`：记忆、理解、运用、分析、评价、创造。
- `unitId`、`difficultyFlag`、`ideologyFlag`、`ideologyCase` 可选，未传时从查询结果回填；最终 `ideologyFlag=1` 时 `ideologyCase` 不能为空。
- `fileIds`、`quizIds` 非空时先调用对应关联接口。

```json
{
  "tool": "create_knowledge_graph_point",
  "arguments": {
    "instanceId": 5442,
    "selectPointId": 1001,
    "pointName": "牛顿第二定律的应用",
    "description": "使用牛顿第二定律分析实际问题",
    "type": 20,
    "pointType": "程序性知识",
    "cognitionLevel": "运用",
    "unitId": 1024,
    "difficultyFlag": 1,
    "ideologyFlag": 0,
    "fileIds": [2001],
    "quizIds": [3001]
  }
}
```

后端链路：`queryPoint` → 可选 `listCodeByType` → 可选 `newResFilePoint` / `newResQuizPoint` → `newPoint`。

成功返回示例：

```json
{"code":200,"message":"新增知识点成功！","data":{"pointId":99,"previewUrl":"https://example.com/teacher/course/1/column/actual-graph-column/graph"}}
```

`data.previewUrl`为知识图谱预览地址，创建成功后按开课栏目columnCode=knowledge-graph获取uniqueId；查询失败或未命中时回退uniqueId=column，缺少服务端根地址配置时返回相对路径，不影响创建成功。原data对象字段保留；无data时仍返回预览地址；非对象的原data保留于data.backendData。创建失败不额外查询预览地址。

#### update_course_point

- 名称：`update_course_point`
- 说明：编辑知识图谱知识点。先 `queryPoint` 回填未传可选字段；若传入中文 `pointType`/`cognitionLevel` 则经码表解析为 `typeId`/`cognitionId`；`fileIds`/`quizIds` 非空时先关联资源/题目，再 `updatePoint`

输入参数：

- `instanceId`（int，必填）：课程实例标识
- `pointId`（int，必填）：知识点标识
- `pointName`（string，必填）：知识点名称
- `description`（string，可选）：描述；未传则用详情回填
- `pointType`（string，可选）：事实性知识/概念性知识/程序性知识/元认知知识（内部按 `code` 10/20/30/40 匹配码表）；未传或码表未命中则沿用详情 `typeId`
- `cognitionLevel`（string，可选）：记忆/理解/运用/分析/评价/创造（内部按 `code` 10/20/30/40/50/60 匹配码表）；未传或码表未命中则沿用详情 `cognitionId`
- `unitId`（int，可选）：所属单元；未传则用详情回填
- `difficultyFlag`（int，可选）：重难点 0/1；未传则用详情回填
- `ideologyFlag`（int，可选）：思政 0/1；未传则用详情回填；最终为 1 时 `ideologyCase` 去空白后不能为空
- `ideologyCase`（string，可选）：思政案例；未传则用详情回填
- `fileIds`（array[int]，可选）：关联资源文件 ID；非空才调用关联接口
- `quizIds`（array[int]，可选）：关联题目 ID；非空才调用关联接口

示例：

```json
{
  "tool": "update_course_point",
  "arguments": {
    "instanceId": 5442,
    "pointId": 1001,
    "pointName": "链表基本操作",
    "pointType": "程序性知识",
    "cognitionLevel": "运用",
    "difficultyFlag": 1,
    "fileIds": [11, 12],
    "quizIds": [21]
  }
}
```

成功返回示例：

```json
{
  "code": 200,
  "message": "编辑知识点成功！"
}
```

后端链路：`POST /api/teach/point/queryPoint` →（可选）`POST /api/base/code/listCodeByType` →（可选）`POST /api/teach/point/newResFilePoint` / `newResQuizPoint` → `POST /api/teach/point/updatePoint`

#### edit_point_knowledge_relation

- 名称：`edit_point_knowledge_relation`
- 说明：将源知识点与目标知识点通过指定关系主键关联（后端为关联关系新增）

输入参数：

- `instanceId`（int，必填）：课程实例标识
- `fromPointId`（int，必填）：源知识点标识
- `toPointId`（int，必填）：目标关联知识点标识
- `relationId`（int，必填）：关系主键（可由 `add_knowledge_relation` / 关系列表获得）

示例：

```json
{
  "tool": "edit_point_knowledge_relation",
  "arguments": {
    "instanceId": 5442,
    "fromPointId": 1001,
    "toPointId": 1002,
    "relationId": 501
  }
}
```

后端：`POST /api/teach/kg/newPointAssociation`

#### edit_course_detail

- 名称：`edit_course_detail`
- 说明：更新开课实例的课程详情文案（简介、教学大纲、学习目标、考核标准、参考资料）；字段按调用方原文透传

输入参数：

- `instanceId`（int，必填）：课程实例标识
- `introduction`（string，必填）：课程简介
- `syllabus`（string，必填）：教学大纲
- `learnObject`（string，必填）：学习目标
- `assessmentCriteria`（string，必填）：考核标准
- `material`（string，必填）：参考资料

示例：

```json
{
  "tool": "edit_course_detail",
  "arguments": {
    "instanceId": 5442,
    "introduction": "本课程介绍……",
    "syllabus": "第一章……",
    "learnObject": "掌握……",
    "assessmentCriteria": "平时成绩……",
    "material": "教材……"
  }
}
```

后端：`POST /api/teach/instance/updateInstanceDetail`

#### copy_instance

- 名称：`copy_instance`
- 说明：按源开课实例复制/克隆一门新课程。非必填参数为空时，先 `queryInstance` 用源课对应字段回填，再复制
- Profile：`implement`

输入参数：

- `instanceId`（int，必填）：被复制的课程实例标识
- `instanceName`（string，可选）：新课程名称；未传/空串则用源课 `instanceName`
- `departmentId`（int，可选）：院系标识；未传/`0` 则用源课 `departmentId`
- `natureCode`（string，可选）：课程属性编码；未传/空串则用源课 `natureCode`
- `subjectCode`（string，可选）：课程学科编码；未传/空串则用源课 `subjectCode`

示例：

```json
{
  "tool": "copy_instance",
  "arguments": {
    "instanceId": 5442,
    "instanceName": "高等数学-副本",
    "departmentId": 1001,
    "natureCode": "REQUIRED",
    "subjectCode": "MATH"
  }
}
```

后端链路：（可选）`POST /api/teach/instance/queryInstance` → `POST /api/teach/instance/newInstanceByCopy`

#### save_instance_cover

- 名称：`save_instance_cover`
- 说明：上传并保存课程封面图片；保留已有片花配置，仅更新封面

输入参数：

- `instanceId`（int，必填）：课程实例标识
- `imagePath`（string，必填）：封面图片本地路径或网络地址，支持 jpg、png、bmp

示例：

```json
{
  "tool": "save_instance_cover",
  "arguments": {
    "instanceId": 5442,
    "imagePath": "D:/covers/course-cover.png"
  }
}
```

成功返回示例：

```json
{
  "code": 200,
  "message": "保存课程封面成功！"
}
```

后端链路：`POST /api/resource/upload/file`（`uploadType=cover`）→ `POST /api/teach/cover/queryInstanceCover` → `POST /api/teach/cover/saveInstanceCover`

#### query_course_ai_applications

- 名称：`query_course_ai_applications`
- 说明：获取指定课程的 AI 特色应用列表
- Profile：`design`
- 后端：`POST /api/teach/instance/application/listAIApplication`

输入参数：

- `instanceId`（int，必填）：课程实例标识

响应精简：成功响应的 `data.applications` 列表中，每个对象会移除 `createdBy`、`createdTime`、`updatedBy`、`updatedTime`；其他字段、列表顺序和 `applicationDesc` 保持不变。

示例：

```json
{
  "tool": "query_course_ai_applications",
  "arguments": {
    "instanceId": 5442
  }
}
```

#### update_course_ai_applications

- 名称：`update_course_ai_applications`
- 说明：更新课程 AI 特色应用，提交前查询并回填不可编辑字段
- Profile：`design`
- 后端链路：`POST /api/teach/instance/application/listAIApplication` → 可选 `POST /api/resource/upload/file`（`uploadType=instanceApplication`）→ `POST /api/teach/instance/application/saveAIApplication`

输入参数：

- `instanceId`（int，必填）：课程实例标识；保存时会写入每个应用对象
- `applications`（array，必填且非空）：每项包含必填 `applicationId`、`icon`、`title`、`content`，以及可选 `changeCoverPath`
- `changeCoverPath` 仅支持 MCP Server 可读的本地 jpg、jpeg、png 文件（扩展名不区分大小写）

`icon` 为 1–58 时使用 `/repository/column/{icon}.svg`，否则沿用查询结果的 `iconPath`。未提供新封面时沿用已有 `cover`；附件、视频和排序字段也从查询结果回填。

示例：

```json
{
  "tool": "update_course_ai_applications",
  "arguments": {
    "instanceId": 5442,
    "applications": [
      {
        "applicationId": 101,
        "icon": 12,
        "title": "AI 助教",
        "content": "为学生提供智能答疑",
        "changeCoverPath": "D:/images/ai-assistant.png"
      }
    ]
  }
}
```

#### query_course_detail

- 名称：`query_course_detail`
- 说明：按开课标识查询课程详情；成功后按需解析院系与码表名称写入 `departmentName` / `courseLanguage` / `courseAttributes` / `courseSubject`
- Profile：`implement`
- 后端：`POST /api/teach/instance/queryInstance`

输入参数：

- `instanceId`（int，必填）：课程实例标识

响应增强（主查询 `code=200` 且对应编码字段有值时）：

- `departmentId` → `POST /api/core/tenant/department/listDepartment`（`pageNo=1, pageSize=1000`）→ `departmentName`
- `languageCodeId` → `listCodeByType(codeType=courseLanguage)` → `courseLanguage`（`codeName`）
- `natureCode`（转数字）→ `listCodeByType(codeType=courseAttributes)` → `courseAttributes`
- `subjectCode`（转数字）→ `listCodeByType(codeType=courseSubject)` → `courseSubject`

示例：

```json
{
  "tool": "query_course_detail",
  "arguments": {
    "instanceId": 5442
  }
}
```

成功返回示例（含增强字段）：

```json
{
  "code": 200,
  "message": "查询课程详情成功！",
  "data": {
    "instanceId": 5442,
    "instanceName": "高等数学",
    "departmentId": 10,
    "departmentName": "数学学院",
    "languageCodeId": 1,
    "courseLanguage": "中文",
    "natureCode": "20",
    "courseAttributes": "必修",
    "subjectCode": "30",
    "courseSubject": "理学"
  }
}
```

#### query_my_messages

- 名称：`query_my_messages`
- 说明：分页查询我的消息列表；成功且 `rowList` 非空时每项仅保留 `appMessageId`/`messageTitle`/`messageContent`/`avatarUrl`/`readFlag`
- Profile：`common`
- 后端：`POST /api/core/message/listReceiverAppMessage`

输入参数：

- `page`（object，可选）：分页对象，含 `pageNo`、`pageSize`；未传默认 `{pageNo: 1, pageSize: 20}`
- `readFlag`（int，可选）：`0` 未读消息、`1` 已读消息；不传表示全部消息

示例：

```json
{
  "tool": "query_my_messages",
  "arguments": {
    "page": {"pageNo": 1, "pageSize": 20},
    "readFlag": 0
  }
}
```

成功返回示例（`rowList` 已优化）：

```json
{
  "code": 200,
  "message": "查询我的消息列表成功！",
  "data": {
    "rowList": [
      {
        "appMessageId": 101,
        "messageTitle": "系统通知",
        "messageContent": "您有一条新消息",
        "avatarUrl": "https://example.com/a.png",
        "readFlag": 0
      }
    ]
  }
}
```

#### update_app_message_read_flag

- 名称：`update_app_message_read_flag`
- 说明：将指定消息标记为已读
- Profile：`common`
- 后端：`POST /api/core/message/updateAppMessageReadFlag`

输入参数：

- `appMessageIds`（int[]，必填）：消息标识列表

示例：

```json
{
  "tool": "update_app_message_read_flag",
  "arguments": {
    "appMessageIds": [101, 102]
  }
}
```

成功返回示例：

```json
{
  "code": 200,
  "message": "更新消息为已读成功！"
}
```

#### query_my_instances

- 名称：`query_my_instances`
- 说明：分页查询我教的课程列表 / 我的课程列表；成功且 `rowList` 非空时每项仅保留 `instanceId`/`instanceName`/`instanceCode`/`coverUrl`/`introduction`/`lecturerName`/`finishFlag`
- Profile：`implement`
- 替代：原生成 Tool `teach_instance_querymyinstancebypage_post`（已移除）

输入参数：

- `page`（object，可选）：分页对象，含 `pageNo`、`pageSize`；未传默认 `{pageNo: 1, pageSize: 20}`
- `searchText`（string，可选）：搜索关键字（课程名称/课程代码/院系/主讲教师姓名），映射为后端 `instanceName`
- `finishFlag`（int，可选）：结课标记
- `createdFlag`（int，可选）：`1` 表示我创建的
- `instanceType`（integer/int32，可选）：`0` 全部，`10` 智慧课，`20` 共享课；未传时省略后端该字段

自动请求参数：`instanceType=10` 时写入 `manageFlag=0`；其他情况（包括未传 `instanceType`）写入 `manageFlag=1`，无需调用方提供。

示例：

```json
{
  "tool": "query_my_instances",
  "arguments": {
    "instanceType": 10,
    "page": {"pageNo": 1, "pageSize": 20},
    "searchText": "高等数学",
    "createdFlag": 1
  }
}
```

成功返回示例（`rowList` 已优化）：

```json
{
  "code": 200,
  "message": "查询我的课程列表成功！",
  "data": {
    "rowList": [
      {
        "instanceId": "5442",
        "instanceName": "高等数学",
        "instanceCode": "MATH101",
        "coverUrl": "https://example.com/cover.png",
        "introduction": "课程简介",
        "lecturerName": "张三",
        "finishFlag": 0
      }
    ]
  }
}
```

后端链路：`POST /api/teach/instance/queryMyInstanceByPage`

#### publish_instance

- 名称：`publish_instance`
- 说明：发布/取消发布开课实例；发布成功后依次更新实例配置（`updateType` 10/20/30），取消发布时不执行配置更新

输入参数：

- `instanceId`（int，必填）：课程实例标识
- `publishFlag`（integer/int32，可选，默认 `1`）：`1` 发布，`0` 取消发布

示例：

```json
{
  "tool": "publish_instance",
  "arguments": {
    "instanceId": 5442
  }
}
```

取消发布示例：

```json
{"tool":"publish_instance","arguments":{"instanceId":5442,"publishFlag":0}}
```

后端：`POST /api/teach/instance/publishInstance`；仅发布成功后调用 `POST /api/teach/instance/updateInstanceConfig`。成功文案分别为「发布课程成功！」和「取消发布课程成功！」。

#### query_course_lecturer_list

- 名称：`query_course_lecturer_list`
- 说明：查询开课实例的主讲教师列表；成功时透传完整 `data`（通常含 `lecturerList`，不解包）

输入参数：

- `instanceId`（int，必填）：课程实例标识

示例：

```json
{
  "tool": "query_course_lecturer_list",
  "arguments": {
    "instanceId": 5442
  }
}
```

后端：`POST /api/teach/lecturer/listInstanceLecturer`

#### query_course_paper_list

- 名称：`query_course_paper_list`
- 说明：分页查询开课实例的课程试卷列表；成功时透传完整 `data`（通常含 `rowList`、`totalRowCount` 等）

输入参数：

- `instanceId`（int，必填）：课程实例标识
- `directoryId`（int，可选）：课程资源目录标识
- `searchText`（string，可选）：要检索的试卷名称
- `page`（object，可选）：分页对象，含 `pageNo`、`pageSize`；未传时默认 `{"pageNo": 1, "pageSize": 50}`

示例：

```json
{
  "tool": "query_course_paper_list",
  "arguments": {
    "instanceId": 5442,
    "searchText": "期中",
    "directoryId": 100000744,
    "page": {
      "pageNo": 1,
      "pageSize": 50
    }
  }
}
```

后端：`POST /api/teach/paper/listResPaper`

#### query_course_paper_strategy_list

- 名称：`query_course_paper_strategy_list`
- 说明：分页查询开课实例的课程组卷策略列表；成功时透传完整 `data`（通常含 `rowList`、`totalRowCount` 等）

输入参数：

- `instanceId`（int，必填）：课程实例标识
- `directoryId`（int，可选）：课程资源目录标识
- `searchText`（string，可选）：要检索的组卷策略名称
- `page`（object，可选）：分页对象，含 `pageNo`、`pageSize`；未传时默认 `{"pageNo": 1, "pageSize": 20}`

示例：

```json
{
  "tool": "query_course_paper_strategy_list",
  "arguments": {
    "instanceId": 5442,
    "searchText": "期中",
    "directoryId": 100000744,
    "page": {
      "pageNo": 1,
      "pageSize": 20
    }
  }
}
```

后端：`POST /api/resource/paperstrategy/listResPaperStrategy`

#### create_paper_strategy

- 名称：`create_paper_strategy`
- 说明：创建课程组卷策略；根据 `quizTypes` 计算题量与汇总分值并补全题型元数据；成功时透传 `data`（通常含 `strategyId`）

输入参数：

- `instanceId`（int，必填）：课程实例标识
- `strategyName`（string，必填）：组卷策略名称
- `totalScore`（number，必填）：试卷总分声明值（实际提交以 `quizTypes` 计算结果为准）
- `difficultyType`（int，必填）：难度类型
- `selectType`（int，必填）：抽题方式，`10` 按目录，`20` 按知识点
- `advanceType`（int，必填）：高级抽题方式，`10` 普通，`20` 高级
- `quizTypes`（object[]，必填）：组卷策略题型设置
  - `quizType`（int，必填）：题目题型值，`10` 单选 / `20` 多选 / `30` 判断 / `40` 填空 / `50` 简答
  - `quizNum`（int，可选，默认 `0`）：题型的题量
  - `easyNum`（int，可选，默认 `0`）：简单题的题量
  - `relativelyEasyNum`（int，可选，默认 `0`）：较简单题的题量
  - `middleNum`（int，可选，默认 `0`）：中等题的题量
  - `relativelyHardNum`（int，可选，默认 `0`）：较困难题的题量
  - `hardNum`（int，可选，默认 `0`）：困难题的题量
  - `quizScore`（int，必填）：每题分值
- `directoryId`（int，可选）：目录标识，默认 `-1`
- `directories`（array，可选）：按目录抽题时的目录配置
- `points`（array，可选）：按知识点抽题时的知识点配置

示例：

```json
{
  "tool": "create_paper_strategy",
  "arguments": {
    "instanceId": 5442,
    "strategyName": "期中组卷策略",
    "totalScore": 100,
    "difficultyType": 10,
    "selectType": 10,
    "advanceType": 10,
    "quizTypes": [
      {
        "quizType": 10,
        "quizNum": 10,
        "quizScore": 10
      }
    ],
    "directories": [
      {
        "directoryId": 100000744
      }
    ]
  }
}
```

后端：`POST /api/resource/paperstrategy/newResPaperStrategy`

#### update_paper_strategy

- 名称：`update_paper_strategy`
- 说明：更新课程组卷策略；按 `quizType` 补全题型元数据（兼容旧 `quizTypeName`）；高级抽题模式下为 `directories`/`points` 写入 `strategyId`；提交的 `totalScore` 使用入参值

输入参数：

- `strategyId`（int，必填）：组卷策略标识（可由 `query_course_paper_strategy_list` 获得）
- `instanceId`（int，必填）：课程实例标识
- `directoryId`（int，必填）：目录标识
- `strategyName`（string，必填）：组卷策略名称
- `totalScore`（number，必填）：试卷总分
- `difficultyType`（int，必填）：难度类型
- `selectType`（int，必填）：抽题方式，`10` 按目录，`20` 按知识点
- `advanceType`（int，必填）：高级抽题方式，`10` 普通，`20` 高级
- `quizTypes`（object[]，必填）：组卷策略题型设置（字段同 `create_paper_strategy`）
  - `quizType`（int，必填）：`10`/`20`/`30`/`40`/`50`
  - `quizNum`（int，可选，默认 `0`）
  - `easyNum` / `relativelyEasyNum` / `middleNum` / `relativelyHardNum` / `hardNum`（int，可选，默认 `0`）
  - `quizScore`（int，必填）：每题分值
- `directories`（array，可选）：按目录抽题时的目录配置
- `points`（array，可选）：按知识点抽题时的知识点配置

示例：

```json
{
  "tool": "update_paper_strategy",
  "arguments": {
    "strategyId": 10001,
    "instanceId": 5442,
    "directoryId": 100000744,
    "strategyName": "期中组卷策略（修订）",
    "totalScore": 100,
    "difficultyType": 10,
    "selectType": 10,
    "advanceType": 10,
    "quizTypes": [
      {
        "quizType": 10,
        "quizNum": 10,
        "quizScore": 10
      }
    ],
    "directories": [
      {
        "directoryId": 100000744
      }
    ]
  }
}
```

后端：`POST /api/resource/paperstrategy/updateResPaperStrategy`

#### copy_course_paper

- 名称：`copy_course_paper`
- 说明：按试卷标识复制课程试卷；成功时透传 `data`（通常含新试卷信息）

输入参数：

- `paperId`（int，必填）：试卷标识（可由 `query_course_paper_list` 获得）

示例：

```json
{
  "tool": "copy_course_paper",
  "arguments": {
    "paperId": 100012345
  }
}
```

后端：`POST /api/teach/paper/copyResPaper`

#### export_course_paper

- 名称：`export_course_paper`
- 说明：按试卷标识与导出类型导出课程试卷；后端返回文件流后保存到本地；若为 zip 格式则自动解压并返回解压后文件路径

输入参数：

- `paperId`（int，必填）：试卷标识（可由 `query_course_paper_list` 获得）
- `downloadType`（int，必填）：导出类型，仅允许 `10`、`20`、`30`

成功返回 `data` 示例（非 zip）：

```json
{
  "filePath": "C:\\Users\\...\\AppData\\Local\\Temp\\haoke-mcp-export-paper\\期中测验.docx",
  "fileName": "期中测验.docx"
}
```

成功返回 `data` 示例（zip，已解压）：

```json
{
  "filePath": "C:\\Users\\...\\AppData\\Local\\Temp\\haoke-mcp-export-paper\\考试试卷\\试卷.docx",
  "fileName": "试卷.docx",
  "filePaths": [
    "C:\\Users\\...\\AppData\\Local\\Temp\\haoke-mcp-export-paper\\考试试卷\\试卷.docx"
  ]
}
```

调用示例：

```json
{
  "tool": "export_course_paper",
  "arguments": {
    "paperId": 100012345,
    "downloadType": 10
  }
}
```

后端：`POST /api/teach/paper/exportResPaper`（响应为文件流 + `filename` 响应头）

#### analyze_course_paper

- 名称：`analyze_course_paper`
- 说明：分析开课实例下的课程试卷；成功时对 `data` 中字段名为 `score` 或以 `score` 结尾的非空分值除以 100

输入参数：

- `instanceId`（int，必填）：课程实例标识
- `paperId`（int，必填）：试卷标识（可由 `query_course_paper_list` 获得）

示例：

```json
{
  "tool": "analyze_course_paper",
  "arguments": {
    "instanceId": 5442,
    "paperId": 100012345
  }
}
```

后端：`POST /api/teach/paper/analyzePaper`

#### create_instance_paper

- 名称：`create_instance_paper`
- 说明：按开课实例手动选题创建课程试卷；提交前将 `totalScore` 与各题 `score` 乘以 100；根据题目与题型列表组装 `quizTypeVos`；通过 `paperUse` 码表将 `useName` 解析为 `useId`

输入参数：

- `instanceId`（int，必填）：课程实例标识
- `paperName`（string，必填）：试卷名称
- `totalScore`（number，必填）：试卷总分
- `duration`（int，必填）：考试时长（分钟）
- `useName`（string，必填）：试卷用途，逗号分隔，如「考试,作业」
- `quizVos`（array，必填）：题目列表，各题含 `quizTypeId`、`score` 等字段
- `directoryId`（int，可选）：目录标识
- `paperDescription`（string，可选）：试卷说明，默认 `""`
- `fileName`（string，可选）：文件名，仅兼容旧版入参，不触发截图

示例：

```json
{
  "tool": "create_instance_paper",
  "arguments": {
    "instanceId": 5442,
    "paperName": "期中测验",
    "totalScore": 100,
    "duration": 90,
    "useName": "考试",
    "paperDescription": "第一章测验",
    "quizVos": [
      {
        "quizTypeId": 1,
        "score": 10,
        "quizId": 10001
      }
    ]
  }
}
```

后端：`POST /api/teach/paper/newResPaper`（`createType=10`）

#### strategy_create_instance_paper

- 名称：`strategy_create_instance_paper`
- 说明：用组卷策略为课程创建试卷；先调用 `genPaperByStrategy` 获取题目，再创建试卷；`strategyId` 与 `strategyName` 二选一且必须有一个

输入参数：

- `instanceId`（int，必填）：课程实例标识
- `paperName`（string，必填）：试卷名称
- `totalScore`（number，必填）：试卷满分
- `duration`（int，必填）：考试时长（分钟）
- `useName`（string，必填）：试卷用途，逗号分隔，如「考试,作业」
- `strategyId`（int，可选）：组卷策略标识（与 `strategyName` 二选一）
- `strategyName`（string，可选）：组卷策略名称（与 `strategyId` 二选一，按名称查列表首条）
- `paperDescription`（string，可选）：试卷说明，默认 `""`

示例：

```json
{
  "tool": "strategy_create_instance_paper",
  "arguments": {
    "instanceId": 5442,
    "paperName": "期中测验",
    "totalScore": 100,
    "duration": 90,
    "useName": "考试",
    "strategyId": 10001,
    "paperDescription": "按组卷策略生成"
  }
}
```

后端：`POST /api/teach/paper/genPaperByStrategy` → `POST /api/teach/paper/newResPaper`（`createType=10`）

#### update_course_paper

- 名称：`update_course_paper`
- 说明：编辑课程试卷；组卷逻辑与 `create_instance_paper` 相同（分值 ×100、组装 `quizTypeVos`、`useName` → `useId`）

输入参数：

- `paperId`（int，必填）：试卷标识（可由 `query_course_paper_list` 获得）
- `instanceId`（int，必填）：课程实例标识
- `paperName`（string，必填）：试卷名称
- `totalScore`（number，必填）：试卷总分
- `duration`（int，必填）：考试时长（分钟）
- `useName`（string，必填）：试卷用途，逗号分隔，如「考试,作业」
- `quizVos`（array，必填）：题目列表，各题含 `quizTypeId`、`score` 等字段
- `directoryId`（int，可选）：目录标识
- `paperDescription`（string，可选）：试卷说明，默认 `""`
- `fileName`（string，可选）：文件名，仅兼容旧版入参，不触发截图

示例：

```json
{
  "tool": "update_course_paper",
  "arguments": {
    "paperId": 100012345,
    "instanceId": 5442,
    "paperName": "期中测验（修订）",
    "totalScore": 100,
    "duration": 90,
    "useName": "考试",
    "paperDescription": "第一章测验修订版",
    "quizVos": [
      {
        "quizTypeId": 1,
        "score": 10,
        "quizId": 10001
      }
    ]
  }
}
```

后端：`POST /api/teach/paper/updateResPaper`（`createType=10`）

#### edit_course_lecturer

- 名称：`edit_course_lecturer`
- 说明：更新开课实例下主讲教师信息；可选字段仅在提供时写入请求体

输入参数：

- `lecturerId`（int，必填）：主讲教师标识（可由 `query_course_lecturer_list` 获得）
- `instanceId`（int，必填）：课程实例标识
- `name`（string，必填）：姓名
- `avatar`（string，可选）：头像地址
- `titleId`（int，可选）：职称 code_id
- `title`（string，可选）：职称名称
- `schoolName`（string，可选）：学校
- `department`（string，可选）：院系
- `introduction`（string，可选）：个人介绍

示例：

```json
{
  "tool": "edit_course_lecturer",
  "arguments": {
    "lecturerId": 1001,
    "instanceId": 5442,
    "name": "张三",
    "title": "教授",
    "schoolName": "某某大学"
  }
}
```

后端：`POST /api/teach/lecturer/updateInstanceLecturer`

#### query_point_list

- 名称：`query_point_list`
- 说明：查询课程知识点列表；成功时对 `data.pointItemList` 树形结构做字段精简

输入参数：

- `instanceId`（int，必填）：课程实例标识
- `searchText`（string，可选）：知识点名称搜索关键字

固定请求参数（MCP 自动写入）：`viewType=10`

示例：

```json
{
  "tool": "query_point_list",
  "arguments": {
    "instanceId": 5442,
    "searchText": "变量"
  }
}
```

成功返回示例（`pointItemList` 已优化）：

```json
{
  "code": 200,
  "message": "查询知识点列表成功！",
  "data": {
    "pointItemList": [
      {
        "pointId": 1001,
        "pointName": "变量与类型",
        "description": "基础概念",
        "parentId": -1,
        "resCount": 2,
        "quizCount": 5,
        "pointType": 1,
        "pointCognition": 3,
        "children": [
          {
            "pointId": 1002,
            "pointName": "整型",
            "parentId": 1001,
            "resCount": 0,
            "quizCount": 1,
            "pointType": 1,
            "pointCognition": 2
          }
        ]
      }
    ]
  }
}
```

后端链路：`POST /api/teach/point/listPoint`

#### query_ability_list

- 名称：`query_ability_list`
- 说明：查询课程能力图谱列表；成功时对 `data.abilityList` 树形结构做字段精简，不返回 `enableFlag`

输入参数：

- `instanceId`（int，必填）：课程实例标识

示例：

```json
{
  "tool": "query_ability_list",
  "arguments": {
    "instanceId": 5442
  }
}
```

成功返回示例（`abilityList` 已优化）：

```json
{
  "code": 200,
  "message": "查询能力图谱列表成功！",
  "data": {
    "abilityList": [
      {
        "abilityId": 101,
        "title": "问题解决能力",
        "description": "能够分析并解决实际问题",
        "parentId": -1,
        "childAbilityList": [
          {
            "abilityId": 102,
            "title": "问题分析",
            "description": "识别关键问题要素",
            "parentId": 101
          }
        ]
      }
    ]
  }
}
```

后端链路：`POST /api/teach/ability/listAbility`

#### delete_course_abilities

- 名称：`delete_course_abilities`
- 说明：删除一个或多个能力图谱能力节点（`abilityIdList` 长度为 1 即为单能力删除）
- Profile：`common`

输入参数：

- `abilityIdList`（int | int[]，必填）：能力标识列表

示例（批量）：

```json
{
  "tool": "delete_course_abilities",
  "arguments": {
    "abilityIdList": [101, 102]
  }
}
```

示例（单能力）：

```json
{
  "tool": "delete_course_abilities",
  "arguments": {
    "abilityIdList": [101]
  }
}
```

后端链路：`POST /api/teach/ability/delAbility`

#### add_course_ability

- 名称：`add_course_ability`
- 说明：人工添加课程能力图谱的能力（固定 `createType=10`）

输入参数：

- `instanceId`（int，必填）：课程实例标识
- `title`（string，必填）：能力名称
- `description`（string，可选）：能力描述
- `parentId`（int，可选）：父级能力标识
- `tagList`（array[string]，可选）：标签列表
- `pointIdList`（array[int]，可选）：关联知识点标识列表
- `questionIdList`（array[int]，可选）：关联问题标识列表
- `taskIdList`（array[int]，可选）：关联任务标识列表
- `childAbilityList`（array[object]，可选）：随主能力同时添加的子能力列表；每项 `title` 必填，可选 `description`、`tagList`、`pointIdList`、`questionIdList`、`taskIdList`

提交前会为 `childAbilityList` 每个对象注入工具级 `instanceId`；调用方无需提供该字段，即使提供也会被顶层 `instanceId` 覆盖。

示例：

```json
{
  "tool": "add_course_ability",
  "arguments": {
    "instanceId": 5442,
    "title": "问题解决能力",
    "description": "能够分析并解决实际问题",
    "parentId": -1,
    "tagList": ["核心", "重点"],
    "pointIdList": [101, 102],
    "childAbilityList": [
      {
        "title": "分析问题",
        "description": "识别问题的关键条件",
        "tagList": ["分析"],
        "pointIdList": [101],
        "questionIdList": [201],
        "taskIdList": [301]
      },
      {
        "title": "解决问题",
        "description": "选择并执行解决方案"
      }
    ]
  }
}
```

成功返回示例：

```json
{
  "code": 200,
  "message": "添加课程能力成功！",
  "data": {
    "abilityId": 101,
    "previewUrl": "https://.../teacher/course/5442/column/{uniqueId}/graph/ability"
  }
}
```

后端：`POST /api/teach/ability/newAbility`（请求体固定写入 `createType=10`）→ 成功后 `queryInstanceInfo` 解析 `columnCode=ability-graph` 拼装 `data.previewUrl`

#### update_course_ability

- 名称：`update_course_ability`
- 说明：编辑课程能力图谱的主能力（固定 `createType=10`），并支持新增、修改或删除子能力。提交前先 `queryAbility`：查询子能力删除 `delChildAbilityIds` 命中项后，与入参 `childAbilityList` 按 `abilityId` 有序合并；最终所有子能力统一注入工具级 `instanceId`

输入参数：

- `abilityId`（int，必填）：能力标识
- `instanceId`（int，必填）：课程实例标识
- `title`（string，必填）：能力名称
- `description`（string，可选）：能力描述；未传时从 `queryAbility` 回填（查询非 null）
- `parentId`（int，可选）：父级能力标识
- `tagList`（array[string]，可选）：标签列表；未传时从 `queryAbility` 回填（查询非 null）
- `pointIdList`（array[int]，可选）：关联知识点标识列表；未传时从查询回填
- `questionIdList`（array[int]，可选）：关联问题标识列表；未传时从查询回填
- `taskIdList`（array[int]，可选）：关联任务标识列表；未传时从查询回填
- `delChildAbilityIds`（array[int]，可选）：待删除的子能力标识列表；仅用于本地合并，不提交给后端
- `childAbilityList`（array[object]，可选）：待新增或修改的子能力列表
  - `abilityId`（int，可选）：已有子能力标识；为空表示新增
  - `title`（string，必填）：子能力名称
  - `description`、`tagList`、`pointIdList`、`questionIdList`、`taskIdList`：子能力可选字段；修改时未传字段沿用查询值

查询子能力先移除待删除项；带 `abilityId` 的输入项在原位置修改，不带 `abilityId` 的新增项按输入顺序追加。重复、未知或同时删除并修改的 `abilityId` 会返回参数错误。

示例：

```json
{
  "tool": "update_course_ability",
  "arguments": {
    "abilityId": 101,
    "instanceId": 5442,
    "title": "问题解决能力（修订）",
    "description": "能够分析并解决实际问题",
    "parentId": -1,
    "tagList": ["核心", "重点"],
    "pointIdList": [101, 102],
    "delChildAbilityIds": [201],
    "childAbilityList": [
      {
        "abilityId": 202,
        "title": "已有子能力（修订）",
        "pointIdList": [103]
      },
      {
        "title": "新增子能力",
        "description": "新增说明"
      }
    ]
  }
}
```

成功返回示例：

```json
{
  "code": 200,
  "message": "编辑课程能力成功！",
  "data": {
    "abilityId": 101,
    "previewUrl": "https://.../teacher/course/5442/column/{uniqueId}/graph/ability"
  }
}
```

后端链路：`POST /api/teach/ability/queryAbility` → `POST /api/teach/ability/updateAbility`（`createType=10`）→ 成功后 `queryInstanceInfo` 解析 `columnCode=ability-graph` 拼装 `data.previewUrl`

#### set_class_ability_rate

- 名称：`set_class_ability_rate`
- 说明：班级能力设置，保存能力图谱得分占比配置

输入参数：

- `instanceId`（int，必填）：开课主键
- `classIds`（array[int]，必填）：占比设置生效班级标识列表
- `rateConfigs`（array[object]，必填）：能力占比配置列表
  - 项字段：`abilityId`、`parentId`、`rate`、`title`、`childAbilityList`
  - `childAbilityList` 项字段：`abilityId`、`parentId`、`rate`、`title`（可继续嵌套）
  - `rate`：小数比例（0–1）；提交前 MCP 自动 ×100
- `updateScore`（int，可选）：是否更新能力得分（0-否 1-是）；未传默认 `0`

示例：

```json
{
  "tool": "set_class_ability_rate",
  "arguments": {
    "instanceId": 5442,
    "classIds": [24135],
    "updateScore": 0,
    "rateConfigs": [
      {
        "abilityId": 101,
        "parentId": -1,
        "rate": 0.5,
        "title": "问题解决能力",
        "childAbilityList": [
          {
            "abilityId": 102,
            "parentId": 101,
            "rate": 0.5,
            "title": "问题分析"
          }
        ]
      }
    ]
  }
}
```

后端：`POST /api/teach/ability/saveRateConfig`（提交前将所有 `rate` ×100）

#### query_ai_quiz_num

- 名称：`query_ai_quiz_num`
- 说明：查询课程中 AI 生成的题目数量；成功时 `data` 仅保留 `aiQuizNum`

输入参数：

- `instanceId`（int，必填）：课程实例标识

示例：

```json
{
  "tool": "query_ai_quiz_num",
  "arguments": {
    "instanceId": 5442
  }
}
```

成功返回示例：

```json
{
  "code": 200,
  "message": "查询 AI 生成题目数成功！",
  "data": {
    "aiQuizNum": 128
  }
}
```

后端链路：`POST /api/teach/instance/overview/quizStatisData`

#### query_adaptive_learn_plan_list

- 名称：`query_adaptive_learn_plan_list`
- 说明：分页查询班级自适应学习计划列表

输入参数：

- `classId`（int，必填）：教学班标识
- `instanceId`（int，必填）：开课标识
- `searchText`（string，可选）：搜索关键字
- `page`（object，可选）：分页对象，含 `pageNo`、`pageSize`；默认 `{pageNo: 1, pageSize: 20}`
- `startTime`（string，可选）：开始时间，格式 `yyyy-MM-dd HH:mm:ss`
- `endTime`（string，可选）：结束时间，格式 `yyyy-MM-dd HH:mm:ss`

示例：

```json
{
  "tool": "query_adaptive_learn_plan_list",
  "arguments": {
    "classId": 24135,
    "instanceId": 5442,
    "page": {"pageNo": 1, "pageSize": 20},
    "searchText": "",
    "startTime": "2026-01-01 00:00:00",
    "endTime": "2026-12-31 23:59:59"
  }
}
```

后端：`POST /api/teach/learn/plan/listPlans`

#### query_adaptive_learn_plan_detail

- 名称：`query_adaptive_learn_plan_detail`
- 说明：按计划标识查看自适应学习计划详情

输入参数：

- `planId`（int，必填）：计划标识

示例：

```json
{
  "tool": "query_adaptive_learn_plan_detail",
  "arguments": {
    "planId": 10001
  }
}
```

后端：`POST /api/learn/task/plan/queryPlanDetail`

#### query_adaptive_learn_plan_view_log

- 名称：`query_adaptive_learn_plan_view_log`
- 说明：按计划标识查看自适应学习计划生成记录；`viewLogVOList` 每项仅返回白名单字段

输入参数：

- `planId`（int，必填）：计划标识

返回字段白名单（`data.viewLogVOList[]`）：

- `logId`、`logTextParams`、`screenshots`、`webUrl`、`fileContent`、`createdTime`、`createdBy`、`relativeId`、`relativeIdPath`

示例：

```json
{
  "tool": "query_adaptive_learn_plan_view_log",
  "arguments": {
    "planId": 10001
  }
}
```

后端：`POST /api/learn/task/plan/log/listViewLog`

#### query_adaptive_learn_point_stat

- 名称：`query_adaptive_learn_point_stat`
- 说明：按班级与开课查询自适应学习知识点统计

输入参数：

- `classId`（int，必填）：教学班标识
- `instanceId`（int，必填）：开课标识

示例：

```json
{
  "tool": "query_adaptive_learn_point_stat",
  "arguments": {
    "classId": 24135,
    "instanceId": 5442
  }
}
```

后端：`POST /api/teach/learn/plan/queryPointStatInfo`

#### query_adaptive_learn_point_plan_echart

- 名称：`query_adaptive_learn_point_plan_echart`
- 说明：按班级与开课查询各知识点关联学习计划数量（图表数据）

输入参数：

- `classId`（int，必填）：教学班标识
- `instanceId`（int，必填）：开课标识

固定请求参数（MCP 自动写入）：`orderBy="planNum"`、`orderType="desc"`

示例：

```json
{
  "tool": "query_adaptive_learn_point_plan_echart",
  "arguments": {
    "classId": 24135,
    "instanceId": 5442
  }
}
```

后端：`POST /api/teach/learn/plan/queryPointPlanEChart`

#### query_adaptive_learn_point_complete_echart

- 名称：`query_adaptive_learn_point_complete_echart`
- 说明：按班级与开课查询核心知识点掌握度（图表数据）

输入参数：

- `classId`（int，必填）：教学班标识
- `instanceId`（int，必填）：开课标识

固定请求参数（MCP 自动写入）：`orderBy="complete"`、`orderType="desc"`

示例：

```json
{
  "tool": "query_adaptive_learn_point_complete_echart",
  "arguments": {
    "classId": 24135,
    "instanceId": 5442
  }
}
```

后端：`POST /api/teach/learn/plan/queryPointCompleteEChart`

#### query_adaptive_learn_student_stat

- 名称：`query_adaptive_learn_student_stat`
- 说明：按开课与班级查询自适应学习学生统计

输入参数：

- `instanceId`（int，必填）：开课标识
- `classId`（int，必填）：教学班标识；`0` 表示全部班级

示例：

```json
{
  "tool": "query_adaptive_learn_student_stat",
  "arguments": {
    "instanceId": 5442,
    "classId": 0
  }
}
```

后端：`POST /api/teach/learn/plan/queryStudentStatInfo`

#### query_adaptive_learn_stu_plan_list

- 名称：`query_adaptive_learn_stu_plan_list`
- 说明：按开课与班级分页查询自适应学习学生学习计划列表

输入参数：

- `instanceId`（int，必填）：开课标识
- `classId`（int，必填）：教学班标识；`0` 表示全部班级
- `searchText`（string，可选）：搜索关键字
- `page`（object，可选）：分页对象，含 `pageNo`、`pageSize`；默认 `{pageNo: 1, pageSize: 20}`

示例：

```json
{
  "tool": "query_adaptive_learn_stu_plan_list",
  "arguments": {
    "instanceId": 5442,
    "classId": 0,
    "searchText": "",
    "page": {"pageNo": 1, "pageSize": 20}
  }
}
```

后端：`POST /api/teach/learn/plan/queryStuPlanList`

#### query_stu_point_mastery_list

- 名称：`query_stu_point_mastery_list`
- 说明：分页查询班级学生知识点掌握度列表

输入参数：

- `instanceId`（int，必填）：课程实例标识
- `classId`（int，必填）：班级主键
- `page`（object，可选）：分页对象，含 `pageNo`、`pageSize`；默认 `{pageNo: 1, pageSize: 20}`
- `searchText`（string，可选）：搜索关键字（姓名/学号等）

固定请求参数（MCP 自动写入）：`orderBy=""`、`orderType=""`

示例：

```json
{
  "tool": "query_stu_point_mastery_list",
  "arguments": {
    "instanceId": 5442,
    "classId": 24135,
    "page": {"pageNo": 1, "pageSize": 20},
    "searchText": ""
  }
}
```

成功返回示例：

```json
{
  "code": 200,
  "message": "查询学生知识点掌握度列表成功！",
  "data": {
    "pageNo": 1,
    "pageSize": 20,
    "totalRowCount": 2,
    "rowList": [
      {
        "userId": 1001,
        "realName": "张三",
        "studentNo": "2026001",
        "avgCompleteRate": 0.85,
        "overAvgNmu": 3,
        "lessAvgNum": 1,
        "groupId": 10,
        "groupName": "第一组"
      }
    ]
  }
}
```

后端链路：`POST /api/teach/point/analysis/queryStuPointMasteryList`

#### query_stu_ability_list

- 名称：`query_stu_ability_list`
- 说明：分页查询班级学生能力统计列表

输入参数：

- `instanceId`（int，必填）：课程实例标识
- `classId`（int，必填）：班级主键
- `page`（object，可选）：分页对象，含 `pageNo`、`pageSize`；默认 `{pageNo: 1, pageSize: 20}`
- `searchText`（string，可选）：搜索关键字（姓名/学号等）

固定请求参数（MCP 自动写入）：`orderBy=""`、`orderType=""`

示例：

```json
{
  "tool": "query_stu_ability_list",
  "arguments": {
    "instanceId": 5442,
    "classId": 24135,
    "page": {"pageNo": 1, "pageSize": 20},
    "searchText": ""
  }
}
```

成功返回示例：

```json
{
  "code": 200,
  "message": "查询学生能力统计列表成功！",
  "data": {
    "pageNo": 1,
    "pageSize": 20,
    "totalRowCount": 2,
    "rowList": [
      {
        "userId": 1001,
        "realName": "张三",
        "studentNo": "2026001",
        "score": 8500,
        "groupName": "第一组",
        "className": "计科1班",
        "mainAbilityScores": []
      }
    ]
  }
}
```

后端链路：`POST /api/teach/ability/analysis/queryStuAbilityList`

#### query_repository_resource_counts

- 名称：`query_repository_resource_counts`
- 说明：统计课程知识库中各类型资源数量；按 `resType` 分别查询并汇总 `totalRowCount`

输入参数：

- `instanceId`（int，必填）：课程实例标识

固定请求参数（MCP 自动写入，每种类型各请求一次）：

- `directoryId=-1`、`indexType=-1`、`pointId=-1`、`useId=-1`
- `searchText=""`
- `page={pageNo: 1, pageSize: 1}`
- `resType`：`10` 视频 / `20` 文档 / `30` 图片 / `40` 题目 / `21` 音频 / `50` 链接

示例：

```json
{
  "tool": "query_repository_resource_counts",
  "arguments": {
    "instanceId": 5442
  }
}
```

成功返回示例：

```json
{
  "code": 200,
  "message": "统计知识库各类型资源数量成功！",
  "data": {
    "videoNum": 12,
    "documentNum": 35,
    "imageNum": 8,
    "quizNum": 120,
    "audioNum": 3,
    "linkNum": 2
  }
}
```

后端链路：`POST /api/resource/repository/listRepositoryItems`（六种 `resType` 各一次）

#### create_instance_unit_by_md

- 名称：`create_instance_unit_by_md`
- 说明：用 Markdown 文本批量创建课程目录；`#` 为章，`##`、`###`、`####`、`#####`、`######`、`#######` 分别为第1～6级节，各节挂载到最近的直接上级。同级为兄弟节点，遇到新章重新建立节层级。
- 整体标题结构先校验再创建：不允许无章的节、跳级、空标题或超过 `#######`；无有效标题时报错。代码围栏内的标题不创建目录。创建中后端失败或缺少有效节点ID时立即停止，已创建节点不自动回滚。

输入参数：

- `instanceId`（int，必填）：课程实例标识
- `markdownContent`（string，必填）：Markdown 章节目录文本
- `screenShot`（int，可选，默认 1）：是否截图，`0` 否 `1` 是

Markdown 格式示例：

```markdown
# 第一章 绪论
## 第一节 课程介绍
## 第二节 学习目标
# 第二章 基础知识
## 第一节 核心概念
```

调用示例：

```json
{
  "tool": "create_instance_unit_by_md",
  "arguments": {
    "instanceId": 5442,
    "markdownContent": "# 第一章 机器学习方法\n## 1.1 无监督学习\n### 1.1.1 聚类\n#### 1.1.1.1 相似性度量\n##### 1.1.1.1.1 常用聚类分析\n###### 1.1.1.1.1.1 Python算法\n####### 1.1.1.1.1.1.1 聚类实验\n## 1.2 有监督学习",
    "screenShot": 1
  }
}
```

成功返回示例：

```json
{
  "code": 200,
  "message": "创建章节目录成功！",
  "data": {
    "previewUrl": "https://example.com/teacher/course/5442/column/chapters-col/chapters"
  },
  "screenshot": [{ "fileUrl": "https://..." }],
  "webUrl": "https://.../teacher/course/5442/column/.../chapters?..."
}
```

说明：`data.previewUrl` 路径为 `/teacher/course/{instanceId}/column/{uniqueId}/chapters`；通过 `queryInstanceInfo`（`version=2`）按 `columnCode=chapters` 解析 `uniqueId`，取不到则回退为 `column`。截图相关字段仅在 `screenShot=1` 时出现。

后端链路：`POST /api/teach/unit/newUnit`（章）→ `POST /api/teach/unit/newLecture`（节）→ `queryInstanceInfo` 组装预览链接 → 章节目录截图（`screenShot=1` 时）

#### query_lecture_tasks

- 名称：`query_lecture_tasks`
- 说明：查询节目录下的任务列表；成功且 `datas` 非空时每项仅保留 `taskId`/`taskName`/`taskType`/`displayOrder`/`pushFlag`
- Profile：`develop`

输入参数：

- `instanceId`（int，必填）：课程实例标识
- `unitId`（int，必填）：节目录标识（请求体字段名为 `unit`）
- `classIds`（int[]，可选）：班级标识列表；不传表示全部
- `searchText`（string，可选）：搜索关键字
- `requireFlag`（integer/int32，可选）：完成要求；0：选做；1：必做；2：不要求；未传时省略
- `taskTypes`（array[int32]，可选）：任务类型列表；显式空数组也透传，未传时省略

示例：

```json
{
  "tool": "query_lecture_tasks",
  "arguments": {
    "instanceId": 5442,
    "unitId": 201,
    "classIds": [11, 12],
    "searchText": "练习"
  }
}
```

成功返回示例（`datas` 已优化）：

```json
{
  "code": 200,
  "message": "查询节目录任务列表成功！",
  "data": {
    "datas": [
      {
        "taskId": 1001,
        "taskName": "第一节练习",
        "taskType": 50,
        "displayOrder": 1,
        "pushFlag": true
      }
    ]
  }
}
```

后端链路：`POST /api/teach/unit/queryLectureTasks`

#### query_unit_toc

- 名称：`query_unit_toc`
- 说明：获取课程章节目录树；成功且 `datas` 非空时精简字段（章保留 `unitId`/`unitName`/`parentId`/`children`，节保留三字段）

输入参数：

- `instanceId`（int，必填）：课程实例标识

固定请求参数（MCP 自动写入）：`learnFlag=false`、`queryTask=false`

示例：

```json
{
  "tool": "query_unit_toc",
  "arguments": {
    "instanceId": 5442
  }
}
```

成功返回示例（`datas` 已优化）：

```json
{
  "code": 200,
  "message": "获取课程章节目录成功！",
  "data": {
    "datas": [
      {
        "unitId": 101,
        "unitName": "第一章 绪论",
        "parentId": -1,
        "children": [
          {
            "unitId": 201,
            "unitName": "第一节 课程介绍",
            "parentId": 101
          }
        ]
      }
    ]
  }
}
```

后端：`POST /api/teach/unit/queryTOC`

#### reorder_course_units

- 名称：`reorder_course_units`
- 说明：调整课程章/节排序；提交前查询 TOC 校验 `unitIds` 集合，再调用 `saveUnitMap`
- Profile：`develop`

输入参数：

- `instanceId`（int，必填）：课程实例标识
- `parentId`（int，必填）：章排序传 `-1`；节排序传对应章的 `unitId`
- `unitIds`（int[]，必填）：
  - 章排序：仅为全部章的 `unitId` 新顺序（不含节）
  - 节排序：仅为 `parentId` 对应章下全部节的 `unitId` 新顺序

示例（章排序）：

```json
{
  "tool": "reorder_course_units",
  "arguments": {
    "instanceId": 5442,
    "parentId": -1,
    "unitIds": [102, 101]
  }
}
```

示例（节排序）：

```json
{
  "tool": "reorder_course_units",
  "arguments": {
    "instanceId": 5442,
    "parentId": 101,
    "unitIds": [202, 201]
  }
}
```

后端链路：`POST /api/teach/unit/queryTOC`（`queryTask=false`）→ `POST /api/teach/unit/saveUnitMap`

#### query_course_directory_list

- 名称：`query_course_directory_list`
- 说明：查询课程目录列表（资源库/题库等视图）

输入参数：

- `instanceId`（int，必填）：课程实例标识
- `viewType`（int，可选）：视图类型，未传时默认 `0`
  - `0`：无
  - `10`：资源库
  - `20`：题库
  - `30`：试卷库
  - `40`：知识库
  - `50`：策略库

示例：

```json
{
  "tool": "query_course_directory_list",
  "arguments": {
    "instanceId": 5442,
    "viewType": 20
  }
}
```

后端：`POST /api/teach/directory/list`

#### create_course_directory

- 名称：`create_course_directory`
- 说明：在指定父节点下创建课程目录

输入参数：

- `parentId`（int，必填）：父节点标识
- `directoryName`（string，必填）：目录名称

示例：

```json
{
  "tool": "create_course_directory",
  "arguments": {
    "parentId": 1001,
    "directoryName": "第一章 概论"
  }
}
```

后端：`POST /api/teach/directory/new`

#### update_course_directory

- 名称：`update_course_directory`
- 说明：按目录标识更新课程目录名称

输入参数：

- `directoryId`（int，必填）：目录标识
- `directoryName`（string，必填）：目录名称

示例：

```json
{
  "tool": "update_course_directory",
  "arguments": {
    "directoryId": 1001,
    "directoryName": "第一章 概论（修订）"
  }
}
```

后端：`POST /api/teach/directory/update`

#### add_unit_directory

- 名称：`add_unit_directory`
- 说明：单条添加课程章或节目录；未传 `parentId`（或为 `0`）时创建章，传入有效 `parentId`（>0）时创建节

输入参数：

- `instanceId`（int，必填）：课程实例标识
- `unitName`（string，必填）：章节名称
- `parentId`（int，可选）：所属章标识；**添加节时必填且须大于 0**

添加章示例：

```json
{
  "tool": "add_unit_directory",
  "arguments": {
    "instanceId": 5442,
    "unitName": "第一章 绪论"
  }
}
```

添加节示例（需先通过 `query_unit_toc` 获取章 `unitId` 作为 `parentId`）：

```json
{
  "tool": "add_unit_directory",
  "arguments": {
    "instanceId": 5442,
    "unitName": "第一节 课程介绍",
    "parentId": 101
  }
}
```

成功返回示例：

```json
{
  "code": 200,
  "message": "为课程添加章目录成功！",
  "data": {
    "unitId": 101,
    "unitName": "第一章 绪论"
  }
}
```

后端：`POST /api/teach/unit/newUnit`（章）或 `POST /api/teach/unit/newLecture`（节）

#### update_unit_name

- 名称：`update_unit_name`
- 说明：编辑课程章或节的名称

输入参数：

- `instanceId`（int，必填）：课程实例标识
- `unitId`（int，必填）：章节/节目录标识（可先通过 `query_unit_toc` 获取）
- `unitName`（string，必填）：新的章节名称

示例：

```json
{
  "tool": "update_unit_name",
  "arguments": {
    "instanceId": 5442,
    "unitId": 101,
    "unitName": "第一章 绪论（修订）"
  }
}
```

成功返回示例：

```json
{
  "code": 200,
  "message": "更新章节目录成功！"
}
```

后端：`POST /api/teach/unit/updateUnit`

#### add_tasks_to_custom_columns

将已有任务批量添加到自定义栏目，归属 `develop`，调用 `POST /api/teach/task/addToColumnItem`。

- `instanceId`、`taskIds`、`columnIds` 均必填。开课标识及两个列表中的元素必须为正整数 int64，两个列表必须非空，原序及重复项保持不变。
- 单次批量提交，不前置查询、不逐任务或逐栏目拆分。可通过 `query_course_custom_columns` 获取目标栏目标识。
- 完整保留后端 `code`、`message`、`data` 和额外字段；`taskCount`、`successNum`、`errorNum` 以后端响应为准，不按栏目数倍增或覆盖部分失败，也不自动补发。
- 不额外发送班级、章节、任务类型或联网参数；鉴权和 `requestId` 由共享层处理，配置及 HTTP 错误沿用现有约定。

```json
{
  "tool": "add_tasks_to_custom_columns",
  "arguments": {"instanceId": 5442, "taskIds": [101, 102], "columnIds": [21, 22]}
}
```

#### add_task_to_unit_item

- 名称：`add_task_to_unit_item`
- 说明：将一批任务添加到指定章节目录（节）下。**成功时**在 `data` 中附加章节预览链接 `previewUrl`（`queryInstanceInfo` 匹配 `columnCode=chapters`，路径 `/teacher/course/{instanceId}/column/{uniqueId}/chapters/index/{unitId}`）

输入参数：

- `instanceId`（int，必填）：课程实例标识
- `unitId`（int，必填）：章节/节目录标识
- `taskIds`（int[]，必填）：任务标识列表

示例：

```json
{
  "tool": "add_task_to_unit_item",
  "arguments": {
    "instanceId": 5442,
    "unitId": 201,
    "taskIds": [10001, 10002]
  }
}
```

成功返回示例：

```json
{
  "code": 200,
  "message": "添加任务到章节成功！",
  "data": {
    "previewUrl": "http://brains.v2.zhituxueyuan.cn/teacher/course/5442/column/chap-col/chapters/index/201"
  }
}
```

说明：`previewUrl` 基址取自 `SERVER_API_URL`/`Origin`；栏目查询失败时 `uniqueId` 回退为 `column`；添加失败不附加预览字段。

后端：`POST /api/teach/task/addToUnitItem`（成功后另调 `POST /api/teach/instance/queryInstanceInfo`）

#### delete_unit_lecture_tasks

- 名称：`delete_unit_lecture_tasks`
- 说明：从指定章节移除已挂载任务（仅解除章节挂载，不删除任务本体）
- Profile：`common`

输入参数：

- `instanceId`（int，必填）：课程实例标识
- `unitId`（int，必填）：章节/节目录标识
- `taskIds`（int[]，必填）：待移除的任务标识列表

示例：

```json
{
  "tool": "delete_unit_lecture_tasks",
  "arguments": {
    "instanceId": 5442,
    "unitId": 201,
    "taskIds": [10001, 10002]
  }
}
```

成功返回示例：

```json
{
  "code": 200,
  "message": "从章节移除任务成功！"
}
```

后端：`POST /api/teach/unit/removeLectureTasks`

#### delete_course_units

- 名称：`delete_course_units`
- 说明：批量删除课程章节目录（章/讲通用）。注意：与 `delete_unit_lecture_tasks` 不同，本工具删除目录节点本身，而非仅移除挂载任务
- Profile：`common`

输入参数：

- `instanceId`（int，必填）：课程实例标识
- `unitIds`（int[]，必填）：待删除的章节目录标识列表

示例：

```json
{
  "tool": "delete_course_units",
  "arguments": {
    "instanceId": 5442,
    "unitIds": [101, 102]
  }
}
```

成功返回示例：

```json
{
  "code": 200,
  "message": "删除课程章节成功！"
}
```

后端：`POST /api/teach/unit/batchDeleteUnits`

#### query_course_question_list

- 名称：`query_course_question_list`
- 说明：查询课程问题图谱列表

输入参数：

- `instanceId`（int，必填）：课程实例标识

示例：

```json
{
  "tool": "query_course_question_list",
  "arguments": {
    "instanceId": 5442
  }
}
```

后端：`POST /api/teach/question/listQuestion`

#### delete_course_questions

- 名称：`delete_course_questions`
- 说明：删除一个或多个问题图谱问题节点（`questionIdList` 长度为 1 即为单问题删除）
- Profile：`common`

输入参数：

- `instanceId`（int，必填）：课程实例标识
- `questionIdList`（int | int[]，必填）：问题标识列表

示例（批量）：

```json
{
  "tool": "delete_course_questions",
  "arguments": {
    "instanceId": 5442,
    "questionIdList": [101, 102]
  }
}
```

示例（单问题）：

```json
{
  "tool": "delete_course_questions",
  "arguments": {
    "instanceId": 5442,
    "questionIdList": [101]
  }
}
```

后端链路：`POST /api/teach/question/deleteQuestion`（请求体含 `instanceId`、`questionIdList`）

#### add_course_question

- 名称：`add_course_question`
- 说明：人工添加课程问题图谱的问题（固定 `createType=10`）

输入参数：

- `instanceId`（int，必填）：课程实例标识
- `title`（string，必填）：问题标题名称
- `hierarchy`（int，必填）：问题级别
  - `1`：一级
  - `2`：二级
  - `3`：三级
- `description`（string，可选）：问题说明
- `tags`（string，可选）：标签列表，逗号分隔
- `difficultyLevel`（int，可选）：难点，未传时默认 `0`
  - `0`：容易
  - `1`：中等
  - `2`：困难
- `pointIdList`（array[int]，可选）：关联知识点标识列表
- `taskIdList`（array[int]，可选）：关联任务标识列表
- `subIdList`（array[int]，可选）：关联子问题 `questionId` 列表
- `fileList`（array，可选）：文件列表（透传后端结构）

示例：

```json
{
  "tool": "add_course_question",
  "arguments": {
    "instanceId": 5442,
    "title": "什么是操作系统？",
    "hierarchy": 1,
    "difficultyLevel": 1,
    "description": "从定义与功能角度说明",
    "pointIdList": [101, 102]
  }
}
```

成功返回示例：

```json
{
  "code": 200,
  "message": "添加课程问题成功！",
  "data": {
    "nodeVO": {"questionId": 9},
    "previewUrl": "https://.../teacher/course/5442/column/{uniqueId}/graph/question"
  }
}
```

后端：`POST /api/teach/question/newQuestion`（请求体固定写入 `createType=10`）→ 成功后 `queryInstanceInfo` 解析 `columnCode=question-graph` 拼装 `data.previewUrl`

#### update_course_question

- 名称：`update_course_question`
- 说明：编辑课程问题图谱的问题（固定 `createType=10`）

输入参数：

- `questionId`（int，必填）：问题标识
- `instanceId`（int，必填）：课程实例标识
- `title`（string，必填）：问题标题名称
- `hierarchy`（int，必填）：问题级别
  - `1`：一级
  - `2`：二级
  - `3`：三级
- `description`（string，可选）：问题说明
- `tags`（string，可选）：标签列表，逗号分隔
- `difficultyLevel`（int，可选）：难点，未传时默认 `0`
  - `0`：容易
  - `1`：中等
  - `2`：困难
- `pointIdList`（array[int]，可选）：关联知识点标识列表
- `taskIdList`（array[int]，可选）：关联任务标识列表
- `subIdList`（array[int]，可选）：关联子问题 `questionId` 列表；`hierarchy=1/2` 且为空或未传时，先调用 `queryQuestion`，从响应 `subList[].questionId` 回填后更新
- `fileList`（array，可选）：文件列表（透传后端结构）

示例：

```json
{
  "tool": "update_course_question",
  "arguments": {
    "questionId": 9001,
    "instanceId": 5442,
    "title": "什么是操作系统？（修订）",
    "hierarchy": 1,
    "difficultyLevel": 1,
    "description": "从定义与功能角度说明",
    "pointIdList": [101, 102]
  }
}
```

成功返回示例：

```json
{
  "code": 200,
  "message": "编辑课程问题成功！",
  "data": {
    "previewUrl": "https://.../teacher/course/5442/column/{uniqueId}/graph/question"
  }
}
```

后端：`POST /api/teach/question/updateQuestion`（请求体固定写入 `createType=10`）→ 成功后 `queryInstanceInfo` 解析 `columnCode=question-graph` 拼装 `data.previewUrl`

#### add_course_question_link

- 名称：`add_course_question_link`
- 说明：在课程问题图谱中添加源问题到目标问题的连线

输入参数：

- `instanceId`（int，必填）：课程实例标识
- `sourceId`（int，必填）：原问题（上一级）标识
- `targetId`（int，必填）：目标问题（下一级）标识

示例：

```json
{
  "tool": "add_course_question_link",
  "arguments": {
    "instanceId": 5442,
    "sourceId": 9001,
    "targetId": 9002
  }
}
```

后端：`POST /api/teach/question/addQuestionLink`


#### screenshot_website

- 名称：`screenshot_website`
- 说明：对课程指定模块的教师端/主页页面截图，上传资源服务后返回截图地址列表

**依赖（截图功能）**：

```bash
pip install -e ".[screenshot]"
playwright install chromium
```

输入参数：

- `instanceId`（int，必填）：课程实例标识
- `shotModelName`（string，必填）：模块名称，可选值：
  课程信息、课程详情、章节目录、文件资源、题库、试卷库、任务库、AI工具箱、知识图谱、问题图谱、能力图谱、课程主页、首页、能力得分占比

栏目匹配：需栏目的模块按 `queryInstanceInfo.columnList[].columnCode` 解析（如章节目录=`chapters`、AI工具箱=`tools`、首页=`dashboard`），不依赖 `columnName` 文案。

示例：

```json
{
  "tool": "screenshot_website",
  "arguments": {
    "instanceId": 5442,
    "shotModelName": "章节目录"
  }
}
```

成功返回示例：

```json
{
  "code": 200,
  "message": "截图成功！",
  "data": [{ "fileUrl": "https://..." }],
  "webUrl": "https://.../teacher/course/5442/column/.../chapters?..."
}
```

说明：使用 Playwright 无头浏览器访问页面，需服务器可访问前端基址（`Origin` / `SERVER_API_URL`）且 Token 有效。

#### query_stu_grade_detail

- 名称：`query_stu_grade_detail`
- 说明：查询单个学生在指定开课、班级下的成绩详情；返回前将分数字段（字段名含 `Score` 或等于 `score`）除以 100 归一化为实际得分

输入参数：

- `instanceId`（int，必填）：课程实例标识
- `classId`（int，必填）：班级主键
- `userId`（int，必填）：学生主键
- `searchText`（string，可选）：搜索关键字
- `orderType`（string，可选）：排序方式，`desc` 倒序，`asc` 正序

示例：

```json
{
  "tool": "query_stu_grade_detail",
  "arguments": {
    "instanceId": 5442,
    "classId": 24135,
    "userId": 10001
  }
}
```

成功返回示例（节选）：

```json
{
  "code": 200,
  "message": "查询学生成绩详情成功！",
  "data": {
    "userId": 10001,
    "realName": "张三",
    "exerciseScore": 85.0,
    "grades": [{ "score": 90.0, "originalScore": 90.0 }]
  }
}
```

后端：`POST /api/teach/grade/analysis/queryStuGradeDetail`

#### export_stu_grade_detail

- 名称：`export_stu_grade_detail`
- 说明：导出班级学生成绩；接口返回文件流与 `filename`，保存到本地后返回文件绝对路径
- Profile：`evaluate`
- 默认保存目录：系统临时目录下 `haoke-mcp-export-stu-grade`

输入参数（全部必填）：

- `instanceId`（int）：开课标识
- `classId`（int）：班级标识

示例：

```json
{
  "tool": "export_stu_grade_detail",
  "arguments": {
    "instanceId": 5442,
    "classId": 24135
  }
}
```

成功返回示例：

```json
{
  "code": 200,
  "message": "导出班级学生成绩成功！",
  "data": {
    "filePath": "C:\\Users\\...\\AppData\\Local\\Temp\\haoke-mcp-export-stu-grade\\成绩明细.xlsx",
    "fileName": "成绩明细.xlsx"
  }
}
```

后端：`POST /api/teach/grade/analysis/exportStuGradeDetailList`

#### export_stu_schedule_data

- 名称：`export_stu_schedule_data`
- 说明：导出学情报表；接口返回文件流与 `filename`，保存到本地后返回文件绝对路径
- Profile：`evaluate`
- 默认保存目录：系统临时目录下 `haoke-mcp-export-stu-schedule`

输入参数（全部必填）：

- `instanceId`（int）：开课标识
- `classId`（int）：班级标识

示例：

```json
{
  "tool": "export_stu_schedule_data",
  "arguments": {
    "instanceId": 5442,
    "classId": 24135
  }
}
```

成功返回示例：

```json
{
  "code": 200,
  "message": "导出学情报表成功！",
  "data": {
    "filePath": "C:\\Users\\...\\AppData\\Local\\Temp\\haoke-mcp-export-stu-schedule\\学情报表.xlsx",
    "fileName": "学情报表.xlsx"
  }
}
```

后端：`POST /api/teach/instance/learnStatistic/exportStuScheduleData`

#### list_task_stu_info_data

- 名称：`list_task_stu_info_data`
- 说明：查询学生任务详情统计信息列表；成功且 `rowList` 非空时优化完成率字段（`8.33%(1/12)` → `1`）、`lowAvgValueFlag` 空值置 `0`、剔除 `null` 字段

输入参数：

- `instanceId`（int，必填）：课程实例标识
- `classId`（int，必填）：班级主键
- `page`（object，可选）：分页对象，含 `pageNo`、`pageSize`
- `searchText`（string，可选）：搜索关键字

示例：

```json
{
  "tool": "list_task_stu_info_data",
  "arguments": {
    "instanceId": 5442,
    "classId": 24135,
    "page": { "pageNo": 1, "pageSize": 50 },
    "searchText": "张三"
  }
}
```

成功返回示例（节选，`rowList` 已优化）：

```json
{
  "code": 200,
  "message": "操作成功",
  "data": {
    "pageNo": 1,
    "pageSize": 50,
    "rowList": [
      {
        "userId": 5972,
        "userName": "张三1",
        "taskComValue": "1",
        "lowAvgValueFlag": 0,
        "requireTaskValue": "0",
        "sceneNumValue": 0
      }
    ]
  }
}
```

后端：`POST /api/teach/instance/runtime/listTaskStuInfoData`

#### new_group_plan

- 名称：`new_group_plan`
- 说明：在指定班级创建自动分组方案；固定 `planType=1`、`randomLeader=1`

输入参数：

- `classId`（int，必填）：班级主键
- `groupNum`（int，必填）：分组数量
- `planName`（string，必填）：分组方案名称

示例：

```json
{
  "tool": "new_group_plan",
  "arguments": {
    "classId": 24135,
    "groupNum": 4,
    "planName": "第1次自动分组"
  }
}
```

成功返回示例：

```json
{
  "code": 200,
  "message": "创建分组方案成功！",
  "data": {}
}
```

后端：`POST /api/teach/class/group/newGroupPlan`

#### query_group_plan_detail

- 名称：`query_group_plan_detail`
- 说明：查看班级分组详情
- Profile：`develop`

输入参数（必填）：

- `planId`（int）：分组方案标识

示例：

```json
{
  "tool": "query_group_plan_detail",
  "arguments": {
    "planId": 1001
  }
}
```

后端：`POST /api/teach/class/group/queryGroupPlanDetail`

#### new_group_member

- 名称：`new_group_member`
- 说明：添加/移动班级分组中小组成员；`groupId` 未传或为 `0` 表示分配到未分组
- Profile：`develop`

输入参数：

- `planId`（int，必填）：分组方案标识
- `userIdList`（int[]，必填）：成员用户标识列表
- `groupId`（int，可选）：目标小组标识；未传或 `0` 时请求体 `groupId` 为 `null`（未分组）

示例（指定小组）：

```json
{
  "tool": "new_group_member",
  "arguments": {
    "planId": 1001,
    "userIdList": [10, 20],
    "groupId": 5
  }
}
```

示例（分配到未分组）：

```json
{
  "tool": "new_group_member",
  "arguments": {
    "planId": 1001,
    "userIdList": [10, 20]
  }
}
```

后端：`POST /api/teach/class/group/newGroupMember`

#### delete_group_plan

- 名称：`delete_group_plan`
- 说明：删除班级分组
- Profile：`common`

输入参数（必填）：

- `planId`（int）：分组方案标识

示例：

```json
{
  "tool": "delete_group_plan",
  "arguments": {
    "planId": 1001
  }
}
```

后端：`POST /api/teach/class/group/deleteGroupPlanName`

#### new_bulletin

- 名称：`new_bulletin`
- 说明：新增课程公告；创建成功后查询公告列表，取 `bulletinId` 最大的一条返回

输入参数：

- `instanceId`（int，必填）：课程实例标识
- `title`（string，必填）：公告标题
- `content`（string，必填）：公告内容

示例：

```json
{
  "tool": "new_bulletin",
  "arguments": {
    "instanceId": 5442,
    "title": "期中考试安排",
    "content": "请于下周三前完成模拟测验。"
  }
}
```

成功返回示例：

```json
{
  "code": 200,
  "message": "新增课程公告成功！",
  "data": {
    "bulletinId": 10086
  }
}
```

后端链路：`POST /api/teach/bulletin/newBulletin` → `POST /api/teach/bulletin/listBulletin`（内部 `classId=0`，`pageSize=100`）

#### generate_preview_link

- 名称：`generate_preview_link`
- 说明：按类型生成预览链接。归属实施专家 Profile（`implement`）。成功时 `data.previewUrl` 非空
- Profile：`implement`

输入参数：

- `instanceId`（int，必填）：课程实例标识
- `previewType`（string，必填）：`task` / `bulletin` / `resource` / `paper` / `chapter` / `quiz` / `topic` / `knowledge_graph` / `question_graph` / `ability_graph`（亦支持中文「任务」「公告」「资源」「试卷」「章节」「题库」「话题」「知识图谱」「问题图谱」「能力图谱」，以及 `knowledge-graph`/`question-graph`/`ability-graph`）
- `previewId`（int，必填）：任务=`taskId`，公告=`bulletinId`，资源=`fileId`，试卷=`paperId`，章节=`unitId`；题库/话题/三类图谱栏目页不写入路径但仍需传正整数

各类型链路：

| 类型 | 行为 |
|------|------|
| 任务 | `POST /api/teach/task/queryTask` → 按 `taskType` 拼装学生端任务预览（与创建类一致） |
| 公告 | `queryInstanceInfo` 取 `columnCode=bulletins` 的 `uniqueId` → `/teacher/course/{instanceId}/column/{uniqueId}/bulletins` |
| 资源 | `POST /api/resource/file/view` → 取响应 `flvUrl` |
| 试卷 | `/student/course/0/examines/preview?instanceId=...&paperId=...` |
| 章节 | `queryInstanceInfo` 取 `columnCode=chapters` → `/teacher/course/{instanceId}/column/{uniqueId}/chapters/index/{previewId}` |
| 题库 | `queryInstanceInfo` 取 `columnCode=quizs` → `/teacher/course/{instanceId}/column/{uniqueId}/resources/quizs` |
| 话题 | `queryInstanceInfo` 取 `columnCode=topics` → `/teacher/course/{instanceId}/column/{uniqueId}/topics` |
| 知识图谱 | `queryInstanceInfo` 取 `columnCode=knowledge-graph` → `/teacher/course/{instanceId}/column/{uniqueId}/graph` |
| 问题图谱 | `queryInstanceInfo` 取 `columnCode=question-graph` → `/teacher/course/{instanceId}/column/{uniqueId}/graph/question` |
| 能力图谱 | `queryInstanceInfo` 取 `columnCode=ability-graph` → `/teacher/course/{instanceId}/column/{uniqueId}/graph/ability` |

示例（任务）：

```json
{
  "tool": "generate_preview_link",
  "arguments": {
    "instanceId": 5442,
    "previewType": "task",
    "previewId": 10001
  }
}
```

成功响应示例（任务）：

```json
{
  "code": 200,
  "message": "生成预览链接成功！",
  "data": {
    "taskId": 10001,
    "previewId": 10001,
    "previewType": "task",
    "instanceId": 5442,
    "taskType": 50,
    "taskName": "常规练习",
    "previewUrl": "http://brains.v2.zhituxueyuan.cn/student/course/0/studyTask/10001/practiceHome?taskId=10001&instanceId=5442"
  }
}
```

示例（试卷）：

```json
{
  "tool": "generate_preview_link",
  "arguments": {
    "instanceId": 5442,
    "previewType": "试卷",
    "previewId": 300
  }
}
```

#### create_course_topic

- 名称：`create_course_topic`
- 说明：创建课程话题或问题。**成功时**在 `data` 中附加话题栏目预览链接 `previewUrl`（`queryInstanceInfo` 匹配 `columnCode=topics`，路径 `/teacher/course/{instanceId}/column/{uniqueId}/topics`）。已替代原自动 Tool `learn_topic_newinstancethread_post`（白名单已下线）
- Profile：`implement`

输入参数（全部必填）：

- `instanceId`（int）：课程实例标识
- `theme`（string）：话题主题或问题标题
- `content`（string）：话题/问题内容
- `threadType`（int）：帖子类型，`10` 话题 / `20` 问题

示例：

```json
{
  "tool": "create_course_topic",
  "arguments": {
    "instanceId": 5442,
    "theme": "期中讨论",
    "content": "请围绕本章重点展开讨论。",
    "threadType": 10
  }
}
```

成功响应示例：

```json
{
  "code": 200,
  "message": "创建课程话题成功！",
  "data": {
    "previewUrl": "https://example.com/teacher/course/5442/column/t1/topics"
  }
}
```

后端：`POST /api/learn/topic/newInstanceThread` → 成功后 `queryInstanceInfo` 组装 `previewUrl`

#### update_course_topic

- 名称：`update_course_topic`
- 说明：编辑课程话题（主题与内容）。**成功时**在 `data` 中附加话题预览链接 `previewUrl`（`queryInstanceInfo` 匹配 `columnCode=topics`，路径 `/teacher/course/{instanceId}/column/{uniqueId}/topics`）

输入参数（全部必填）：

- `threadId`（int）：话题标识
- `instanceId`（int）：课程实例标识
- `postId`（int）：内容标识
- `theme`（string）：话题主题
- `content`（string）：话题内容

示例：

```json
{
  "tool": "update_course_topic",
  "arguments": {
    "threadId": 1,
    "instanceId": 5442,
    "postId": 10,
    "theme": "期中讨论",
    "content": "请围绕本章重点展开讨论。"
  }
}
```

成功响应示例：

```json
{
  "code": 200,
  "message": "编辑课程话题成功！",
  "data": {
    "previewUrl": "https://example.com/teacher/course/5442/column/t1/topics"
  }
}
```

后端：`POST /api/learn/topic/updateThread` → 成功后 `queryInstanceInfo` 组装 `previewUrl`

#### feature_course_topic

- 名称：`feature_course_topic`
- 说明：课程话题加精。`id` 为贴子主键，须与 `threadId` 一致

输入参数（全部必填）：

- `threadId`（int）：话题标识
- `id`（int）：贴子主键标识（对应 `threadId`，取值须相同）

示例：

```json
{
  "tool": "feature_course_topic",
  "arguments": {
    "threadId": 100,
    "id": 100
  }
}
```

后端：`POST /api/learn/topic/updateInstanceThread`

#### set_course_topic_best_answer

- 名称：`set_course_topic_best_answer`
- 说明：课程话题设置最佳答案。`id` 为回复/帖子标识，对应 `postId`

输入参数（全部必填）：

- `threadId`（int）：话题标识
- `id`（int）：回复/帖子标识（对应 `postId`）

示例：

```json
{
  "tool": "set_course_topic_best_answer",
  "arguments": {
    "threadId": 100,
    "id": 20
  }
}
```

后端：`POST /api/learn/topic/updatePostBestFlag`

#### like_course_topic

- 名称：`like_course_topic`
- 说明：课程话题赞/踩

输入参数（全部必填）：

- `threadId`（int）：话题标识
- `instanceId`（int）：课程实例标识
- `postId`（int）：内容标识
- `type`（int）：类型，`10` 赞/取消赞，`20` 踩

示例：

```json
{
  "tool": "like_course_topic",
  "arguments": {
    "threadId": 1,
    "instanceId": 5442,
    "postId": 10,
    "type": 10
  }
}
```

后端：`POST /api/learn/topic/newThreadUpDown`

#### top_course_topic

- 名称：`top_course_topic`
- 说明：置顶话题
- Profile：`implement`

输入参数（全部必填）：

- `instanceId`（int）：开课标识
- `threadId`（int）：话题标识

示例：

```json
{
  "tool": "top_course_topic",
  "arguments": {
    "instanceId": 5442,
    "threadId": 1001
  }
}
```

后端：`POST /api/learn/topic/top/addTop`

#### delete_course_topic

- 名称：`delete_course_topic`
- 说明：删除话题
- Profile：`common`

输入参数（全部必填）：

- `instanceId`（int）：开课标识
- `threadId`（int）：话题标识

示例：

```json
{
  "tool": "delete_course_topic",
  "arguments": {
    "instanceId": 5442,
    "threadId": 1001
  }
}
```

后端：`POST /api/learn/topic/removeInstanceThread`

#### delete_course_topic_post

- 名称：`delete_course_topic_post`
- 说明：删除话题评论/回复
- Profile：`common`

输入参数（全部必填）：

- `instanceId`（int）：开课标识
- `postId`（int）：评论/回复标识

示例：

```json
{
  "tool": "delete_course_topic_post",
  "arguments": {
    "instanceId": 5442,
    "postId": 2001
  }
}
```

后端：`POST /api/learn/topic/removeInstanceThreadPost`

#### delete_tasks

- 名称：`delete_tasks`
- 说明：删除一个或多个教学任务（`taskIds` 长度为 1 即为单任务删除）
- Profile：`common`

输入参数：

- `taskIds`（int[]，必填）：任务标识列表

示例（批量）：

```json
{
  "tool": "delete_tasks",
  "arguments": {
    "taskIds": [1001, 1002]
  }
}
```

示例（单任务）：

```json
{
  "tool": "delete_tasks",
  "arguments": {
    "taskIds": [1001]
  }
}
```

后端链路：`POST /api/teach/task/removeTask`

#### delete_points

- 名称：`delete_points`
- 说明：删除一个或多个知识图谱知识点节点（`pointIds` 长度为 1 即为单节点删除）
- Profile：`common`

输入参数：

- `pointIds`（int | int[]，必填）：知识点标识列表

示例（批量）：

```json
{
  "tool": "delete_points",
  "arguments": {
    "pointIds": [101, 102]
  }
}
```

示例（单节点）：

```json
{
  "tool": "delete_points",
  "arguments": {
    "pointIds": [101]
  }
}
```

后端链路：`POST /api/teach/point/removePoint`

#### delete_quizzes

- 名称：`delete_quizzes`
- 说明：删除一道或多道题库题目（`quizIdList` 长度为 1 即为单题删除）
- Profile：`common`

输入参数：

- `instanceId`（int，必填）：课程实例标识
- `quizIdList`（int[]，必填）：题目标识列表

示例（批量）：

```json
{
  "tool": "delete_quizzes",
  "arguments": {
    "instanceId": 5442,
    "quizIdList": [1001, 1002]
  }
}
```

示例（单题）：

```json
{
  "tool": "delete_quizzes",
  "arguments": {
    "instanceId": 5442,
    "quizIdList": [1001]
  }
}
```

后端链路：`POST /api/resource/quiz/removeQuiz`（请求体含 `instanceId`、`quizIdList`）

#### delete_resource_files

- 名称：`delete_resource_files`
- 说明：删除一名或多名资源库文件（`fileIds` 长度为 1 即为单文件删除）
- Profile：`common`

输入参数：

- `instanceId`（int，必填）：课程实例标识
- `fileIds`（int[]，必填）：资源文件标识列表

示例（批量）：

```json
{
  "tool": "delete_resource_files",
  "arguments": {
    "instanceId": 5442,
    "fileIds": [90001, 90002]
  }
}
```

示例（单文件）：

```json
{
  "tool": "delete_resource_files",
  "arguments": {
    "instanceId": 5442,
    "fileIds": [90001]
  }
}
```

后端链路：`POST /api/resource/file/deleteFile`

#### upload_instance_resource

- 名称：`upload_instance_resource`
- 说明：将本地文件上传并登记到课程资源库（支持视频、音频、图片、文档）

输入参数：

- `instanceId`（int，必填）：课程实例标识
- `filePath`（string，必填）：本地文件路径（须为 MCP Server 可读路径）
- `directoryId`（int，可选）：资源目录标识，默认 `0`
- `pointIds`（int[]，可选）：关联知识点 ID 列表，默认 `[]`

示例：

```json
{
  "tool": "upload_instance_resource",
  "arguments": {
    "instanceId": 5442,
    "filePath": "D:/resources/lecture.pdf",
    "directoryId": 0,
    "pointIds": [101, 102]
  }
}
```

成功返回示例：

```json
{
  "code": 200,
  "message": "创建资源成功！",
  "data": {
    "fileId": 90001
  }
}
```

后端链路：`POST /api/resource/upload/file`（`uploadType=resource`）→ `POST /api/resource/file/newRes`

#### save_file_attr

- 名称：`save_file_attr`
- 说明：编辑资源库文件属性（资源名称、目录、关联知识点、是否开放给学生及班级权限）

输入参数：

- `instanceId`（int，必填）：课程标识，原样传入后端请求；不能为空或为 `0`
- `fileId`（int，必填）：资源文件标识（内部转换为 `fileIds` 数组）
- `fileName`（string，可选）：资源名称
- `directoryId`（int，可选）：资源目录标识
- `pointList`（array，可选）：关联知识点，每项含 `pointId`、`pointName`
- `studentFlag`（int，可选）：`0` 不开放给学生，`1` 开放给学生
- `classIds`（int[]，可选）：开放班级列表；**当 `studentFlag=1` 时必填**，否则返回参数错误并提示提供班级列表
- `studentAuth`（int，可选）：`10` 仅查看，`20` 查看和下载；`studentFlag=1` 时默认 `10`

固定请求参数（MCP 自动写入）：`repositoryFlag=1`

示例：

```json
{
  "tool": "save_file_attr",
  "arguments": {
    "instanceId": 5442,
    "fileId": 90001,
    "fileName": "第一章讲义",
    "directoryId": 100,
    "pointList": [
      {"pointId": 101, "pointName": "变量与类型"}
    ],
    "studentFlag": 1,
    "classIds": [201, 202],
    "studentAuth": 20
  }
}
```

成功返回示例：

```json
{
  "code": 200,
  "message": "保存资源属性成功！"
}
```

后端链路：`POST /api/resource/file/saveFileAttr`

#### save_teach_class

- 名称：`save_teach_class`
- 说明：新增或重命名开课班级；`classId=0` 时新增，否则修改班级名称

输入参数：

- `classId`（int，必填）：班级标识，**新增班级传 `0`**
- `className`（string，必填）：班级名称
- `instanceId`（int，必填）：开课标识（新增与修改均须提供；修改时仅作入参校验，不写入 `updateClassName` 请求体）

新增班级示例：

```json
{
  "tool": "save_teach_class",
  "arguments": {
    "classId": 0,
    "className": "2026春季班",
    "instanceId": 5442
  }
}
```

修改班级名称示例：

```json
{
  "tool": "save_teach_class",
  "arguments": {
    "classId": 24135,
    "className": "2026春季班（修订）",
    "instanceId": 5442
  }
}
```

成功返回示例（新增）：

```json
{
  "code": 200,
  "message": "新增开课班级成功！",
  "data": {
    "classId": 24135
  }
}
```

后端链路：`classId=0` → `POST /api/teach/class/newClass`；`classId≠0` → `POST /api/teach/class/updateClassName`

#### rename_teach_class

- 名称：`rename_teach_class`
- 说明：重命名开课班级；仅修改班级名称，不负责新增班级（新增请用 `save_teach_class` 且 `classId=0`）

输入参数：

- `classId`（int，必填）：班级标识，须为大于 0 的整数
- `className`（string，必填）：班级名称

示例：

```json
{
  "tool": "rename_teach_class",
  "arguments": {
    "classId": 24135,
    "className": "2026春季班（修订）"
  }
}
```

成功返回示例：

```json
{
  "code": 200,
  "message": "重命名班级成功！"
}
```

后端链路：`POST /api/teach/class/updateClassName`

#### update_class_finish

- 名称：`update_class_finish`
- 说明：课程班级结课；`continueFlag=2` 时 `startTime`、`endTime` 必填
- Profile：`implement`

输入参数：

- `classId`（int，必填）：班级标识
- `continueFlag`（int，必填）：结课后是否允许继续学习，仅 `0`/`1`/`2`
  - `0`：不继续
  - `1`：继续
  - `2`：在指定时间内可进入（此时 `startTime`、`endTime` 必填）
- `startTime`（string，可选）：结课后可再进入课程开始时间（`yyyy-MM-dd HH:mm:ss`）
- `endTime`（string，可选）：结课后可再进入课程截止时间（`yyyy-MM-dd HH:mm:ss`）

示例：

```json
{
  "tool": "update_class_finish",
  "arguments": {
    "classId": 24135,
    "continueFlag": 2,
    "startTime": "2026-07-01 00:00:00",
    "endTime": "2026-07-31 23:59:59"
  }
}
```

后端链路：`POST /api/teach/class/updateClassFinish`

#### update_class_config

- 名称：`update_class_config`
- 说明：课程班级综合设置；若干字段有默认值；`continueFlag=2` 时 `startTime`、`endTime` 必填

输入参数：

- `classId`（int，必填）：班级标识
- `semesterId`（int，可选）：学期标识；为空时默认 `0`
- `joinType`（int，可选）：加入方式，仅 `10`/`20`；为空时默认 `10`
- `cancelFlag`（int，可选）：取消标记；为空时默认 `0`
- `effectClassIds`（array[int]，可选）：影响班级标识列表
- `continueFlag`（int，可选）：续交标记，仅 `0`/`1`/`2`；为空时默认 `0`；为 `2` 时 `startTime`、`endTime` 必填
- `startTime`（string，可选）：开始时间
- `endTime`（string，可选）：结束时间

示例：

```json
{
  "tool": "update_class_config",
  "arguments": {
    "classId": 24135,
    "joinType": 10,
    "continueFlag": 2,
    "startTime": "2026-07-01 00:00:00",
    "endTime": "2026-07-31 23:59:59",
    "effectClassIds": [24135]
  }
}
```

后端：`POST /api/teach/class/updateClassConfig`

#### set_grade_progress

- 名称：`set_grade_progress`
- 说明：设置班级学习任务进度占比明细；`progressThreshold` 提交前自动 ×100（如入参 `80` → 请求体 `8000`）

输入参数：

- `classId`（int，必填）：班级标识
- `progressThreshold`（number，必填）：进度达到满分的阈值（人类可读，如 `80` 表示 80%）
- `taskTypeList`（array[int]，必填）：任务类型列表，仅允许 `10`/`20`/`30`/`40`/`50`/`51`/`60`/`70`/`80`/`90`/`100`/`110`/`130`/`140`
- `progressOptionalFlag`（integer/int32，可选）：选做进度标记，`0` 未选中、`1` 选中
- `progressRequiredFlag`（integer/int32，可选）：必做进度标记，`0` 未选中、`1` 选中
- `progressSchemaFlag`（integer/int32，可选）：按计分方案计算进度，`0` 未选中、`1` 选中
- `progressUnrestrictedFlag`（integer/int32，可选）：不要求进度标记，`0` 未选中、`1` 选中

示例：

```json
{
  "tool": "set_grade_progress",
  "arguments": {
    "classId": 24135,
    "progressThreshold": 80,
    "taskTypeList": [10, 20, 30]
  }
}
```

后端：`POST /api/teach/class/grade/saveGradeProgress`

#### set_task_detailed

- 名称：`set_task_detailed`
- 说明：按班级与任务类型设置各任务权重占比。提交前自动查询 `queryTaskDetailed`，若 `gradeTaskList` 非空则按 `taskId` 回填 `rpId`；`gradeDetailList` 中所有 `rate` 之和必须为 `100`

输入参数：

- `classId`（int，必填）：班级标识
- `taskType`（int，必填）：任务类型（如 `0` 考试、`10` 视频文档图文等）
- `percentageType`（int，必填）：占比方式，仅 `10`（平均）/`20`（自定义）
- `gradeDetailList`（array，必填）：任务占比明细，元素字段：
  - `taskId`（int，必填）：任务标识
  - `rate`（number，必填）：占比；列表合计必须为 `100`

示例：

```json
{
  "tool": "set_task_detailed",
  "arguments": {
    "classId": 24135,
    "taskType": 50,
    "percentageType": 20,
    "gradeDetailList": [
      {"taskId": 1001, "rate": 40},
      {"taskId": 1002, "rate": 60}
    ]
  }
}
```

后端：先 `POST /api/teach/class/grade/queryTaskDetailed`，再 `POST /api/teach/class/grade/saveTaskDetailed`

#### publish_examine

向指定班级发布考试，归属 `implement`，调用 `POST /api/teach/examine/push`。

- 必填：`examineId` 正整数int64，`examineType` 为10统一考试/20随到随考，`classIdList` 非空正整数int64数组，`beginTime` 为有效的 `YYYY-MM-DD HH:mm:ss` 字符串。班级数组保留原序和重复项。
- 随到随考必须提供晚于开始时间的 `endTime`；统一考试可不传/null/空串，空值不发送，提供有效结束时间时也校验顺序。时间不转换时区、不与当前时间比较。
- `scorePublishFlag` 为0不公布/1公布；统一考试公布时始终必填 `scorePublishTime`，即使 `scorePublishType=2`。随到随考公布时必填方式：1统一时间发布需公布时间，2批阅结束后发布不要求；若提供非空日期仍严格校验。不公布时省略方式与公布时间。
- `reviseFlag` 为0/1；`enableAIflag` 默认0，映射后端 `enableAIFlag`；`admissionFlag` 默认0。AI和准入显式null按默认0，其余可选开关null不发送。
- 准入开启时 `ruleGroups` 非空，每组 `rules` 非空。规则必含 `ruleType`（10任务/20考试）、`conditionType`（10分数/20完成）、正整数 `relativeId`。任务规则需正整数int32 `taskType`；考试规则可为null，默认发送null。
- 分数条件 `scoreLimit` 传存储整数（实际分×100），默认8000；8520/7000原值提交，不再次放大。完成条件省略/null或有限数值（含-0.8）均提交整数0。保留组和规则顺序，不推断组间逻辑。关闭准入时不提交规则，但显式提供的结构仍校验。
- `locationFlag=1` 时 `location` 为非空字符串并原样传递，0时省略位置；不传flag时不反推开关。可选字段显式提供时均需符合类型/范围。
- 单次发布，完整保留后端响应及错误，不添加预览或自动重发；鉴权和 `requestId` 由共享层处理。不发送通用classId、description或webSearchFlag。

统一考试公布成绩并启用准入：

```json
{"tool":"publish_examine","arguments":{"examineId":1566,"examineType":10,"classIdList":[5280,5279],"beginTime":"2026-10-22 00:00:00","endTime":"","scorePublishFlag":1,"scorePublishType":2,"scorePublishTime":"2026-10-30 00:00:00","reviseFlag":0,"enableAIflag":0,"admissionFlag":1,"ruleGroups":[{"rules":[{"ruleType":10,"conditionType":20,"scoreLimit":-0.8,"relativeId":35062,"taskType":10},{"ruleType":20,"conditionType":10,"scoreLimit":8520,"relativeId":1571,"taskType":null}]},{"rules":[{"ruleType":20,"conditionType":10,"scoreLimit":7000,"relativeId":1569,"taskType":null}]}],"locationFlag":0,"location":null}}
```

随到随考，批阅结束后公布（无需公布时间）：

```json
{"tool":"publish_examine","arguments":{"examineId":1566,"examineType":20,"classIdList":[5280],"beginTime":"2026-10-22 00:00:00","endTime":"2026-10-31 00:00:00","scorePublishFlag":1,"scorePublishType":2}}
```

#### create_course_examine

- 名称：`create_course_examine`
- 说明：为课程创建一场考试。**成功时**在 `data` 中附加考试列表页预览链接 `previewUrl`（`queryInstanceInfo` 匹配 `columnCode=examines`，路径 `/teacher/course/{instanceId}/column/{uniqueId}/examines`）。`commitmentFlag=1` 时自动注入固定诚信承诺书标题与正文，否则标题/内容为空字符串
- Profile：`design`

输入参数：

- `instanceId`（int，必填）：课程实例标识
- `examineName`（string，必填）：考试名称
- `duration`（int，必填）：考试时长（分钟）
- `paperIdList`（array[int]，必填）：正考试卷 ID 列表
- `makeupPaperIdList`（array[int]，可选）：补考试卷 ID 列表
- `limitDurationFlag`（int，可选）：是否限制交卷最短答题时长，`0`/`1`，默认 `0`
- `limitDuration`（int，可选）：交卷最短答题时长（分钟），默认 `0`
- `watermarkFlag`（int，可选）：是否显示水印，`0`/`1`，默认 `0`
- `quizOutorderFlag`（int，可选）：题目是否乱序，`0`/`1`，默认 `0`
- `optionOutorderFlag`（int，可选）：选项是否乱序，`0`/`1`，默认 `0`
- `limitLeaveFlag`（int，可选）：是否限制切屏，`0`/`1`，默认 `0`
- `limitLeaveTimes`（int，可选）：限制切屏次数，默认 `0`
- `limitEnterFlag`（int，可选）：是否开启考试登录限制，`0`/`1`，默认 `0`
- `viewFlag`（int，可选）：是否允许学生考后查看试卷，`0`/`1`，默认 `0`
- `reviewType`（int，可选）：`20` 教师审核 AI 批阅（默认）/ `30` 教师手工批阅
- `commitmentFlag`（int，可选）：是否签订诚信承诺书，`0`/`1`，默认 `0`

示例：

```json
{
  "tool": "create_course_examine",
  "arguments": {
    "instanceId": 5442,
    "examineName": "期中考试",
    "duration": 120,
    "paperIdList": [1001, 1002],
    "makeupPaperIdList": [1003],
    "commitmentFlag": 1,
    "reviewType": 20
  }
}
```

成功响应示例：

```json
{
  "code": 200,
  "message": "创建考试成功！",
  "data": {
    "examineId": 9001,
    "previewUrl": "https://example.com/teacher/course/5442/column/e1/examines"
  }
}
```

后端：`POST /api/teach/examine/new` → 成功后 `queryInstanceInfo` 组装 `previewUrl`

#### set_examine_detailed

- 名称：`set_examine_detailed`
- 说明：按班级设置考试权重占比。提交前自动以 `taskType=0` 查询 `queryTaskDetailed`，若 `gradeTaskList` 非空则按 `examineId`（对列表项 `taskId`/`examineId`）回填 `rpId`；`rate` 之和必须为 `100`

输入参数：

- `classId`（int，必填）：班级标识
- `percentageType`（int，必填）：占比方式，仅 `10`（平均）/`20`（自定义）
- `gradeDetailList`（array，必填）：考试占比明细，元素字段：
  - `examineId`（int，必填）：考试标识
  - `rate`（number，必填）：占比；列表合计必须为 `100`
  - `selectFlag`（integer/int32，可选）：勾选状态，`1` 勾选、`0` 不勾选；仅 `scoreType=20` 时生效
- `scoreNum`（integer/int32，可选）：取最高计分数量
- `scoreType`（integer/int32，可选）：计分规则；`10` 权重分配、`20` 取最高若干次平均

示例：

```json
{
  "tool": "set_examine_detailed",
  "arguments": {
    "classId": 24135,
    "percentageType": 20,
    "gradeDetailList": [
      {"examineId": 2001, "rate": 40},
      {"examineId": 2002, "rate": 60}
    ]
  }
}
```

后端：先 `POST /api/teach/class/grade/queryTaskDetailed`（`taskType=0`），再 `POST /api/teach/class/grade/saveExamineDetailed`

#### set_grade_strategy

- 名称：`set_grade_strategy`
- 说明：设置班级不限要求任务计分策略；每项 `scoreRate` 提交前自动 ×100（如入参 `80` → 请求体 `8000`）；每项 `taskTypeList` 内取值不得重复

输入参数：

- `classId`（int，必填）：班级标识
- `gradeStrategyList`（array，必填）：计分策略列表，元素字段：
  - `score`（int，必填）：得分
  - `scoreRate`（number，必填）：得分率（人类可读，如 `80` 表示 80%）
  - `taskTypeList`（array[int]，必填）：任务类型列表；同一项内不得重复

示例：

```json
{
  "tool": "set_grade_strategy",
  "arguments": {
    "classId": 24135,
    "gradeStrategyList": [
      {
        "score": 10,
        "scoreRate": 80,
        "taskTypeList": [10, 20]
      }
    ]
  }
}
```

后端：`POST /api/teach/class/grade/saveGradeStrategy`

#### set_grade_plan

- 名称：`set_grade_plan`
- 说明：设置班级选做任务计分方案；提交前校验全部 `rate` 之和为 100，且每项 `scoredTaskCount` 等于该项 `taskIdList` 的元素数量（`rate` 不做 ×100）

输入参数：

- `classId`（int，必填）：班级标识
- `gradePlanList`（array，必填）：计分方案列表，元素字段：
  - `scoredTaskCount`（int，必填）：用于计分的任务数，须等于 `taskIdList` 长度
  - `rate`（int，必填）：占比；全部项合计须为 100
  - `taskIdList`（array[int]，必填）：任务标识列表

示例：

```json
{
  "tool": "set_grade_plan",
  "arguments": {
    "classId": 24135,
    "gradePlanList": [
      {
        "scoredTaskCount": 2,
        "rate": 60,
        "taskIdList": [1001, 1002]
      },
      {
        "scoredTaskCount": 1,
        "rate": 40,
        "taskIdList": [1003]
      }
    ]
  }
}
```

后端：`POST /api/teach/class/grade/saveGradePlan`

#### add_class_custom_grade_dimension

- 名称：`add_class_custom_grade_dimension`
- 说明：为班级新增自定义成绩维度（成绩项名称与说明）

输入参数（全部必填）：

- `teachClassId`（int）：班级标识
- `customName`（string）：成绩项名称
- `description`（string）：成绩说明

示例：

```json
{
  "tool": "add_class_custom_grade_dimension",
  "arguments": {
    "teachClassId": 24135,
    "customName": "课堂表现",
    "description": "平时表现分"
  }
}
```

后端：`POST /api/teach/class/grade/newClassCustomConfig`

#### edit_class_custom_grade_dimension

- 名称：`edit_class_custom_grade_dimension`
- 说明：按自定义标识更新班级自定义成绩维度（成绩项名称与说明）

输入参数（全部必填）：

- `customId`（int）：自定义标识
- `customName`（string）：成绩项名称
- `description`（string）：成绩说明

示例：

```json
{
  "tool": "edit_class_custom_grade_dimension",
  "arguments": {
    "customId": 9,
    "customName": "课堂表现",
    "description": "平时表现分（更新）"
  }
}
```

后端：`POST /api/teach/class/grade/updateClassCustomConfig`

#### set_course_grade_weight

- 名称：`set_course_grade_weight`
- 说明：设置课程成绩权重；提交前查询 `queryClassGradeConfigById` 回填 `configId`；所有 `*Rate` 之和须为 100；Type 仅允许 10/20；当 `customNRate>0` 时从查询结果 `customConfigList` 按 `customKey` 匹配并写入提交列表

输入参数：

- `teachClassId`（int，必填）：班级标识
- Rate（未传按 0，合计须为 100）：`progressRate`、`exerciseRate`、`examineRate`、`experimentRate`、`sceneRate`、`writingRate`、`fileRate`、`pblRate`、`optionalRate`、`unrestrictedRate`、`custom1Rate`…`custom5Rate`
- Type（可选，10 平均 / 20 自定义）：`exerciseType`、`examineType`、`experimentType`、`sceneType`、`writingType`、`fileType`、`pblType`
- `examineScoreNum`（integer/int32，可选）：取最高计分数量
- `examineScoreType`（integer/int32，可选）：考试计分规则；`10` 权重分配、`20` 取最高若干次平均
- `exemptRate`（integer/int32，可选）：免学
- `interviewRate`（integer/int32，可选）：沉浸式任务占比；计入所有 Rate 合计 100 的校验
- `interviewType`（integer/int32，可选）：沉浸式任务占比方式；`10` 平均、`20` 自定义
- `syncFlag`（int，可选）：0 不同步 / 1 同步
- `classIdList`（array[int]，可选）：同步班级列表

示例：

```json
{
  "tool": "set_course_grade_weight",
  "arguments": {
    "teachClassId": 24135,
    "progressRate": 20,
    "exerciseRate": 30,
    "exerciseType": 10,
    "examineRate": 30,
    "examineType": 10,
    "optionalRate": 10,
    "unrestrictedRate": 10,
    "custom1Rate": 0,
    "syncFlag": 0
  }
}
```

处理流程：

1. 校验 Rate 合计为 100、Type 合法
2. `POST /api/teach/class/grade/queryClassGradeConfigById`（`classId=teachClassId`）取 `configId` / `customConfigList`
3. `POST /api/teach/class/grade/newClassGradeConfig` 保存

#### audio_to_text

- 名称：`audio_to_text`
- 说明：将本地或网络音频转为文本；网络地址先下载到临时文件；提交 `audioToText` 后轮询 `queryAudioText`（`status=10` 等待 100ms；`20` 成功返回 content；`30` 失败仍返回 content）

输入参数：

- `filePath`（string，必填）：本地音频路径或 HTTP/HTTPS 地址

示例：

```json
{
  "tool": "audio_to_text",
  "arguments": {
    "filePath": "D:/media/lecture.mp3"
  }
}
```

处理流程：

1. 解析 `filePath`（HTTP 则下载到临时目录）
2. `POST /api/learn/task/audioToText` 获取 `taskId`
3. 循环 `POST /api/learn/task/queryAudioText` 直至 `status` 为 20/30 或超时

#### delete_class_students

- 名称：`delete_class_students`
- 说明：从班级移除一名或多名开课学生（`memberIds` 长度为 1 即为单人移除）
- Profile：`common`

输入参数：

- `memberIds`（int[]，必填）：开课成员标识列表

示例（批量）：

```json
{
  "tool": "delete_class_students",
  "arguments": {
    "memberIds": [1001, 1002]
  }
}
```

示例（单人）：

```json
{
  "tool": "delete_class_students",
  "arguments": {
    "memberIds": [1001]
  }
}
```

后端链路：`POST /api/teach/member/student/removeStudent`

#### add_class_students

- 名称：`add_class_students`
- 说明：将学生名单加入指定教学班；按学号先查用户中心，已存在走批量添加，不存在则逐条新建

输入参数（全部必填）：

- `instanceId`（int）：课程实例标识
- `classId`（int）：教学班标识
- `studentList`（array）：学生列表，每项含：
  - `realName`（string）：姓名
  - `studentNo`（string）：学号

处理流程：

1. 遍历 `studentList`，调用 `POST /api/core/student/listStudentToClassByPage`（`searchText=学号`）
2. 结果中按学号精确匹配 → 收集 `studentId`/`userId`/`tenantId`
3. 未匹配 → 收集姓名/学号
4. 已存在队列非空 → `POST /api/teach/member/student/newStudent`
5. 不存在队列非空 → 逐条 `POST /api/teach/member/student/insertStudent`

示例：

```json
{
  "tool": "add_class_students",
  "arguments": {
    "instanceId": 5442,
    "classId": 24135,
    "studentList": [
      {"realName": "张三", "studentNo": "2026001"},
      {"realName": "李四", "studentNo": "2026002"}
    ]
  }
}
```

成功返回示例：

```json
{
  "code": 200,
  "message": "添加开课学生成功！",
  "data": {
    "existedCount": 1,
    "insertedCount": 1,
    "failureCount": 0
  }
}
```

#### update_student_class

- 名称：`update_student_class`
- 说明：将指定成员批量变更到目标教学班

输入参数（全部必填）：

- `instanceId`（int）：课程实例标识
- `teachClassId`（int）：目标教学班标识
- `memberId`（array[int]）：成员标识列表（非空）

示例：

```json
{
  "tool": "update_student_class",
  "arguments": {
    "instanceId": 5442,
    "teachClassId": 24135,
    "memberId": [1001, 1002]
  }
}
```

后端：`POST /api/teach/member/student/updateStudent`

#### add_teacher_to_team

- 名称：`add_teacher_to_team`
- 说明：向教师团队新增单个教师；可选头像支持本地文件路径或 HTTP/HTTPS 网络路径

输入参数：

- `instanceId`（int，必填）：开课实例标识
- `realName`（string，必填）：教师姓名
- `teacherNo`（string，必填）：教师工号
- `gender`（int，可选）：性别，`0` 保密、`1` 男、`2` 女
- `departmentId`（int，可选）：所属院系标识
- `avatarPath`（string，可选）：头像本地文件路径或 HTTP/HTTPS 网络文件路径

头像处理流程：

1. 网络头像先下载为临时本地文件；本地头像直接校验并使用。
2. 调用 `POST /api/resource/upload/file`，固定 `uploadType=avatar`。
3. 将响应中的 `fileRelativePath`、`fileTempPath` 封装为 `fileUploadVo`。
4. 调用 `POST /api/teach/teacher/insertTeacher` 新增教师。

头像下载或上传失败时不会调用教师新增接口；网络临时文件会在调用结束后清理。

示例：

```json
{
  "tool": "add_teacher_to_team",
  "arguments": {
    "instanceId": 5442,
    "realName": "张老师",
    "teacherNo": "T2026001",
    "gender": 1,
    "departmentId": 1001,
    "avatarPath": "https://example.com/avatar.png"
  }
}
```

#### remove_class_teacher

- 名称：`remove_class_teacher`
- 说明：按成员标识移除课程教师

输入参数：

- `memberId`（int，必填）：成员标识

示例：

```json
{
  "tool": "remove_class_teacher",
  "arguments": {
    "memberId": 12345
  }
}
```

后端：`POST /api/teach/teacher/removeClassTeacher`

#### query_teacher_instance_menu

- 名称：`query_teacher_instance_menu`
- 说明：查询教师课程菜单权限列表。请求后端时将 `instanceId` 映射为 `dataId`，并固定 `menuType=3`（开课管理菜单）

输入参数：

- `instanceId`（int，必填）：开课实例标识
- `userId`（int，必填）：用户标识
- `teacherType`（integer/int32，可选）：教师人员性质；10：课程负责人；20：团队教师；30：助教。未传或为 `null` 时默认 20

示例：

```json
{
  "tool": "query_teacher_instance_menu",
  "arguments": {
    "instanceId": 5442,
    "userId": 1001
  }
}
```

后端：`POST /api/teach/teacher/listInstanceMenu`

#### set_teacher_instance_menu

- 名称：`set_teacher_instance_menu`
- 说明：设置教师课程菜单权限。请求后端时固定 `menuType=3`；调用前对 `userMenuAuthItemList` 每一项补齐 `dataId=instanceId`、`userId=userId`（覆盖列表项中已有值）

输入参数：

- `instanceId`（int，必填）：开课实例标识
- `userId`（int，必填）：用户标识
- `userMenuAuthItemList`（array，必填）：菜单权限项列表，每项包含：
  - `userMenuId`（int）：用户菜单主键标识，新建可为 `0`
  - `menuId`（int）：菜单标识
  - `menuCode`（string）：菜单编码
  - `writeFlag`（int）：权限值，`0` 无权限 / `1` 只看 / `2` 管理
- `teacherType`（integer/int32，可选，默认 20）：教师身份类型；10：课程负责人；20：团队教师；30：助教；未传或传 null 时使用 20

示例：

```json
{
  "tool": "set_teacher_instance_menu",
  "arguments": {
    "instanceId": 5442,
    "userId": 1001,
    "userMenuAuthItemList": [
      {
        "userMenuId": 10,
        "menuId": 20,
        "menuCode": "course",
        "writeFlag": 2
      }
    ]
  }
}
```

后端：`POST /api/teach/teacher/newTeacherLimitsConfig`

#### batch_push_task

- 名称：`batch_push_task`
- 说明：将已创建的任务批量发布到班级，按 `taskType` 自动校验必填字段并补全默认值

输入参数（通用必填）：

- `instanceId`（int，必填）：课程实例标识
- `taskType`（int，必填）：任务类型 `30`(AI辩论) `40`(场景模拟) `50`(常规练习) `51`(自互评练习) `60`(个性化练习) `80`(写作任务) `140`(PBL任务)
- `taskIds`（array[int]，必填）：待发布任务标识列表
- `classIds`（array[int]，必填）：发布目标班级标识列表
- `timeFlag`（int，可选，默认 1）：是否有时间限制
- `startTime`、`endTime`（string，格式 `yyyy-MM-dd HH:mm:ss`）
- `requireFlag`（int，可选，默认 1）：是否必做
- `markFlag`（integer/int32，可选）：是否评分/评价标识；0：否；1：是。映射到 `pushTaskVOList[].markFlag`，未传或为 `null` 时省略

按任务类型的额外规则：

| taskType | 额外必填 | 自动填充默认值 |
|----------|---------|--------------|
| 30/50/60 | — | `submitTimes=1` |
| 40/80 | — | `submitTimes=1`、`duration=30` |
| 51 | `startTime`、`endTime`、`reviewBeginTime`、`reviewEndTime` | `evaluateType=10`、`evaluateNum=1`、`timeFlag=1` |
| 140 | `startTime`、`endTime`、`groupPlanList`；组间/组内/自评时需 `reviewBeginTime/EndTime` | `timeFlag=1` |

示例（常规练习）：

```json
{
  "tool": "batch_push_task",
  "arguments": {
    "instanceId": 5442,
    "taskType": 50,
    "taskIds": [209346],
    "classIds": [24135],
    "startTime": "2026-06-02 00:00:00",
    "endTime": "2026-06-08 23:59:59"
  }
}
```

#### change_push_task

- 名称：`change_push_task`
- 说明：调整已发布任务设置；入参对齐 `batch_push_task`，但使用单个 `taskId`，且不传 `classIds`（由已发布任务的 `taskClassItemList` 自动构建）
- Profile：`develop`

输入参数（通用必填）：

- `instanceId`（int，必填）：课程实例标识
- `taskType`（int，必填）：任务类型（规则同 `batch_push_task`）
- `taskId`（int，必填）：已发布任务标识
- `markFlag`（integer/int32，可选）：是否评分/评价标识；0：否；1：是。映射到 `pushTaskVOList[].markFlag`，未传或为 `null` 时省略
- 其余可选设置字段与 `batch_push_task` 一致（无 `classIds` / `taskIds`）

处理逻辑：

1. 调用 `POST /api/teach/task/listTask`（`publishStatus=20`，`page={pageNo:1,pageSize:100}`）
2. 在 `rowList` 按 `taskId` 匹配；匹配不到则提示「任务未发布，请调用 batch_push_task 工具发布」
3. 匹配到后从 `taskClassItemList` 构建 `classIds`，并以 `taskIds=[taskId]` 提交 `POST /api/teach/taskPush/changePushTask`

示例：

```json
{
  "tool": "change_push_task",
  "arguments": {
    "instanceId": 5442,
    "taskType": 50,
    "taskId": 209346,
    "startTime": "2026-06-02 00:00:00",
    "endTime": "2026-06-15 23:59:59"
  }
}
```

#### update_batch_continue

- 名称：`update_batch_continue`
- 说明：打回重做任务或发布补交任务；先按 `taskId` 调用 `queryTask` 识别 `taskType`。PBL（`taskType=140`）必填 `taskGroupIds`，请求体 `userList=[]`；其它任务必填 `userList`（每项含 `userId`、`classId`），请求体 `taskGroupIds=[]`

输入参数：

- `taskId`（int，必填）：任务标识
- `endTime`（string，必填）：续交截止时间（如 `yyyy-MM-dd HH:mm:ss`）
- `continueType`（int，必填）：续交类型
- `continueFlag`（int，必填）：续交标记
- `userList`（array，非 PBL 必填）：学生列表，每项含 `userId`、`classId`
- `taskGroupIds`（array[int]，PBL 必填）：任务小组标识列表

示例（非 PBL）：

```json
{
  "tool": "update_batch_continue",
  "arguments": {
    "taskId": 10001,
    "endTime": "2026-07-30 23:59:59",
    "continueType": 10,
    "continueFlag": 1,
    "userList": [{"userId": 1001, "classId": 24135}]
  }
}
```

示例（PBL）：

```json
{
  "tool": "update_batch_continue",
  "arguments": {
    "taskId": 10002,
    "endTime": "2026-07-30 23:59:59",
    "continueType": 10,
    "continueFlag": 1,
    "taskGroupIds": [11, 12]
  }
}
```

后端编排：`POST /api/teach/task/queryTask` → `POST /api/teach/task/updateBatchContinue`

示例（自互评练习）：

```json
{
  "tool": "batch_push_task",
  "arguments": {
    "instanceId": 5442,
    "taskType": 51,
    "taskIds": [209347],
    "classIds": [24135],
    "startTime": "2026-06-02 00:00:00",
    "endTime": "2026-06-08 23:59:59",
    "reviewBeginTime": "2026-06-09 00:00:00",
    "reviewEndTime": "2026-06-15 23:59:59",
    "evaluateType": 20,
    "evaluateNum": 3
  }
}
```

后端链路：

- `POST /api/teach/taskPush/batchPushTask`
- 成功后查询 `POST /api/teach/instance/queryInstanceInfo`（`version=2`）按 `columnCode=current-tasks` 解析栏目 `uniqueId`，组装教师端预览链接

成功返回示例：

```json
{
  "code": 200,
  "message": "发布任务成功！",
  "data": {
    "previewUrl": "https://example.com/teacher/course/5442/column/current-tasks/tasks/current"
  }
}
```

说明：`data.previewUrl` 路径为 `/teacher/course/{instanceId}/column/{uniqueId}/tasks/current`；若未找到 `columnCode=current-tasks`，则 `uniqueId` 回退为 `column`。

### 白名单与生成器

- 接口文档：`api/` 目录下所有 `*.json`（Swagger 2.0 / OpenAPI 3.x，如 `教学接口_OpenAPI.json`、`资源接口_OpenAPI.json`）
- 白名单文件：`tool_whitelist.json`（`path + method`）
- 生成命令：`python scripts/haoke_openapi_toolgen.py`（默认遍历 `api/`；也可用 `--openapi` 指定单个文件）
- 生成产物：`src/haoke_mcp_server/generated/tool_registry.py`
- **入参约定**：白名单自动生成的 Tool 将请求体字段**展平**为顶层参数（生成物 `parameters` 无 `body` 包装）；**不包含** `requestId`、`webSearchFlag`；除 `listTask`/`sendReminder`/`queryGroupConfig`/`newClass`/`listStudentData`/`listTaskStuInfoData`/`listBotUsageDetails`/`listWarnUser` 外**不包含** `classId`。`requestId` 由 `http_client` 自动注入；`webSearchFlag` 省略时使用后端默认。

### query_course_custom_columns

查询指定课程的自定义栏目，属于 develop profile。`instanceId` 为必填正整数。父项仅返回 `columnId`、`columnName`、`iconPath`、`linkUrl`、`children`，子项仅返回前四字段，不继续展开孙级；保留栏目顺序、链接和图标原值。

```json
{"tool":"query_course_custom_columns","arguments":{"instanceId":5442}}
```

成功返回示例：

```json
{"code":200,"message":"查询课程自定义栏目列表成功！","data":{"columnList":[{"columnId":101,"columnName":"实践任务","iconPath":"/icons/practice.svg","linkUrl":"","children":[{"columnId":102,"columnName":"案例分析","iconPath":null,"linkUrl":"https://example.com/case"}]}]}}
```

无栏目时 `data.columnList=[]`，无子栏目时 `children=[]`，缺失基础字段返回 `null`；保留 `data` 其他字段。调用 `POST /api/teach/task/queryCustomColumn`；失败透传后端错误和存在的 `data`，异常结构返回非 200。

### MCP 工具清单（测试参考）

从运行时 `list_tools()` 生成 Tool 的 Markdown 参数契约（含入参表与调用示例）。全量清单仅作离线对照；线上按 profile 分册挂载。

分页查询开课学生工具 `teach_member_student_liststudentbypage_post` 支持可选 `parentClassId`（integer/int64，父班级标识）。传入时原样写入后端请求，未传时省略该字段。

开课教师查询工具 `teach_teacher_listinstanceteacher_post` 支持可选 `teacherType`（integer/int32）：0 全部、10 课程负责人、20 团队教师、30 助教。传入时透传（包括 0），未传时省略，无默认筛选值。

资源列表工具 `resource_file_listpage_post` 支持可选 `createType`（integer/int32）：`0` 查询全部创建者，`10` 仅查询我创建的资源。传入时原样写入 `POST /api/resource/file/listPage` 请求体，未传时省略。

题目列表工具 `resource_quiz_listquiz_post` 支持可选 `createType`（integer/int32）：`0` 查询全部创建者，`10` 仅查询我创建的题目。传入时原样写入 `POST /api/resource/quiz/listQuiz` 请求体，未传时省略。

新建开课工具 `teach_instance_newinstance_post` 支持可选 `instanceType`（integer/int32）：`10` 为智慧课程，`20` 为共享课。提供时原样传入 `POST /api/teach/instance/newInstance`，未提供时省略该字段，使用后端默认行为。

```json
{"tool":"teach_instance_newinstance_post","arguments":{"instanceName":"共享课程示例","instanceType":20}}
```

- 全量文档：[docs/mcp-tools.md](docs/mcp-tools.md)
- 分册目录：`docs/mcp-tools-by-profile/<profile>.md`

```bash
python scripts/generate_mcp_tool_docs.py
python scripts/generate_mcp_tool_docs.py --profile develop
python scripts/generate_mcp_tool_docs.py --check
python scripts/generate_mcp_tool_docs.py --profile common --check
```

可选：`--output path/to/file.md` 指定输出路径。

### 最小自检

执行：

```bash
python scripts/self_check.py
```

覆盖项：
- 白名单限制
- 鉴权头注入
- `docs/mcp-tools.md` 与运行时 Tool 清单一致
- 五类 profile 归属文件可加载、覆盖完整、拒绝 `all`；专家默认不含仅属 `common` 的 Tool

### 错误响应说明

- 当好课后端接口返回 HTTP **401**（Token 失效/未授权）时，所有 Tool 均返回：
  ```json
  {"code": 401, "message": "Token 失效，请重新登录"}
  ```
  调用方应检测 `code == 401` 并提示用户重新配置 `LOGIN_TOKEN`。
- 其他后端错误（网络/5xx 等）返回 `{"code": 500, "message": "..."}` 并携带 `status_code` 字段。

### 首批不支持范围

- 上传/下载（二进制）接口：当前未纳入首批白名单，后续按 base64/路径/Host 附件方案单独决策

### SSE 响应支持说明

- 当后端返回 `text/event-stream` 时，tool 会自动识别并解析 SSE。
- 返回结构为：
  - `response_type: "sse"`
  - `event_count`: 事件数量
  - `events`: 事件数组（包含 `event`、`data`，可选 `id`、`retry`）


### 交互式网页课件任务

`save_interactive_page_task` 属于 develop 专家。`taskId=0` 创建，正整数编辑；`instanceId`、`taskName`、`file_path` 均必填。后端保存接口为 `POST /api/v1/teach/task/interactive-page/save`。

外部 URL 创建：

```json
{"tool":"save_interactive_page_task","arguments":{"taskId":0,"instanceId":2,"taskName":"交互网页任务","file_path":"https://example.com/activity","description":"完成网页中的练习","scoringFlag":1,"scoreMaximum":10000,"targetFlag":10,"pointIdList":[9],"abilityIdList":[8],"unitId":245}}
```

本地 ZIP 创建（HTML/HTM 同样支持）：

```json
{"tool":"save_interactive_page_task","arguments":{"taskId":0,"instanceId":2,"taskName":"网页课件","file_path":"D:/course/page.zip"}}
```

编辑任务：

```json
{"tool":"save_interactive_page_task","arguments":{"taskId":42,"instanceId":2,"taskName":"更新后的网页任务","file_path":"https://example.com/activity-v2","scoringFlag":1,"scoreMaximum":10000,"pointIdList":[9],"abilityIdList":[8]}}
```

计分参数默认 `scoringFlag=0`、`scoreMaximum=0`；`scoringFlag=1` 时总分须大于 0。总分单位为已乘 100 的整数，例如 `10000` 表示 100 分，工具原样透传。`targetFlag` 为网页打开方式，`10` 表示 iframe、`20` 表示新标签页，默认 `20`；旧参数 `generatePrompt` 已移除。编辑为完整保存：需要保留的文本与关联应显式传入，省略知识点/能力列表会清空关联。

本地文件以 `uploadType=html`、`moveFlag=0` 上传，使用响应路径组装 `taskFile`；外部 URL 直接作为 `external_url` 保存。ZIP 必须包含 `index.html`，由后端校验；上传返回 `250016` 时，工具原样返回错误码和错误信息，停止保存。

成功返回保留 `data.taskId` 并增加 `data.previewUrl`，其值为服务基础地址拼接 `/student/course/0/studyTask/{task_id}/interactionWebHome?taskId={task_id}&instanceId={instance_id}`。
