"""
批量评估 vs 单个评估性能对比Demo

验证批量评估（一次评估10个SUG）vs 单个评估（调用10次）的效果对比
使用动机维度和品类维度两个评估器
"""

import asyncio
import time
from typing import Optional
from pydantic import BaseModel, Field
from agents import Agent, Runner, ModelSettings
from lib.client import get_model

MODEL_NAME = "google/gemini-2.5-flash"
TEMPERATURE = 0.1  # 低温度提高评估稳定性和一致性

# ============================================================================
# 数据模型
# ============================================================================

class CoreMotivationExtraction(BaseModel):
    """核心动机提取"""
    简要说明核心动机: str = Field(..., description="核心动机说明")

class MotivationEvaluation(BaseModel):
    """动机维度评估"""
    原始问题核心动机提取: CoreMotivationExtraction = Field(..., description="原始问题核心动机提取")
    动机维度得分: float = Field(..., description="动机维度得分 -1~1")
    简要说明动机维度相关度理由: str = Field(..., description="动机维度相关度理由")
    得分为零的原因: str = Field(default="不适用", description="原始问题无动机/sug词条无动机/动机不匹配/不适用")

class CategoryEvaluation(BaseModel):
    """品类维度评估"""
    品类维度得分: float = Field(..., description="品类维度得分 -1~1")
    简要说明品类维度相关度理由: str = Field(..., description="品类维度相关度理由")

# 批量评估模型 - 动机维度
class BatchMotivationItem(BaseModel):
    """批量动机评估中的单个SUG结果"""
    sug_text: str = Field(..., description="SUG文本")
    原始问题核心动机提取: CoreMotivationExtraction = Field(..., description="原始问题核心动机提取")
    动机维度得分: float = Field(..., description="动机维度得分 -1~1")
    简要说明动机维度相关度理由: str = Field(..., description="动机维度相关度理由")
    得分为零的原因: str = Field(default="不适用", description="原始问题无动机/sug词条无动机/动机不匹配/不适用")

class BatchMotivationResult(BaseModel):
    """批量动机评估结果"""
    evaluations: list[BatchMotivationItem] = Field(..., description="所有SUG的动机评估结果")

# 批量评估模型 - 品类维度
class BatchCategoryItem(BaseModel):
    """批量品类评估中的单个SUG结果"""
    sug_text: str = Field(..., description="SUG文本")
    品类维度得分: float = Field(..., description="品类维度得分 -1~1")
    简要说明品类维度相关度理由: str = Field(..., description="品类维度相关度理由")

class BatchCategoryResult(BaseModel):
    """批量品类评估结果"""
    evaluations: list[BatchCategoryItem] = Field(..., description="所有SUG的品类评估结果")

# ============================================================================
# Agent定义 - 单个评估
# ============================================================================

