# CoT 思维链演示项目 — 学生阅读指南

## 一、这个项目是做什么的？

这是一个**真实调用阿里云百炼大模型**的教学演示项目，不是写死的假答案，也不是本地模拟。

程序会对**同一道题**分别用三种提示词策略去问 `qwen-plus`，让你亲眼看到：

| 对比维度 | 无 CoT | 有 CoT |
|----------|--------|--------|
| 输出形式 | 往往只有一行答案 | 有「第一步 / 第二步…」推理链 |
| 复杂题准确率 | 容易跳步、答错 | 分步计算，更不容易错 |
| 过程是否可检查 | 看不到推理过程 | 每一步都能审查、纠错 |

**课堂推荐先看第 1 题**（`9.9 和 9.10 谁大？`），无 CoT 容易答错，有 CoT 容易答对，对比最明显。

---

## 二、是不是真实运行？

**是的，100% 真实运行。**

每次你看到的「模型回复」，都是程序通过网络请求阿里云百炼接口得到的实时结果：

```
你的电脑  →  dashscope SDK  →  百炼云端 qwen-plus  →  返回真实生成文本
```

证据：

1. 需要有效的 `DASHSCOPE_API_KEY`，否则会报错，无法运行
2. 每次调用都会消耗百炼账号的 Token（可在百炼控制台查看用量）
3. 同一道题多次运行，措辞可能略有不同（但 `temperature=0` 已尽量保持稳定）
4. 断网或 Key 无效时，程序会抛出 API 错误，不会假装成功

本项目**没有**以下东西：

- 没有写死的「假回复」
- 没有 `if question == xxx: return "4"` 这类硬编码
- 没有用本地规则引擎代替大模型

---

## 三、项目是怎么构建的？

### 3.1 目录结构

```
cot_demo/
├── main.py           # 【入口】解析命令行、组织演示流程、打印对比小结
├── demo.py           # 【核心】三种 CoT 模式的 messages 组装与调用
├── prompts.py        # 【提示词】无CoT / 零样本CoT / 少样本CoT 的 system 和 user 模板
├── questions.py      # 【题库】3 道经典演示题及参考答案
├── llm_client.py     # 【API层】封装 dashscope.Generation.call，真正发网络请求
├── config.py         # 【配置】模型名、温度、种子、API Key 读取
├── display.py        # 【展示】终端输出格式化、项目说明打印
├── run.bat           # 双击运行（默认第 1 题）
├── run_all.bat       # 双击运行全部 3 题
├── requirements.txt  # 依赖：dashscope
└── docs/
    └── 项目说明.md   # 本文件
```

### 3.2 模块分工（谁负责什么）

| 文件 | 职责 | 学生应关注什么 |
|------|------|----------------|
| `config.py` | 读取系统环境变量 `DASHSCOPE_API_KEY`，设置模型参数 | API Key 从哪来、为什么用 `qwen-plus` |
| `llm_client.py` | 调用 `dashscope.Generation.call()` 发请求 | **真正联网的地方**，所有模式最终都走这里 |
| `prompts.py` | 存放三种模式的提示词文本 | **改提示词就在这里**，对比实验的核心 |
| `questions.py` | 存放演示题目 | 可换成自己的题做实验 |
| `demo.py` | 把 prompts + question 拼成 messages 列表 | 理解 system/user/assistant 怎么组 |
| `main.py` | 控制运行顺序、提取答案、判断对错 | 理解一整道题的演示流程 |
| `display.py` | 美化终端输出 | 可忽略，不影响核心逻辑 |

### 3.3 设计原则

1. **提示词与代码分离**：改 CoT 效果主要改 `prompts.py`，不用动 API 调用逻辑
2. **一种 API 封装**：三种模式共用 `llm_client.call_llm()`，保证对比公平（同一模型、同一参数）
3. **参数稳定**：`temperature=0`、`seed=42`，课堂演示尽量可复现

---

## 四、程序运行流程（一步一步）

以 `python main.py`（默认第 1 题）为例，完整执行顺序如下：

```
第 0 步：启动 main.py
    ↓
第 1 步：check_env()
    读取 os.getenv("DASHSCOPE_API_KEY")
    确认系统环境变量里已配置百炼密钥
    ↓
第 2 步：print_intro()
    打印 CoT 概念说明、三种模式介绍、项目是否真实运行
    ↓
第 3 步：run_one_question(1)
    从 questions.py 取出第 1 道题
    ↓
第 4 步：依次执行三种模式（每种模式 = 1 次真实 API 调用）
    │
    ├─ 模式 A：demo.run_direct()
    │     prompts.py 取 SYSTEM_DIRECT + 题目
    │     组装 messages → **先打印【发送给模型的提示词】**
    │     → llm_client.call_llm() → 百炼 API
    │     打印【模型回复】→ 提取最终答案 → 判断对错/是否有分步
    │
    ├─ 模式 B：demo.run_zero_shot_cot()
    │     prompts.py 取 SYSTEM_ZERO_SHOT_COT + 题目 +「请一步一步思考」
    │     组装 messages → llm_client.call_llm() → 百炼 API
    │     打印回复 → 提取最终答案 → 判断对错/是否有分步
    │
    └─ 模式 C：demo.run_few_shot_cot()
          prompts.py 取 SYSTEM_FEW_SHOT_COT
          + 示例 user（罗杰网球题）
          + 示例 assistant（带推理链的标准答案）
          + 真正要答的新题目
          组装 messages → llm_client.call_llm() → 百炼 API
          打印回复 → 提取最终答案 → 判断对错/是否有分步
    ↓
第 5 步：打印「本题对比小结」
    汇总 A/B/C 的提示词结构差异 + 最终答案、正确性、是否有分步推理
    ↓
第 6 步：演示结束
```

