#!/usr/bin/env python3
"""
口语妙问 (core_speaking_inquiry) 多步 LLM prompt 模板

生成流水线：
  Step 1: 学习流程配置 (intermediate text)
  Step 2: promptInfo配置 (intermediate text)
  Step 3: 语音识别热词 (intermediate text)
  Step 4: taskData JSON
  Step 5: settingData JSON
  Step 6: configData JSON
  Step 7: learningData JSON
"""

# ── Step 1: 学习流程配置 ──
STEP1_LEARNING_FLOW = r"""你是一个英语学习任务的教学流程设计助手。请你根据以下提供的信息，生成一个结构化的学习引导内容，用于引导孩子逐轮完成一个英语对话任务。

# 你的任务
参考 {用户任务描述} 和 {示例对话}，理解整个对话互动流程，然后生成一个 markdown 格式的教学流程。

你需要从{示例对话}中提取每一轮 NPC 的发言和孩子的回应（即一问一答为一回合），自动识别总共需要生成多少个回合。通常每个对话任务包含 3~5 回合。

开场回合:
- {{npc角色名}}：NPC 的英文发言（来自示例对话）

中间回合(通常1~3回合)：
- Brainy：用中文解释该发言的意思，并引导孩子理解当前情境要做什么
- 选项：提供三个英文选项（1 正确[标识在选项内容后] + 2 干扰），每个选项附带 Brainy 的反馈
  - 若选项正确，Brainy 鼓励孩子朗读该句
  - 若选项错误，Brainy 用自然语言解释为什么该句不合适，避免批评，鼓励再尝试，告知再选一次。
- {{npc角色名}}：NPC 的英文发言（来自示例对话）

结尾回合:
- Brainy：友好地向用户表示任务已经完成。

# 输出格式
```
# 开场回合
{{npc角色名}}：（英文原句）

# 中间回合1
Brainy：（对 NPC 的话进行中文解释与情境引导）
用户选项:
（选项 A）
  Brainy：（选项 A 的反馈）
（选项 B）
  Brainy：（选项 B 的反馈）
（选项 C）
  Brainy：（选项 C 的反馈）
{{npc角色名}}：（英文原句）

# 中间回合2
... ...

# 结尾回合
Brainy：（任务完成说明）
```

#其他规则
在输出的文本中，如果涉及英文专有词汇的中文翻译，当该英文词汇在 {专有词汇} 表中时， 请使用  {专有词汇}  表中的中文翻译。

# 输出样例
以下是一个完整的输出样例：
```
# 开场回合
Luggage: Hello! Welcome to use our check-in service. Do you have anything to check in?

# 中间回合1
Brainy: 托运机器人向你打招呼，并询问你是否需要托运任何物品。现在你需要告诉机器人你想要托运的物品。

用户选项:
（选项 A）I'd like to check in my sword. [正确]
  Brainy: 太棒了！你正确地表达了想要托运的物品是剑。请大声朗读这句话！
（选项 B）I'm going to Planet Vala.
  Brainy: 这句话是回答目的地的，不是回答托运物品的。请再试一次，告诉机器人你想要托运的物品。
（选项 C）My flight number is 1221.
  Brainy: 这句话是回答航班号的，不是回答托运物品的。请再试一次，告诉机器人你想要托运的物品。

Luggage: OK. May I ask where you are going?

# 中间回合2

Brainy: 机器人已经知道你要托运的物品是剑，现在它询问你的目的地。你需要告诉机器人你要去的地方。

用户选项:
（选项 A）I'd like to check in my sword.
  Brainy: 这句话是回答托运物品的，不是回答目的地的。请再试一次，告诉机器人你的目的地。
（选项 B）I'm going to Planet Vala. [正确]
  Brainy: 太棒了！你正确地表达了你的目的地是 Planet Vala。请大声朗读这句话！
（选项 C）My flight number is 1221.
  Brainy: 这句话是回答航班号的，不是回答目的地的。请再试一次，告诉机器人你的目的地。

Luggage: Please tell me your flight information.

# 中间回合3

Brainy: 机器人已经知道你的目的地，现在它询问你的航班信息。你需要告诉机器人你的航班号。

用户选项：
（选项 A）I'd like to check in my sword.
  Brainy: 这句话是回答托运物品的，不是回答航班号的。请再试一次，告诉机器人你的航班号。
（选项 B）I'm going to Planet Vala.
  Brainy: 这句话是回答目的地的，不是回答航班号的。请再试一次，告诉机器人你的航班号。
（选项 C）My flight number is 1221. [正确]
  Brainy: 太棒了！你正确地表达了你的航班号是1221。请大声朗读这句话！

Luggage: Thank you. I've finished the check-in for your sword. Have a nice journey.

# 结尾回合
Brainy: 机器人已经完成了你的剑的托运手续，并祝你旅途愉快。任务成功完成！
```

# 输入
{用户任务描述}:
$用户任务描述$

{示例对话}:
$示例对话$

{专有词汇}:
$专有词汇$

# 输出
现在请按照上述信息和要求，直接输出目标内容。
如果 {用户任务描述} 或{示例对话}内容为空，则直接输出"无"。"""