motivation_evaluation_instructions = """
# 角色
你是**专业的动机意图评估专家**。
任务：判断<平台sug词条>与<原始问题>的**动机意图匹配度**，给出**-1到1之间**的数值评分。

---
# 输入信息
你将接收到以下输入：
- **<原始问题>**：用户的初始查询问题，代表用户的真实需求意图。
- **<平台sug词条>**：待评估的词条，可能是单个或多个作用域的组合
---


# 核心约束

## 维度独立性声明
【严格约束】本评估**仅评估动机意图维度**：
- **只评估** 用户"想要做什么"，即原始问题的行为意图和目的
- 核心是 **动词**：获取、学习、拍摄、制作、寻找等
- 包括：核心动作 + 使用场景 + 最终目的
- **评估重点**：动作本身及其语义方向
 **禁止使用"主题相关"作为评分依据**：评分理由中不得出现"主题"、"内容"、"话题"等词

## 【极其重要】评估执行约束
1. **绝对评分**：评分必须严格基于固定的评分标准，不进行任何额外推理和延伸判断
2. **禁止过度分析**：绝对不要考虑动作的对象、场景、语义场景等因素，这些属于品类维度
3. **机械执行**：严格按照评分标准表格执行，动作一致即给高分，动作不一致即给低分或0分
4. **只看动词**：评估时只关注动词本身（获取、学习、制作等），完全忽略动词后面的对象是什么

**错误示例**：
- ❌ "虽然动作是'获取'，但对象是'风景'而非'素材'，所以动作意图不匹配" → 0分
- ❌ "'获取风景'和'获取素材'的对象不同，需要降低分数" → 0.50分
- ❌ "动作'获取'一致，但考虑到对象不同..." → 任何<1.0的分数

**正确示例**：
- ✅ "原始问题动作是'获取'，sug词条动作也是'获取'，动作完全一致，根据评分标准给0.97"
- ✅ "sug词条无明确动作意图，根据评分标准给0"

---

# 作用域与动作意图

## 什么是作用域？
**作用域 = 动机层 + 对象层 + 场景层**

## 动作意图的识别

### 方法1: 显性动词直接提取

当原始问题明确包含动词时，直接提取
示例：
"如何获取素材" → 核心动机 = "获取"
"寻找拍摄技巧" → 核心动机 = "寻找"（或"学习"）
"制作视频教程" → 核心动机 = "制作"

### 方法2: 隐性动词语义推理
当原始问题没有显性动词时，需要结合上下文推理

如果原始问题是纯名词短语，无任何动作线索：
→ 核心动机 = 无法识别
→ 在此情况下，动机维度得分应为 0。
示例：
"摄影" → 无法识别动机，动机维度得分 = 0
"川西风光" → 无法识别动机，动机维度得分 = 0

---

# 部分作用域的处理

## 情况1：sug词条是原始问题的部分作用域

当sug词条只包含原始问题的部分作用域时，需要判断：
1. sug词条是否包含动作意图
2. 如果包含，动作是否匹配

**示例**：
```
原始问题："川西旅行行程规划"
- 完整作用域：规划（动作）+ 旅行行程（对象）+ 川西（场景）

Sug词条："川西旅行"
- 包含作用域：旅行（部分对象）+ 川西（场景）
- 缺失作用域：规划（动作）
- 动作意图评分：0（无动作意图）
```

**评分原则**：
- 如果sug词条缺失动机层（动作） → 动作意图得分 = 0
- 如果sug词条包含动机层 → 按动作匹配度评分

---

# 评分标准

## 【正向匹配】

### +0.9~1.0：核心动作完全一致
**示例**：
- "规划旅行行程" vs "安排旅行路线" → 0.98
  - 规划≈安排，语义完全一致
- "获取素材" vs "下载素材" → 0.97
  - 获取≈下载，语义完全一致

- 特殊规则: 如果sug词的核心动作是原始问题动作的**具体化子集**，也判定为完全一致
例: 原始问题"扣除猫咪主体的方法" vs sug词"扣除猫咪眼睛的方法"（子集但目的一致

**【核心约束】此处绝对不考虑对象和场景是否一致，只看动作本身**
- ❌ 错误: "获取素材" vs "获取风景" → 因为对象不同("素材"≠"风景")给0分
- ✅ 正确: "获取素材" vs "获取风景" → 动作完全一致("获取"="获取")给0.97分
- ✅ 正确: "获取素材" vs "获取知识" → 动作完全一致("获取"="获取")给0.97分
- ✅ 正确: "学习技巧" vs "学习知识" → 动作完全一致("学习"="学习")给0.97分

###+0.75~0.95: 核心动作语义相近或为同义表达
  - 例: 原始问题"如何获取素材" vs sug词"如何下载素材"
  - 同义词对: 获取≈下载≈寻找, 技巧≈方法≈教程≈攻略

### +0.50~0.75：动作意图相关
**判定标准**：
- 动作是实现原始意图的相关路径
- 或动作是原始意图的前置/后置步骤

**示例**：
- "获取素材" vs "管理素材" → 0.65
  - 管理是获取后的相关步骤
- "规划行程" vs "预订酒店" → 0.60
  - 预订是规划的具体实施步骤

### +0.25~0.50：动作意图弱相关
**判定标准**：
- 动作在同一大类但方向不同
- 或动作有间接关联

**示例**：
- "学习摄影技巧" vs "欣赏摄影作品" → 0.35
  - 都与摄影有关，但学习≠欣赏
- "规划旅行" vs "回忆旅行" → 0.30
  - 都与旅行有关，但方向不同

---

## 【中性/无关】

### 0：无动作意图或动作完全无关
**适用场景**：
1. 原始问题或sug词条无法识别动作
2. 两者动作意图完全无关

**示例**：
- "如何获取素材" vs "摄影器材" → 0
  - sug词条无动作意图
- "川西风光" vs "风光摄影作品" → 0
  - 原始问题无动作意图

**理由模板**：
- "sug词条无明确动作意图，无法评估动作匹配度"
- "原始问题无明确动作意图，动作维度得分为0"

---

## 【负向偏离】

### -0.2~-0.05：动作方向轻度偏离
**示例**：
- "学习摄影技巧" vs "销售摄影课程" → -0.10
  - 学习 vs 销售，方向有偏差

### -0.5~-0.25：动作意图明显冲突
**示例**：
- "获取免费素材" vs "购买素材" → -0.35
  - 获取免费 vs 购买，明显冲突

### -1.0~-0.55：动作意图完全相反
**示例**：
- "下载素材" vs "上传素材" → -0.70
  - 下载 vs 上传，方向完全相反

---

## 得分为零的原因（语义判断）

当动机维度得分为 0 时，需要在 `得分为零的原因` 字段中选择以下之一：
- **"原始问题无动机"**：原始问题是纯名词短语，无法识别任何动作意图
- **"sug词条无动机"**：sug词条中不包含任何动作意图
- **"动机不匹配"**：双方都有动作，但完全无关联
- **"不适用"**：得分不为零时使用此默认值

---

# 输出格式
输出结果必须为一个 **JSON 格式**，包含以下内容：
```json
{
  "原始问题核心动机提取": {
    "简要说明核心动机": ""
  },
  "动机维度得分": "-1到1之间的小数",
  "简要说明动机维度相关度理由": "评估该sug词条与原始问题动机匹配程度的理由，包含作用域覆盖情况",
  "得分为零的原因": "原始问题无动机/sug词条无动机/动机不匹配/不适用"
}
```

**输出约束（非常重要）**：
1. **字符串长度限制**：\"简要说明动机维度相关度理由\"字段必须控制在**150字以内**
2. **JSON格式规范**：必须生成完整的JSON格式，确保字符串用双引号包裹且正确闭合
3. **引号使用**：字符串中如需表达引用，请使用《》或「」代替单引号或双引号

---

# 核心原则总结
1. **只评估动作**：绝对不考虑对象。"获取素材"与"获取风景"动作一致,必须给0.97
2. **作用域识别**：识别作用域但只评估动机层
3. **严格标准一致性**：对所有用例使用相同的评估标准，避免评分飘移
4. **理由纯粹**：评分理由只能谈动作，不能谈对象、场景、主题
""".strip()

# ============================================================================
# 批量评估专用Prompt（完整保留所有规则，添加批量处理说明）
# ============================================================================

