"""
Few-shot CoT 教学演示入口。
运行前:
pip install -r requirements.txt
配置系统环境变量 DASHSCOPE_API_KEY
运行:
python main.py # 默认对比三种模式(各 1 次 API 调用)
python main.py --shots 1 # Few-shot 只用 1 个示例(观察示例数量的影响)
python main.py --shots 2 # Few-shot 用 2 个示例(默认)
"""
from __future__ import annotations
import argparse
import re
import sys
from config import API_KEY
from demo import run_direct, run_few_shot_cot, run_zero_shot_cot
from display import print_banner, print_intro, print_kv, print_model_reply, print_section
from examples import FEW_SHOT_EXAMPLES
from llm_client import get_call_count
from question import MAIN_QUESTION
def _configure_stdout() -> None:
if hasattr(sys.stdout, "reconfigure"):
try:
sys.stdout.reconfigure(encoding="utf-8")
except Exception:
pass
def check_env() -> None:
if not API_KEY:
raise SystemExit(
"未读取到系统环境变量 DASHSCOPE_API_KEY。\n"
"请在 Windows「系统属性 → 环境变量」中配置后,重新打开终端再运行。"
)
def extract_final_answer(text: str) -> str:
match = re.search(r"最终答案[::]\s*(.+)", text)
if match:
return match.group(1).strip()
lines = [line.strip() for line in text.splitlines() if line.strip()]
return lines[-1] if lines else "(未识别到最终答案)"
def has_reasoning_steps(text: str) -> bool:
markers = ("第一步", "第二步", "第三步", "年龄差", "最终答案")
return sum(marker in text for marker in markers) >= 2
def is_answer_correct(final_answer: str, expected: str) -> bool:
numbers = re.findall(r"\d+", expected)
return all(num in final_answer for num in numbers)
def print_few_shot_preview(shot_count: int) -> None:
print_section(f"本次 Few-shot CoT 将使用 {shot_count} 个示例")
for example in FEW_SHOT_EXAMPLES[:shot_count]:
print(f"\n · {example.title}")
print_kv(" 教学点", example.teaching_point)
def run_demo(shot_count: int) -> None:
question = MAIN_QUESTION
few_shot_examples = FEW_SHOT_EXAMPLES[:shot_count]
print_banner(question.title)
print_kv("题目", question.question)
print_kv("参考答案", question.expected_answer)
print_kv("常见误区", question.common_mistake)
print_kv("标准推理", question.correct_reasoning)
print_few_shot_preview(shot_count)
modes = [
("模式 A · 无 CoT(直接要答案)", lambda: run_direct(question)),
(
"模式 B · 零样本 CoT(只说「请一步一步思考」,不给示例)",
lambda: run_zero_shot_cot(question),
),
(
f"模式 C · 少样本 CoT(先给 {shot_count} 个推理示例,再答本题)",
lambda: run_few_shot_cot(question, few_shot_examples),
),
]
results: list[tuple[str, str, bool, bool]] = []
for step, (mode_name, runner) in enumerate(modes, start=1):
print_section(mode_name)
print(
f" [步骤 {step}/3] 正在调用百炼 API...\n"
f" 流程:prompts.py / examples.py → demo.py 拼 messages → llm_client.py"
)
reply = runner()
final_answer = extract_final_answer(reply)
correct = is_answer_correct(final_answer, question.expected_answer)
stepped = has_reasoning_steps(reply)
print_model_reply(reply)
print(f"\n 提取到的最终答案:{final_answer}")
print(f" 答案是否正确:{'✓ 正确' if correct else '✗ 错误'}")
print(f" 是否含分步推理:{'是' if stepped else '否'}")
results.append((mode_name, final_answer, correct, stepped))
print_section("三种模式对比小结")
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(
"\n 课堂观察要点:\n"
" · 模式 A 可能直接给出 105、175 等错误数字(把「一半」误用到现在);\n"
" · 模式 B 常会展开推理,但步骤是否抓住「年龄差不变」不一定稳定;\n"
" · 模式 C 会模仿示例格式,更容易写出「先算年龄差 → 再算现在年龄」。\n"
" · 可尝试 --shots 1 与 --shots 2,观察示例数量对效果的影响。"
)
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Few-shot CoT 教学演示")
parser.add_argument(
"--shots",
type=int,
choices=range(1, len(FEW_SHOT_EXAMPLES) + 1),
default=len(FEW_SHOT_EXAMPLES),
metavar="N",
help=f"Few-shot 示例数量(1-{len(FEW_SHOT_EXAMPLES)}),默认 {len(FEW_SHOT_EXAMPLES)}",
)
return parser.parse_args()
def main() -> None:
_configure_stdout()
check_env()
args = parse_args()
print_intro()
run_demo(args.shots)
print_banner("演示结束")
print(f"本次共发起 {get_call_count()} 次百炼 API 调用。")
print("题目位置:question.py(主题目)、examples.py(Few-shot 示例题)")
print("完整说明:docs/项目说明.md")
print("讲课话术:docs/课堂讲解指南.md")
print("对比数学陷阱题:../cot_demo/")
if __name__ == "__main__":
main()