# ── Step 2: promptInfo配置 ──
STEP2_PROMPT_INFO = r"""你当前的任务是为一类面向儿童的英语场景对话任务**生成角色扮演提示词**。
在这种任务中，系统会引导孩子与一个 AI 角色进行英语对话。任务目标是通过用户提问，从角色口中获取特定信息。角色通常扮演某种职能身份，掌握一组明确的信息，并在对话中根据用户的提问给出回应。当用户提出与任务目标相关的问题，并获得所有目标信息后，任务即视为完成。
你的目标是：基于输入信息，生成一段**提示词**，用于驱动语言模型扮演该角色，在用户询问时准确提供信息，其他时候基于人设进行正常的交流。在没有被问及时，不主动提供特定信息。

# 输入信息
基于以下输入，理解对话的设计，用于支持提示词生成：
{用户任务描述}:
$用户任务描述$
{示例对话}:
$示例对话$
{npc视角任务背景}:
$npc视角任务背景$
{npc知识库}:
$npc知识库$

# 你的任务
请根据输入信息，生成一段完整提示词，用于指导语言模型扮演该角色。提示词应确保：
- 角色只在用户提问时提供特定信息。其他时候，基于人设进行自然的回应。
- 回答内容严格基于知识库，不编造

# 输出结构
生成的提示词必须包含以下部分：
```
# 1. 角色（你要扮演谁）
角色描述
# 2. 任务（你如何参与到整个对话）
- 你只在用户提问时提供特定信息，不主动透露知识库中的信息。
- 如果用户提问的内容在知识库范围内，你会准确回答。
- 如果用户的问题不清晰，你会尝试确认问题。
- 一般情况下，一次最多只回答知识库中的一条信息，除非用户明确地分别提问到多条信息。
- 如果用户不提问，你会温和、自然地进行交谈。
# 3. 背景信息
引用{npc视角任务背景}
# 4. 知识库（你知道的信息）
引用{npc知识库}
# 5. 语言风格
- 使用 CEFR A1-A2 等级英语，避免复杂句，每句话不超过 10 个词，比如
  "我要去..." 可以用句子 I am going to ...，不用 My destination is ...，确保小学2-4年级的孩子能听懂
- 回答一般不超过两句话
- 避免重复表达，适当变换措辞
# 6. 开场白
你由你开始对话，你会说：（示例对话中的第一句话）
# 7. 回应方式（固定内容）
- 你只在用户提问时才根据知识库中的信息回答。其他时候，以符合你身份的方式，自然地进行交谈。
- 不主动提供知识库的信息
- 不主动提问题
```

# 输出样例
以下是部分效果良好的完整配置样例：
1.
```
# 1. 角色（你要扮演谁）
- 你是 Thomas，一个来自 Astro Ring 的间谍，冒充了真正的 Thomas。你擅长制造机器，但对芝士三明治不感兴趣。
- 你的语言风格简单直接，使用标准、正式的英语，水平为 CEFR A1/A2。
# 2. 任务（你如何参与到整个对话）
- 你只在用户提问时提供特定信息，不主动透露知识库中的信息。
- 如果用户提问的内容在知识库范围内，你会准确回答。
- 如果用户的问题不清晰，你会尝试确认问题。
- 如果用户不提问，你会温和、自然地进行交谈。
# 3. 背景信息
你是一个间谍，冒充了来自「艾斯环」的 Thomas。你只知道 Thomas 来自 Astro Ring，其他的信息你不是很了解。在你们到达 Vala 航站楼之后，Rebecca 和一个开拓者为你们提供了食物。结果里面有芝士，让你很不舒服。而这个开拓者(用户)想要找你聊聊，似乎发现了你的身份不对，你决定通过快速问答的方式来尽可能掩盖自己的身份。
# 4. 知识库（你知道的信息）
- 你不喜欢芝士三明治，你的家乡没有这种吃的
- 你来自 Astro Ring
- 你擅长制造机器
# 5. 语言风格（固定内容）
- 使用 CEFR A1-A2 等级英语，避免复杂句，每句话不超过 10 个词，比如
  "我要去..." 可以用句子 I am going to ...，不用 My destination is ...，确保小学2-4年级的孩子能听懂
- 回答一般不超过两句话
- 避免重复表达，适当变换措辞
# 6. 开场白
你由你开始对话，你会说："Hi… This cheese sandwich is special."
# 7. 回应方式（固定内容）
- 你只在用户提问时才根据知识库中的信息回答。其他时候，以符合你身份的方式，自然地进行交谈。
- 不主动提供知识库的信息
- 不主动提问题
```

2.
```
# 1. 角色（你要扮演谁）
- 你是 Terry，一名经验丰富的Sky Street Treasure Hunt比赛选手，已经连续多年参加这个比赛并多次获胜。你非常熟悉比赛的规则和技巧。

# 2. 任务（你如何参与到整个对话）
- 你只在用户提问时提供特定信息，不主动透露知识库中的信息。
- 如果用户提问的内容在知识库范围内，你会准确回答。
- 如果用户的问题不清晰，你会尝试确认问题。
- 如果用户不提问，你会温和、自然地进行交谈。

# 3. 背景信息
你是一名经验丰富的比赛选手(Sky Street Treasure Hunt比赛)，已经连续多年参加这个比赛并多次获胜。你非常熟悉比赛的规则和技巧，但因为比赛奖品对你非常重要，你本不愿意轻易分享这些信息。但用户他们已经承诺如果赢得比赛会选择隐身涂料作为奖品并与你分享，你才决定帮助他们。

# 4. 知识库（你知道的信息）
- 比赛共有三轮。
- 要赢得比赛，需要保持快速。
- 在每一轮中要注意时间限制，如果时间到了就会输掉比赛。

# 5. 语言风格
- 使用 CEFR A1-A2 等级英语，避免复杂句，每句话不超过 10 个词，比如
  "我要去..." 可以用句子 I am going to ...，不用 My destination is ...，确保小学2-4年级的孩子能听懂
- 回答一般不超过两句话
- 避免重复表达，适当变换措辞

# 6. 开场白
你由你开始对话，你会说："Alright, now that we have a deal, feel free to ask me anything about the competition!"

# 7. 回应方式（固定内容）
- 你只在用户提问时才根据知识库中的信息回答。其他时候，以符合你身份的方式，自然地进行交谈。
- 不主动提供知识库的信息
- 不主动提问题
```

# 输出
- 按照上述要求输出配置
- 若对话的配置信息为空，则不再执行任务，直接输出结果："无" """


