# 06 · 工具选型与使用说明

本文专门回答三个问题：

1. **项目里到底有哪些工具？是真实的吗？**
2. **为什么选这些工具？（选型思路）**
3. **怎么用？（学生演示、模型调用、开发者扩展）**

---

## 一、现有文档分别写什么？

| 文档 | 侧重点 | 和本文的关系 |
|------|--------|--------------|
| [02-工具清单与官方链接.md](./02-工具清单与官方链接.md) | 每个工具的 **API 原链接**、请求参数、返回示例 | 本文偏「为什么选、怎么用」，02 偏「链接与接口细节」 |
| [04-运行流程详解.md](./04-运行流程详解.md) | 点击运行后 **整体流程** | 本文聚焦「工具」这一层 |
| [05-课堂演示与自学指南.md](./05-课堂演示与自学指南.md) | 讲课顺序、练习题 | 本文是工具专题讲义 |

**建议**：先读本文建立整体认识，需要查官方 URL 时翻 02。

---

## 二、工具是真实的吗？实际调用了吗？

**结论：是真实的，且在 ReAct 模式下会实际执行。**

| 工具 | 真实吗 | 何时执行 | 数据从哪来 |
|------|--------|----------|------------|
| DashScope `qwen-plus` | 是 | 每次运行必调 | 阿里云百炼 |
| `get_current_datetime` | 是 | 模型发出 `tool_calls` 时 | 本机系统时钟 |
| `calculate` | 是 | 模型发出 `tool_calls` 时 | 本地 `safeCalculate()` |
| `get_weather` | 是 | 模型发出 `tool_calls` 时 | Open-Meteo HTTP 请求 |
| `get_exchange_rate` | 是 | 模型发出 `tool_calls` 时 | Frankfurter HTTP 请求 |

**不是假数据**：天气、汇率来自公开 API；计算结果是程序算出来的；时间是系统时间。

**不会乱调**：只有模型在 Function Calling 里返回 `tool_calls` 时，`react-loop.ts` 才会调用 `executeTool()`。普通模式故意不传 `tools`，所以**永远不会**走工具。

---

## 三、工具选型思路（为什么选这四个？）

### 3.1 选型总原则

教学演示选工具，要满足 **「对比明显、零门槛、可验证」**：

| 原则 | 含义 | 本项目怎么做 |
|------|------|--------------|
| 对比明显 | 无工具时模型容易错 | 大数计算、实时天气、汇率、当前时间 |
| 零门槛 | 学生只需 DashScope Key | 天气/汇率 API 免费、无需再注册 |
| 可验证 | 结果能核对 | 计算器可手算；天气可对照手机天气 App |
| 稳定 | 课堂少翻车 | 选文档完善、CORS 友好的公开 API |
| 覆盖典型场景 | 代表常见 Agent 能力 | 计算 / 检索 / 时间 / 结构化查询 |

### 3.2 为什么没选搜索、数据库、Coze 插件？

| 未选方案 | 原因 |
|----------|------|
| 网页搜索 API | 多数要额外 Key；结果格式不稳定，不利于课堂讲解 |
| 假天气（随机数） | 无法体现「Observation 必须真实」 |
| 后端 Python 执行 | 课程统一 React + 浏览器演示，工具放前端更直观 |
| 更多工具 | 四个已覆盖主要类型，太多反而分散注意力 |

### 3.3 四个工具各自「代表」什么能力

```mermaid
mindmap
  root((ReAct 工具箱))
    精确计算
      calculate
      模型算术弱点
    实时检索
      get_weather
      外部事实查不到
    结构化查询
      get_exchange_rate
      数值型 API 结果
    环境感知
      get_current_datetime
      模型不知道此刻
```

| 工具 | 代表能力 | 无工具时典型问题 |
|------|----------|------------------|
| `calculate` | 确定性计算 | `12345×6789` 算错 |
| `get_weather` | 实时外部事实 | 编造「深圳今天晴 28°C」 |
| `get_exchange_rate` | 动态数据查询 | 用过时汇率 |
| `get_current_datetime` | 当前环境状态 | 不知道现在几点、星期几 |

### 3.4 外部 API 为什么选 Open-Meteo 和 Frankfurter？

