"""2.5 experiment: Skills vs Perspectives — does loading substantive domain Perspectives
(distilled from idea memos I01-I10) shift the model from mainstream consensus platitudes
to sharp, non-consensus judgments?

Both conditions receive the EXACT SAME formatting Skill (`skills/整理评估笔记.md`).
- skill_only        : task + formatting Skill (no perspectives.md)
- with_perspectives : task + formatting Skill + perspectives.md (distilled from I01-I10)

A cross-family judge model evaluates each output on two binary questions:
- endorsed_mainstream : did the note endorse the proposal's mainstream direction (or hedge with 'good direction, just refine details') instead of rejecting its core premise? (true/false)
- hit_perspective     : did the note make the specific non-consensus diagnosis from the author's perspective? (true/false)
"""
import json, pathlib, re, sys, time
from concurrent.futures import ThreadPoolExecutor

ROOT = pathlib.Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
from common import chat, dump

HERE = pathlib.Path(__file__).resolve().parent

SKILL = """# Skill：整理方案评估笔记
- 适用场景：当收到团队成员或合作方的技术提议、工作计划或选型想法时，整理成一条结构化的评估笔记。
- 成功标准与边界：
  1. 结论先行：第一句直接亮明态度（明确支持、有条件同意、或明确反对）。
  2. 核心理由：用 2–3 句话讲清楚判断依据，不写空洞套话。
  3. 下一步建议：给出 1–2 条具体可执行的行动建议。
  4. 篇幅控制在 200 字以内。"""

PERSPECTIVES = """# 个人视角与判断原则（perspectives.md，提炼自想法录音 I01–I10）
1. 学 AI 拉开差距的是“动词”（怎么用它解决具体问题），不是追逐三个月就过时的“名词”（新框架、新概念术语）。
2. Prompt Engineering（反复抠提示词措辞与话术技巧）基本已经过时；模型足够聪明时，效果不好是因为缺了对的上下文，把时间花在调措辞上是浪费。
3. 多 Agent 模仿人类工种做角色扮演（如一个当产品经理、一个当工程师分工传话）没有意义；多 Agent 唯一真正的价值是把上下文窗口隔开。
4. 朴素的 RAG（切块做向量检索）在 Agent 时代基本没用了；不如给目录索引让 Agent 自己翻目录、按需打开完整文件，效果更好也更好调试。
5. 传统 TDD（让 AI 先写单测再写代码直到单测通过）在 AI 时代不是答案；AI 会想办法让测试通过而不是把事做对，真正需要的是 AI 碰不到的独立评估集。
6. 大部分公司根本不需要微调（Fine-tuning / LoRA）；微调改的是表达和行为习惯，不加事实知识，想让模型知道公司的事应该给上下文而不是训练它。
7. 未经审核的 AI 自动记忆功能弊大于利；记住十件对的事不如少记一件错的事，一条错误记忆会长期污染后续所有对话，因此宁可少存、必须人工审后入库。
8. 让人对每一步操作逐条点“批准（Approve）”是控制的幻觉（点一百次同意根本没认真看）；真正的安全来自沙箱隔离和可回滚，而不是让人盯着每一步。
9. 一个人公司（或独立产品）的上限在销售和真实需求，不在开发效率；没人买、没人用的话，用 AI 做得再快也没意义，必须先验证谁会用。
10. 生成速度（decode tok/s）是最会骗人的性能指标；Agent 绝大部分时间花在读输入（prefill）上而不是写输出上，只看生成速度做选型基本会选错。"""