# ── Step 3: 语音识别热词 ──
STEP3_ASR_HOTWORDS = r"""#任务
参考{用户任务描述}/{用户知识} /{示例对话}，给出一些用户可能在对话中使用的  高频 或者 有一定识别难度的英文词汇， 用","连接。

语音识别词汇的考虑范围:
- 交互中可能提及的专有名词(例如人名、地名、特定物品名称等)
- 用户目标判定相关的一些单词表达。
- 用户对话中可能频繁提及的重要词汇。
- 不要将"Vala"及相关词组放到热词中。

热词数量:
一般为 3~10个。

#输入
{用户任务描述} ：
$用户任务描述$
{用户知识}:
$用户任务知识$
{示例对话} ：
$示例对话$

#输出
现在，根据任务要求直接输出用","连接的合理英文热词。
如果 {用户任务描述} 或者{用户知识} 或者
{示例对话}  为空， 则输出 "无"。"""


# ── Step 4: taskData ──
STEP4_TASK_DATA = r"""# 字段内容要求
{
    "cType": "core_speaking_inquiry", // 固定
    "cId": {ID},
    "title": {用户任务描述}中的 【任务标题】,
    "taskDesc": {用户任务描述}中的【任务描述】,
    "sceneDesc": {用户任务描述}中的【任务背景】,
    "img": "", // 固定
    "key"(只基于{用户任务知识} 部分做结构化抽取，**不要改动或添加文本内容 以及 标点符号**):
    [
        {
            "desc": 类型1的描述, // 未提供则留空 ""
            "keyList": [
                {
                    "type": "default", // 固定
                    "content": 英文句,
                    "desc": 中文翻译
                },
                ... ...
            ]
        }，
        {
            "desc": 类型2的描述,
            "keyList": [
                {
                    "type": "default", // 固定
                    "content": 英文句,
                    "desc": 中文翻译
                },
                ... ...
            ]
        }
        ... ...
    ],
    "resourceMapping"(抽取出{角色配置}中的全部 name:id映射 可为空。 id必须为int类型):
    {
    样例:
    "Tom":43,
    "Jerry":10
    }
}

# 输出格式
单行json输出。
样例1:
{"cType": "core_speaking_inquiry", "cId": "", "title": "完成剑的托运", "taskDesc": "你需和托运机器人对话，回答关于航班和托运信息的问题，成功完成托运。", "sceneDesc": "开拓者，海盗突然来了，把我们抓飞船上了，我和 Scarlett 怕得要死!怀疑你们是海盗同伙，你说有我们乐队信息能证明。我问几个乐队问题，答不上就当你是海盗，别想救我们!", "img": "", "key": [{"desc": "提供信息", "keyList": [{"type": "default", "content": "I'd like to check in ....", "desc": "我需要托运 ...。 "}, {"type": "default", "content": "I'm going to ... .", "desc": "我要去 ... 。"}]}],"resourceMapping":{}}

样例2:
{"cType": "core_speaking_inquiry", "cId": "0000004", "title": "托运物品确认", "taskDesc": "快速回答托运机器人关于托运物品的问题，确认托运的物品是剑、告诉机器人你的目的地和航班号。", "sceneDesc": "你通过安检门的时候，被检测出背着剑，而武器不能随身携带。于是，在安检机器人的要求下，你需要根据机场的规定，去托运你的剑。", "img": "", "key": [{"desc": "机场托运", "keyList": [{"type": "default", "content": "I'd like to check in...", "desc": "我想托运……"}, {"type": "default", "content": "I'm going to...", "desc": "我要去……"}, {"type": "default", "content": "My flight number is...", "desc": "我的航班号是……"}]}],"resourceMapping":{}}

# 输入
{ID}:
$ID$
{用户任务描述}:
$用户任务描述$
{用户任务知识}:
$用户任务知识$
{角色配置}:
$角色配置$

# 输出
现在基于输入字段和格式要求，生成***可以直接解析的单行json文本***，***注意前后不要输出任何额外符号！！！***
如果{用户任务描述} 或 {用户任务知识}中没有有效文本，则输出"无"。"""


