# 03 · 开发思路与项目架构
本文说明:**为什么这样设计**、**代码怎么分层**、**关键设计决策**。
---
## 学习目标
1. 理解本项目的教学目标与技术选型
2. 能根据目录找到对应源码
3. 理解「思考步骤补全」等健壮性设计
---
## 1. 教学目标(先定目标,再写代码)
| 目标 | 页面/代码如何支撑 |
|------|-------------------|
| 让学生理解 ReAct 概念 | 左侧 `ConceptPanel` 概念面板 |
| 对比「无工具 vs 有工具」 | `ComparePanel` 左右并列 |
| 看见完整推理链 | `ReActChain` 按轮展示 T→A→O→A |
| 工具效果真实可验证 | 接入 Open-Meteo、Frankfurter 等公开 API |
| 课堂只需 DashScope | 模型统一走百炼 `qwen-plus` |
设计原则:**能看得见、能对比、能复现**。
---
## 2. 技术选型
| 层次 | 选择 | 原因 |
|------|------|------|
| 前端框架 | React 18 + TypeScript | 组件化清晰,适合教学演示 UI |
| 构建工具 | Vite 5 | 启动快,内置 dev 代理 |
| 大模型 | DashScope `qwen-plus` | 课程统一要求;支持 Function Calling |
| API 格式 | OpenAI 兼容模式 | `fetch` 即可调用,无需额外 SDK |
| 工具数据 | 公开免费 API | 学生无需再申请天气/汇率 Key |
---
## 3. 项目目录结构
```
06_代码/Day_02/react-demo/
├── docs/ # 学生学习文档(本目录)
├── scripts/
│ ├── test-tools.mjs # 工具 API 自测
│ └── test-react.mjs # ReAct 完整链路自测
├── src/
│ ├── App.tsx # 主界面:输入、运行、状态管理
│ ├── main.tsx # React 入口
│ ├── types/index.ts # 类型定义(Step、Round、Message)
│ ├── data/questions.ts # 预设示例问题
│ ├── lib/
│ │ ├── dashscope.ts # 大模型 API 封装(普通模式 + 底层请求)
│ │ ├── react-loop.ts # ReAct 主循环(核心逻辑)
│ │ ├── thought.ts # 思考文案生成(健壮性关键)
│ │ ├── rounds.ts # 步骤分组为「轮次」
│ │ └── tools.ts # 工具定义 + 真实执行
│ ├── components/
│ │ ├── ConceptPanel.tsx # 概念讲解
│ │ ├── ComparePanel.tsx # 左右对比容器
│ │ ├── ReActChain.tsx # ReAct 推理链 UI
│ │ └── PlainChain.tsx # 普通模式流程 UI
│ └── styles/index.css # 样式
├── vite.config.ts # 开发代理配置
├── package.json
└── README.md
```
---
## 4. 分层架构图
```mermaid
flowchart TB
subgraph UI层
App[App.tsx]
Compare[ComparePanel]
ReactUI[ReActChain]
PlainUI[PlainChain]
end
subgraph 业务层
Loop[react-loop.ts<br/>ReAct 循环]
Plain[dashscope.ts<br/>plainChat]
Thought[thought.ts<br/>思考补全]
Rounds[rounds.ts<br/>轮次分组]
end
subgraph 工具层
Tools[tools.ts<br/>executeTool]
end
subgraph 外部服务
DS[DashScope qwen-plus]
OM[Open-Meteo]
FK[Frankfurter]
Local[本地时间/计算]
end
App --> Compare
Compare --> ReactUI
Compare --> PlainUI
App --> Loop
App --> Plain
Loop --> Thought
Loop --> Rounds
Loop --> Tools
Loop --> DS
Plain --> DS
Tools --> OM
Tools --> FK
Tools --> Local
```
---
## 5. 核心模块职责
### 5.1 `dashscope.ts` — 大模型网关
- 封装 `chatCompletion()`:统一发请求、解析错误
- `plainChat()`:普通模式,**不传 tools**
- 开发环境请求走 `/api/dashscope/...`,由 Vite 代理到阿里云
### 5.2 `react-loop.ts` — ReAct 心脏
伪代码逻辑:
```
while 轮次 < 6:
回复 = 调用模型(messages, tools)
if 回复没有 tool_calls:
生成最终 Thought + Answer
结束
else:
生成 Thought(调用前)
记录 Action(工具名 + 参数)
执行工具 → 得到 Observation
把 tool 结果追加到 messages
继续下一轮
```
### 5.3 `thought.ts` — 为什么需要它?
**问题**:DashScope 在 Function Calling 时,经常**不返回**可见的思考文字(`content` 为空)。
**后果**:页面上只能看到 Action → Observation,学生看不到 Thought。
**解决**:`resolveThought()` 三层策略:
1. 优先用模型返回的 `reasoning_content` 或正文
2. 否则根据「问题 + 工具类型」**自动生成结构化思考文案**
3. 最终回答前再生成「综合 Observation 的思考」
这样课堂上**链条始终完整**。
### 5.4 `tools.ts` — 工具双职责
每个工具包含两部分:
| 部分 | 作用 | 谁使用 |
|------|------|--------|
| `TOOL_DEFINITIONS` | JSON Schema 描述 | 发给大模型,供其选择调用 |
| `executeTool()` | 真正执行逻辑 | 浏览器/脚本在收到 tool_calls 后执行 |
### 5.5 UI 组件分工
| 组件 | 职责 |
|------|------|
| `ReActChain` | 按「第 N 轮」展示 Thought → Action → Observation → Answer |
| `PlainChain` | 展示普通模式「跳过循环、直接输出」 |
| `ComparePanel` | 顶部 VS 摘要 + 左右对比 + 教学提示条 |
---
## 6. 关键设计决策记录
### 决策 1:对比模式默认开启
学生第一眼就能看到「无工具」和「ReAct」的差异,降低讲解成本。
### 决策 2:工具在浏览器端执行
- 模型只负责「决定调什么、传什么参数」
- 真实 HTTP 请求由 `executeTool()` 在浏览器发起
- 天气/汇率 API 支持 CORS,可直接访问
### 决策 3:最多 6 轮推理
防止模型反复调用工具导致死循环或费用失控(`MAX_LOOPS = 6`)。
### 决策 4:API Key 存 sessionStorage
- 方便课堂演示(不用每次输入)
- 关闭标签页即清除
- 不上传到任何自建服务器
### 决策 5:思考步骤可合成
教学演示优先「链条可见」,在模型沉默时由程序补全文案,并标注为「第 N 轮思考」。
---
## 7. 数据流(从用户点击到界面更新)
```mermaid
sequenceDiagram
participant U as 用户
participant App as App.tsx
participant Loop as react-loop
participant DS as DashScope
participant Tool as executeTool
participant UI as ReActChain
U->>App: 点击「运行演示」
App->>Loop: runReAct(key, question, onStep)
Loop->>UI: onStep(input)
Loop->>DS: chatCompletion + tools
DS-->>Loop: tool_calls
Loop->>UI: onStep(thought, action)
Loop->>Tool: executeTool(name, args)
Tool-->>Loop: observation 文本
Loop->>UI: onStep(observation)
Loop->>DS: messages + tool 结果
DS-->>Loop: 最终 content
Loop->>UI: onStep(thought, answer)
Loop-->>App: RunResult
```
---
## 8. 随堂检查
1. `TOOL_DEFINITIONS` 和 `executeTool` 分别做什么?
2. 为什么单独拆出 `thought.ts`?
3. Vite 代理解决了什么问题?
<details>
<summary>参考答案</summary>
1. 前者描述工具给模型看;后者真正执行工具逻辑。
2. 模型调用工具时常不输出思考文字,需要程序补全以保证教学可见性。
3. 浏览器直接请求 DashScope 会有 CORS 跨域限制,代理转发可绕过。
</details>
---
下一篇:[04-运行流程详解.md](./04-运行流程详解.md)