**跑 1 道题 = 3 次 API 调用；跑全部 3 题 = 9 次 API 调用。**

---

## 五、三种模式分别怎么工作？

### 模式 A：无 CoT（直接要答案）

**messages 结构：**

```python
[
  {"role": "system", "content": "只输出一行最终答案，禁止写推理过程..."},
  {"role": "user",   "content": "9.9 和 9.10 谁大？"},
]
```

**作用：** 模拟「只要结果、不要过程」的提问方式。复杂题容易跳步出错。

---

### 模式 B：零样本 CoT（Zero-shot CoT）

**messages 结构：**

```python
[
  {"role": "system", "content": "必须先写推理过程，按第一步/第二步组织..."},
  {"role": "user",   "content": "9.9 和 9.10 谁大？\n\n请一步一步思考..."},
]
```

**作用：** 不给示例，只靠提示词要求模型自己展开推理链。对应课程里的「给模型思考时间」。

---

### 模式 C：少样本 CoT（Few-shot CoT）

**messages 结构：**

```python
[
  {"role": "system",    "content": "请模仿示例的推理格式..."},
  {"role": "user",      "content": "罗杰有 5 个网球..."},      # 示例题
  {"role": "assistant", "content": "第一步：...\n最终答案：11"}, # 示例推理链
  {"role": "user",      "content": "9.9 和 9.10 谁大？"},       # 真正要答的题
]
```

**作用：** 用「示例问题 + 示例推理」教会模型输出格式，再让它答新题。对应课程的「文本参考 / Few-shot」。

---

## 六、API 调用层详解（llm_client.py）

这是**唯一真正联网**的函数：

```python
response = dashscope.Generation.call(
    api_key=API_KEY,           # 从 os.getenv("DASHSCOPE_API_KEY") 读取
    model="qwen-plus",         # 百炼上的通义千问 Plus
    messages=messages,         # 上面三种模式拼好的对话列表
    temperature=0,             # 低随机性，结果更稳定
    seed=42,                   # 固定种子，尽量可复现
    result_format="message",   # 返回标准 message 格式
)
return response.output.choices[0].message.content
```

如果 `status_code != 200`，程序会报错退出，不会返回假数据。

---

## 七、怎么运行？

### 前置条件

1. 已安装 Python 3.8+
2. 已执行 `pip install -r requirements.txt`
3. Windows 系统环境变量中已配置 `DASHSCOPE_API_KEY`（无需每次在终端手动设置）

### 网页版（推荐课堂演示）

```powershell
python web_app.py
# 或双击 run_web.bat
```

浏览器打开：**http://127.0.0.1:8765**

页面会实时展示：
1. 运行环境（模型、API Key 状态、是否真实调用）
2. 运行过程三步进度（模式 A → B → C）
3. 三列卡片：每列显示完整提示词 + 模型回复
4. 底部对比小结表（提示词差异 + 结果差异）

技术说明：前端通过 **SSE（Server-Sent Events）** 订阅 `/api/stream/<题号>`，
后端每完成一种模式就推送一次事件，学生能清楚看到「正在调用 API → 返回结果」的全过程。

### 终端版

```powershell
python main.py              # 默认第 1 题
python main.py --all        # 全部 3 题
python main.py --only 2     # 指定第 2 题
```

或直接双击 `run.bat` / `run_all.bat`。

---

## 八、建议的学习顺序

1. **先运行**：`python main.py`，看终端输出的 A/B/C 对比
2. **再读** `prompts.py`：对比三种提示词差在哪
3. **然后读** `demo.py`：看 messages 是怎么拼的
4. **最后读** `llm_client.py`：确认是真实 API 调用
5. **动手改**：试着改 `SYSTEM_ZERO_SHOT_COT` 里的措辞，重新运行看效果变化

---

## 九、常见问题

**Q：为什么模式 A 有时也写了很多字？**  
A：早期版本提示词不够严格，模型会「偷偷推理」。当前版已要求「只输出一行」，模式 A 应只有一行答案。

**Q：为什么有时无 CoT 也答对了？**  
A：简单题（如第 2、3 题）模型直接心算也能对。CoT 的价值在复杂题（如第 1 题）更明显。

**Q：运行报错「未读取到 DASHSCOPE_API_KEY」？**  
A：检查 Windows 系统环境变量是否配置，配置后需**重新打开终端**。

**Q：和课程里的 OpenAI 库写法一样吗？**  
A：思路一样（都是 `messages` 列表 + `system/user/assistant`），只是本项目按课堂要求改用 **DashScope 官方 SDK** 调用百炼。
