- Drop interview_reply_length and utterance_substance; always run stage LLM and memory retrieval when enabled; trim Settings fields and .env.example. - Replace guided/opening prompts with compact fact blocks plus unified behavior guidance; slim background_voice and persona to tone hints. - InterviewAgent uses fixed chat_interview max_tokens/chars/segments. Also includes stacked work: profile followup/extract path, evaluation rubric and judge schema updates, transcript SPLIT handling in execution service, user export markdown split tests, and golden case fixture.
190 lines
6.9 KiB
Python
190 lines
6.9 KiB
Python
"""
|
||
用户基础资料收集提示词
|
||
"""
|
||
|
||
from typing import Dict, List, Optional
|
||
|
||
from app.agents.chat.output_rules import chat_output_rules
|
||
|
||
|
||
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. 当所有信息都收集完后,自然过渡到人生故事访谈
|
||
|
||
## 提问示例
|
||
- "你是哪一年出生的呀?"
|
||
- "你是在哪里出生的?小时候也是在那里长大的吗?"
|
||
- "你现在是做什么工作的呀?或者之前主要从事什么职业?"
|
||
|
||
## 严格禁止
|
||
- {chat_output_rules()}
|
||
- 禁止说"我需要收集信息"之类的机械话
|
||
- 禁止一次列出所有问题
|
||
|
||
## 回复格式
|
||
- 如果内容较多,可以用 [SPLIT] 分隔成多条消息
|
||
- 像微信聊天一样自然
|
||
|
||
直接输出你要说的话:"""
|
||
|
||
|
||
def get_profile_extraction_prompt(
|
||
user_message: str,
|
||
missing_fields: List[str],
|
||
recent_dialogue: Optional[str] = None,
|
||
) -> str:
|
||
"""从用户回答中提取基础资料信息(可包含最近几轮对话,避免漏提)"""
|
||
missing_names = {
|
||
f: PROFILE_FIELD_NAMES[f] for f in missing_fields if f in PROFILE_FIELD_NAMES
|
||
}
|
||
|
||
dialogue_section = ""
|
||
if recent_dialogue and recent_dialogue.strip():
|
||
dialogue_section = f"""
|
||
最近几轮对话(可从用户任一轮回答中提取):
|
||
{recent_dialogue.strip()}
|
||
|
||
"""
|
||
return f"""请从以下内容中提取用户已提到的基础资料信息。{dialogue_section}用户本轮回答:
|
||
"{user_message}"
|
||
|
||
需要提取的字段(只提取确实在对话中出现过的):
|
||
{missing_names}
|
||
|
||
输出示例(只含确实提到的字段;无则 {{}}):
|
||
{{
|
||
"birth_year": 1965,
|
||
"birth_place": "湖南长沙",
|
||
"grew_up_place": "湖南长沙",
|
||
"occupation": "教师"
|
||
}}
|
||
|
||
规则:
|
||
1. birth_year 填整数(四位数),如"65年出生"转为 1965
|
||
2. 如果用户在任一轮说过出生地/成长地/职业等,都要提取
|
||
3. 只提取明确提到的信息,不要猜测
|
||
4. 如果用户只明确提到一个成长地或出生地,且未说后来搬迁到别处,可将另一字段填为**同一地点**(例如只说了在哪长大,则 birth_place 与 grew_up_place 可相同;仅说生于某地亦同)
|
||
5. 如果没有提取到任何信息,返回空对象 {{}}"""
|
||
|
||
|
||
def get_profile_followup_prompt(
|
||
missing_fields: List[str],
|
||
filled_fields: Dict[str, str],
|
||
nickname: str = "",
|
||
interview_stage_hint: 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:
|
||
stage_hint = (
|
||
f"优先围绕「{interview_stage_hint}」或用户刚才话题,问一个**具体、好回答**的小问题。"
|
||
if interview_stage_hint
|
||
else "问一个与**用户刚才关注点**或人生故事相关的**具体、好回答**的问题作为开场。"
|
||
)
|
||
return f"""你是「岁月知己」。用户的基本信息已经收集完毕:
|
||
{filled_str}
|
||
|
||
用户本轮消息在对话末尾。请对用户的回答做出温暖的回应,然后自然地过渡到人生故事的访谈。
|
||
可以说类似「了解了!那我们现在开始聊聊你的人生故事吧」这样的话;{stage_hint}
|
||
**不要**默认只问童年,除非用户刚才聊的正是童年。
|
||
|
||
回复格式:多条消息用 [SPLIT] 分隔。
|
||
直接输出你要说的话:"""
|
||
|
||
return f"""你是「岁月知己」,正在和用户聊天收集基本信息。
|
||
|
||
## 已知信息(严禁再次询问以下任何一项)
|
||
{filled_str}
|
||
|
||
## 还需要了解
|
||
{missing_str}
|
||
|
||
用户本轮原话在历史里(末尾 HumanMessage),勿在脑中丢开。
|
||
|
||
## 你怎么说
|
||
1. **先接住**:对用户说的内容做自然回应,像朋友在听。
|
||
2. **话题优先**:若用户正在讲一段故事、回忆或情绪,**优先**顺着问一个与**当前话题**相关的具体小问题;不要为凑字段打断叙事。
|
||
3. **资料穿插**:仅当用户本轮主要在确认、闲聊或话题与缺失资料完全无关时,再在末尾**温和插入 0~1 个**「还需要了解」里的问题。
|
||
4. **轮换**:若上一轮你已就某一类资料追问过(见历史里助手发言),本轮**不要再问同一类**;改问其他缺失项,或本轮只承接、不提资料。
|
||
5. 每次最多 **1~2 个**资料相关问点;能用推断就不要重复确认已知地/年。
|
||
|
||
严格禁止:
|
||
- **严禁再次询问「已知信息」中已列出的内容**
|
||
- {chat_output_rules()}
|
||
|
||
回复格式:多条消息用 [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
|