"""
=============================================================================
第 15 讲 · 01：自主的算术 —— 长程任务里错误怎样累积，检查、恢复和人的监督值多少
=============================================================================

对应讲义：lectures/lecture15_autonomous_agents.md 第 4 节

-----------------------------------------------------------------------------
【设定】
-----------------------------------------------------------------------------
一个任务有 n 步。agent 每一步做对的概率是 p（各步独立）。一步做错而没有被发现，整个任务就失败了。
几种做法：
    A. 一路做到底：不检查。成功率 = pⁿ（第 1 讲的错误累积）。
    B. 自我检查 + 重试：每一步做完自己检查一遍。做错时，检查以 r 的概率发现（召回率），发现就重做这一步；
       没发现就带着错误继续（任务失败）。检查也会误报（做对了却以为错了，概率 f），误报只浪费一次重做。
    C. 人定期审查：agent 不自我检查；人每 k 步审查一次（1 分钟固定开销 + 每步 0.2 分钟），以 h = 0.95 的概率
       发现这一段里的错误，发现就让 agent 重做这一段。
    D. B + C：agent 自我检查 + 重试，人再每 k 步审查一次，只需要抓住 agent 自己没发现的错误。
时间：agent 每步 0.2 分钟；如果人亲自做这个任务，每步 1 分钟，几乎不出错。

所有结果都是**解析计算**的（没有随机性），参数都是**设定**的。演示的是"可靠性怎样随任务长度变化"的算术，
不是任何真实 agent 的数值。

运行时间：不到 1 秒（只用标准库）。
"""

AGENT_MIN_PER_STEP = 0.2
HUMAN_MIN_PER_STEP = 1.0
REVIEW_FIXED_MIN, REVIEW_PER_STEP_MIN = 1.0, 0.2
REVIEW_RECALL = 0.95


def step_success_with_retry(p, r):
    """
    "检查 + 重试"时，一步最终做对的概率。

    【思路】做对（p）→ 成功；做错（1 − p）且被发现（r）→ 重做，重做时情况和第一次一样。设最终成功率为 s：
            s = p + (1 − p) · r · s   ⇒   s = p / (1 − (1 − p) r)
    【边界】r = 0 时 s = p（不检查）；r = 1 时 s = 1（每个错误都能发现并重做，只是多花时间）。
    """
    return p / (1 - (1 - p) * r)


def expected_tries_per_step(p, r, f):
    """
    "检查 + 重试"时，一步平均要做几次（包括误报带来的重做）。

    【思路】每做一次，"需要再做一次"的概率 = 做错且被发现 (1 − p) r + 做对但误报 p f。
        次数服从几何分布，期望 = 1 / (1 − 再做的概率)。（误报时那一次其实是对的，这里简化为重做。）
    """
    return 1 / (1 - ((1 - p) * r + p * f))


def policy_a(n, p):
    return p ** n, n * AGENT_MIN_PER_STEP, 0.0


def policy_b(n, p, r, f):
    s = step_success_with_retry(p, r)
    return s ** n, n * expected_tries_per_step(p, r, f) * AGENT_MIN_PER_STEP, 0.0