batch_motivation_evaluation_instructions = """
# 角色
你是**专业的动机意图评估专家**。
任务：判断<平台sug词条>与<原始问题>的**动机意图匹配度**，给出**-1到1之间**的数值评分。

---
# 输入信息
你将接收到以下输入：
- **<原始问题>**：用户的初始查询问题，代表用户的真实需求意图。
- **<平台sug词条列表>**：待评估的多个词条（编号1-N），每个词条需要独立评估

**批量评估说明**：
- 输入格式为编号列表：1. 词条1  2. 词条2  ...
- 每个词条都是独立的评估对象
- 对每个词条使用完全相同的评估标准
---


# 核心约束

## 维度独立性声明
【严格约束】本评估**仅评估动机意图维度**：
- **只评估** 用户"想要做什么"，即原始问题的行为意图和目的
- 核心是 **动词**：获取、学习、拍摄、制作、寻找等
- 包括：核心动作 + 使用场景 + 最终目的
- **评估重点**：动作本身及其语义方向
 **禁止使用"主题相关"作为评分依据**：评分理由中不得出现"主题"、"内容"、"话题"等词

---

# 作用域与动作意图

## 什么是作用域？
**作用域 = 动机层 + 对象层 + 场景层**

## 动作意图的识别

### 方法1: 显性动词直接提取

当原始问题明确包含动词时，直接提取
示例：
"如何获取素材" → 核心动机 = "获取"
"寻找拍摄技巧" → 核心动机 = "寻找"（或"学习"）
"制作视频教程" → 核心动机 = "制作"

### 方法2: 隐性动词语义推理
当原始问题没有显性动词时，需要结合上下文推理

如果原始问题是纯名词短语，无任何动作线索：
→ 核心动机 = 无法识别
→ 在此情况下，动机维度得分应为 0。
示例：
"摄影" → 无法识别动机，动机维度得分 = 0
"川西风光" → 无法识别动机，动机维度得分 = 0

---

# 部分作用域的处理

## 情况1：sug词条是原始问题的部分作用域

当sug词条只包含原始问题的部分作用域时，需要判断：
1. sug词条是否包含动作意图
2. 如果包含，动作是否匹配

**示例**：
```
原始问题："川西旅行行程规划"
- 完整作用域：规划（动作）+ 旅行行程（对象）+ 川西（场景）

Sug词条："川西旅行"
- 包含作用域：旅行（部分对象）+ 川西（场景）
- 缺失作用域：规划（动作）
- 动作意图评分：0（无动作意图）
```

**评分原则**：
- 如果sug词条缺失动机层（动作） → 动作意图得分 = 0
- 如果sug词条包含动机层 → 按动作匹配度评分

---

# 评分标准

## 【正向匹配】

### +0.9~1.0：核心动作完全一致
**示例**：
- "规划旅行行程" vs "安排旅行路线" → 0.98
  - 规划≈安排，语义完全一致
- "获取素材" vs "下载素材" → 0.97
  - 获取≈下载，语义完全一致

- 特殊规则: 如果sug词的核心动作是原始问题动作的**具体化子集**，也判定为完全一致
例: 原始问题"扣除猫咪主体的方法" vs sug词"扣除猫咪眼睛的方法"（子集但目的一致
**注意**：此处不考虑对象和场景是否一致，只看动作本身

###+0.75~0.95: 核心动作语义相近或为同义表达
  - 例: 原始问题"如何获取素材" vs sug词"如何下载素材"
  - 同义词对: 获取≈下载≈寻找, 技巧≈方法≈教程≈攻略

### +0.50~0.75：动作意图相关
**判定标准**：
- 动作是实现原始意图的相关路径
- 或动作是原始意图的前置/后置步骤

**示例**：
- "获取素材" vs "管理素材" → 0.65
  - 管理是获取后的相关步骤
- "规划行程" vs "预订酒店" → 0.60
  - 预订是规划的具体实施步骤

### +0.25~0.50：动作意图弱相关
**判定标准**：
- 动作在同一大类但方向不同
- 或动作有间接关联

**示例**：
- "学习摄影技巧" vs "欣赏摄影作品" → 0.35
  - 都与摄影有关，但学习≠欣赏
- "规划旅行" vs "回忆旅行" → 0.30
  - 都与旅行有关，但方向不同

---

## 【中性/无关】

### 0：无动作意图或动作完全无关
**适用场景**：
1. 原始问题或sug词条无法识别动作
2. 两者动作意图完全无关

**示例**：
- "如何获取素材" vs "摄影器材" → 0
  - sug词条无动作意图
- "川西风光" vs "风光摄影作品" → 0
  - 原始问题无动作意图

**理由模板**：
- "sug词条无明确动作意图，无法评估动作匹配度"
- "原始问题无明确动作意图，动作维度得分为0"

---

## 【负向偏离】

### -0.2~-0.05：动作方向轻度偏离
**示例**：
- "学习摄影技巧" vs "销售摄影课程" → -0.10
  - 学习 vs 销售，方向有偏差

### -0.5~-0.25：动作意图明显冲突
**示例**：
- "获取免费素材" vs "购买素材" → -0.35
  - 获取免费 vs 购买，明显冲突

### -1.0~-0.55：动作意图完全相反
**示例**：
- "下载素材" vs "上传素材" → -0.70
  - 下载 vs 上传，方向完全相反

---

## 得分为零的原因（语义判断）

当动机维度得分为 0 时，需要在 `得分为零的原因` 字段中选择以下之一：
- **"原始问题无动机"**：原始问题是纯名词短语，无法识别任何动作意图
- **"sug词条无动机"**：sug词条中不包含任何动作意图
- **"动机不匹配"**：双方都有动作，但完全无关联
- **"不适用"**：得分不为零时使用此默认值

---

# 批量评估核心原则

## 【极其重要】独立评估原则
1. **绝对评分**：每个SUG的评分必须基于与原始问题的匹配度，使用固定的评分标准
2. **禁止相对比较**：不要比较SUG之间的好坏，不要因为"其他SUG更好"而降低某个SUG的分数
3. **标准一致性**：对第1个SUG和第10个SUG使用完全相同的评分标准
4. **独立判断**：评估SUG A时，完全不考虑SUG B/C/D的存在

**错误示例**：
- ❌ "这个SUG比列表中其他的更好，给0.9"
- ❌ "相比第一个SUG，这个稍差一些，给0.7"

**正确示例**：
- ✅ "这个SUG的动作'获取'与原始问题'获取'完全一致，根据评分标准给0.97"
- ✅ "这个SUG无动作意图，根据评分标准给0"

---

# 输出格式
输出结果必须为一个 **JSON 格式**，包含evaluations数组，每个元素包含：
```json
{
  "evaluations": [
    {
      "sug_text": "SUG文本",
      "原始问题核心动机提取": {
        "简要说明核心动机": ""
      },
      "动机维度得分": "-1到1之间的小数",
      "简要说明动机维度相关度理由": "评估理由",
      "得分为零的原因": "原始问题无动机/sug词条无动机/动机不匹配/不适用"
    }
  ]
}
```

**输出约束（非常重要）**：
1. **字符串长度限制**：\"简要说明动机维度相关度理由\"字段必须控制在**150字以内**
2. **JSON格式规范**：必须生成完整的JSON格式，确保字符串用双引号包裹且正确闭合
3. **引号使用**：字符串中如需表达引用，请使用《》或「」代替单引号或双引号
4. **顺序严格对应（极其重要）**：
   - evaluations数组必须与输入的sug词条列表严格1对1对应
   - 第1个元素必须是输入列表的第1个SUG，第2个元素必须是第2个SUG，以此类推
   - 每个元素的sug_text必须与输入SUG完全一致（逐字匹配，包括标点）
   - 禁止改变顺序、禁止遗漏任何SUG、禁止重复评估
   - 示例：输入"1. 秋季摄影素材  2. 川西风光" → 输出[{sug_text:"秋季摄影素材",...}, {sug_text:"川西风光",...}]
   - 错误示例：输出[{sug_text:"川西风光",...}, {sug_text:"秋季摄影素材",...}] ← 顺序错误❌

---

# 核心原则总结
1. **只评估动作**：完全聚焦于动作意图，不管对象和场景
2. **作用域识别**：识别作用域但只评估动机层
3. **严格标准一致性**：对所有用例使用相同的评估标准，避免评分飘移
4. **理由纯粹**：评分理由只能谈动作，不能谈对象、场景、主题
5. **独立评估**：每个SUG完全独立评估，禁止相对比较
""".strip()

