#!/usr/bin/env python3
"""03_orchestrator_workers_skeleton.py — 编排器-工人模式骨架（无需 API key）

实现官方 orchestrator-workers 工作流的可运行骨架：
  中央 LLM（编排器）动态分解任务 -> 委托给 worker LLM（各自独立上下文）-> 综合结果。
两个关键工程点同时演示：
  1. 上下文隔离 (context isolation)：每个 worker 的中间过程留在自己的上下文里，
     只有最终摘要回传编排器（对齐 Agent SDK subagents 的四大收益之一）；
  2. 并行执行：workers 用线程池并发（耗时≈最慢者，而非各步之和）；
  3. 内嵌一个 mini evaluator-optimizer 精修环（评估不达标则带反馈重写一轮）。

所有 LLM 调用点用 `# <-- LLM call` 标注：换成真实 Claude 时，只替换这些函数体
（os.getenv("ANTHROPIC_API_KEY") 取 key），编排结构不变。

官方依据（访问于 2026-08-30）:
  https://www.anthropic.com/engineering/building-effective-agents        (orchestrator-workers)
  https://www.anthropic.com/engineering/multi-agent-research-system      (orchestrator-worker 实战)
  https://code.claude.com/docs/en/agent-sdk/subagents                    (context isolation 等)

运行: python3 03_orchestrator_workers_skeleton.py
"""

from __future__ import annotations

import time
from concurrent.futures import ThreadPoolExecutor
from dataclasses import dataclass, field

# 若接入真实 Claude，key 从环境变量读取（本演示不需要任何网络与 key）：
# key 的读取统一用 os.getenv（写法见 main() 末尾提示行，本演示不联网不读 key）


# ---------- 数据结构 ----------

@dataclass
class Subtask:
    """编排器产出的委托单。官方要求：目标、输出格式、工具/信源指引、清晰边界。"""

    title: str
    objective: str
    output_format: str
    sources: list[str]


@dataclass
class WorkerReport:
    title: str
    findings: str
    tokens_burned: int = 0          # 每个 worker 独立上下文的近似开销
    transcript: list[str] = field(default_factory=list)  # 中间过程——不回传编排器


# ---------- 模拟 LLM 调用点（替换为真实 Claude 即成生产骨架） ----------

def orchestrator_plan(task: str) -> list[Subtask]:
    """# <-- LLM call: 真实系统中让 Claude 按任务动态分解子任务（不可预定义）"""
    if "研究报告" in task:
        return [
            Subtask("市场格局", "梳理主要玩家与份额", "3 条要点",
                    ["官网", "新闻稿"]),
            Subtask("技术路线", "总结技术差异", "3 条要点", ["论文", "文档"]),
            Subtask("风险信号", "找监管/供应链风险", "2 条要点", ["监管网站"]),
        ]
    return [Subtask("通用调研", task, "要点列表", ["web"])]


def worker_execute(sub: Subtask) -> WorkerReport:
    """# <-- LLM call: 每个 worker 是独立 agent，在自己的上下文里多轮搜索-压缩"""
    report = WorkerReport(sub.title, findings="")
    # 模拟 worker 内部多轮（中间 transcript 留在本地上下文，不回传）
    for step in ["搜索", "筛选", "压缩"]:
        report.transcript.append(f"{step}: {sub.sources}")
        report.tokens_burned += 400
    report.findings = f"[{sub.title}] 按 {sub.output_format}: ...（{len(sub.sources)} 个信源蒸馏）"
    return report


def orchestrator_synthesize(task: str, reports: list[WorkerReport]) -> str:
    """# <-- LLM call: 综合各 worker 摘要（注意：拿到的是摘要，不是 transcript）"""
    parts = " / ".join(r.findings for r in reports)
    return f"《{task}》综合结论：{parts}"


def evaluate(draft: str) -> tuple[bool, str]:
    """# <-- LLM call: evaluator——对照清晰评估标准打分并给反馈"""
    covered = all(k in draft for k in ("市场格局", "技术路线", "风险信号"))
    return covered, "" if covered else "缺少维度：风险信号未覆盖"


def optimize(draft: str, feedback: str) -> str:
    """# <-- LLM call: optimizer——按反馈重写"""
    return draft.replace("综合结论", "补全后综合结论") + "（已补：风险信号 2 条）"


# ---------- 编排骨架 ----------

def run_orchestrator_workers(task: str, max_workers: int = 3,
                             refine_rounds: int = 2) -> str:
    print(f"任务: {task}")
    t0 = time.perf_counter()

    # 1) 编排器动态分解（关键：子任务集合由输入决定，不是预定义分支）
    subtasks = orchestrator_plan(task)
    print(f"\n[orchestrator] 分解为 {len(subtasks)} 个子任务: "
          f"{[s.title for s in subtasks]}")

    # 2) workers 并行执行，各自独立上下文（context isolation）
    with ThreadPoolExecutor(max_workers=max_workers) as pool:
        reports = list(pool.map(worker_execute, subtasks))
    total = sum(r.tokens_burned for r in reports)
    print(f"[workers] 并行完成，耗时≈最慢者；各 worker 中间过程均未回传编排器"
          f"（隔离的 transcript 行数: {[len(r.transcript) for r in reports]}，"
          f"合计 token ~{total}）")

    # 3) 编排器综合（只看摘要——这就是多智能体对搜索的『压缩』价值）
    draft = orchestrator_synthesize(task, reports)
    print(f"[orchestrator] 初稿: {draft[:80]}...")

    # 4) mini evaluator-optimizer 精修环（带轮数上限——又是停止条件）
    for round_no in range(1, refine_rounds + 1):
        passed, feedback = evaluate(draft)
        print(f"[evaluator] 第 {round_no} 轮评估: {'PASS' if passed else 'FAIL - ' + feedback}")
        if passed:
            break
        draft = optimize(draft, feedback)
        print(f"[optimizer] 已按反馈重写")

    dt = (time.perf_counter() - t0) * 1000
    print(f"[done] {dt:.1f}ms（模拟），最终输出:\n  {draft}\n")
    return draft


def main() -> None:
    # 场景 1：三路并行研究（评估一次通过路径 + 全维度覆盖）
    final = run_orchestrator_workers("AI 编码助手行业研究报告")
    assert "市场格局" in final and "风险信号" in final

    # 场景 2：evaluator 抓到缺维度 -> optimizer 补写 -> 复评通过
    partial_reports = [
        WorkerReport("市场格局", "[市场格局] ..."),
        WorkerReport("技术路线", "[技术路线] ..."),
    ]
    draft = orchestrator_synthesize("测试", partial_reports)
    passed, feedback = evaluate(draft)
    assert not passed and "风险信号" in feedback
    draft2 = optimize(draft, feedback)
    passed2, _ = evaluate(draft2)
    assert passed2, "optimizer 一轮修复后应通过评估"
    print("evaluator-optimizer 精修环验证通过：FAIL(缺维度) -> 重写 -> PASS")

    print("提示：四个 `# <-- LLM call` 标注点替换为真实 Claude 调用（key 统一用 "
          "os.getenv(\"ANTHROPIC_API_KEY\") 读取），编排结构（分解/并行/隔离/综合/精修）保持不变。")


if __name__ == "__main__":
    main()