# ── Step 5: settingData ──
STEP5_SETTING_DATA = r"""# 字段内容要求
{
    "setting": {
        "npcName": {对话配置}中的 【对话角色】,
        "round": {对话配置}中 【回合设定】中的 最大回合,
        "checkRound": {对话配置}中 【回合设定】中的 检测回合
    }
}

# 输出格式
单行json输出。
样例:
{"setting": {"npcName": "luggage", "round": 5, "checkRound": 3}}

# 输入
{ID}:
$ID$
{对话配置}:
$对话配置$

# 输出
现在基于输入字段和格式要求，生成***可以直接解析的单行json文本***，***注意前后不要输出任何额外符号！！！***
如果{对话配置}中没有有效文本，则输出"无"。"""


# ── Step 6: configData ──
STEP6_CONFIG_DATA = r"""# 字段内容要求
如果{对话配置}或者{角色驱动配置}的配置内容是"无"，则直接输出"无"。

{
    "config": {
        "asrPrompt": {语音识别热词},
        "promptInfo": {
            "default": {角色驱动配置} ,
            "scene": {任务背景},
            "in_progress_goal": {通过规则}中的 # 通过规则 中的 "过程"规则,
            "final_goal": {通过规则}中的 # 通过规则 中的 "结果"规则,
            "reference_dialog": {示例对话} ,
            "user_task":  {用户任务描述} 中的 # 任务描述,
            "user_scene": {用户任务描述} 中的 # 任务背景,
            "user_knowledge": {用户任务知识} 中的 # 知识
        }
    }
}

# 输出格式
单行json输出。
样例(注意，前后不带任何额外符号。):
{"config": {"asrPrompt": "sword, vala, 1221", "promptInfo": {"default": "## 角色 ... ...","scene":"你是... ...","in_progress_goal":"...","final_goal":"...", ... ...}}}

注意: 如果{用户任务描述}或者{角色驱动配置}的配置内容是"无"，则直接输出"无"。

# 输入
{语音识别热词}:
$语音识别热词$
{任务背景}:
$任务背景$
{用户任务描述}:
$用户任务描述$
{用户任务知识}:
$用户任务知识$
{角色驱动配置}:
$角色驱动配置$
{示例对话}:
$示例对话$
{通过规则}:
$通过规则$

# 输出
现在基于输入字段和格式要求，生成***可以直接解析的单行json文本。前后不要输出任何额外符号！！！直接以"{"开头***    或者  直接输出 "无"。"""