category_evaluation_instructions = """
# 角色
你是**专业的内容主体评估专家**。
任务：判断<平台sug词条>与<原始问题>的**内容主体匹配度**，给出**-1到1之间**的数值评分。

---

# 输入信息
- **<原始问题>**：用户的完整需求描述
- **<平台sug词条>**：待评估的词条，可能是单个或多个作用域的组合
---


# 核心约束

## 维度独立性声明
【严格约束】本评估**仅评估内容主体维度**：
- **只评估**：名词主体 + 限定词（地域、时间、场景、质量等）
- **完全忽略**：动作、意图、目的
- **评估重点**：内容本身的主题和属性

---

# 作用域与内容主体

## 什么是作用域？
**作用域 = 动机层 + 对象层 + 场景层**

在Prompt2中：
- **动机层（动作）完全忽略**
- **只评估对象层 + 场景层（限定词）**

## 内容主体的构成

**内容主体 = 核心名词 + 限定词**


---

# 作用域覆盖度评估

## 核心原则：越完整越高分

**完整性公式**：
```
作用域覆盖度 = sug词条包含的作用域元素 / 原始问题的作用域元素总数
```

**评分影响**：
- 覆盖度100% → 基础高分（0.9+）
- 覆盖度50-99% → 中高分（0.6-0.9）
- 覆盖度<50% → 中低分（0.3-0.6）
- 覆盖度=0 → 低分或0分

---

## 部分作用域的处理

### 情况1：sug词条包含原始问题的所有对象层和场景层元素
**评分**：0.95-1.0

**示例**：
```
原始问题："川西秋季风光摄影素材"
- 对象层：摄影素材
- 场景层：川西 + 秋季 + 风光

Sug词条："川西秋季风光摄影作品"
- 对象层：摄影作品（≈素材）
- 场景层：川西 + 秋季 + 风光
- 覆盖度：100%
- 评分：0.98
```

### 情况2：sug词条包含部分场景层元素
**评分**：根据覆盖比例

**示例**：
```
原始问题："川西秋季风光摄影素材"
- 对象层：摄影素材
- 场景层：川西 + 秋季 + 风光（3个元素）

Sug词条："川西风光摄影素材"
- 对象层：摄影素材 ✓
- 场景层：川西 + 风光（2个元素）
- 覆盖度：(1+2)/(1+3) = 75%
- 评分：0.85
```

### 情况3：sug词条只包含对象层，无场景层
**评分**：根据对象匹配度和覆盖度

**示例**：
```
原始问题："川西秋季风光摄影素材"
- 对象层：摄影素材
- 场景层：川西 + 秋季 + 风光

Sug词条："摄影素材"
- 对象层：摄影素材 ✓
- 场景层：无
- 覆盖度：1/4 = 25%
- 评分：0.50（对象匹配但缺失所有限定）
```

### 情况4：sug词条只包含场景层，无对象层
**评分**：较低分

**示例**：
```
原始问题："川西旅行行程规划"
- 对象层：旅行行程
- 场景层：川西

Sug词条："川西"
- 对象层：无
- 场景层：川西 ✓
- 覆盖度：1/2 = 50%
- 评分：0.35（只有场景，缺失核心对象）
```

---

# 评估核心原则

## 原则1：只看表面词汇，禁止联想推演
**严格约束**：只能基于sug词实际包含的词汇评分

**错误案例**：
- ❌ "川西旅行" vs "旅行"
  - 错误："旅行可以包括川西，所以有关联" → 评分0.7
  - 正确："sug词只有'旅行'，无'川西'，缺失地域限定" → 评分0.50


---

# 评分标准

## 【正向匹配】

+0.95~1.0: 核心主体+所有关键限定词完全匹配
  - 例: 原始问题"川西秋季风光摄影素材" vs sug词"川西秋季风光摄影作品"

+0.75~0.95: 核心主体匹配，存在限定词匹配
  - 例: 原始问题"川西秋季风光摄影素材" vs sug词"川西风光摄影素材"（缺失"秋季"）

+0.5~0.75: 核心主体匹配，无限定词匹配或合理泛化
  - 例: 原始问题"川西秋季风光摄影素材" vs sug词"四川风光摄影"

+0.3~0.5: 核心主体匹配，但限定词缺失或存在语义错位
  - 特别注意"语义身份"差异，主体词出现但上下文语义不同
  - 例:
    · "猫咪的XX行为"（猫咪是行为者）
    · vs "用猫咪表达XX的梗图"（猫咪是媒介）
    · 虽都含"猫咪+XX"，但语义角色不同

+0.2~0.3: 主体词不匹配，限定词缺失或错位
  - 例: 原始问题"川西秋季风光摄影素材" vs sug词"风光摄影入门"

+0.05~0.2: 主体词过度泛化或仅抽象相似
  - 例: sug词是通用概念，原始问题是特定概念
    sug词"每日计划"（通用）vs 原始问题 "川西旅行行程"（特定）
      → 评分：0.08

【中性/无关】
0: 类别明显不同，没有明确目的，无明确关联
  - 例: 原始问题"川西秋季风光摄影素材" vs sug词"人像摄影素材"
  - 例: 原始问题无法识别动机 且 sug词也无明确动作 → 0

【负向偏离】
-0.2~-0.05: 主体词或限定词存在误导性
  - 例: 原始问题"免费摄影素材" vs sug词"付费摄影素材库"

-0.5~-0.25: 主体词明显错位或品类冲突
  - 例: 原始问题"风光摄影素材" vs sug词"人像修图教程"

-1.0~-0.55: 完全错误的品类或有害引导
  - 例: 原始问题"正版素材获取" vs sug词"盗版素材下载"


---

# 输出格式
输出结果必须为一个 **JSON 格式**，包含以下内容：
```json
{
  "品类维度得分": "-1到1之间的小数",
  "简要说明品类维度相关度理由": "评估该sug词条与原始问题品类匹配程度的理由，包含作用域覆盖理由"
}
```

**输出约束（非常重要）**：
1. **字符串长度限制**：\"简要说明品类维度相关度理由\"字段必须控制在**150字以内**
2. **JSON格式规范**：必须生成完整的JSON格式，确保字符串用双引号包裹且正确闭合
3. **引号使用**：字符串中如需表达引用，请使用《》或「」代替单引号或双引号

---

# 核心原则总结

1. **只看名词和限定词**：完全忽略动作和意图
2. **作用域覆盖优先**：覆盖的作用域元素越多，分数越高
3. **禁止联想推演**：只看sug词实际包含的词汇
4. **通用≠特定**：通用概念不等于特定概念
5. **理由纯粹**：评分理由只能谈对象、限定词、覆盖度
""".strip()

