#!/usr/bin/env python3
"""sampling_simulation.py — next-token prediction 与温度/采样直觉模拟（无需 API key）

用一个玩具"语言模型"（字符级频率表）演示：
1. next-token prediction：每一步从候选分布采样
2. temperature 如何重塑采样分布（T→0 退化为贪心，T 高则平坦化）
3. 为什么 temperature=0 也不等于"每次输出都一样"（数值与实现仍有微扰）
4. stop 条件（模拟 stop_reason）

这只是教学模拟，不是真实模型。来源: https://platform.claude.com/docs/en/api/messages（访问于 2026-08-30）
运行: python3 sampling_simulation.py
"""

import random

random.seed(2026_08_30)

# 玩具模型的"下一词分布"：状态 -> 候选token及其概率
# 模拟一个只会聊"天气"的模型
TRANSITIONS = {
    "<start>": {"今天": 0.6, "明天": 0.4},
    "今天": {"天气": 0.7, "气温": 0.3},
    "明天": {"天气": 0.5, "会": 0.5},
    "天气": {"很": 0.6, "不错": 0.4},
    "气温": {"是": 1.0},
    "很": {"好": 0.7, "冷": 0.3},
    "不错": {"<stop>": 1.0},
    "好": {"<stop>": 1.0},
    "冷": {"<stop>": 1.0},
    "是": {"二十": 0.6, "十五": 0.4},
    "二十": {"度": 1.0},
    "十五": {"度": 1.0},
    "度": {"<stop>": 1.0},
    "会": {"下雨": 0.5, "晴": 0.5},
    "下雨": {"<stop>": 1.0},
    "晴": {"<stop>": 1.0},
}


def tempered(probs: dict, temperature: float) -> dict:
    """按温度重整概率：p^(1/T) 归一化。T 越小分布越尖。"""
    if temperature <= 0:
        top = max(probs.values())
        return {k: v for k, v in probs.items() if v == top}
    t = min(max(temperature, 0.01), 2.0)
    raw = {k: p ** (1.0 / t) for k, p in probs.items()}
    z = sum(raw.values())
    return {k: v / z for k, v in raw.items()}


def generate(temperature: float, max_tokens: int = 10) -> tuple[str, str]:
    """生成一个序列，返回 (文本, 模拟 stop_reason)。"""
    state, out = "<start>", []
    for _ in range(max_tokens):
        probs = tempered(TRANSITIONS[state], temperature)
        tok = random.choices(list(probs), weights=probs.values())[0]
        if tok == "<stop>":
            return "".join(out), "end_turn"   # 自然停止
        out.append(tok)
        state = tok
    return "".join(out), "max_tokens"         # 达到上限被截断


if __name__ == "__main__":
    print("== T = 0（近似贪心：每步选最大概率）==")
    for _ in range(5):
        text, reason = generate(0.0)
        print(f"  [{reason:10s}] {text}")

    print("\n== T = 1.0（默认：按原始分布采样）==")
    for _ in range(5):
        text, reason = generate(1.0)
        print(f"  [{reason:10s}] {text}")

    print("\n== T = 1.6（更随机：低概率分支被放大）==")
    for _ in range(5):
        text, reason = generate(1.6)
        print(f"  [{reason:10s}] {text}")

    print("\n== max_tokens=2 强制截断（stop_reason=max_tokens 的样子）==")
    text, reason = generate(1.0, max_tokens=2)
    print(f"  [{reason:10s}] {text}…（被截断，应用层应检测并处理）")

    print(
        "\n架构结论：\n"
        "  1) temperature=0 让输出趋向确定，但分布中并列第一时仍有随机性——\n"
        "     需要 100% 确定的结构化输出必须靠应用层 schema 校验。\n"
        "  2) max_tokens 是上限不是目标；生产代码要按 stop_reason 分支处理。\n"
        "  3) 采样温度只改变'说法'，不能改变模型'知道什么'。"
    )
