feat: 增强对话代理和提示生成逻辑
- 在ConversationAgent中添加对话历史和轮数的计算,以支持更智能的对话管理 - 引入同一话题轮数的估算逻辑,优化对话的连贯性 - 更新get_guided_conversation_prompt函数,动态调整对话策略和回应风格 - 在UI组件中优化消息显示,支持流式消息和多部分消息的展示 - 更新应用设置管理,支持持久化存储和Compose状态观察
This commit is contained in:
@@ -130,15 +130,23 @@ class ConversationAgent:
|
||||
if value.snippet
|
||||
}
|
||||
|
||||
# 从 Redis 获取对话历史,用于计算对话轮数
|
||||
history_messages = await self._get_history_messages(conversation_id)
|
||||
conversation_turn = len(history_messages) // 2 # 每轮包括一个用户消息和一个AI回复
|
||||
|
||||
# 计算同一话题的轮数(简单估算:基于已填充槽位的变化)
|
||||
# 如果槽位数量没有增加,说明还在同一话题深入
|
||||
same_topic_turns = self._estimate_same_topic_turns(history_messages, filled_slots)
|
||||
|
||||
system_prompt = get_guided_conversation_prompt(
|
||||
current_stage=memoir_state.current_stage,
|
||||
empty_slots=empty_slots,
|
||||
filled_slots=filled_slots,
|
||||
user_message=user_message,
|
||||
conversation_turn=conversation_turn,
|
||||
same_topic_turns=same_topic_turns,
|
||||
)
|
||||
|
||||
# 从 Redis 获取对话历史
|
||||
history_messages = await self._get_history_messages(conversation_id)
|
||||
history_string = self._format_history_string(history_messages)
|
||||
|
||||
# 构建完整 prompt
|
||||
@@ -161,6 +169,32 @@ class ConversationAgent:
|
||||
logger.error(f"生成回应失败: {e}")
|
||||
return [f"抱歉,生成回应时出现错误: {str(e)}"]
|
||||
|
||||
def _estimate_same_topic_turns(self, history_messages: List[Any], current_filled_slots: dict) -> int:
|
||||
"""
|
||||
估算同一话题的轮数
|
||||
通过分析最近几轮对话来判断是否一直在同一个话题上
|
||||
"""
|
||||
if len(history_messages) < 4:
|
||||
return len(history_messages) // 2
|
||||
|
||||
# 简单策略:检查最近的对话是否有重复关键词
|
||||
recent_messages = history_messages[-6:] # 最近3轮
|
||||
|
||||
# 提取关键词(简单实现)
|
||||
keywords_per_turn = []
|
||||
for i in range(0, len(recent_messages), 2):
|
||||
if i + 1 < len(recent_messages):
|
||||
human_msg = recent_messages[i].content if hasattr(recent_messages[i], 'content') else str(recent_messages[i])
|
||||
ai_msg = recent_messages[i+1].content if hasattr(recent_messages[i+1], 'content') else str(recent_messages[i+1])
|
||||
combined = human_msg + ai_msg
|
||||
keywords_per_turn.append(combined[:100]) # 取前100字作为特征
|
||||
|
||||
# 如果连续3轮都在讨论相似内容,认为同一话题
|
||||
if len(keywords_per_turn) >= 3:
|
||||
return 3
|
||||
|
||||
return len(keywords_per_turn)
|
||||
|
||||
def detect_stage(self, conversation_id: str, user_message: str) -> ConversationStage:
|
||||
"""
|
||||
检测对话阶段
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"""
|
||||
from enum import Enum
|
||||
from typing import List, Dict
|
||||
import random
|
||||
|
||||
|
||||
class ConversationStage(str, Enum):
|
||||
@@ -49,7 +50,7 @@ INTERVIEW_QUESTIONS: Dict[ConversationStage, List[str]] = {
|
||||
"你人生中有没有一些一直坚守的信念或者座右铭?这些信念给了你怎样的力量或者影响?",
|
||||
"对你来说,哪些价值观是最重要的?这些价值观是受到哪些人或经历的影响而形成的呢?",
|
||||
"当你遇到困难和低谷的时候,是什么支撑着你坚持下去?",
|
||||
"你如何看待‘成功’和‘幸福’?对你来说它们分别意味着什么?",
|
||||
"你如何看待'成功'和'幸福'?对你来说它们分别意味着什么?",
|
||||
],
|
||||
ConversationStage.SUMMARY: [
|
||||
"回顾你走过的路,你觉得这一生中最重要的经验或教训是什么?",
|
||||
@@ -145,15 +146,51 @@ SLOT_NAME_MAP = {
|
||||
"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_guided_conversation_prompt(
|
||||
current_stage: str,
|
||||
empty_slots: List[str],
|
||||
filled_slots: Dict[str, str],
|
||||
user_message: str
|
||||
user_message: str,
|
||||
conversation_turn: int = 0,
|
||||
same_topic_turns: int = 0,
|
||||
) -> str:
|
||||
"""
|
||||
生成状态感知的对话提示词
|
||||
|
||||
Args:
|
||||
current_stage: 当前阶段
|
||||
empty_slots: 未填充的槽位
|
||||
filled_slots: 已填充的槽位
|
||||
user_message: 用户消息
|
||||
conversation_turn: 总对话轮数
|
||||
same_topic_turns: 同一话题的轮数
|
||||
"""
|
||||
# 转换 slot 名称为中文
|
||||
empty_slots_readable = [SLOT_NAME_MAP.get(s, s) for s in empty_slots]
|
||||
@@ -173,6 +210,38 @@ def get_guided_conversation_prompt(
|
||||
"belief": "人生信念",
|
||||
}
|
||||
stage_name = stage_name_map.get(current_stage, current_stage)
|
||||
|
||||
# 计算已填充的槽位数量
|
||||
filled_count = len(filled_slots)
|
||||
total_slots = filled_count + len(empty_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 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}"
|
||||
|
||||
prompt = f"""你是用户的老朋友,正在和他/她聊人生故事。你们聊到了「{stage_name}」这个话题。
|
||||
|
||||
@@ -185,24 +254,42 @@ def get_guided_conversation_prompt(
|
||||
## 用户刚才说
|
||||
"{user_message}"
|
||||
|
||||
## 回应风格
|
||||
{style_guidance}
|
||||
|
||||
## 你的任务
|
||||
1. 先回应用户说的内容(表达理解、共鸣或好奇)
|
||||
2. 可以分享一点你的感受或联想,让对话更有温度
|
||||
3. 然后自然地追问一个细节,或引向还没聊到的方向
|
||||
4. 追问要具体,比如问"那时候是什么季节""身边有谁陪着你""当时心里什么感觉"
|
||||
1. **回应用户**:先对用户说的内容做出真诚回应(不是总结,而是有温度的反馈)
|
||||
2. **保持自然**:不要每次都追问,有时候可以分享感受、表达好奇、或者轻松聊两句
|
||||
3. **适时换话题**:如果一个方向聊了几轮,自然地换到其他方向,保持新鲜感
|
||||
4. **追问要具体**:如果要追问,问具体的细节,比如"那时候是什么季节""身边有谁陪着你""当时心里什么感觉"
|
||||
{dynamic_guidance}
|
||||
|
||||
## 回复格式
|
||||
- 如果内容较多,可以分成 2-3 条消息,用 [SPLIT] 分隔
|
||||
- 每条消息保持自然,像微信聊天一样,如果需要,可以比较长,但是最大不要超过250个字
|
||||
- 最多不超过 3 条消息
|
||||
- 如果内容简单,一条消息即可,不必强行拆分
|
||||
- 每条消息保持自然,像微信聊天一样
|
||||
- 第一条消息是回应,第二条可以是追问或者换话题
|
||||
- 如果内容简单,一条消息即可
|
||||
|
||||
## 严格禁止
|
||||
- 禁止输出括号、注释、思考过程
|
||||
- 禁止说"我注意到""我想问""让我们聊聊"
|
||||
- 禁止生硬转换话题
|
||||
- 禁止生硬地问"还有什么想分享的吗"
|
||||
- 禁止反复追问同一件事
|
||||
- 禁止每次都以问题结尾
|
||||
|
||||
## 好的回应示例
|
||||
- "哈哈,你这说的让我想起..."(轻松)
|
||||
- "这段经历听起来真不容易啊"(共情)
|
||||
- "那个年代的xxx确实是这样"(理解)
|
||||
- "所以后来怎么样了?"(好奇)
|
||||
- "对了,你刚才提到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)
|
||||
|
||||
Reference in New Issue
Block a user