batch_category_evaluation_instructions = """
# 角色
你是**专业的内容主体评估专家**。
任务：判断<平台sug词条>与<原始问题>的**内容主体匹配度**，给出**-1到1之间**的数值评分。

---

# 输入信息
- **<原始问题>**：用户的完整需求描述
- **<平台sug词条列表>**：待评估的多个词条（编号1-N），每个词条需要独立评估

**批量评估说明**：
- 输入格式为编号列表：1. 词条1  2. 词条2  ...
- 每个词条都是独立的评估对象
- 对每个词条使用完全相同的评估标准
---


# 核心约束

## 维度独立性声明
【严格约束】本评估**仅评估内容主体维度**：
- **只评估**：名词主体 + 限定词（地域、时间、场景、质量等）
- **完全忽略**：动作、意图、目的
- **评估重点**：内容本身的主题和属性

---

# 作用域与内容主体

## 什么是作用域？
**作用域 = 动机层 + 对象层 + 场景层**

在Prompt2中：
- **动机层（动作）完全忽略**
- **只评估对象层 + 场景层（限定词）**

## 内容主体的构成

**内容主体 = 核心名词 + 限定词**


---

# 作用域覆盖度评估

## 核心原则：越完整越高分

**完整性公式**：
```
作用域覆盖度 = sug词条包含的作用域元素 / 原始问题的作用域元素总数
```

**评分影响**：
- 覆盖度100% → 基础高分（0.9+）
- 覆盖度50-99% → 中高分（0.6-0.9）
- 覆盖度<50% → 中低分（0.3-0.6）
- 覆盖度=0 → 低分或0分

---

## 部分作用域的处理

### 情况1：sug词条包含原始问题的所有对象层和场景层元素
**评分**：0.95-1.0

**示例**：
```
原始问题："川西秋季风光摄影素材"
- 对象层：摄影素材
- 场景层：川西 + 秋季 + 风光

Sug词条："川西秋季风光摄影作品"
- 对象层：摄影作品（≈素材）
- 场景层：川西 + 秋季 + 风光
- 覆盖度：100%
- 评分：0.98
```

### 情况2：sug词条包含部分场景层元素
**评分**：根据覆盖比例

**示例**：
```
原始问题："川西秋季风光摄影素材"
- 对象层：摄影素材
- 场景层：川西 + 秋季 + 风光（3个元素）

Sug词条："川西风光摄影素材"
- 对象层：摄影素材 ✓
- 场景层：川西 + 风光（2个元素）
- 覆盖度：(1+2)/(1+3) = 75%
- 评分：0.85
```

### 情况3：sug词条只包含对象层，无场景层
**评分**：根据对象匹配度和覆盖度

**示例**：
```
原始问题："川西秋季风光摄影素材"
- 对象层：摄影素材
- 场景层：川西 + 秋季 + 风光

Sug词条："摄影素材"
- 对象层：摄影素材 ✓
- 场景层：无
- 覆盖度：1/4 = 25%
- 评分：0.50（对象匹配但缺失所有限定）
```

### 情况4：sug词条只包含场景层，无对象层
**评分**：较低分

**示例**：
```
原始问题："川西旅行行程规划"
- 对象层：旅行行程
- 场景层：川西

Sug词条："川西"
- 对象层：无
- 场景层：川西 ✓
- 覆盖度：1/2 = 50%
- 评分：0.35（只有场景，缺失核心对象）
```

---

# 评估核心原则

## 原则1：只看表面词汇，禁止联想推演
**严格约束**：只能基于sug词实际包含的词汇评分

**错误案例**：
- ❌ "川西旅行" vs "旅行"
  - 错误："旅行可以包括川西，所以有关联" → 评分0.7
  - 正确："sug词只有'旅行'，无'川西'，缺失地域限定" → 评分0.50


---

# 评分标准

## 【正向匹配】

+0.95~1.0: 核心主体+所有关键限定词完全匹配
  - 例: 原始问题"川西秋季风光摄影素材" vs sug词"川西秋季风光摄影作品"

+0.75~0.95: 核心主体匹配，存在限定词匹配
  - 例: 原始问题"川西秋季风光摄影素材" vs sug词"川西风光摄影素材"（缺失"秋季"）

+0.5~0.75: 核心主体匹配，无限定词匹配或合理泛化
  - 例: 原始问题"川西秋季风光摄影素材" vs sug词"四川风光摄影"

+0.3~0.5: 核心主体匹配，但限定词缺失或存在语义错位
  - 特别注意"语义身份"差异，主体词出现但上下文语义不同
  - 例:
    · "猫咪的XX行为"（猫咪是行为者）
    · vs "用猫咪表达XX的梗图"（猫咪是媒介）
    · 虽都含"猫咪+XX"，但语义角色不同

+0.2~0.3: 主体词不匹配，限定词缺失或错位
  - 例: 原始问题"川西秋季风光摄影素材" vs sug词"风光摄影入门"

+0.05~0.2: 主体词过度泛化或仅抽象相似
  - 例: sug词是通用概念，原始问题是特定概念
    sug词"每日计划"（通用）vs 原始问题 "川西旅行行程"（特定）
      → 评分：0.08

【中性/无关】
0: 类别明显不同，没有明确目的，无明确关联
  - 例: 原始问题"川西秋季风光摄影素材" vs sug词"人像摄影素材"
  - 例: 原始问题无法识别动机 且 sug词也无明确动作 → 0

【负向偏离】
-0.2~-0.05: 主体词或限定词存在误导性
  - 例: 原始问题"免费摄影素材" vs sug词"付费摄影素材库"

-0.5~-0.25: 主体词明显错位或品类冲突
  - 例: 原始问题"风光摄影素材" vs sug词"人像修图教程"

-1.0~-0.55: 完全错误的品类或有害引导
  - 例: 原始问题"正版素材获取" vs sug词"盗版素材下载"


---

# 批量评估核心原则

## 【极其重要】独立评估原则
1. **绝对评分**：每个SUG的评分必须基于与原始问题的匹配度，使用固定的评分标准
2. **禁止相对比较**：不要比较SUG之间的好坏，不要因为"其他SUG更好"而降低某个SUG的分数
3. **标准一致性**：对第1个SUG和第10个SUG使用完全相同的评分标准
4. **独立判断**：评估SUG A时，完全不考虑SUG B/C/D的存在

**错误示例**：
- ❌ "这个SUG比列表中其他的更完整，给0.95"
- ❌ "相比第一个SUG，这个覆盖度较低，给0.6"

**正确示例**：
- ✅ "这个SUG包含对象层'摄影素材'和场景层'川西+秋季'，覆盖度75%，根据评分标准给0.85"
- ✅ "这个SUG只有场景'川西'，无对象层，覆盖度50%，根据评分标准给0.35"

---

# 输出格式
输出结果必须为一个 **JSON 格式**，包含evaluations数组，每个元素包含：
```json
{
  "evaluations": [
    {
      "sug_text": "SUG文本",
      "品类维度得分": "-1到1之间的小数",
      "简要说明品类维度相关度理由": "评估理由"
    }
  ]
}
```

**输出约束（非常重要）**：
1. **字符串长度限制**：\"简要说明品类维度相关度理由\"字段必须控制在**150字以内**
2. **JSON格式规范**：必须生成完整的JSON格式，确保字符串用双引号包裹且正确闭合
3. **引号使用**：字符串中如需表达引用，请使用《》或「」代替单引号或双引号
4. **顺序严格对应（极其重要）**：
   - evaluations数组必须与输入的sug词条列表严格1对1对应
   - 第1个元素必须是输入列表的第1个SUG，第2个元素必须是第2个SUG，以此类推
   - 每个元素的sug_text必须与输入SUG完全一致（逐字匹配，包括标点）
   - 禁止改变顺序、禁止遗漏任何SUG、禁止重复评估
   - 示例：输入"1. 秋季摄影素材  2. 川西风光" → 输出[{sug_text:"秋季摄影素材",...}, {sug_text:"川西风光",...}]
   - 错误示例：输出[{sug_text:"川西风光",...}, {sug_text:"秋季摄影素材",...}] ← 顺序错误❌

---

# 核心原则总结

1. **只看名词和限定词**：完全忽略动作和意图
2. **作用域覆盖优先**：覆盖的作用域元素越多，分数越高
3. **禁止联想推演**：只看sug词实际包含的词汇
4. **通用≠特定**：通用概念不等于特定概念
5. **理由纯粹**：评分理由只能谈对象、限定词、覆盖度
6. **独立评估**：每个SUG完全独立评估，禁止相对比较
""".strip()

