"""
演示业务层:组织题目、三种模式运行与结果序列化。
终端 main.py 与 Web web_app.py 共用此模块,避免逻辑重复。
"""
from __future__ import annotations
from dataclasses import dataclass
from typing import Any, Callable, Iterator
from analysis import extract_final_answer, has_reasoning_steps, is_answer_correct
from config import API_KEY, MODEL_NAME, SEED, TEMPERATURE
from demo import ModeResult, run_direct, run_few_shot_cot, run_zero_shot_cot
from questions import DEMO_QUESTIONS, DemoQuestion
ROLE_LABELS = {
"system": "system(系统角色)",
"user": "user(用户角色)",
"assistant": "assistant(示例回答)",
}
@dataclass(frozen=True)
class ModeSpec:
id: str
title: str
subtitle: str
badge: str
runner: Callable[[DemoQuestion], ModeResult]
MODES: list[ModeSpec] = [
ModeSpec("direct", "模式 A", "无 CoT", "直接要答案", run_direct),
ModeSpec("zero_shot", "模式 B", "零样本 CoT", "分步思考·无示例", run_zero_shot_cot),
ModeSpec("few_shot", "模式 C", "少样本 CoT", "示例推理链", run_few_shot_cot),
]
def check_api_key() -> None:
if not API_KEY:
raise RuntimeError(
"未读取到系统环境变量 DASHSCOPE_API_KEY,"
"请在 Windows 系统环境变量中配置后重新打开终端。"
)
def question_to_dict(index: int) -> dict[str, Any]:
q = DEMO_QUESTIONS[index - 1]
return {
"index": index,
"title": q.title,
"question": q.question,
"expected_answer": q.expected_answer,
"why_hard": q.why_hard,
}
def list_questions() -> list[dict[str, Any]]:
return [question_to_dict(i) for i in range(1, len(DEMO_QUESTIONS) + 1)]
def mode_result_to_dict(mode: ModeSpec, result: ModeResult, question: DemoQuestion) -> dict[str, Any]:
final_answer = extract_final_answer(result.reply)
correct = is_answer_correct(final_answer, question.expected_answer)
stepped = has_reasoning_steps(result.reply)
messages = [
{
"index": i,
"role": msg["role"],
"role_label": ROLE_LABELS.get(msg["role"], msg["role"]),
"content": msg["content"],
}
for i, msg in enumerate(result.messages, start=1)
]
return {
"id": mode.id,
"title": mode.title,
"subtitle": mode.subtitle,
"badge": mode.badge,
"prompt_summary": result.prompt_summary,
"message_count": len(result.messages),
"messages": messages,
"reply": result.reply,
"final_answer": final_answer,
"correct": correct,
"has_steps": stepped,
}
def get_runtime_info() -> dict[str, Any]:
masked = "未配置"
if API_KEY:
masked = f"{API_KEY[:4]}...{API_KEY[-4:]}" if len(API_KEY) > 8 else "已配置"
return {
"model": MODEL_NAME,
"temperature": TEMPERATURE,
"seed": SEED,
"api_key_status": masked,
"api_configured": bool(API_KEY),
"call_path": "dashscope.Generation.call() → 阿里云百炼(真实 API,消耗 Token)",
}
def run_question_events(question_index: int) -> Iterator[dict[str, Any]]:
"""按步骤产出事件,供 Web SSE 流式展示运行过程。"""
check_api_key()
question = DEMO_QUESTIONS[question_index - 1]
yield {
"type": "init",
"runtime": get_runtime_info(),
"question": question_to_dict(question_index),
"mode_count": len(MODES),
}
results: list[dict[str, Any]] = []
for step, mode in enumerate(MODES, start=1):
yield {
"type": "mode_start",
"step": step,
"total": len(MODES),
"mode_id": mode.id,
"title": mode.title,
"subtitle": mode.subtitle,
}
result = mode.runner(question)
payload = mode_result_to_dict(mode, result, question)
results.append(payload)
yield {
"type": "mode_done",
"step": step,
"total": len(MODES),
"result": payload,
}
yield {
"type": "summary",
"prompt_diff": [
{
"title": r["title"],
"message_count": r["message_count"],
"prompt_summary": r["prompt_summary"],
}
for r in results
],
"result_diff": [
{
"title": r["title"],
"final_answer": r["final_answer"],
"correct": r["correct"],
"has_steps": r["has_steps"],
}
for r in results
],
"results": results,
}
yield {"type": "done", "api_calls": len(MODES)}
def run_question_sync(question_index: int) -> dict[str, Any]:
"""一次性返回完整结果(非流式备用接口)。"""
final: dict[str, Any] = {}
for event in run_question_events(question_index):
if event["type"] == "init":
final["runtime"] = event["runtime"]
final["question"] = event["question"]
elif event["type"] == "summary":
final["summary"] = event
final["results"] = event["results"]
elif event["type"] == "done":
final["api_calls"] = event["api_calls"]
return final