| API | 选中理由 | 官方入口 |
|-----|----------|----------|
| **Open-Meteo** | 免费、无需 Key、支持中文城市、浏览器可直连（CORS） | https://open-meteo.com/ |
| **Frankfurter** | 免费、无 Key、汇率数据清晰、请求简单 | https://www.frankfurter.app/ |

---

## 四、每个工具怎么用？

### 使用方式分三层

```mermaid
flowchart LR
    A[① 学生在页面提问] --> B[② 模型选择工具]
    B --> C[③ executeTool 执行]
    C --> D[④ 结果展示在 Observation]
```

---

### 工具 1：`calculate`（数学计算）

**中文名**：数学计算  
**代码**：`src/lib/tools.ts` → `safeCalculate()`

#### 什么时候会被调用？

用户问题包含**精确算术**且数字较大或易错时，例如：

- 「请计算 12345 × 6789 等于多少？」

#### 模型传入的参数

```json
{ "expression": "12345 * 6789" }
```

支持：`+ - * / ^ % ( )`，`×` 需在模型侧转为 `*`。

#### 返回示例

```text
12345 * 6789 = 83810205
```

#### 你怎么验证它真执行了？

1. 看右侧 **Action** 是否出现 `calculate`
2. 看 **Observation** 数字是否等于手算结果
3. 命令行：`node scripts/test-tools.mjs`

---

### 工具 2：`get_weather`（查询天气）

**中文名**：查询天气  
**代码**：`src/lib/tools.ts` → `fetchWeather()`

#### 什么时候会被调用？

问**某城市当前天气**，例如：

- 「深圳现在的天气怎么样？」

#### 模型传入的参数

```json
{ "city": "深圳" }
```

#### 背后真实请求（两步）

1. 地理编码：  
   `https://geocoding-api.open-meteo.com/v1/search?name=深圳&...`
2. 天气预报：  
   `https://api.open-meteo.com/v1/forecast?latitude=...&longitude=...`

#### 返回示例

```text
深圳（中国）当前天气：多云
温度：25.2°C
湿度：78%
风速：12.5 km/h
```

#### 你怎么验证？

- 浏览器 F12 → Network → 过滤 `open-meteo`
- 对比手机天气 App 温度是否接近（不必完全一致，数据源可能不同）

#### 注意

城市名要常见（深圳、北京、上海）；生僻地名可能「未找到城市」。

---

### 工具 3：`get_exchange_rate`（汇率换算）

**中文名**：汇率换算  
**代码**：`src/lib/tools.ts` → `fetchExchangeRate()`

#### 什么时候会被调用？

涉及**货币兑换、汇率**，例如：

- 「100 美元现在能换多少人民币？」

#### 模型传入的参数

```json
{
  "from_currency": "USD",
  "to_currency": "CNY",
  "amount": "100"
}
```

`amount` 可省略，默认按 1 换算。

#### 背后真实请求

`https://api.frankfurter.app/latest?from=USD&to=CNY`

#### 返回示例

```text
汇率日期：2026-06-08
1 USD = 6.7819 CNY
100 USD = 678.1900 CNY
```

#### 你怎么验证？

- Network 里看到 `frankfurter.app` 请求
- 汇率日期应为近期工作日

---

### 工具 4：`get_current_datetime`（获取时间）

**中文名**：获取时间  
**代码**：`src/lib/tools.ts` → `get_current_datetime` 分支

#### 什么时候会被调用？

问**现在几点、星期几、今天日期**，例如：

- 「现在北京时间是几点？星期几？」

#### 模型传入的参数

```json
{ "timezone": "Asia/Shanghai" }
```

#### 返回示例

```text
时区 Asia/Shanghai 的当前时间：2026/06/09 星期二 14:30:25
```

#### 你怎么验证？

对照电脑右下角系统时间。

#### 说明

此工具**不访问互联网**，用的是运行 demo 的那台电脑/浏览器的时钟。

---

## 五、在页面上怎么用？（学生操作步骤）

### 步骤 1：启动项目

```bash
cd 06_代码/Day_02/react-demo
npm run dev
```

打开 http://localhost:5173 ，填入 DashScope API Key。

### 步骤 2：选对问题