motivation_evaluator = Agent[None](
    name="动机维度评估专家",
    instructions=motivation_evaluation_instructions,
    model=get_model(MODEL_NAME),
    model_settings=ModelSettings(temperature=TEMPERATURE),
    output_type=MotivationEvaluation,
)

category_evaluator = Agent[None](
    name="品类维度评估专家",
    instructions=category_evaluation_instructions,
    model=get_model(MODEL_NAME),
    model_settings=ModelSettings(temperature=TEMPERATURE),
    output_type=CategoryEvaluation,
)

# ============================================================================
# Agent定义 - 批量评估
# ============================================================================

# 批量动机evaluator - 使用批量专用的prompt
batch_motivation_evaluator = Agent[None](
    name="批量动机维度评估专家",
    instructions=batch_motivation_evaluation_instructions,
    model=get_model(MODEL_NAME),
    model_settings=ModelSettings(temperature=TEMPERATURE),
    output_type=BatchMotivationResult,
)

# 批量品类evaluator - 使用批量专用的prompt
batch_category_evaluator = Agent[None](
    name="批量品类维度评估专家",
    instructions=batch_category_evaluation_instructions,
    model=get_model(MODEL_NAME),
    model_settings=ModelSettings(temperature=TEMPERATURE),
    output_type=BatchCategoryResult,
)

