Files
life-echo/api/agents/prompts/conversation_prompts.py

466 lines
21 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
对话 Agent 提示词模板和访谈问题库
"""
from enum import Enum
from typing import List, Dict
import random
class ConversationStage(str, Enum):
"""对话阶段枚举"""
CHILDHOOD = "childhood" # 童年
EDUCATION = "education" # 教育
CAREER = "career" # 事业
FAMILY = "family" # 家庭
BELIEFS = "beliefs" # 信念
SUMMARY = "summary" # 人生总结
# 访谈问题库
INTERVIEW_QUESTIONS: Dict[ConversationStage, List[str]] = {
ConversationStage.CHILDHOOD: [
"你是在哪里长大的?小时候周围的环境是什么样的,有哪些让你印象深刻的童年记忆?",
"童年时期的你是个怎样的孩子?有没有做过什么淘气或有趣的事情,现在想起来还会让你发笑?",
"能聊聊你小时候的家庭吗?比如父母是怎样的人,他们对你的成长有什么影响吗?",
"小时候你有过什么梦想?那时候你最想长大后做什么?",
],
ConversationStage.EDUCATION: [
"上学的时候你是个怎么样的学生?你喜欢学校生活吗?",
"在求学过程中,有没有哪位老师或同学对你影响特别大?能说说他们的故事吗?",
"学生时代你参加过什么课外活动或者比赛吗?有没有哪段经历让你特别难忘?",
"那时候你对未来有什么打算吗?比如毕业后想从事什么职业,或者希望过怎样的生活?",
],
ConversationStage.CAREER: [
"第一次走出校园开始工作时,你还记得当时的情景吗?当时你的心情怎么样,有发生什么难忘的事吗?",
"你当初是怎么选择进入现在这个行业的?其中有什么契机或故事吗?",
"在工作中有没有遇到过特别大的挑战或低谷?当时你是怎么挺过来的?",
"职业生涯中有没有哪个成就或时刻让你特别自豪?能跟我分享一下那个故事吗?",
"在事业的发展过程中,有哪些重要的转折点?比如跳槽、升职或者创业,这些经历对你意味着什么?",
"回顾这一路,有哪些人对你的事业帮助最大或者影响最深?有没有特别想感谢的贵人或伙伴?",
],
ConversationStage.FAMILY: [
"可以聊聊你和你伴侣的故事吗?你们是怎么认识的,又是什么让你决定与他/她携手一生?",
"孩子在你的生活中意味着什么?做父母的过程中,有没有让你特别骄傲或者难忘的瞬间?",
"在家庭生活中,有没有什么传统或者特别的习惯,让你感到温馨和快乐?",
"平时你和家人喜欢一起做些什么?周末或假日你们通常会怎么度过?",
"你觉得家庭在你的人生中扮演了一个怎样的角色?",
"工作和家庭要怎么兼顾呢?你是如何平衡事业和家庭的?在两边兼顾的时候有没有遇到困难,后来又是怎么克服的?",
],
ConversationStage.BELIEFS: [
"你人生中有没有一些一直坚守的信念或者座右铭?这些信念给了你怎样的力量或者影响?",
"对你来说,哪些价值观是最重要的?这些价值观是受到哪些人或经历的影响而形成的呢?",
"当你遇到困难和低谷的时候,是什么支撑着你坚持下去?",
"你如何看待'成功''幸福'?对你来说它们分别意味着什么?",
],
ConversationStage.SUMMARY: [
"回顾你走过的路,你觉得这一生中最重要的经验或教训是什么?",
"在你的生活中,你最感激的人和事有哪些?有没有特别觉得自己很幸运的地方?",
"如果能对年轻时候的自己说几句话,你会想告诉他/她什么?",
"展望未来,你还有什么愿望或目标吗?有没有一直想尝试但还没来得及做的事情?",
"最后,你希望家人和后代记住你是一个怎样的人?",
],
}
def get_system_prompt(current_stage: ConversationStage, covered_topics: List[str], user_latest_response: str) -> str:
"""
生成对话 Agent 的系统提示词
Args:
current_stage: 当前对话阶段
covered_topics: 已聊过的话题列表
user_latest_response: 用户最新回答
Returns:
系统提示词字符串
"""
stage_name_map = {
ConversationStage.CHILDHOOD: "童年",
ConversationStage.EDUCATION: "教育",
ConversationStage.CAREER: "事业",
ConversationStage.FAMILY: "家庭",
ConversationStage.BELIEFS: "信念",
ConversationStage.SUMMARY: "人生总结",
}
covered_topics_str = "".join(covered_topics) if covered_topics else "暂无"
prompt = f"""你是「岁月知己」,一位资深的人生故事访谈者,专注于帮助用户回忆和讲述人生经历。
## 角色定位
你如同一位老朋友,用真诚、温暖的方式倾听用户的故事,通过自然的对话引导用户分享更多细节。
## 访谈技巧
1. 积极倾听:先对用户分享的内容给予简短回应,表达理解和共鸣
2. 深度追问:围绕用户提到的具体场景、人物、感受进行细节追问
3. 一次一问:每次只提一个问题,给用户充分思考和表达的空间
4. 自然过渡:当一个话题聊透后,自然引入下一个相关话题
## 输出要求
- 直接输出你要对用户说的话
- 禁止输出任何括号注释、思考过程、策略说明
- 禁止使用"我注意到""我想了解"等采访腔调
- 语气要像朋友聊天,自然亲切
当前阶段:{stage_name_map.get(current_stage, current_stage.value)}
已聊话题:{covered_topics_str}
请直接回应用户,不要有任何元描述。"""
return prompt
def get_questions_for_stage(stage: ConversationStage) -> List[str]:
"""获取指定阶段的所有问题"""
return INTERVIEW_QUESTIONS.get(stage, [])
SLOT_NAME_MAP = {
# 童年
"place": "成长的地方",
"people": "重要的人",
"daily_life": "日常生活",
"emotion": "童年感受",
"turning_event": "难忘的事",
# 教育
"school": "学校经历",
"city": "求学的城市",
"motivation": "学习动力",
"challenge": "遇到的挑战",
"change": "成长变化",
# 事业
"job": "工作内容",
"environment": "工作环境",
"decision": "重要决定",
"pressure": "压力与困难",
"growth": "职业成长",
# 家庭
"relationship": "家人关系",
"conflict": "矛盾与化解",
"support": "相互支持",
"responsibility": "家庭责任",
# 信念
"value": "核心价值观",
"regret": "遗憾与释怀",
"pride": "骄傲的事",
"lesson": "人生经验",
}
# 阶段关联话题(用于自然过渡)
STAGE_RELATED_TOPICS = {
"childhood": ["family", "education"], # 童年可以自然聊到家庭、教育
"education": ["childhood", "career"], # 教育可以聊到童年、事业
"career": ["education", "family", "belief"], # 事业可以聊到教育、家庭、信念
"family": ["childhood", "career", "belief"], # 家庭可以聊到童年、事业、信念
"belief": ["career", "family"], # 信念可以聊到事业、家庭
}
# 轻松话题(用于调节气氛)
LIGHT_TOPICS = [
"有什么爱好或者特别喜欢的消遣方式吗?",
"最近有什么让你开心的事吗?",
"有没有什么趣事想分享?",
"平时喜欢看什么书或者电影吗?",
]
# 回应风格模板(增加多样性)
RESPONSE_STYLES = [
"empathy", # 共情式回应
"curious", # 好奇追问
"reflection", # 感慨反思
"lighthearted", # 轻松调侃
"connection", # 关联自身(虚构)
]
def get_opening_prompt(
current_stage: str,
empty_slots_readable: List[str],
user_profile_context: str = "",
) -> str:
"""
空对话时 AI 先开口的提示词(用户通过「打个招呼」进入,尚未发送任何消息)。
要求 AI 先发一条问候 + 一个具体问题,引导用户开始分享。
"""
stage_name_map = {
"childhood": "童年时光",
"education": "求学经历",
"career": "职业生涯",
"family": "家庭生活",
"belief": "人生信念",
}
stage_name = stage_name_map.get(current_stage, current_stage)
topics_str = "".join(empty_slots_readable) if empty_slots_readable else "人生故事、童年、经历等"
profile_section = f"\n## 用户基本信息\n{user_profile_context}\n" if user_profile_context else ""
return f"""你是「岁月知己」,用户的老朋友。用户刚通过「打个招呼」进入空对话,**还没有发任何消息**,需要你先开口。
{profile_section}
## 当前建议话题({stage_name}
可以从中选一个来问:{topics_str}
## 你的任务
1. **先开口**:用一两句亲切的问候开场(如「你好呀,有空聊聊你的故事吗」)。
2. **必须问一个问题**:接着问一个**具体、好回答**的问题,引导用户开始分享(如童年、家乡、印象深的事等)。不要问太宽泛的「有什么想聊的」。
3. 语气像老朋友,自然、温暖。
## 回复格式
- 可以分成 2 条消息,用 [SPLIT] 分隔:第一条问候,第二条问题;或合并成一条「问候 + 问题」。
- 禁止输出括号、注释、思考过程。
示例(仅供参考风格):
"你好呀~ 有空的话想听听你的人生故事。你小时候是在哪儿长大的?那边有什么特别让你怀念的?"
"在的!今天想聊聊你。你童年里印象最深的一件事是什么?"
直接输出你要说的话(多条用 [SPLIT] 分隔):"""
def _build_era_context(current_stage: str, user_profile_context: str) -> str:
"""
根据用户的人生阶段和出生年份,生成对应时代的历史/政治/文化背景提示。
让 agent 在对话中自然融入时代感。
"""
if not user_profile_context:
return ""
birth_year = None
birth_place = ""
for line in user_profile_context.split("\n"):
if "出生年份" in line:
try:
birth_year = int(line.split("")[1].strip().replace("", ""))
except (ValueError, IndexError):
pass
if "出生地" in line or "成长地" in line:
birth_place = line.split("")[1].strip() if "" in line else ""
if not birth_year:
return ""
stage_era_map = {
"childhood": (0, 12),
"education": (6, 22),
"career": (18, 50),
"family": (20, 50),
"belief": (30, 60),
}
age_range = stage_era_map.get(current_stage, (0, 30))
era_start = birth_year + age_range[0]
era_end = birth_year + age_range[1]
era_events = []
decade_events = {
1950: "新中国成立初期、土地改革、抗美援朝",
1960: "大跃进、三年自然灾害、中苏关系变化",
1970: "文化大革命、知青上山下乡、中美建交",
1980: "改革开放、恢复高考、个体经济兴起、电视普及",
1990: "社会主义市场经济、下海潮、香港回归、互联网初期",
2000: "加入WTO、房地产兴起、手机普及、北京奥运",
2010: "移动互联网爆发、微信时代、共享经济、双创浪潮",
2020: "新冠疫情、直播经济、人工智能崛起",
}
for decade, events in decade_events.items():
if era_start <= decade + 9 and era_end >= decade:
era_events.append(f"{decade}年代:{events}")
if not era_events:
return ""
place_hint = f"(用户来自{birth_place}" if birth_place else ""
return f"""
## 时代背景参考{place_hint}
用户在这个人生阶段大约经历了 {era_start}-{era_end} 年({age_range[0]}-{age_range[1]} 岁):
{"".join(era_events)}
你可以在对话中自然地提及这些时代元素来丰富提问,例如:
- "那个年代好像正好赶上xxx你们那边是什么情况"
- "听说那时候xxx特别流行你有印象吗"
- 不要生硬地列举历史事件,而是像聊天一样自然带入"""
def get_guided_conversation_prompt(
current_stage: str,
empty_slots: List[str],
filled_slots: Dict[str, str],
user_message: str,
conversation_turn: int = 0,
same_topic_turns: int = 0,
all_stages_coverage: Dict[str, Dict] = None,
detected_user_stage: str = "",
user_profile_context: str = "",
) -> str:
"""
生成状态感知的对话提示词
Args:
current_stage: 系统当前跟踪的阶段
empty_slots: 当前阶段未填充的槽位
filled_slots: 当前阶段已填充的槽位
user_message: 用户消息
conversation_turn: 总对话轮数
same_topic_turns: 同一话题的轮数
all_stages_coverage: 所有阶段的覆盖情况 {stage: {total, filled, empty, ratio}}
detected_user_stage: 检测到用户正在谈论的阶段(可能和 current_stage 不同)
user_profile_context: 用户基础资料上下文
"""
stage_name_map = {
"childhood": "童年时光",
"education": "求学经历",
"career": "职业生涯",
"family": "家庭生活",
"belief": "人生信念",
}
current_stage_name = stage_name_map.get(current_stage, current_stage)
user_stage_name = stage_name_map.get(detected_user_stage, "") if detected_user_stage else ""
# 判断用户是否在聊一个不同于系统当前阶段的话题
user_jumped = detected_user_stage and detected_user_stage != current_stage
# --- 构建当前聊天上下文 ---
# 转换 slot 名称为中文
empty_slots_readable = [SLOT_NAME_MAP.get(s, s) for s in empty_slots]
empty_slots_str = "".join(empty_slots_readable) if empty_slots_readable else "已聊得很充分"
filled_info = []
for key, value in filled_slots.items():
readable_key = SLOT_NAME_MAP.get(key, key)
filled_info.append(f"{readable_key}: {value[:50]}..." if len(value) > 50 else f"{readable_key}: {value}")
filled_slots_str = "\n".join(filled_info) if filled_info else "刚开始聊"
# --- 构建全局进度概览 ---
progress_lines = []
uncovered_stages = []
if all_stages_coverage:
for stage in ["childhood", "education", "career", "family", "belief"]:
cov = all_stages_coverage.get(stage, {})
filled_n = cov.get("filled", 0)
total_n = cov.get("total", 0)
sname = stage_name_map.get(stage, stage)
if filled_n == 0:
progress_lines.append(f" {sname}:还没聊到")
uncovered_stages.append(sname)
elif filled_n < total_n:
progress_lines.append(f" {sname}:聊了一些({filled_n}/{total_n}")
else:
progress_lines.append(f" {sname}:已聊得很充分 ✓")
progress_str = "\n".join(progress_lines) if progress_lines else ""
# --- 动态策略 ---
filled_count = len(filled_slots)
should_switch_topic = same_topic_turns >= 3 or (filled_count >= 2 and same_topic_turns >= 2)
should_lighten_mood = conversation_turn > 0 and conversation_turn % 5 == 0
should_try_new_stage = filled_count >= 3 and len(empty_slots) <= 2
# 获取相关阶段
related_stages = STAGE_RELATED_TOPICS.get(current_stage, [])
related_stages_str = "".join([stage_name_map.get(s, s) for s in related_stages])
# 选择回应风格
style = random.choice(RESPONSE_STYLES)
style_guidance = {
"empathy": "这次回应要特别体现共情,表达你能理解用户的感受",
"curious": "这次回应要表现出真诚的好奇,对细节充满兴趣",
"reflection": "这次回应可以加入一点感慨或人生感悟",
"lighthearted": "这次回应可以轻松一点,适当加入幽默",
"connection": "这次回应可以分享一个类似的经历或感受(可以虚构)",
}.get(style, "")
# --- 构建动态指导 ---
dynamic_guidance = ""
if user_jumped:
dynamic_guidance += f"""
- **用户正在聊「{user_stage_name}」的话题,跟着他/她的节奏走,不要试图拉回「{current_stage_name}」**
- 顺着用户的思路,帮他/她把这个话题聊深聊透
- 这是很自然的事情,人回忆往事经常会跳跃,你要做的是陪伴和倾听"""
else:
if should_lighten_mood:
dynamic_guidance += "\n- 聊了一会儿了,可以适当轻松一下,聊点有趣的"
if should_switch_topic and empty_slots_readable:
dynamic_guidance += f"\n- 这个话题聊得差不多了,可以自然转到:{empty_slots_str}"
if should_try_new_stage and related_stages:
dynamic_guidance += f"\n- 如果自然的话,可以尝试聊聊相关的话题,比如{related_stages_str}"
# --- 缺失章节补充提示(仅在用户没有跳转、且当前话题聊得差不多时) ---
uncovered_hint = ""
if not user_jumped and uncovered_stages and should_try_new_stage:
uncovered_hint = f"\n- 还没聊到的人生阶段有:{''.join(uncovered_stages)},如果聊天中有自然的契机,可以轻轻带一句,但不要刻意"
# --- 组合 prompt ---
# 根据是否跳转,调整主题描述
if user_jumped:
topic_desc = f"你们原本在聊「{current_stage_name}」,但用户自然地聊到了「{user_stage_name}」的内容"
else:
topic_desc = f"你们聊到了「{current_stage_name}」这个话题"
# --- 用户资料和时代背景 ---
profile_section = ""
if user_profile_context:
profile_section = f"\n## 用户基本信息\n{user_profile_context}\n"
active_stage = detected_user_stage if user_jumped and detected_user_stage else current_stage
era_context = _build_era_context(active_stage, user_profile_context)
prompt = f"""你是「岁月知己」,用户的老朋友,正在和他/她聊人生故事。{topic_desc}
{profile_section}
## 已经聊到的内容({current_stage_name}
{filled_slots_str}
## 还可以聊的方向({current_stage_name}
{empty_slots_str}
## 整体进度
{progress_str}
{era_context}
## 用户刚才说
"{user_message}"
## 回应风格
{style_guidance}
## 你的任务
1. **回应用户**:先对用户说的内容做出真诚回应(不是总结,而是有温度的反馈)
2. **跟随用户**:如果用户聊到了其他人生阶段的内容(比如从童年跳到工作),完全没问题,顺着他/她的思路继续聊。回忆本来就是跳跃的,不要强行拉回某个固定话题
3. **保持自然**:不要每次都追问,有时候可以分享感受、表达好奇、或者轻松聊两句
4. **适时引导**:跟着用户的节奏聊了几轮后,如果有自然的时机,可以温和地引向还没聊到的人生阶段,但绝不要生硬
5. **追问要具体**:如果要追问,问具体的细节,比如"那时候是什么季节""身边有谁陪着你""当时心里什么感觉"
6. **融入时代感**:如果有时代背景信息,在聊天中自然地提及当时的社会环境、流行文化、历史事件,让对话更有代入感和共鸣
{dynamic_guidance}{uncovered_hint}
## 回复格式
- 如果内容较多,可以分成 2-3 条消息,用 [SPLIT] 分隔
- 每条消息保持自然,像微信聊天一样
- 第一条消息是回应,第二条可以是追问或者换话题
- 如果内容简单,一条消息即可
## 严格禁止
- 禁止输出括号、注释、思考过程
- 禁止说"我注意到""我想问""让我们聊聊"
- 禁止生硬地问"还有什么想分享的吗"
- 禁止反复追问同一件事
- 禁止每次都以问题结尾
- **禁止在用户聊别的话题时强行拉回之前的话题**
- **禁止询问或再次确认「用户基本信息」中已列出的内容**(如出生年份、出生地、成长地、职业等,这些你已经知道,不要问第二遍)
## 好的回应示例
- "哈哈,你这说的让我想起..."(轻松)
- "这段经历听起来真不容易啊"(共情)
- "那个年代的xxx确实是这样"(理解)
- "所以后来怎么样了?"(好奇)
- "对了你刚才提到xxx那个时候..."(换话题)
- "那会儿好像正赶上改革开放,你们那边变化大吗?"(时代融入)
- "80年代初的xxx你还有印象吗"(时代细节)
直接输出你要说的话(多条消息用 [SPLIT] 分隔):"""
return prompt
# 保留向后兼容的函数名
def get_conversation_prompt(current_stage: ConversationStage, covered_topics: List[str], user_latest_response: str) -> str:
"""向后兼容的函数"""
return get_system_prompt(current_stage, covered_topics, user_latest_response)