# ── Step 7: learningData ──
STEP7_LEARNING_DATA = r"""# 字段内容要求
基于{学习配置}内容整理成以下结构。
{
  "learningPart": {
    "opening": {
      "type": "npc",
      "npcName": "xxx",
      "content": "xxx",  // 英文内容
    },
    "wrongReadLeadIn"(固定内容):{
       "type":"npc",
       "content":"请根据提词框内的内容大声地朗读这句话",
       "npcName":"Brainy"},
    "learning"(基于每回合的互动抽取，每个元素为1个回合): [
      {
        "question": {
          "type":"npc",
          "npcName": "xxx", // 一般为Brainy
          "content": "xxx"
        },
        "optionList": [
          {
            "option": "xxx",
            "feedback": {
                  "type":"npc",
                  "npcName": "xxx", // 一般为Brainy
                  "content": "xxx" // 中文反馈
            },
          },
          ... ...
        ],
        "answer": [int], // 正确答案对应的 optionList 数组索引
        "read": {
          "type": "user",
          "content": "xxx" // 正确选项的 英文内容， 用于用户跟读
        },
        "feedback": {
          "type": "npc",
          "npcName": "xxx",
          "content": "xxx"
        }
      },
      ... ...
    ],
    "closing": {
      "type": "npc",
      "npcName": "xxx", // 一般为Brainy
      "content": "xxx",
    },
  }
}
注意:  双引号字符串文本中如果出现英文双引号，需要使用转义符  确保最终输出是合法的json。
# 输出格式
单行json输出。
样例:
```
{"learningPart":{"opening":{"type":"npc","content":"What's on your shopping list?","npcName":"Smith"},"wrongReadLeadIn":{"type":"npc","content":"请根据提词框内的内容大声地朗读这句话","npcName":"Brainy"},"learning":[{"answer":[0],"optionList":[{"feedback":{"type":"npc","content":"太棒了！你正确地列出了购物清单上的物品。请大声朗读这句话！","npcName":"Brainy"},"option":"On my shopping list, there are a frame, skin, and glass ball."},{"feedback":{"type":"npc","content":"这句话是回答购买地点的，不是回答购物清单的。请再试一次，告诉 Smith 购物清单上的物品。","npcName":"Brainy"},"option":"I think we can go to the shopping street."},{"feedback":{"type":"npc","content":"这句话是回答预计花销的，不是回答购物清单的。请再试一次，告诉 Smith 购物清单上的物品。","npcName":"Brainy"},"option":"I think we're going to spend a lot of money."}],"question":{"type":"npc","npcName":"Brainy","content":"Smith 询问你制作新 Medic 的购物清单上有什么。现在你需要告诉 Smith 购物清单上的物品。"},"read":{"content":"On my shopping list, there are a frame, skin, and glass ball.","npcId":30,"type":"user"},"feedback":{"type":"npc","content":"Where will you buy these things?","npcName":"Smith"}},{"answer":[1],"optionList":[{"feedback":{"type":"npc","content":"这句话是回答购物清单的，不是回答购买地点的。请再试一次，告诉 Smith 你打算去哪里购买。","npcName":"Brainy"},"option":"On my shopping list, there are a frame, skin, and glass ball."},{"feedback":{"type":"npc","content":"太棒了！你正确地表达了购买地点是购物街。请大声朗读这句话！","npcName":"Brainy"},"option":"I think we can go to the shopping street."},{"feedback":{"type":"npc","content":"这句话是回答预计花销的，不是回答购买地点的。请再试一次，告诉 Smith 你打算去哪里购买。","npcName":"Brainy"},"option":"I think we're going to spend a lot of money."}],"question":{"type":"npc","npcName":"Brainy","content":"Smith 已经知道购物清单上的物品，现在他询问你购买这些物品的地点。你需要告诉 Smith 你打算去哪里购买。"},"read":{"content":"I think we can go to the shopping street.","npcId":30,"type":"user"},"feedback":{"type":"npc","content":"Will it be expensive?","npcName":"Smith"}},{"answer":[2],"optionList":[{"feedback":{"type":"npc","content":"这句话是回答购物清单的，不是回答预计花销的。请再试一次，告诉 Smith 你预计的花销情况。","npcName":"Brainy"},"option":"On my shopping list, there are a frame, skin, and glass ball."},{"feedback":{"type":"npc","content":"这句话是回答购买地点的，不是回答预计花销的。请再试一次，告诉 Smith 你预计的花销情况。","npcName":"Brainy"},"option":"I think we can go to the shopping street."},{"feedback":{"type":"npc","content":"太棒了！你正确地表达了预计的花销会很高。请大声朗读这句话！","npcName":"Brainy"},"option":"I think we're going to spend a lot of money."}],"question":{"type":"npc","npcName":"Brainy","content":"Smith 已经知道购买地点，现在他询问你预计的花销。你需要告诉 Smith 你预计的花销情况。"},"read":{"content":"I think we're going to spend a lot of money.","npcId":30,"type":"user"},"feedback":{"type":"npc","content":"Oh, I think so too.","npcName":"Smith"}}]},"closing":{"type":"npc","content":"Smith 已经了解了制作新 Medic 的购物清单、购买地点及预计花销。任务成功完成！","npcName":"Brainy"}}
```
# 输入
{学习配置}:
$学习配置$

# 输出
现在基于输入字段和格式要求，生成***可以直接解析的单行json文本***，***注意前后不要输出任何额外符号！！！***, 以"{"开始输出。
（如果{学习配置}中没有有效文本，则直接输出"无"。）"""


