💻 源码
# 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** 调用百炼。