点击带 **「需工具」** 标签的示例，或自己输入同类问题：

| 想测哪个工具 | 推荐问题 |
|--------------|----------|
| calculate | 请计算 12345 × 6789 等于多少？ |
| get_weather | 深圳现在的天气怎么样？ |
| get_exchange_rate | 100 美元现在能换多少人民币？ |
| get_current_datetime | 现在北京时间是几点？星期几？ |
| 不调用工具 | 用一句话解释什么是 ReAct？ |

### 步骤 3：开启对比模式，点击「运行演示」

- **左侧**：普通模式，无工具
- **右侧**：ReAct 模式，应出现 Thought → Action → Observation → Answer

### 步骤 4：看摘要行

顶部应显示类似：

```text
普通模式：0 次工具调用
ReAct 模式：1 次工具调用 · 2 轮推理
```

---

## 六、在代码里工具怎么接入？（开发者视角）

每个工具在代码里都有 **两份定义**：

### 6.1 给模型看的：`TOOL_DEFINITIONS`

在 `src/lib/tools.ts` 中，描述工具名、用途、参数 Schema。  
发给 DashScope 时放在请求体的 `tools` 字段，模型据此决定调谁。

### 6.2 给程序执行的：`executeTool()`

同一文件中的 `switch (name)`，根据模型返回的 `function.name` 和 `arguments` **真正执行**。

```text
模型说：call calculate({"expression":"12345*6789"})
    ↓
executeTool("calculate", '{"expression":"12345*6789"}')
    ↓
返回："12345 * 6789 = 83810205"
    ↓
作为 role:"tool" 消息塞回 messages
    ↓
模型读 Observation，写最终 Answer
```

### 6.3 想新增第五个工具？

1. 在 `TOOL_DEFINITIONS` 加一条 `function` 描述  
2. 在 `executeTool()` 加 `case` 分支  
3. 在 `TOOL_LABELS` 加中文显示名  
4. 在 `thought.ts` 的 `reasonByTool` 加选型说明（可选，用于 Thought 文案）  
5. 在 `questions.ts` 加一道示例题  

---

## 七、命令行单独测工具（不打开浏览器）

```bash
# 只测四个业务工具，不需要 DashScope Key
node scripts/test-tools.mjs

# 测「普通模式 + ReAct 模式」完整链路，需要 DASHSCOPE_API_KEY
node scripts/test-react.mjs
```

预期 `test-tools.mjs` 输出：

```text
✅ calculate: 12345 * 6789 = 83810205
✅ get_current_datetime: ...
✅ get_exchange_rate: 1 USD = x.xxxx CNY
✅ get_weather: 深圳 ...
工具测试: 4/4 通过
```

---

## 八、常见问题

**Q：工具是老师写死的假回答吗？**  
A：不是。除时间/计算在本地算外，天气和汇率都是实时 HTTP 请求。

**Q：模型一定会调用工具吗？**  
A：不保证。靠 System Prompt 引导；简单题模型可能直接答。课堂建议用「需工具」示例题。

**Q：为什么有时看不到 Thought 原文？**  
A：DashScope 调工具时常不返回思考文字，程序会用 `thought.ts` **补全**教学用 Thought，Observation 数据仍是真实的。

**Q：VPN 会影响吗？**  
A：可能影响访问 DashScope；Open-Meteo / Frankfurter 一般不受影响。课堂建议关 VPN 测大模型。

---

## 九、随堂检查

1. 本项目四个工具分别解决模型的什么弱点？
2. `TOOL_DEFINITIONS` 和 `executeTool` 各给谁用？
3. 如何证明 `get_weather` 真的发了网络请求？

<details>
<summary>参考答案</summary>

1. 算术错误、不知实时天气、不知实时汇率、不知当前时间。
2. 前者给大模型选工具；后者在收到 tool_calls 后由程序执行。
3. 浏览器 Network 面板看到 `open-meteo.com` 请求，或运行 `test-tools.mjs`。

</details>

---

相关文档：

- API 原链接详情 → [02-工具清单与官方链接.md](./02-工具清单与官方链接.md)
- 整体运行流程 → [04-运行流程详解.md](./04-运行流程详解.md)