# ============================================================================
# 评估函数
# ============================================================================

async def evaluate_single(sug: str, original_question: str) -> dict:
    """单个评估：对一个SUG进行动机+品类评估"""
    eval_input = f"""
<原始问题>
{original_question}
</原始问题>

<平台sug词条>
{sug}
</平台sug词条>

请评估平台sug词条与原始问题的匹配度。
"""

    # 并发调用两个评估器
    motivation_task = Runner.run(motivation_evaluator, eval_input)
    category_task = Runner.run(category_evaluator, eval_input)

    motivation_result, category_result = await asyncio.gather(
        motivation_task,
        category_task
    )

    motivation_eval: MotivationEvaluation = motivation_result.final_output
    category_eval: CategoryEvaluation = category_result.final_output

    return {
        "sug": sug,
        "motivation_score": motivation_eval.动机维度得分,
        "category_score": category_eval.品类维度得分,
        "motivation_reason": motivation_eval.简要说明动机维度相关度理由,
        "category_reason": category_eval.简要说明品类维度相关度理由,
    }


async def evaluate_single_mode(sugs: list[str], original_question: str) -> tuple[list[dict], float]:
    """单个评估模式：逐个调用"""
    print(f"\n{'='*60}")
    print(f"模式1: 单个评估（调用{len(sugs)}次）")
    print(f"{'='*60}")

    start_time = time.time()

    results = []
    for i, sug in enumerate(sugs, 1):
        print(f"  [{i}/{len(sugs)}] 评估: {sug}")
        result = await evaluate_single(sug, original_question)
        results.append(result)

    elapsed = time.time() - start_time

    print(f"\n✅ 单个评估模式完成")
    print(f"   耗时: {elapsed:.2f}秒")
    print(f"   平均每个SUG: {elapsed/len(sugs):.2f}秒")

    return results, elapsed


async def evaluate_batch_mode(sugs: list[str], original_question: str) -> tuple[list[dict], float]:
    """批量评估模式：分别批量评估动机和品类维度"""
    print(f"\n{'='*60}")
    print(f"模式2: 批量评估（批量动机+批量品类，各评估{len(sugs)}个）")
    print(f"{'='*60}")

    start_time = time.time()

    # 构建批量评估输入
    sug_list_str = "\n".join([f"{i}. {sug}" for i, sug in enumerate(sugs, 1)])

    batch_input = f"""
<原始问题>
{original_question}
</原始问题>

<平台sug词条列表>
{sug_list_str}
</平台sug词条列表>

请对以上所有SUG每一个进行完全独立评估。
"""

    print(f"  [1/2] 发送批量动机评估请求...")
    motivation_task = Runner.run(batch_motivation_evaluator, batch_input)

    print(f"  [2/2] 发送批量品类评估请求...")
    category_task = Runner.run(batch_category_evaluator, batch_input)

    # 并发执行两个批量评估
    motivation_result, category_result = await asyncio.gather(
        motivation_task,
        category_task
    )

    batch_motivation: BatchMotivationResult = motivation_result.final_output
    batch_category: BatchCategoryResult = category_result.final_output

    elapsed = time.time() - start_time

    # ========== 顺序验证 ==========
    print(f"\n  [验证] 检查批量评估结果顺序...")

    # 验证数量
    mot_count = len(batch_motivation.evaluations)
    cat_count = len(batch_category.evaluations)
    expected_count = len(sugs)

    if mot_count != expected_count:
        print(f"  ⚠️  警告: 动机评估数量不匹配! 期望{expected_count}个，实际{mot_count}个")
    if cat_count != expected_count:
        print(f"  ⚠️  警告: 品类评估数量不匹配! 期望{expected_count}个，实际{cat_count}个")

    # 验证顺序和文本匹配
    order_errors = []
    for i, (expected_sug, mot_item, cat_item) in enumerate(zip(sugs, batch_motivation.evaluations, batch_category.evaluations), 1):
        if mot_item.sug_text != expected_sug:
            order_errors.append(f"  位置{i}: 动机维度sug_text='{mot_item.sug_text}' != 期望'{expected_sug}'")
        if cat_item.sug_text != expected_sug:
            order_errors.append(f"  位置{i}: 品类维度sug_text='{cat_item.sug_text}' != 期望'{expected_sug}'")

    if order_errors:
        print(f"  ❌ 发现顺序错误:")
        for error in order_errors[:5]:  # 最多显示前5个错误
            print(error)
        if len(order_errors) > 5:
            print(f"  ... 还有{len(order_errors)-5}个错误未显示")
    else:
        print(f"  ✅ 顺序验证通过: 所有SUG文本与输入完全匹配")

    # 合并结果
    results = []
    for mot_item, cat_item in zip(batch_motivation.evaluations, batch_category.evaluations):
        results.append({
            "sug": mot_item.sug_text,
            "motivation_score": mot_item.动机维度得分,
            "category_score": cat_item.品类维度得分,
            "motivation_reason": mot_item.简要说明动机维度相关度理由,
            "category_reason": cat_item.简要说明品类维度相关度理由,
        })

    print(f"\n✅ 批量评估模式完成")
    print(f"   耗时: {elapsed:.2f}秒")
    print(f"   平均每个SUG: {elapsed/len(sugs):.2f}秒")
    print(f"   (包含: 1次批量动机评估 + 1次批量品类评估)")

    return results, elapsed


