💻 源码
"""
CoT 思维链教学演示(终端版)

运行前准备:
  1. pip install -r requirements.txt
  2. 系统环境变量中已配置 DASHSCOPE_API_KEY(程序通过 os.getenv 读取)

运行方式:
  python main.py              # 默认演示第 1 道题
  python main.py --all        # 演示全部 3 道题
  python main.py --only 2     # 只演示第 2 道题
  python web_app.py           # 启动网页版(推荐课堂演示)
  或直接双击 run.bat / run_web.bat
"""

from __future__ import annotations

import argparse
import sys

from analysis import extract_final_answer, has_reasoning_steps, is_answer_correct
from display import (
    print_banner,
    print_intro,
    print_kv,
    print_messages,
    print_model_reply,
    print_section,
)
from llm_client import get_call_count
from questions import DEMO_QUESTIONS
from service import MODES, check_api_key, question_to_dict


def _configure_stdout() -> None:
    if hasattr(sys.stdout, "reconfigure"):
        try:
            sys.stdout.reconfigure(encoding="utf-8")
        except Exception:
            pass


def run_one_question(index: int) -> None:
    q = question_to_dict(index)

    print_banner(f"题目 {index}:{q['title']}")
    print_kv("题目", q["question"])
    print_kv("参考答案", q["expected_answer"])
    print_kv("为什么适合演示 CoT", q["why_hard"])

    question = DEMO_QUESTIONS[index - 1]
    results: list[tuple[str, object, str, bool, bool]] = []

    for step, mode in enumerate(MODES, start=1):
        mode_name = f"{mode.title} · {mode.subtitle}({mode.badge})"
        print_section(mode_name)
        result = mode.runner(question)
        print_messages(result.messages, result.prompt_summary)
        print(f"\n  [步骤 {step}/3] 正在调用百炼 API...")
        print("  流程:上方提示词 → llm_client.py → 百炼 qwen-plus")
        print_model_reply(result.reply)
        final_answer = extract_final_answer(result.reply)
        correct = is_answer_correct(final_answer, question.expected_answer)
        stepped = has_reasoning_steps(result.reply)
        print(f"\n  提取到的最终答案:{final_answer}")
        print(f"  答案是否正确:{'正确' if correct else '错误'}")
        print(f"  是否含分步推理:{'是' if stepped else '否'}")
        results.append((mode_name, result, final_answer, correct, stepped))

    print_section("本题对比小结")
    print("  ┌─ 提示词差异 ─────────────────────────────────────────")
    for mode_name, result, _, _, _ in results:
        print(f"  │ {mode_name}")
        print(f"  │   消息条数:{len(result.messages)} 条  |  {result.prompt_summary}")
    print("  └──────────────────────────────────────────────────────")
    print()
    print("  ┌─ 结果对比 ─────────────────────────────────────────")
    for mode_name, _, final_answer, correct, stepped in results:
        print(f"  │ {mode_name}")
        print(f"  │   最终答案:{final_answer}({'正确' if correct else '错误'})")
        print(f"  │   分步推理:{'有' if stepped else '无'}")
    print("  └──────────────────────────────────────────────────────")

    print(
        "\n  课堂观察要点:\n"
        "  · 对比上方【发送给模型的提示词】:A 最短,B 多了分步要求,C 多了示例推理链;\n"
        "  · 无 CoT 往往只有一行答案,复杂题容易跳步;\n"
        "  · 零样本 CoT 会展开推理链,过程可审查;\n"
        "  · 少样本 CoT 会模仿示例格式,输出更稳定。"
    )


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(description="CoT 思维链 DashScope 教学演示")
    group = parser.add_mutually_exclusive_group()
    group.add_argument("--all", action="store_true", help="演示全部 3 道题")
    group.add_argument(
        "--only",
        type=int,
        choices=range(1, len(DEMO_QUESTIONS) + 1),
        metavar="N",
        help=f"只运行第 N 道题(1-{len(DEMO_QUESTIONS)}),默认 N=1",
    )
    return parser.parse_args()


def main() -> None:
    _configure_stdout()
    try:
        check_api_key()
    except RuntimeError as exc:
        raise SystemExit(str(exc)) from exc

    args = parse_args()
    print_intro()

    if args.all:
        for idx in range(1, len(DEMO_QUESTIONS) + 1):
            run_one_question(idx)
    else:
        run_one_question(args.only or 1)

    print_banner("演示结束")
    print(f"本次共发起 {get_call_count()} 次百炼 API 真实调用(每次调用均消耗 Token)。")
    print("网页版演示:python web_app.py  或双击 run_web.bat")
    print("详细项目说明见:docs/项目说明.md")


if __name__ == "__main__":
    main()