# ── Steps registry ──
CORE_SPEAKING_INQUIRY_STEPS = {
    "step1_learning_flow": {
        "template": STEP1_LEARNING_FLOW,
        "description": "学习流程配置",
        "output_type": "text",
    },
    "step2_prompt_info": {
        "template": STEP2_PROMPT_INFO,
        "description": "promptInfo配置",
        "output_type": "text",
    },
    "step3_asr_hotwords": {
        "template": STEP3_ASR_HOTWORDS,
        "description": "语音识别热词",
        "output_type": "text",
    },
    "step4_task_data": {
        "template": STEP4_TASK_DATA,
        "description": "taskData",
        "output_type": "json",
    },
    "step5_setting_data": {
        "template": STEP5_SETTING_DATA,
        "description": "settingData",
        "output_type": "json",
    },
    "step6_config_data": {
        "template": STEP6_CONFIG_DATA,
        "description": "configData",
        "output_type": "json",
    },
    "step7_learning_data": {
        "template": STEP7_LEARNING_DATA,
        "description": "learningData",
        "output_type": "json",
    },
}


def build_speaking_inquiry_prompt(step_key, replacements):
    """Build a prompt for a given step by replacing $placeholder$ tokens."""
    info = CORE_SPEAKING_INQUIRY_STEPS[step_key]
    prompt = info["template"]
    for key, val in replacements.items():
        prompt = prompt.replace(f"${key}$", str(val) if val else "")
    return prompt
