# 04 · 运行流程详解
本文按时间顺序说明:**从安装到点击「运行演示」,系统内部发生了什么**。
---
## 一、环境准备流程
```mermaid
flowchart LR
A[安装 Node.js] --> B[npm install]
B --> C[配置 DASHSCOPE_API_KEY]
C --> D[npm run dev]
D --> E[浏览器打开 localhost:5173]
```
### 步骤说明
| 步骤 | 命令/操作 | 说明 |
|------|-----------|------|
| 1 | 安装 Node.js 18+ | https://nodejs.org/ |
| 2 | `npm install` | 安装 React、Vite、TypeScript 等依赖 |
| 3 | 配置 API Key | 页面输入,或系统环境变量 `DASHSCOPE_API_KEY` |
| 4 | `npm run dev` | 启动 Vite,默认端口 5173 |
| 5 | 打开浏览器 | 访问 http://localhost:5173 |
### 开发服务器启动后
- Vite 编译 `src/` 下的 React 代码
- 注册代理:浏览器访问 `/api/dashscope/*` → 转发到 `https://dashscope.aliyuncs.com/*`
---
## 二、用户点击「运行演示」后的流程
### 2.1 总览
```mermaid
flowchart TD
Start[用户点击运行演示] --> Validate{API Key 和问题是否有效?}
Validate -->|否| Error[显示错误提示]
Validate -->|是| Parallel[并行启动两个任务]
Parallel --> Plain[普通模式 plainChat]
Parallel --> React[ReAct 模式 runReAct]
Plain --> PlainDS[请求 DashScope<br/>不带 tools]
React --> ReactDS[请求 DashScope<br/>带 tools]
ReactDS -->|有 tool_calls| Exec[executeTool 执行工具]
Exec --> ReactDS2[再次请求 DashScope<br/>带上 tool 结果]
ReactDS -->|无 tool_calls| Answer[生成最终回答]
PlainDS --> PlainUI[更新左侧 PlainChain]
ReactDS2 --> ReactUI[更新右侧 ReActChain]
Answer --> ReactUI
```
### 2.2 对比模式下的并行执行
`App.tsx` 中使用 `Promise.all` 同时运行:
```ts
const [reactResult, plainResult] = await Promise.all([
runReAct(key, question, onStep), // 右侧
plainChat(key, question, onStep), // 左侧
])
```
这样学生能同时看到两种模式的耗时和结果差异。
---
## 三、ReAct 模式逐步拆解(以计算题为例)
**用户问题**:`请计算 12345 × 6789 等于多少?`
### 第 0 步:记录用户输入
| 步骤类型 | 内容 |
|----------|------|
| input | 用户提问原文 |
### 第 1 轮推理
#### ① 请求大模型(带 tools)
发送内容:
- `system`:ReAct 规则提示词
- `user`:用户问题
- `tools`:四个工具的定义
#### ② 模型返回 tool_calls
```json
{
"tool_calls": [{
"id": "call_xxx",
"function": {
"name": "calculate",
"arguments": "{\"expression\": \"12345 * 6789\"}"
}
}]
}
```
#### ③ 界面展示 Thought
`thought.ts` 生成类似:
```text
【第 1 轮思考】
用户问题包含精确计算……大模型心算容易出错,应交给计算工具。
→ 结论:调用工具「数学计算」(calculate)
→ 参数:expression=12345 * 6789
```
#### ④ 界面展示 Action
```text
调用工具:数学计算(calculate)
参数:{"expression": "12345 * 6789"}
```
#### ⑤ 执行工具 → Observation
`executeTool('calculate', ...)` 本地计算:
```text
12345 * 6789 = 83810205
```
#### ⑥ 把结果追加到 messages
```json
{
"role": "tool",
"tool_call_id": "call_xxx",
"content": "12345 * 6789 = 83810205"
}
```
### 第 2 轮推理
#### ⑦ 再次请求大模型
此时 messages 已包含:system、user、assistant(tool_calls)、tool(结果)。
#### ⑧ 模型返回最终文本(无 tool_calls)
#### ⑨ 界面展示 Thought + Answer
```text
【第 2 轮思考】
已收到工具返回的真实数据……信息足够,可以生成最终答案。
```
```text
12345 × 6789 = 83,810,205
```
### 完整链条示意
```text
❓ 用户提问
↓
💭 第1轮 Thought → ⚡ Action(calculate) → 👁 Observation(83810205)
↓
💭 第2轮 Thought → ✅ Answer
```
---
## 四、普通模式逐步拆解
| 步骤 | 界面显示 | 背后发生的事 |
|------|----------|--------------|
| 1 | 用户提问 | 记录 input |
| 2 | 跳过 ReAct 循环 | 静态说明:无 T/A/O |
| 3 | 直接输出 | 一次 `chatCompletion`,**不传 tools** |
| 4 | 最终回答 | 模型凭训练知识生成,可能心算或编造 |
---
## 五、消息列表(messages)的变化
ReAct 模式下,`messages` 数组会**不断增长**:
```mermaid
sequenceDiagram
participant M as messages 数组
Note over M: [system, user]
M->>M: + assistant (含 tool_calls)
M->>M: + tool (计算结果)
Note over M: 第二次请求
M->>M: + assistant (最终回答)
```
这正是 Function Calling 的标准模式:**工具结果必须回传给模型**,模型才能基于真实数据回答。
---
## 六、界面实时更新机制
`runReAct` 接受回调 `onStep`:
```ts
runReAct(apiKey, question, (step) => {
setReactSteps((prev) => [...prev, step])
})
```
每产生一个 Thought / Action / Observation / Answer,立刻追加到 state,**不用等全部完成**,所以学生能看到「实时推理」效果。
`groupStepsIntoRounds()` 把扁平 steps 按 `round` 字段分组,供 `ReActChain` 按轮渲染。
---
## 七、命令行测试流程
### 7.1 只测工具(无需大模型)
```bash
node scripts/test-tools.mjs
```
```
✅ calculate
✅ get_current_datetime
✅ get_exchange_rate
✅ get_weather
工具测试: 4/4 通过
```
### 7.2 测完整 ReAct 链路
```bash
# 需先设置环境变量 DASHSCOPE_API_KEY
node scripts/test-react.mjs
```
预期输出包含:
```text
💭 Thought → ⚡ Action → 👁 Observation → ✅ Answer
链条完整: 是 | 结果正确: 是
```
---
## 八、生产构建流程
```bash
npm run build # tsc 类型检查 + vite build → dist/
npm run preview # 本地预览 dist/
```
生产环境需自行配置 Nginx 等反向代理,将 `/api/dashscope` 转发到 `https://dashscope.aliyuncs.com`。
---
## 九、常见问题与排查
| 现象 | 可能原因 | 处理 |
|------|----------|------|
| 提示请先输入 API Key | 未填写 Key | 页面输入或设置 `DASHSCOPE_API_KEY` |
| 请求失败 / 500 | VPN 干扰 TLS | 关闭 VPN 后重试 |
| 右侧无 Thought | 旧版代码 | 确认使用本仓库最新版(含 `thought.ts`) |
| 天气查不到 | 城市名不标准 | 换「深圳」「北京」等常见名 |
| 普通模式也算对了 | 简单题模型可能碰巧对 | 换大数乘法或天气题对比 |
---
## 十、随堂检查
1. ReAct 模式下,为什么要有「第二次」请求大模型?
2. `onStep` 回调的作用是什么?
3. 工具执行结果以什么 role 写回 messages?
<details>
<summary>参考答案</summary>
1. 第一次拿到工具结果后,需要再把结果交给模型,让它组织成自然语言答案。
2. 每完成一步就更新 UI,实现实时展示推理链。
3. `role: "tool"`,并带 `tool_call_id` 与对应调用匹配。
</details>
---
下一篇:[05-课堂演示与自学指南.md](./05-课堂演示与自学指南.md)