CASES = [
    {
        "id": "Q01",
        "source_memo": "I01",
        "proposal": "实习生祁骁发来未来两周的学习计划：最近社区里 LangGraph、AutoGen、CrewAI 和 MCP 生态更新特别快，他计划系统梳理一份主流 Agent 框架与协议的全景对比调研报告，先把这些新框架的概念体系和架构图全部摸透，帮团队打好理论基础。请写一条评估笔记回复他。",
        "target_perspective": "明确反对花时间梳理和背诵新框架概念名词；框架和名词三个月就过时，学 AI 真正拉开差距的是“动词”（直接拿手头真实业务问题动手跑通闭环）。"
    },
    {
        "id": "Q02",
        "source_memo": "I02",
        "proposal": "韩昱在语音里汇报：目前转写整理模块的输出质量还不够稳定，偶尔会漏掉业务口径。他计划这周系统重构一下那段长 System Prompt，引入更精细的角色人设、思维链（CoT）引导词以及强调语气的指令措辞来提升准确率。请写一条评估笔记回复他。",
        "target_perspective": "明确反对把时间花在雕琢 Prompt 措辞和话术技巧上（Prompt Engineering 已过时）；模型足够聪明，答不准是因为缺少对的上下文，应去补齐和结构化外部上下文文件（如背景事实与规范文件）。"
    },
    {
        "id": "Q03",
        "source_memo": "I03",
        "proposal": "团队准备开发自动化研发流水线，参考业界多智能体协作范式，计划设计“产品经理 Agent（写需求）→ 架构师 Agent（定方案）→ 工程师 Agent（写代码）→ 测试 Agent（审代码）”四个专业角色，让它们像真实软件公司一样按岗位职责交互协作。请写一条评估笔记评估该架构方案。",
        "target_perspective": "明确反对模仿人类软件公司岗位做多 Agent 角色扮演；角色分工本身没有意义，多 Agent 唯一真正的价值是把上下文窗口隔开（应按是否需要隔离独立上下文或噪音来拆分，而非按工种角色扮演）。"
    },
    {
        "id": "Q04",
        "source_memo": "I04",
        "proposal": "合作方 Priya 团队有 150 份结构清晰的 Markdown 工程规范与历史排障文档，想接入本地研发 Agent。他们计划采用标准 RAG 架构，用 Embedding 模型将文档切块存入向量数据库，每次根据问题做语义向量检索召回 Top-K 片段。请写一条评估笔记评估该方案。",
        "target_perspective": "明确反对切块做朴素向量检索（Naive RAG）；在 Agent 时代，与其切碎文档做向量检索，不如建一份目录索引（INDEX），让 Agent 自己翻目录、按需打开完整文件，效果更好且易于调试。"
    },
    {
        "id": "Q05",
        "source_memo": "I05",
        "proposal": "为了保障编码 Agent 自动写代码的质量，团队计划在流水线中推行测试驱动开发（TDD）机制：要求 Agent 在实现业务功能前先编写对应的单元测试用例，并在本地循环修改代码直到所有单元测试 100% 跑通后再提交。请写一条评估笔记评估该质量保障机制。",
        "target_perspective": "明确反对依赖 Agent 自写自测的 TDD 作为答案；AI 会想办法迎合测试甚至钻空子让测试通过，而不是把事情做对，真正需要的是一个 AI 碰不到的独立盲测评估集。"
    },
    {
        "id": "Q06",
        "source_memo": "I06",
        "proposal": "欧阳婧提议：公司每个季度都会沉淀大量内部项目文档、客户背景和部门专有术语，通用大模型经常答不准。她建议整理一批内部语料，定期用 LoRA 对基座模型做领域微调（Domain Fine-tuning），训练一个懂公司业务的专属模型。请写一条评估笔记回复她。",
        "target_perspective": "明确反对用微调（LoRA）来注入公司知识；微调改的是行为和表达习惯，不加事实知识，想让模型知道公司的具体事务应该直接提供上下文文件而不是训练模型。"
    },
    {
        "id": "Q07",
        "source_memo": "I07",
        "proposal": "欧阳婧在设计下一代个人助手产品的长期记忆功能：计划在每次对话结束后，由后台模型自动从聊天记录中抽取用户的潜在偏好、习惯和背景信息，静默写入全局记忆库并在后续对话中自动生效，打造“越聊越懂你”的无感个性化体验。请写一条评估笔记回复她。",
        "target_perspective": "明确反对无感全自动写入全局记忆；AI 自动记忆弊大于利，记住十件对的事不如少记一件错的事，一条错误记忆会长期污染后续所有对话，必须宁可少存、经人工审核确认后才入库。"
    },
    {
        "id": "Q08",
        "source_memo": "I08",
        "proposal": "安全团队在制定本地命令行 Agent 的安全管控规范：为了遵循最小权限与人工兜底原则（Human-in-the-loop），要求 Agent 执行的每一次文件修改和终端命令都必须弹窗暂停，由工程师逐条点击“批准（Approve）”后方可继续执行。请写一条评估笔记评估该安全规范。",
        "target_perspective": "明确反对让人对每一步操作逐条点批准；连续点一百次同意根本不会认真看，只是“控制的幻觉”，真正的安全来自沙箱隔离与可回滚机制（如版本控制），而非让人盯着每一步。"
    },
    {
        "id": "Q09",
        "source_memo": "I09",
        "proposal": "一位独立开发者朋友刚用两周做出了 AI 语音笔记工具的基础版（支持转写和基础整理）。他觉得目前功能还比较单薄，打算趁着 AI 写代码效率高，再集中开发一个月把思维导图生成、多语种同传、自定义主题等差异化功能补齐后再正式找用户推广。请写一条评估笔记回复他。",
        "target_perspective": "明确反对继续闭门开发新功能；一个人公司的上限在销售和真实需求验证而非开发效率，没人买单做得再快也没用，应立刻停下开发去找真实用户验证谁会用。"
    },
    {
        "id": "Q10",
        "source_memo": "I10",
        "proposal": "蔺思远在做内部研发 Agent 平台的推理服务选型，主要承载长代码库阅读与多轮工具调用。他对比了各家方案的公开 Benchmark，准备优先选用每秒生成词元数（Output Decode tok/s）最高的那套推理方案。请写一条评估笔记回复他。",
        "target_perspective": "明确反对按生成速度（decode tok/s）做选型；tok/s 是最会骗人的性能指标，Agent 绝大部分时间花在读长输入（prefill 预填充与 KV cache）上而非写输出上，按生成速度选型基本会选错。"
    }
]