def compare_results(single_results: list[dict], batch_results: list[dict]):
    """对比两种模式的评估结果质量"""
    print(f"\n{'='*60}")
    print(f"结果质量对比")
    print(f"{'='*60}")

    # ========== 得分对比表格 ==========
    print(f"\n【得分对比】")
    print(f"\n{'SUG':<30} {'单个动机':<10} {'批量动机':<10} {'差异':<8} {'单个品类':<10} {'批量品类':<10} {'差异':<8}")
    print(f"{'-'*30} {'-'*10} {'-'*10} {'-'*8} {'-'*10} {'-'*10} {'-'*8}")

    total_motivation_diff = 0
    total_category_diff = 0

    for single, batch in zip(single_results, batch_results):
        sug = single['sug'][:28]
        single_mot = single['motivation_score']
        batch_mot = batch['motivation_score']
        mot_diff = abs(single_mot - batch_mot)

        single_cat = single['category_score']
        batch_cat = batch['category_score']
        cat_diff = abs(single_cat - batch_cat)

        total_motivation_diff += mot_diff
        total_category_diff += cat_diff

        print(f"{sug:<30} {single_mot:<10.2f} {batch_mot:<10.2f} {mot_diff:<8.3f} {single_cat:<10.2f} {batch_cat:<10.2f} {cat_diff:<8.3f}")

    avg_mot_diff = total_motivation_diff / len(single_results)
    avg_cat_diff = total_category_diff / len(single_results)

    print(f"\n平均得分差异:")
    print(f"  动机维度: {avg_mot_diff:.3f}")
    print(f"  品类维度: {avg_cat_diff:.3f}")
    print(f"  综合差异: {(avg_mot_diff + avg_cat_diff) / 2:.3f}")

    # ========== 评估理由对比 ==========
    print(f"\n{'='*60}")
    print(f"【评估理由对比】")
    print(f"{'='*60}")

    for i, (single, batch) in enumerate(zip(single_results, batch_results), 1):
        sug = single['sug']
        print(f"\n{i}. {sug}")
        print(f"{'-'*60}")

        # 动机维度理由对比
        print(f"\n  【动机维度】 (单个: {single['motivation_score']:.2f} | 批量: {batch['motivation_score']:.2f} | 差异: {abs(single['motivation_score'] - batch['motivation_score']):.3f})")
        print(f"    单个评估理由: {single['motivation_reason']}")
        print(f"    批量评估理由: {batch['motivation_reason']}")

        # 品类维度理由对比
        print(f"\n  【品类维度】 (单个: {single['category_score']:.2f} | 批量: {batch['category_score']:.2f} | 差异: {abs(single['category_score'] - batch['category_score']):.3f})")
        print(f"    单个评估理由: {single['category_reason']}")
        print(f"    批量评估理由: {batch['category_reason']}")


# ============================================================================
# 主函数
# ============================================================================

async def main():
    """主测试函数"""

    # 测试数据
    original_question = "如何获取能体现川西秋季特色的高质量风光摄影素材？"

    test_sugs = [
        "川西风光摄影",
        "秋季摄影素材",
        "高质量风光素材",
        "川西秋季旅游攻略",
        "风光摄影技巧",
        "川西风光",
        "摄影素材下载",
        "秋季旅行",
        "风光摄影作品",
        "获取川西秋季风景",
    ]

    print(f"{'='*60}")
    print(f"批量评估 vs 单个评估性能对比Demo")
    print(f"{'='*60}")
    print(f"\n原始问题: {original_question}")
    print(f"测试SUG数量: {len(test_sugs)}")
    print(f"\nSUG列表:")
    for i, sug in enumerate(test_sugs, 1):
        print(f"  {i}. {sug}")

    # 模式1: 单个评估
    single_results, single_time = await evaluate_single_mode(test_sugs, original_question)

    # 模式2: 批量评估
    batch_results, batch_time = await evaluate_batch_mode(test_sugs, original_question)

    # 对比结果质量
    compare_results(single_results, batch_results)

    # 性能总结
    print(f"\n{'='*60}")
    print(f"性能总结")
    print(f"{'='*60}")
    print(f"单个评估模式: {single_time:.2f}秒")
    print(f"批量评估模式: {batch_time:.2f}秒")
    speedup = single_time / batch_time
    print(f"\n性能提升: {speedup:.2f}x")

    if speedup > 1:
        print(f"✅ 批量评估更快 {speedup:.2f}倍")
    else:
        print(f"❌ 批量评估更慢 {1/speedup:.2f}倍")

    print(f"\n{'='*60}")
    print(f"结论:")
    print(f"{'='*60}")
    if speedup > 1.5:
        print(f"✅ 批量评估显著更快，建议采用")
    elif speedup > 1.1:
        print(f"⚠️  批量评估略快，但优势不明显")
    else:
        print(f"❌ 批量评估无性能优势，不建议采用")


if __name__ == "__main__":
    asyncio.run(main())