def policy_c(n, p, k):
    """
    人每 k 步审查一次。

    【思路】一段 k 步全对的概率 q = pᵏ。有错时人以 h 的概率发现，发现就重做这一段（再审查一次）；没发现 → 任务失败。
        一段最终通过的概率：s = q + (1 − q) h s ⇒ s = q / (1 − (1 − q) h)。
        每段平均做几次（每次都要审查）：1 / (1 − (1 − q) h)。
    【边界】n 不是 k 的整数倍时，最后一段较短，这里简化为 ⌈n / k⌉ 段、每段 k 步。
    """
    segments = -(-n // k)                      # 【Python 小知识】-(-n // k) 是向上取整的整数除法
    q = p ** k
    s = q / (1 - (1 - q) * REVIEW_RECALL)
    tries = 1 / (1 - (1 - q) * REVIEW_RECALL)
    agent = segments * tries * k * AGENT_MIN_PER_STEP
    human = segments * tries * (REVIEW_FIXED_MIN + REVIEW_PER_STEP_MIN * k)
    return s ** segments, agent, human


def policy_d(n, p, r, f, k):
    """
    自我检查 + 重试，再加上人每 k 步审查一次。

    【思路】和 policy_c 一样，只是每一步的成功率换成"检查 + 重试"之后的 s_b = p / (1 − (1 − p) r)，
        agent 每步的用时乘以平均重做次数。人只需要抓住 agent 自己没发现的那些错误。
    """
    s_b = step_success_with_retry(p, r)
    segments = -(-n // k)
    q = s_b ** k
    s = q / (1 - (1 - q) * REVIEW_RECALL)
    tries = 1 / (1 - (1 - q) * REVIEW_RECALL)
    agent = segments * tries * k * expected_tries_per_step(p, r, f) * AGENT_MIN_PER_STEP
    human = segments * tries * (REVIEW_FIXED_MIN + REVIEW_PER_STEP_MIN * k)
    return s ** segments, agent, human


def fmt_min(m):
    if m < 60:
        return f"{m:.0f} 分钟"
    return f"{m / 60:.1f} 小时"


def main():
    lengths = (10, 100, 1000)
    print("=" * 78)
    print("表 1：任务成功率 vs 任务长度（每步 p = 0.99）")
    print("=" * 78)
    print(f"{'':34}" + "".join(f"{'n = ' + str(n):>12}" for n in lengths))
    rows = [("A 一路做到底", lambda n: policy_a(n, 0.99)[0])]
    for r in (0.5, 0.8, 0.95):
        rows.append((f"B 自我检查（召回率 {r}）+ 重试", lambda n, r=r: policy_b(n, 0.99, r, 0.05)[0]))
    for name, fn in rows:
        width = sum(2 if ord(ch) > 127 else 1 for ch in name)
        print(name + " " * max(1, 34 - width) + "".join(f"{fn(n):12.3f}" for n in lengths))

    print("\n" + "=" * 78)
    print("表 2：提高每一步的成功率，还是提高发现错误的能力？（n = 100 和 n = 1000）")
    print("=" * 78)
    print(f"{'':40}{'n = 100':>12}{'n = 1000':>12}")
    for name, p, r in (("p = 0.99，不检查", 0.99, 0.0), ("p = 0.995，不检查（错误减半）", 0.995, 0.0),
                       ("p = 0.999，不检查（错误减为十分之一）", 0.999, 0.0), ("p = 0.99，召回率 0.9 的检查", 0.99, 0.9),
                       ("p = 0.99，召回率 0.99 的检查", 0.99, 0.99)):
        width = sum(2 if ord(ch) > 127 else 1 for ch in name)
        print(name + " " * max(1, 40 - width) + f"{policy_b(100, p, r, 0)[0]:12.3f}{policy_b(1000, p, r, 0)[0]:12.3f}")

    print("\n" + "=" * 78)
    print("表 3：人的监督（p = 0.99；agent 自我检查的召回率 0.8、误报率 0.05；人审查的召回率 0.95）")
    print("=" * 78)
    for n in (100, 1000):
        print(f"n = {n} 步（人亲自做：{fmt_min(n * HUMAN_MIN_PER_STEP)}）")
        print(f"  {'':30}{'成功率':>8}{'agent 用时':>12}{'人的时间':>12}")
        for name, (succ, agent, human) in (
                ("A 一路做到底", policy_a(n, 0.99)),
                ("B 自我检查 + 重试", policy_b(n, 0.99, 0.8, 0.05)),
                ("C 人每步都审查", policy_c(n, 0.99, 1)),
                ("C 人每 10 步审查一次", policy_c(n, 0.99, 10)),
                ("D 自我检查 + 人每 10 步审查", policy_d(n, 0.99, 0.8, 0.05, 10)),
                ("D 自我检查 + 人每 50 步审查", policy_d(n, 0.99, 0.8, 0.05, 50))):
            width = sum(2 if ord(ch) > 127 else 1 for ch in name)
            print("  " + name + " " * max(1, 30 - width) + f"{succ:8.3f}{fmt_min(agent):>12}{fmt_min(human):>12}")

    print("\n观察：")
    print("  · 错误累积是长程任务的头号敌人：每步 99% 的成功率，100 步只剩 37%，1000 步几乎为 0。")
    print("  · 检查 + 重试的效果可以精确地换算：召回率 r 的检查，相当于把每一步的错误率乘以 (1 − r)。")
    print("    \"召回率 0.9 的检查\"和\"每一步的错误减为十分之一\"效果相同（表 2）。对长任务，")
    print("    让 agent 学会**发现并纠正自己的错误**，往往比让它一次做对更现实。")
    print("  · 人的监督能大幅提高成功率，但人的时间不是免费的：每步都审查，人花的时间比自己做还多；")
    print("    审查得越稀疏，人越省时间，但每次审查要读的东西越多。")
    print("  · 最好的组合是 agent 先自我检查、人再稀疏地审查（D）：人只需要抓住 agent 自己没发现的少数错误。")
    print("    1000 步的任务，成功率 0.90，人花约 4 小时（自己做要 16.7 小时）。")
    print("    即使这样，人的时间仍然随任务长度线性增长 —— 怎样监督一个越来越能干、做得越来越长的 agent，")
    print("    是自主 agent 的核心开放问题之一（scalable oversight）。\n")


if __name__ == "__main__":
    main()