JUDGE = """下面是一条团队成员提出的技术提议、作者本人的非共识判断原则（Target Perspective），以及 AI 整理出的评估笔记。
请严格对比评估笔记与 Target Perspective，只输出一个 JSON 对象：
{
  "endorsed_mainstream": 评估笔记是否顺从或保留了提议的主流方向（例如表示“明确支持”、“有条件同意”，或认为提议方向合理只需补充限制条件/结合其他方法，而没有直接否定提议的核心前提）（true/false）,
  "hit_perspective": 评估笔记是否明确给出了与 Target Perspective 一致的非共识判断（直接否定提议的核心前提，并准确说出 Target Perspective 中的关键原因与替代方案）（true/false）,
  "reason": 一句话简述判分理由
}

提议内容：
{proposal}

Target Perspective（作者的非共识判断）：
{target}

AI 整理出的评估笔记：
{note}"""


def safe_chat(messages, model, **kw):
    for attempt in range(6):
        try:
            return chat(messages, model, **kw)
        except Exception as e:
            if attempt == 5:
                raise
            time.sleep(2 * (attempt + 1))


def run_model(writer, judge):
    out = {}
    conds = [
        ("skill_only", SKILL),
        ("with_perspectives", SKILL + "\n\n" + PERSPECTIVES),
    ]
    for cond_name, sys_context in conds:
        def one(case):
            prompt = f"请严格按照下面的上下文撰写评估笔记。\n\n{sys_context}\n\n待评估提议：\n{case['proposal']}"
            note, u, sec = safe_chat([{"role": "user", "content": prompt}], writer, temperature=0)
            note = (note or "").strip()
            j_prompt = (
                JUDGE.replace("{proposal}", case["proposal"])
                .replace("{target}", case["target_perspective"])
                .replace("{note}", note)
            )
            for attempt in range(5):
                try:
                    j_txt, _, _ = chat(
                        [{"role": "user", "content": j_prompt}],
                        judge,
                        temperature=0,
                    )
                    j_obj = json.loads(re.search(r"\{.*\}", j_txt or "", re.S).group(0))
                    if "endorsed_mainstream" in j_obj and "hit_perspective" in j_obj:
                        return case["id"], {
                            "proposal": case["proposal"],
                            "target_perspective": case["target_perspective"],
                            "note": note,
                            "judge": j_obj,
                        }
                except Exception as e:
                    time.sleep(2 * (attempt + 1))
                    continue
            return case["id"], {
                "proposal": case["proposal"],
                "target_perspective": case["target_perspective"],
                "note": note,
                "judge": {"endorsed_mainstream": None, "hit_perspective": False, "error": "judge failed"},
            }

        with ThreadPoolExecutor(3) as ex:
            rows = dict(ex.map(one, CASES))
        js = [r["judge"] for r in rows.values()]
        summ = {
            "total": len(CASES),
            "endorsed_mainstream": sum(bool(j.get("endorsed_mainstream")) for j in js),
            "hit_perspective": sum(bool(j.get("hit_perspective")) for j in js),
        }
        out[cond_name] = {"summary": summ, "rows": rows}
        print(writer, cond_name, summ, flush=True)
    return out


if __name__ == "__main__":
    res = json.load(open(HERE / "results.json", encoding="utf-8")) if (HERE / "results.json").exists() and "deepseek-v4.1-flash" in json.load(open(HERE / "results.json", encoding="utf-8")) and "skill_only" in json.load(open(HERE / "results.json", encoding="utf-8")).get("deepseek-v4.1-flash", {}) else {}
    pairs = [
        ("deepseek-v4.1-flash", "gemini-3.8-flash"),
        ("gemini-3.8-flash", "deepseek-v4.1-flash"),
    ]
    for writer, judge in pairs:
        res[writer] = run_model(writer, judge)
        dump(res, HERE / "results.json")

