Merge branch 'feat/improve-agent-prompt'

This commit is contained in:
penghanyuan
2026-03-01 10:12:23 +01:00
parent a69d5c625c
commit c1e2fb31a0
11 changed files with 644 additions and 65 deletions

View File

@@ -19,6 +19,14 @@ from .memory_prompts import (
CHAPTER_ORDER,
STAGE_TO_ORDER,
)
from .profile_prompts import (
get_profile_greeting_prompt,
get_profile_extraction_prompt,
get_profile_followup_prompt,
format_user_profile_context,
get_missing_profile_fields,
PROFILE_FIELD_NAMES,
)
__all__ = [
"ConversationStage",
@@ -35,5 +43,11 @@ __all__ = [
"CHAPTER_CATEGORIES",
"CHAPTER_ORDER",
"STAGE_TO_ORDER",
"get_profile_greeting_prompt",
"get_profile_extraction_prompt",
"get_profile_followup_prompt",
"format_user_profile_context",
"get_missing_profile_fields",
"PROFILE_FIELD_NAMES",
]

View File

@@ -173,6 +173,73 @@ RESPONSE_STYLES = [
]
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],
@@ -182,6 +249,7 @@ def get_guided_conversation_prompt(
same_topic_turns: int = 0,
all_stages_coverage: Dict[str, Dict] = None,
detected_user_stage: str = "",
user_profile_context: str = "",
) -> str:
"""
生成状态感知的对话提示词
@@ -195,6 +263,7 @@ def get_guided_conversation_prompt(
same_topic_turns: 同一话题的轮数
all_stages_coverage: 所有阶段的覆盖情况 {stage: {total, filled, empty, ratio}}
detected_user_stage: 检测到用户正在谈论的阶段(可能和 current_stage 不同)
user_profile_context: 用户基础资料上下文
"""
stage_name_map = {
"childhood": "童年时光",
@@ -286,8 +355,16 @@ def get_guided_conversation_prompt(
else:
topic_desc = f"你们聊到了「{current_stage_name}」这个话题"
prompt = f"""你是「岁月知己」,用户的老朋友,正在和他/她聊人生故事。{topic_desc}
# --- 用户资料和时代背景 ---
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}
@@ -296,7 +373,7 @@ def get_guided_conversation_prompt(
## 整体进度
{progress_str}
{era_context}
## 用户刚才说
"{user_message}"
@@ -309,6 +386,7 @@ def get_guided_conversation_prompt(
3. **保持自然**:不要每次都追问,有时候可以分享感受、表达好奇、或者轻松聊两句
4. **适时引导**:跟着用户的节奏聊了几轮后,如果有自然的时机,可以温和地引向还没聊到的人生阶段,但绝不要生硬
5. **追问要具体**:如果要追问,问具体的细节,比如"那时候是什么季节""身边有谁陪着你""当时心里什么感觉"
6. **融入时代感**:如果有时代背景信息,在聊天中自然地提及当时的社会环境、流行文化、历史事件,让对话更有代入感和共鸣
{dynamic_guidance}{uncovered_hint}
## 回复格式
@@ -331,6 +409,8 @@ def get_guided_conversation_prompt(
- "那个年代的xxx确实是这样"(理解)
- "所以后来怎么样了?"(好奇)
- "对了你刚才提到xxx那个时候..."(换话题)
- "那会儿好像正赶上改革开放,你们那边变化大吗?"(时代融入)
- "80年代初的xxx你还有印象吗"(时代细节)
直接输出你要说的话(多条消息用 [SPLIT] 分隔):"""

View File

@@ -2,6 +2,7 @@
回忆录整理 Agent 提示词模板
"""
import json
from typing import Optional
# 章节分类映射
CHAPTER_CATEGORIES = {
@@ -177,42 +178,89 @@ def get_state_extraction_prompt(user_message: str, current_stage: str, stage_slo
"""
def get_creative_title_prompt(stage: str, emotion: str, slots: dict) -> str:
"""生成有创意的章节标题"""
def _build_age_hint(stage: str, birth_year: Optional[int] = None) -> str:
"""根据人生阶段和出生年份推算大致年龄区间"""
if not birth_year:
return ""
stage_age_ranges = {
"childhood": (0, 12),
"education": (6, 22),
"career": (18, 60),
"career_early": (18, 30),
"career_achievement": (25, 55),
"career_challenge": (20, 55),
"family": (20, 60),
"belief": (30, 70),
"beliefs": (30, 70),
"summary": (50, 80),
}
age_range = stage_age_ranges.get(stage)
if not age_range:
return ""
year_start = birth_year + age_range[0]
year_end = birth_year + age_range[1]
return f"大约 {year_start}-{year_end} 年({age_range[0]}-{age_range[1]} 岁)"
def get_creative_title_prompt(
stage: str,
emotion: str,
slots: dict,
user_profile: str = "",
birth_year: Optional[int] = None,
) -> str:
"""生成有创意的章节标题,包含年龄/时间信息"""
age_hint = _build_age_hint(stage, birth_year)
profile_section = f"\n用户基本信息:\n{user_profile}" if user_profile else ""
time_section = f"\n时间参考:{age_hint}" if age_hint else ""
return f"""{get_system_prompt()}
请根据阶段和情绪生成 1 个有创意的章节标题。
阶段:{stage}
情绪:{emotion}
可用信息:{slots}
可用信息:{slots}{profile_section}{time_section}
要求:
1. 标题 12-18 字以内
1. 标题格式:「时间标注 · 标题正文」
- 时间标注用年龄或年代表示,如"6-12岁""1980年代""二十出头"
- 标题正文 12-18 字以内
2. 情绪 + 人生阶段 + 意象
3. 示例风格:
- 《那个夏天,我第一次离开家
- 《在陌生城市站稳脚跟
- 《不是所有选择都被理解
- 《慢下来,人生开始发声》
- 《6-12岁 · 那条巷子尽头的蝉鸣
- 《18岁 · 第一次离开家的夏天
- 《25-35岁 · 在陌生城市站稳脚跟
- 《四十不惑 · 慢下来,人生开始发声》
- 《1990年代 · 不是所有选择都被理解》
只输出标题文字,不要加引号或其他内容
只输出标题文字,不要加引号或书名号
"""
def get_narrative_prompt(stage: str, slots: dict, new_content: str, existing_content: str = "") -> str:
def get_narrative_prompt(
stage: str,
slots: dict,
new_content: str,
existing_content: str = "",
user_profile: str = "",
birth_year: Optional[int] = None,
) -> str:
"""将新对话改写为叙述(只输出新内容的改写,不重复已有内容)"""
# 只取已有内容的末尾作为衔接上下文
context_tail = ""
if existing_content:
context_tail = existing_content[-300:] if len(existing_content) > 300 else existing_content
context_section = f"\n\n【衔接上下文(已有内容的末尾,仅供参考衔接,不要重复)】:\n{context_tail}" if context_tail else ""
profile_section = f"\n\n用户基本信息:\n{user_profile}" if user_profile else ""
age_hint = _build_age_hint(stage, birth_year)
time_section = f"\n时间参考:{age_hint}" if age_hint else ""
return f"""{get_system_prompt()}
请将以下新的对话内容改写为第一人称文学叙述。
阶段:{stage}
可用信息:{slots}
可用信息:{slots}{profile_section}{time_section}
新的对话内容:
{new_content}
@@ -225,6 +273,7 @@ def get_narrative_prompt(stage: str, slots: dict, new_content: str, existing_con
4. 如果有衔接上下文,确保新内容与之自然衔接(语气、时间线连贯)
5. 语气自然,有情绪
6. 在适合配图的地方插入图片占位符
7. 如果有用户的基本信息(出生地、成长地等),在叙述中自然融入地域文化和时代背景
## 图片占位符格式
在描述场景、人物、重要时刻的段落后,插入图片占位符,格式为:

View File

@@ -0,0 +1,163 @@
"""
用户基础资料收集提示词
"""
from typing import Dict, List, Optional
PROFILE_FIELD_NAMES = {
"birth_year": "出生年份",
"birth_place": "出生地",
"grew_up_place": "成长地",
"occupation": "职业",
}
def get_profile_greeting_prompt(missing_fields: List[str], nickname: str = "") -> str:
"""生成初次见面、收集基础资料的引导提示词"""
missing_names = [PROFILE_FIELD_NAMES[f] for f in missing_fields if f in PROFILE_FIELD_NAMES]
missing_str = "".join(missing_names)
name_part = f"{nickname}" if nickname else ""
return f"""你是「岁月知己」,一位温暖真诚的人生故事访谈者。你正在和用户初次见面{name_part}
在正式聊人生故事之前,你需要先了解一些基本信息。还需要了解的信息有:{missing_str}
## 你的任务
用自然、亲切的方式,像老朋友聊天一样,向用户询问这些基础信息。
## 规则
1. 不要一次问所有问题,每次只问 1-2 个
2. 如果用户已经在对话中提到了某些信息,不要重复问
3. 用口语化、亲切的方式提问
4. 当所有信息都收集完后,自然过渡到人生故事访谈
## 提问示例
- "你是哪一年出生的呀?"
- "你是在哪里出生的?小时候也是在那里长大的吗?"
- "你现在是做什么工作的呀?或者之前主要从事什么职业?"
## 严格禁止
- 禁止输出括号注释、思考过程
- 禁止说"我需要收集信息"之类的机械话
- 禁止一次列出所有问题
## 回复格式
- 如果内容较多,可以用 [SPLIT] 分隔成多条消息
- 像微信聊天一样自然
直接输出你要说的话:"""
def get_profile_extraction_prompt(user_message: str, missing_fields: List[str]) -> str:
"""从用户回答中提取基础资料信息"""
missing_names = {f: PROFILE_FIELD_NAMES[f] for f in missing_fields if f in PROFILE_FIELD_NAMES}
return f"""请从用户的回答中提取基础资料信息。
用户的回答:
"{user_message}"
需要提取的字段(只提取确实提到的):
{missing_names}
请返回 JSON 格式,只包含确实提到的字段:
{{
"birth_year": 1965,
"birth_place": "湖南长沙",
"grew_up_place": "湖南长沙",
"occupation": "教师"
}}
规则:
1. birth_year 必须是整数(四位数年份),如"65年出生"应转为 1965
2. 如果用户说"在老家长大"而之前提到了出生地grew_up_place 可以和 birth_place 相同
3. 只提取明确提到的信息,不要猜测
4. 如果没有提取到任何信息,返回空对象 {{}}
只返回 JSON不要其他内容。"""
def get_profile_followup_prompt(
missing_fields: List[str],
filled_fields: Dict[str, str],
user_message: str,
nickname: str = "",
) -> str:
"""在收集资料过程中的跟进提问"""
missing_names = [PROFILE_FIELD_NAMES[f] for f in missing_fields if f in PROFILE_FIELD_NAMES]
missing_str = "".join(missing_names) if missing_names else ""
filled_info = []
for key, value in filled_fields.items():
name = PROFILE_FIELD_NAMES.get(key, key)
filled_info.append(f"{name}: {value}")
filled_str = "\n".join(filled_info) if filled_info else "暂无"
if not missing_names:
return f"""你是「岁月知己」。用户的基本信息已经收集完毕:
{filled_str}
用户刚才说:"{user_message}"
请对用户的回答做出温暖的回应,然后自然地过渡到人生故事的访谈。
可以说类似"了解了!那我们现在开始聊聊你的人生故事吧"这样的话,然后问一个关于童年的问题作为开场。
回复格式:多条消息用 [SPLIT] 分隔。
直接输出你要说的话:"""
return f"""你是「岁月知己」,正在和用户聊天收集基本信息。
已知信息:
{filled_str}
还需要了解:{missing_str}
用户刚才说:"{user_message}"
请先对用户说的内容做出自然回应,然后继续询问还未了解的信息(每次问 1-2 个)。
语气要像朋友聊天一样自然亲切。
严格禁止:
- 禁止输出括号注释、思考过程
- 禁止说"我注意到""我需要了解"
回复格式:多条消息用 [SPLIT] 分隔。
直接输出你要说的话:"""
def format_user_profile_context(
birth_year: Optional[int] = None,
birth_place: Optional[str] = None,
grew_up_place: Optional[str] = None,
occupation: Optional[str] = None,
) -> str:
"""将用户基础信息格式化为上下文字符串,供其他 agent 使用"""
parts = []
if birth_year:
parts.append(f"出生年份:{birth_year}")
if birth_place:
parts.append(f"出生地:{birth_place}")
if grew_up_place:
parts.append(f"成长地:{grew_up_place}")
if occupation:
parts.append(f"职业:{occupation}")
return "\n".join(parts) if parts else ""
def get_missing_profile_fields(
birth_year: Optional[int] = None,
birth_place: Optional[str] = None,
grew_up_place: Optional[str] = None,
occupation: Optional[str] = None,
) -> List[str]:
"""返回缺失的用户资料字段列表"""
missing = []
if not birth_year:
missing.append("birth_year")
if not birth_place:
missing.append("birth_place")
if not grew_up_place:
missing.append("grew_up_place")
if not occupation:
missing.append("occupation")
return missing