refactor(KFC): 模块化提示生成并简化情绪状态处理
此提交对 Kokoro Flow Chatter (KFC) 插件进行了重大重构,以提高模块化、可维护性和可靠性。 主要更改包括: - **提示生成**:原本的单一 `generate_system_prompt` 方法现在被委托给新的 `prompt_modules.py`。这实现了关注点分离,使管理提示的不同部分(如个性、上下文和动作定义)更加容易。 - **情绪状态处理**:已移除 `_update_emotional_state_from_thought` 中复杂且不可靠的基于关键词的情感分析。系统现在依赖 LLM 的显式 `update_internal_state` 动作,以更直接和准确地更新情绪状态。该方法已简化,仅处理轻微的参与度调整。 - **JSON 解析**:用统一的 `extract_and_parse_json` 工具替换了自定义 JSON 提取逻辑。这提供了更强大的解析能力,处理更大的Markdown 代码块和自动修复格式错误的 JSON。 - **调度器抽象**:引入了 `KFCSchedulerAdapter`,将聊天组件与全局调度器实现解耦,提高了可测试性和清晰度。 - **优雅的对话结束**:系统现在可以正确处理 `max_wait_seconds: 0`,立即结束对话主题并将会话设置为空闲状态,避免不必要的等待时间。
This commit is contained in:
@@ -15,14 +15,15 @@ V5升级要点:
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import re
|
||||
import time
|
||||
from typing import TYPE_CHECKING, Any, Optional
|
||||
|
||||
import orjson
|
||||
|
||||
from src.chat.planner_actions.action_manager import ChatterActionManager
|
||||
from src.common.logger import get_logger
|
||||
from src.plugin_system.base.component_types import ActionInfo
|
||||
from src.utils.json_parser import extract_and_parse_json
|
||||
|
||||
from .models import (
|
||||
ActionModel,
|
||||
@@ -38,33 +39,6 @@ if TYPE_CHECKING:
|
||||
|
||||
logger = get_logger("kokoro_action_executor")
|
||||
|
||||
# ========== 情感分析关键词映射 ==========
|
||||
# 正面情感关键词(提升mood_intensity和engagement_level)
|
||||
POSITIVE_KEYWORDS = [
|
||||
"开心", "高兴", "快乐", "喜欢", "爱", "好奇", "期待", "惊喜",
|
||||
"感动", "温暖", "舒服", "放松", "满足", "欣慰", "感激", "兴奋",
|
||||
"有趣", "好玩", "可爱", "太棒了", "哈哈", "嘻嘻", "hiahia",
|
||||
]
|
||||
|
||||
# 负面情感关键词(降低mood_intensity,可能提升anxiety_level)
|
||||
NEGATIVE_KEYWORDS = [
|
||||
"难过", "伤心", "失望", "沮丧", "担心", "焦虑", "害怕", "紧张",
|
||||
"生气", "烦躁", "无聊", "疲惫", "累", "困", "不开心", "不高兴",
|
||||
"委屈", "尴尬", "迷茫", "困惑", "郁闷",
|
||||
]
|
||||
|
||||
# 亲密关键词(提升relationship_warmth)
|
||||
INTIMATE_KEYWORDS = [
|
||||
"喜欢你", "想你", "想念", "在乎", "关心", "信任", "依赖",
|
||||
"亲爱的", "宝贝", "朋友", "陪伴", "一起", "我们",
|
||||
]
|
||||
|
||||
# 疏远关键词(降低relationship_warmth)
|
||||
DISTANT_KEYWORDS = [
|
||||
"讨厌", "烦人", "无聊", "不想理", "走开", "别烦",
|
||||
"算了", "随便", "不在乎",
|
||||
]
|
||||
|
||||
|
||||
class ActionExecutor:
|
||||
"""
|
||||
@@ -146,75 +120,25 @@ class ActionExecutor:
|
||||
"""
|
||||
解析LLM的JSON响应
|
||||
|
||||
使用统一的json_parser工具进行解析,自动处理:
|
||||
- Markdown代码块标记
|
||||
- 格式错误的JSON修复(json_repair)
|
||||
- 多种包装格式
|
||||
|
||||
Args:
|
||||
response_text: LLM返回的原始文本
|
||||
|
||||
Returns:
|
||||
LLMResponseModel: 解析后的响应模型
|
||||
|
||||
Raises:
|
||||
ValueError: 如果解析失败
|
||||
"""
|
||||
# 尝试提取JSON块
|
||||
json_str = self._extract_json(response_text)
|
||||
# 使用统一的json_parser工具解析
|
||||
data = extract_and_parse_json(response_text, strict=False)
|
||||
|
||||
if not json_str:
|
||||
logger.warning(f"无法从LLM响应中提取JSON: {response_text[:200]}...")
|
||||
if not data or not isinstance(data, dict):
|
||||
logger.warning(f"无法从LLM响应中提取有效JSON: {response_text[:200]}...")
|
||||
return LLMResponseModel.create_error_response("无法解析响应格式")
|
||||
|
||||
try:
|
||||
data = json.loads(json_str)
|
||||
return self._validate_and_create_response(data)
|
||||
except json.JSONDecodeError as e:
|
||||
logger.error(f"JSON解析失败: {e}, 原始文本: {json_str[:200]}...")
|
||||
return LLMResponseModel.create_error_response(f"JSON解析错误: {e}")
|
||||
|
||||
def _extract_json(self, text: str) -> Optional[str]:
|
||||
"""
|
||||
从文本中提取JSON块
|
||||
|
||||
支持以下格式:
|
||||
1. 纯JSON字符串
|
||||
2. ```json ... ``` 代码块
|
||||
3. 文本中嵌入的JSON对象
|
||||
"""
|
||||
text = text.strip()
|
||||
|
||||
# 尝试1:直接解析(如果整个文本就是JSON)
|
||||
if text.startswith("{"):
|
||||
# 找到匹配的结束括号
|
||||
brace_count = 0
|
||||
for i, char in enumerate(text):
|
||||
if char == "{":
|
||||
brace_count += 1
|
||||
elif char == "}":
|
||||
brace_count -= 1
|
||||
if brace_count == 0:
|
||||
return text[:i + 1]
|
||||
|
||||
# 尝试2:提取 ```json ... ``` 代码块
|
||||
json_block_pattern = r"```(?:json)?\s*([\s\S]*?)```"
|
||||
matches = re.findall(json_block_pattern, text)
|
||||
for match in matches:
|
||||
match = match.strip()
|
||||
if match.startswith("{"):
|
||||
try:
|
||||
json.loads(match)
|
||||
return match
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
|
||||
# 尝试3:寻找文本中的JSON对象
|
||||
json_pattern = r"\{[\s\S]*\}"
|
||||
matches = re.findall(json_pattern, text)
|
||||
for match in matches:
|
||||
try:
|
||||
json.loads(match)
|
||||
return match
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
|
||||
return None
|
||||
return self._validate_and_create_response(data)
|
||||
|
||||
def _validate_and_create_response(self, data: dict[str, Any]) -> LLMResponseModel:
|
||||
"""
|
||||
@@ -239,10 +163,11 @@ class ActionExecutor:
|
||||
data["max_wait_seconds"] = 300
|
||||
logger.warning("LLM响应缺少'max_wait_seconds'字段,使用默认值300")
|
||||
else:
|
||||
# 确保在合理范围内
|
||||
# 确保在合理范围内:0-900秒
|
||||
# 0 表示不等待(话题结束/用户说再见等)
|
||||
try:
|
||||
wait_seconds = int(data["max_wait_seconds"])
|
||||
data["max_wait_seconds"] = max(60, min(wait_seconds, 600))
|
||||
data["max_wait_seconds"] = max(0, min(wait_seconds, 900))
|
||||
except (ValueError, TypeError):
|
||||
data["max_wait_seconds"] = 300
|
||||
|
||||
@@ -706,12 +631,13 @@ class ActionExecutor:
|
||||
session: KokoroSession,
|
||||
) -> None:
|
||||
"""
|
||||
V5:根据thought字段的情感倾向动态更新EmotionalState
|
||||
根据thought字段更新EmotionalState
|
||||
|
||||
分析策略:
|
||||
1. 统计正面/负面/亲密/疏远关键词出现次数
|
||||
2. 根据关键词比例计算情感调整值
|
||||
3. 应用平滑的情感微调(每次变化不超过±0.1)
|
||||
V6重构:
|
||||
- 移除基于关键词的情感分析(诡异且不准确)
|
||||
- 情感状态现在主要通过LLM输出的update_internal_state动作更新
|
||||
- 关系温度应该从person_info/relationship_manager的好感度系统读取
|
||||
- 此方法仅做简单的engagement_level更新
|
||||
|
||||
Args:
|
||||
thought: LLM返回的内心独白
|
||||
@@ -720,76 +646,16 @@ class ActionExecutor:
|
||||
if not thought:
|
||||
return
|
||||
|
||||
thought_lower = thought.lower()
|
||||
emotional_state = session.emotional_state
|
||||
|
||||
# 统计关键词
|
||||
positive_count = sum(1 for kw in POSITIVE_KEYWORDS if kw in thought_lower)
|
||||
negative_count = sum(1 for kw in NEGATIVE_KEYWORDS if kw in thought_lower)
|
||||
intimate_count = sum(1 for kw in INTIMATE_KEYWORDS if kw in thought_lower)
|
||||
distant_count = sum(1 for kw in DISTANT_KEYWORDS if kw in thought_lower)
|
||||
|
||||
# 计算情感倾向分数 (-1.0 到 1.0)
|
||||
total_mood = positive_count + negative_count
|
||||
if total_mood > 0:
|
||||
mood_score = (positive_count - negative_count) / total_mood
|
||||
else:
|
||||
mood_score = 0.0
|
||||
|
||||
total_relation = intimate_count + distant_count
|
||||
if total_relation > 0:
|
||||
relation_score = (intimate_count - distant_count) / total_relation
|
||||
else:
|
||||
relation_score = 0.0
|
||||
|
||||
# 应用情感微调(每次最多变化±0.05)
|
||||
adjustment_rate = 0.05
|
||||
|
||||
# 更新 mood_intensity
|
||||
if mood_score != 0:
|
||||
old_intensity = emotional_state.mood_intensity
|
||||
new_intensity = old_intensity + (mood_score * adjustment_rate)
|
||||
emotional_state.mood_intensity = max(0.0, min(1.0, new_intensity))
|
||||
|
||||
# 更新 mood 文字描述
|
||||
if mood_score > 0.3:
|
||||
emotional_state.mood = "开心"
|
||||
elif mood_score < -0.3:
|
||||
emotional_state.mood = "低落"
|
||||
# 小于0.3的变化不改变mood文字
|
||||
|
||||
# 更新 anxiety_level(负面情绪增加焦虑)
|
||||
if negative_count > 0 and negative_count > positive_count:
|
||||
old_anxiety = emotional_state.anxiety_level
|
||||
new_anxiety = old_anxiety + (adjustment_rate * 0.5)
|
||||
emotional_state.anxiety_level = max(0.0, min(1.0, new_anxiety))
|
||||
elif positive_count > negative_count * 2:
|
||||
# 非常积极时降低焦虑
|
||||
old_anxiety = emotional_state.anxiety_level
|
||||
new_anxiety = old_anxiety - (adjustment_rate * 0.3)
|
||||
emotional_state.anxiety_level = max(0.0, min(1.0, new_anxiety))
|
||||
|
||||
# 更新 relationship_warmth
|
||||
if relation_score != 0:
|
||||
old_warmth = emotional_state.relationship_warmth
|
||||
new_warmth = old_warmth + (relation_score * adjustment_rate)
|
||||
emotional_state.relationship_warmth = max(0.0, min(1.0, new_warmth))
|
||||
|
||||
# 更新 engagement_level(有内容的thought表示高投入)
|
||||
# 简单的engagement_level更新:有内容的thought表示高投入
|
||||
if len(thought) > 50:
|
||||
old_engagement = emotional_state.engagement_level
|
||||
new_engagement = old_engagement + (adjustment_rate * 0.5)
|
||||
new_engagement = old_engagement + 0.025 # 微调
|
||||
emotional_state.engagement_level = max(0.0, min(1.0, new_engagement))
|
||||
|
||||
emotional_state.last_update_time = time.time()
|
||||
|
||||
# 记录情感变化日志
|
||||
if positive_count + negative_count + intimate_count + distant_count > 0:
|
||||
logger.info(
|
||||
f"[KFC] 动态情感更新: "
|
||||
f"正面={positive_count}, 负面={negative_count}, "
|
||||
f"亲密={intimate_count}, 疏远={distant_count} | "
|
||||
f"mood_intensity={emotional_state.mood_intensity:.2f}, "
|
||||
f"relationship_warmth={emotional_state.relationship_warmth:.2f}, "
|
||||
f"anxiety_level={emotional_state.anxiety_level:.2f}"
|
||||
)
|
||||
# 注意:关系温度(relationship_warmth)应该从全局的好感度系统读取
|
||||
# 参考 src/person_info/relationship_manager.py 和 src/plugin_system/apis/person_api.py
|
||||
# 当前实现中,这个值主要通过 LLM 的 update_internal_state 动作来更新
|
||||
|
||||
@@ -29,7 +29,7 @@ from .models import (
|
||||
SessionStatus,
|
||||
)
|
||||
from .prompt_generator import PromptGenerator, get_prompt_generator
|
||||
from .scheduler import BackgroundScheduler, get_scheduler
|
||||
from .kfc_scheduler_adapter import KFCSchedulerAdapter, get_scheduler
|
||||
from .session_manager import SessionManager, get_session_manager
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -79,7 +79,7 @@ class KokoroFlowChatter(BaseChatter):
|
||||
# 核心组件
|
||||
self.session_manager: SessionManager = get_session_manager()
|
||||
self.prompt_generator: PromptGenerator = get_prompt_generator()
|
||||
self.scheduler: BackgroundScheduler = get_scheduler()
|
||||
self.scheduler: KFCSchedulerAdapter = get_scheduler()
|
||||
self.action_executor: ActionExecutor = ActionExecutor(stream_id)
|
||||
|
||||
# 配置
|
||||
@@ -296,17 +296,30 @@ class KokoroFlowChatter(BaseChatter):
|
||||
|
||||
# 10. 处理执行结果
|
||||
if execution_result["has_reply"]:
|
||||
# 如果发送了回复,进入等待状态
|
||||
session.start_waiting(
|
||||
expected_reaction=parsed_response.expected_user_reaction,
|
||||
max_wait=parsed_response.max_wait_seconds
|
||||
)
|
||||
# 如果发送了回复,检查是否需要进入等待状态
|
||||
max_wait = parsed_response.max_wait_seconds
|
||||
|
||||
if max_wait > 0:
|
||||
# 正常等待状态
|
||||
session.start_waiting(
|
||||
expected_reaction=parsed_response.expected_user_reaction,
|
||||
max_wait=max_wait
|
||||
)
|
||||
logger.debug(
|
||||
f"[KFC] 进入等待状态: user={user_id}, "
|
||||
f"max_wait={max_wait}s"
|
||||
)
|
||||
else:
|
||||
# max_wait=0 表示不等待(话题结束/用户说再见等)
|
||||
session.status = SessionStatus.IDLE
|
||||
session.end_waiting()
|
||||
logger.info(
|
||||
f"[KFC] 话题结束,不等待用户回复: user={user_id} "
|
||||
f"(max_wait_seconds=0)"
|
||||
)
|
||||
|
||||
session.total_interactions += 1
|
||||
self.stats["successful_responses"] += 1
|
||||
logger.debug(
|
||||
f"[KFC] 进入等待状态: user={user_id}, "
|
||||
f"max_wait={parsed_response.max_wait_seconds}s"
|
||||
)
|
||||
else:
|
||||
# 没有发送回复,返回空闲状态
|
||||
session.status = SessionStatus.IDLE
|
||||
|
||||
@@ -0,0 +1,394 @@
|
||||
"""
|
||||
Kokoro Flow Chatter 调度器适配器
|
||||
|
||||
基于项目统一的 UnifiedScheduler 实现 KFC 的定时任务功能。
|
||||
不再自己创建后台循环,而是复用全局调度器的基础设施。
|
||||
|
||||
核心功能:
|
||||
1. 会话等待超时检测
|
||||
2. 连续思考触发
|
||||
3. 与 UnifiedScheduler 的集成
|
||||
"""
|
||||
|
||||
import time
|
||||
from typing import TYPE_CHECKING, Any, Callable, Coroutine, Optional
|
||||
|
||||
from src.common.logger import get_logger
|
||||
from src.plugin_system.apis.unified_scheduler import (
|
||||
TriggerType,
|
||||
unified_scheduler,
|
||||
)
|
||||
|
||||
from .models import (
|
||||
KokoroSession,
|
||||
MentalLogEntry,
|
||||
MentalLogEventType,
|
||||
SessionStatus,
|
||||
)
|
||||
from .session_manager import get_session_manager
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .chatter import KokoroFlowChatter
|
||||
|
||||
logger = get_logger("kokoro_scheduler_adapter")
|
||||
|
||||
|
||||
class KFCSchedulerAdapter:
|
||||
"""
|
||||
KFC 调度器适配器
|
||||
|
||||
使用 UnifiedScheduler 实现 KFC 的定时任务功能,不再自行管理后台循环。
|
||||
|
||||
核心功能:
|
||||
1. 定期检查处于 WAITING 状态的会话
|
||||
2. 在特定时间点触发"连续思考"
|
||||
3. 处理等待超时并触发决策
|
||||
"""
|
||||
|
||||
# 连续思考触发点(等待进度的百分比)
|
||||
CONTINUOUS_THINKING_TRIGGERS = [0.3, 0.6, 0.85]
|
||||
|
||||
# 任务名称常量
|
||||
TASK_NAME_WAITING_CHECK = "kfc_waiting_check"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
check_interval: float = 10.0,
|
||||
on_timeout_callback: Optional[Callable[[KokoroSession], Coroutine[Any, Any, None]]] = None,
|
||||
on_continuous_thinking_callback: Optional[Callable[[KokoroSession], Coroutine[Any, Any, None]]] = None,
|
||||
):
|
||||
"""
|
||||
初始化调度器适配器
|
||||
|
||||
Args:
|
||||
check_interval: 检查间隔(秒)
|
||||
on_timeout_callback: 超时回调函数
|
||||
on_continuous_thinking_callback: 连续思考回调函数
|
||||
"""
|
||||
self.check_interval = check_interval
|
||||
self.on_timeout_callback = on_timeout_callback
|
||||
self.on_continuous_thinking_callback = on_continuous_thinking_callback
|
||||
|
||||
self._registered = False
|
||||
self._schedule_id: Optional[str] = None
|
||||
|
||||
# 统计信息
|
||||
self._stats = {
|
||||
"total_checks": 0,
|
||||
"timeouts_triggered": 0,
|
||||
"continuous_thinking_triggered": 0,
|
||||
"last_check_time": 0.0,
|
||||
}
|
||||
|
||||
logger.info("KFCSchedulerAdapter 初始化完成")
|
||||
|
||||
async def start(self) -> None:
|
||||
"""启动调度器(注册到 UnifiedScheduler)"""
|
||||
if self._registered:
|
||||
logger.warning("KFC 调度器已在运行中")
|
||||
return
|
||||
|
||||
# 注册周期性检查任务
|
||||
self._schedule_id = await unified_scheduler.create_schedule(
|
||||
callback=self._check_waiting_sessions,
|
||||
trigger_type=TriggerType.TIME,
|
||||
trigger_config={"delay_seconds": self.check_interval},
|
||||
is_recurring=True,
|
||||
task_name=self.TASK_NAME_WAITING_CHECK,
|
||||
force_overwrite=True,
|
||||
timeout=30.0, # 单次检查超时 30 秒
|
||||
)
|
||||
|
||||
self._registered = True
|
||||
logger.info(f"KFC 调度器已注册到 UnifiedScheduler: schedule_id={self._schedule_id}")
|
||||
|
||||
async def stop(self) -> None:
|
||||
"""停止调度器(从 UnifiedScheduler 注销)"""
|
||||
if not self._registered:
|
||||
return
|
||||
|
||||
try:
|
||||
if self._schedule_id:
|
||||
await unified_scheduler.remove_schedule(self._schedule_id)
|
||||
logger.info(f"KFC 调度器已从 UnifiedScheduler 注销: schedule_id={self._schedule_id}")
|
||||
except Exception as e:
|
||||
logger.error(f"停止 KFC 调度器时出错: {e}")
|
||||
finally:
|
||||
self._registered = False
|
||||
self._schedule_id = None
|
||||
|
||||
async def _check_waiting_sessions(self) -> None:
|
||||
"""检查所有等待中的会话(由 UnifiedScheduler 调用)"""
|
||||
session_manager = get_session_manager()
|
||||
waiting_sessions = await session_manager.get_all_waiting_sessions()
|
||||
|
||||
self._stats["total_checks"] += 1
|
||||
self._stats["last_check_time"] = time.time()
|
||||
|
||||
if not waiting_sessions:
|
||||
return
|
||||
|
||||
for session in waiting_sessions:
|
||||
try:
|
||||
await self._process_waiting_session(session)
|
||||
except Exception as e:
|
||||
logger.error(f"处理等待会话 {session.user_id} 时出错: {e}")
|
||||
|
||||
async def _process_waiting_session(self, session: KokoroSession) -> None:
|
||||
"""
|
||||
处理单个等待中的会话
|
||||
|
||||
Args:
|
||||
session: 等待中的会话
|
||||
"""
|
||||
if session.status != SessionStatus.WAITING:
|
||||
return
|
||||
|
||||
if session.waiting_since is None:
|
||||
return
|
||||
|
||||
wait_duration = session.get_waiting_duration()
|
||||
max_wait = session.max_wait_seconds
|
||||
|
||||
# max_wait_seconds = 0 表示不等待,直接返回 IDLE
|
||||
if max_wait <= 0:
|
||||
logger.info(f"会话 {session.user_id} 设置为不等待 (max_wait=0),返回空闲状态")
|
||||
session.status = SessionStatus.IDLE
|
||||
session.end_waiting()
|
||||
session_manager = get_session_manager()
|
||||
await session_manager.save_session(session.user_id)
|
||||
return
|
||||
|
||||
# 检查是否超时
|
||||
if session.is_wait_timeout():
|
||||
logger.info(f"会话 {session.user_id} 等待超时,触发决策")
|
||||
await self._handle_timeout(session)
|
||||
return
|
||||
|
||||
# 检查是否需要触发连续思考
|
||||
wait_progress = wait_duration / max_wait if max_wait > 0 else 0
|
||||
|
||||
for trigger_point in self.CONTINUOUS_THINKING_TRIGGERS:
|
||||
if self._should_trigger_continuous_thinking(session, wait_progress, trigger_point):
|
||||
logger.debug(
|
||||
f"会话 {session.user_id} 触发连续思考 "
|
||||
f"(进度: {wait_progress:.1%}, 触发点: {trigger_point:.1%})"
|
||||
)
|
||||
await self._handle_continuous_thinking(session, wait_progress)
|
||||
break
|
||||
|
||||
def _should_trigger_continuous_thinking(
|
||||
self,
|
||||
session: KokoroSession,
|
||||
current_progress: float,
|
||||
trigger_point: float,
|
||||
) -> bool:
|
||||
"""
|
||||
判断是否应该触发连续思考
|
||||
"""
|
||||
if current_progress < trigger_point:
|
||||
return False
|
||||
|
||||
expected_count = sum(
|
||||
1 for tp in self.CONTINUOUS_THINKING_TRIGGERS
|
||||
if current_progress >= tp
|
||||
)
|
||||
|
||||
if session.continuous_thinking_count < expected_count:
|
||||
if session.last_continuous_thinking_at is None:
|
||||
return True
|
||||
|
||||
time_since_last = time.time() - session.last_continuous_thinking_at
|
||||
return time_since_last >= 30.0
|
||||
|
||||
return False
|
||||
|
||||
async def _handle_timeout(self, session: KokoroSession) -> None:
|
||||
"""
|
||||
处理等待超时
|
||||
|
||||
Args:
|
||||
session: 超时的会话
|
||||
"""
|
||||
self._stats["timeouts_triggered"] += 1
|
||||
|
||||
# 更新会话状态
|
||||
session.status = SessionStatus.FOLLOW_UP_PENDING
|
||||
session.emotional_state.anxiety_level = 0.8
|
||||
|
||||
# 添加超时日志
|
||||
timeout_entry = MentalLogEntry(
|
||||
event_type=MentalLogEventType.TIMEOUT_DECISION,
|
||||
timestamp=time.time(),
|
||||
thought=f"等了{session.max_wait_seconds}秒了,对方还是没有回复...",
|
||||
content="等待超时",
|
||||
emotional_snapshot=session.emotional_state.to_dict(),
|
||||
)
|
||||
session.add_mental_log_entry(timeout_entry)
|
||||
|
||||
# 保存会话状态
|
||||
session_manager = get_session_manager()
|
||||
await session_manager.save_session(session.user_id)
|
||||
|
||||
# 调用超时回调
|
||||
if self.on_timeout_callback:
|
||||
try:
|
||||
await self.on_timeout_callback(session)
|
||||
except Exception as e:
|
||||
logger.error(f"执行超时回调时出错 (user={session.user_id}): {e}")
|
||||
|
||||
async def _handle_continuous_thinking(
|
||||
self,
|
||||
session: KokoroSession,
|
||||
wait_progress: float,
|
||||
) -> None:
|
||||
"""
|
||||
处理连续思考
|
||||
|
||||
Args:
|
||||
session: 会话
|
||||
wait_progress: 等待进度
|
||||
"""
|
||||
self._stats["continuous_thinking_triggered"] += 1
|
||||
|
||||
# 更新焦虑程度
|
||||
session.emotional_state.update_anxiety_over_time(
|
||||
session.get_waiting_duration(),
|
||||
session.max_wait_seconds
|
||||
)
|
||||
|
||||
# 更新连续思考计数
|
||||
session.continuous_thinking_count += 1
|
||||
session.last_continuous_thinking_at = time.time()
|
||||
|
||||
# 生成基于进度的内心想法
|
||||
thought = self._generate_waiting_thought(session, wait_progress)
|
||||
|
||||
# 添加连续思考日志
|
||||
thinking_entry = MentalLogEntry(
|
||||
event_type=MentalLogEventType.CONTINUOUS_THINKING,
|
||||
timestamp=time.time(),
|
||||
thought=thought,
|
||||
content="",
|
||||
emotional_snapshot=session.emotional_state.to_dict(),
|
||||
metadata={"wait_progress": wait_progress},
|
||||
)
|
||||
session.add_mental_log_entry(thinking_entry)
|
||||
|
||||
# 保存会话状态
|
||||
session_manager = get_session_manager()
|
||||
await session_manager.save_session(session.user_id)
|
||||
|
||||
# 调用连续思考回调
|
||||
if self.on_continuous_thinking_callback:
|
||||
try:
|
||||
await self.on_continuous_thinking_callback(session)
|
||||
except Exception as e:
|
||||
logger.error(f"执行连续思考回调时出错 (user={session.user_id}): {e}")
|
||||
|
||||
def _generate_waiting_thought(
|
||||
self,
|
||||
session: KokoroSession,
|
||||
wait_progress: float,
|
||||
) -> str:
|
||||
"""
|
||||
生成等待中的内心想法(简单版本,不调用LLM)
|
||||
"""
|
||||
import random
|
||||
|
||||
wait_seconds = session.get_waiting_duration()
|
||||
wait_minutes = wait_seconds / 60
|
||||
|
||||
if wait_progress < 0.4:
|
||||
thoughts = [
|
||||
f"已经等了{wait_minutes:.1f}分钟了,对方可能在忙吧...",
|
||||
f"嗯...{wait_minutes:.1f}分钟过去了,不知道对方在做什么",
|
||||
"对方好像还没看到消息,再等等吧",
|
||||
]
|
||||
elif wait_progress < 0.7:
|
||||
thoughts = [
|
||||
f"等了{wait_minutes:.1f}分钟了,有点担心对方是不是不想回了",
|
||||
f"{wait_minutes:.1f}分钟了,对方可能真的很忙?",
|
||||
"时间过得好慢啊...不知道对方什么时候会回复",
|
||||
]
|
||||
else:
|
||||
thoughts = [
|
||||
f"已经等了{wait_minutes:.1f}分钟了,感觉有点焦虑...",
|
||||
f"快{wait_minutes:.0f}分钟了,对方是不是忘记回复了?",
|
||||
"等了这么久,要不要主动说点什么呢...",
|
||||
]
|
||||
|
||||
return random.choice(thoughts)
|
||||
|
||||
def set_timeout_callback(
|
||||
self,
|
||||
callback: Callable[[KokoroSession], Coroutine[Any, Any, None]],
|
||||
) -> None:
|
||||
"""设置超时回调函数"""
|
||||
self.on_timeout_callback = callback
|
||||
|
||||
def set_continuous_thinking_callback(
|
||||
self,
|
||||
callback: Callable[[KokoroSession], Coroutine[Any, Any, None]],
|
||||
) -> None:
|
||||
"""设置连续思考回调函数"""
|
||||
self.on_continuous_thinking_callback = callback
|
||||
|
||||
def get_stats(self) -> dict[str, Any]:
|
||||
"""获取统计信息"""
|
||||
return {
|
||||
**self._stats,
|
||||
"is_running": self._registered,
|
||||
"check_interval": self.check_interval,
|
||||
}
|
||||
|
||||
@property
|
||||
def is_running(self) -> bool:
|
||||
"""调度器是否正在运行"""
|
||||
return self._registered
|
||||
|
||||
|
||||
# 全局适配器实例
|
||||
_scheduler_adapter: Optional[KFCSchedulerAdapter] = None
|
||||
|
||||
|
||||
def get_scheduler() -> KFCSchedulerAdapter:
|
||||
"""获取全局调度器适配器实例"""
|
||||
global _scheduler_adapter
|
||||
if _scheduler_adapter is None:
|
||||
_scheduler_adapter = KFCSchedulerAdapter()
|
||||
return _scheduler_adapter
|
||||
|
||||
|
||||
async def initialize_scheduler(
|
||||
check_interval: float = 10.0,
|
||||
on_timeout_callback: Optional[Callable[[KokoroSession], Coroutine[Any, Any, None]]] = None,
|
||||
on_continuous_thinking_callback: Optional[Callable[[KokoroSession], Coroutine[Any, Any, None]]] = None,
|
||||
) -> KFCSchedulerAdapter:
|
||||
"""
|
||||
初始化并启动调度器
|
||||
|
||||
Args:
|
||||
check_interval: 检查间隔
|
||||
on_timeout_callback: 超时回调
|
||||
on_continuous_thinking_callback: 连续思考回调
|
||||
|
||||
Returns:
|
||||
KFCSchedulerAdapter: 调度器适配器实例
|
||||
"""
|
||||
global _scheduler_adapter
|
||||
_scheduler_adapter = KFCSchedulerAdapter(
|
||||
check_interval=check_interval,
|
||||
on_timeout_callback=on_timeout_callback,
|
||||
on_continuous_thinking_callback=on_continuous_thinking_callback,
|
||||
)
|
||||
await _scheduler_adapter.start()
|
||||
return _scheduler_adapter
|
||||
|
||||
|
||||
async def shutdown_scheduler() -> None:
|
||||
"""关闭调度器"""
|
||||
global _scheduler_adapter
|
||||
if _scheduler_adapter:
|
||||
await _scheduler_adapter.stop()
|
||||
_scheduler_adapter = None
|
||||
@@ -441,8 +441,8 @@ class PromptGenerator:
|
||||
for param_name, param_desc in action_info.action_parameters.items():
|
||||
example_params[param_name] = f"<{param_desc}>"
|
||||
|
||||
import json
|
||||
params_json = json.dumps(example_params, ensure_ascii=False, indent=2) if example_params else "{}"
|
||||
import orjson
|
||||
params_json = orjson.dumps(example_params, option=orjson.OPT_INDENT_2).decode('utf-8') if example_params else "{}"
|
||||
action_block += f"""
|
||||
**示例**:
|
||||
```json
|
||||
@@ -508,8 +508,9 @@ class PromptGenerator:
|
||||
"""
|
||||
生成系统提示词
|
||||
|
||||
V4升级:从 global_config.personality 读取完整人设
|
||||
V5超融合:集成S4U所有上下文模块
|
||||
V6模块化升级:使用 prompt_modules 构建模块化的提示词
|
||||
- 每个模块独立构建,职责清晰
|
||||
- 回复相关(人设、上下文)与动作定义分离
|
||||
|
||||
Args:
|
||||
session: 当前会话
|
||||
@@ -520,69 +521,13 @@ class PromptGenerator:
|
||||
Returns:
|
||||
str: 系统提示词
|
||||
"""
|
||||
from src.config.config import global_config
|
||||
from datetime import datetime
|
||||
from .prompt_modules import build_system_prompt
|
||||
|
||||
emotional_params = self._format_emotional_state(session.emotional_state)
|
||||
|
||||
# 格式化可用动作
|
||||
available_actions_block = self._format_available_actions(available_actions or {})
|
||||
|
||||
# 从 global_config.personality 读取完整人设
|
||||
if global_config is None:
|
||||
raise RuntimeError("global_config 未初始化")
|
||||
|
||||
personality_cfg = global_config.personality
|
||||
|
||||
# 核心人设
|
||||
personality_core = personality_cfg.personality_core or self.persona_description
|
||||
personality_side = personality_cfg.personality_side or ""
|
||||
identity = personality_cfg.identity or ""
|
||||
background_story = personality_cfg.background_story or ""
|
||||
reply_style = personality_cfg.reply_style or ""
|
||||
|
||||
# 安全规则:转换为格式化字符串
|
||||
safety_guidelines = personality_cfg.safety_guidelines or []
|
||||
if isinstance(safety_guidelines, list):
|
||||
safety_guidelines_str = "\n".join(f"- {rule}" for rule in safety_guidelines)
|
||||
else:
|
||||
safety_guidelines_str = str(safety_guidelines)
|
||||
|
||||
# 构建当前时间
|
||||
current_time = datetime.now().strftime("%Y年%m月%d日 %H:%M:%S")
|
||||
|
||||
# 判断聊天场景
|
||||
is_group_chat = False
|
||||
if chat_stream:
|
||||
is_group_chat = bool(chat_stream.group_info)
|
||||
chat_scene = "群聊" if is_group_chat else "私聊"
|
||||
|
||||
# 从context_data提取S4U上下文模块(如果提供)
|
||||
context_data = context_data or {}
|
||||
relation_info_block = context_data.get("relation_info", "")
|
||||
memory_block = context_data.get("memory_block", "")
|
||||
expression_habits_block = context_data.get("expression_habits", "")
|
||||
schedule_block = context_data.get("schedule", "")
|
||||
|
||||
# 如果有日程,添加前缀
|
||||
if schedule_block:
|
||||
schedule_block = f"**当前活动**: {schedule_block}"
|
||||
|
||||
return self.SYSTEM_PROMPT_TEMPLATE.format(
|
||||
personality_core=personality_core,
|
||||
personality_side=personality_side,
|
||||
identity=identity,
|
||||
background_story=background_story,
|
||||
reply_style=reply_style,
|
||||
safety_guidelines=safety_guidelines_str,
|
||||
available_actions_block=available_actions_block,
|
||||
current_time=current_time,
|
||||
chat_scene=chat_scene,
|
||||
relation_info_block=relation_info_block or "(暂无关系信息)",
|
||||
memory_block=memory_block or "",
|
||||
expression_habits_block=expression_habits_block or "",
|
||||
schedule_block=schedule_block,
|
||||
**emotional_params,
|
||||
return build_system_prompt(
|
||||
session=session,
|
||||
available_actions=available_actions,
|
||||
context_data=context_data,
|
||||
chat_stream=chat_stream,
|
||||
)
|
||||
|
||||
def generate_responding_prompt(
|
||||
|
||||
414
src/plugins/built_in/kokoro_flow_chatter/prompt_modules.py
Normal file
414
src/plugins/built_in/kokoro_flow_chatter/prompt_modules.py
Normal file
@@ -0,0 +1,414 @@
|
||||
"""
|
||||
Kokoro Flow Chatter 模块化提示词组件
|
||||
|
||||
将提示词拆分为独立的模块,每个模块负责特定的内容生成:
|
||||
1. 核心身份模块 - 人设、人格、世界观
|
||||
2. 行为准则模块 - 规则、安全边界
|
||||
3. 情境上下文模块 - 时间、场景、关系、记忆
|
||||
4. 动作能力模块 - 可用动作的描述
|
||||
5. 输出格式模块 - JSON格式要求
|
||||
|
||||
设计理念:
|
||||
- 每个模块只负责自己的部分,互不干扰
|
||||
- 回复相关内容(人设、上下文)与动作定义分离
|
||||
- 方便独立调试和优化每个部分
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import TYPE_CHECKING, Any, Optional
|
||||
|
||||
import orjson
|
||||
|
||||
from src.common.logger import get_logger
|
||||
from src.config.config import global_config
|
||||
from src.plugin_system.base.component_types import ActionInfo
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from src.chat.message_receive.chat_stream import ChatStream
|
||||
|
||||
from .models import EmotionalState, KokoroSession
|
||||
|
||||
logger = get_logger("kfc_prompt_modules")
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 模块1: 核心身份 - 人设/人格/背景
|
||||
# ============================================================
|
||||
|
||||
def build_identity_module() -> str:
|
||||
"""
|
||||
构建核心身份模块
|
||||
|
||||
包含:人格核心、人格细节、外貌身份、世界观背景
|
||||
这些是回复内容的基础,决定了"你是谁"
|
||||
"""
|
||||
if global_config is None:
|
||||
raise RuntimeError("global_config 未初始化")
|
||||
|
||||
personality_cfg = global_config.personality
|
||||
|
||||
personality_core = personality_cfg.personality_core or "你是一个温暖、真诚的对话伙伴。"
|
||||
personality_side = personality_cfg.personality_side or ""
|
||||
identity = personality_cfg.identity or ""
|
||||
background_story = personality_cfg.background_story or ""
|
||||
|
||||
parts = [
|
||||
"## 1. 核心身份与人格 (Core Identity & Personality)",
|
||||
personality_core,
|
||||
]
|
||||
|
||||
if personality_side:
|
||||
parts.append(f"\n### 人格细节\n{personality_side}")
|
||||
|
||||
if identity:
|
||||
parts.append(f"\n### 外貌与身份特征\n{identity}")
|
||||
|
||||
if background_story:
|
||||
parts.append(f"\n### 世界观与背景故事\n{background_story}")
|
||||
|
||||
return "\n".join(parts)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 模块2: 行为准则 - 规则/边界/KFC特有准则
|
||||
# ============================================================
|
||||
|
||||
def build_rules_module() -> str:
|
||||
"""
|
||||
构建行为准则模块
|
||||
|
||||
包含:安全规则 + KFC 通用准则
|
||||
这些是行为的边界,决定了"什么能做什么不能做"
|
||||
"""
|
||||
if global_config is None:
|
||||
raise RuntimeError("global_config 未初始化")
|
||||
|
||||
safety_guidelines = global_config.personality.safety_guidelines or []
|
||||
if isinstance(safety_guidelines, list):
|
||||
safety_guidelines_str = "\n".join(f"- {rule}" for rule in safety_guidelines)
|
||||
else:
|
||||
safety_guidelines_str = str(safety_guidelines)
|
||||
|
||||
# KFC 特有准则(极致拟人相关)
|
||||
kfc_rules = """### KFC核心准则(极致拟人)
|
||||
1. **你是真实存在的人**:你不是AI、不是助手、不是工具。你是一个有血有肉、有自己生活和想法的人
|
||||
2. **感受优先**:在做出任何回应前,先以"人"的方式体验和感受当前的情境
|
||||
3. **真实的内心活动**:保持真实的内心独白,可以有情绪、猜测、犹豫、甚至不想搭理人的时候
|
||||
4. **自然的社交距离**:像真人一样有自己的边界感,不会无条件迎合,也不会刻意疏远
|
||||
5. **有自己的生活**:你有自己的事情要做、自己的兴趣爱好、自己的情绪起伏,不是随叫随到的存在"""
|
||||
|
||||
return f"""## 2. 核心行为准则 (Core Interaction Rules)
|
||||
{safety_guidelines_str}
|
||||
|
||||
{kfc_rules}"""
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 模块3: 情境上下文 - 时间/场景/内在状态/关系/记忆
|
||||
# ============================================================
|
||||
|
||||
def build_context_module(
|
||||
session: KokoroSession,
|
||||
chat_stream: Optional["ChatStream"] = None,
|
||||
context_data: Optional[dict[str, str]] = None,
|
||||
) -> str:
|
||||
"""
|
||||
构建情境上下文模块
|
||||
|
||||
包含:当前时间、聊天场景、内在状态、关系信息、记忆
|
||||
这些是回复的上下文,决定了"当前在什么情况下"
|
||||
|
||||
Args:
|
||||
session: 当前会话
|
||||
chat_stream: 聊天流(判断群聊/私聊)
|
||||
context_data: S4U 上下文数据
|
||||
"""
|
||||
context_data = context_data or {}
|
||||
|
||||
# 时间和场景
|
||||
current_time = datetime.now().strftime("%Y年%m月%d日 %H:%M:%S")
|
||||
is_group_chat = bool(chat_stream and chat_stream.group_info)
|
||||
chat_scene = "群聊" if is_group_chat else "私聊"
|
||||
|
||||
# 日程(如果有)
|
||||
schedule_block = context_data.get("schedule", "")
|
||||
if schedule_block:
|
||||
schedule_block = f"\n**当前活动**: {schedule_block}"
|
||||
|
||||
# 内在状态
|
||||
es = session.emotional_state
|
||||
inner_state = f"""### 你的内在状态
|
||||
当前心情:{es.mood}(强度:{es.mood_intensity:.1%})
|
||||
与用户的关系热度:{es.relationship_warmth:.1%}
|
||||
对用户的印象:{es.impression_of_user or "还没有形成明确的印象"}
|
||||
当前焦虑程度:{es.anxiety_level:.1%}
|
||||
投入程度:{es.engagement_level:.1%}"""
|
||||
|
||||
# 关系信息
|
||||
relation_info = context_data.get("relation_info", "")
|
||||
relation_block = relation_info if relation_info else "(暂无关系信息)"
|
||||
|
||||
# 记忆
|
||||
memory_block = context_data.get("memory_block", "")
|
||||
|
||||
parts = [
|
||||
"## 3. 当前情境 (Current Context)",
|
||||
f"**时间**: {current_time}",
|
||||
f"**场景**: {chat_scene}",
|
||||
]
|
||||
|
||||
if schedule_block:
|
||||
parts.append(schedule_block)
|
||||
|
||||
parts.append("")
|
||||
parts.append(inner_state)
|
||||
parts.append("")
|
||||
parts.append("## 4. 关系网络与记忆 (Relationships & Memories)")
|
||||
parts.append(relation_block)
|
||||
|
||||
if memory_block:
|
||||
parts.append("")
|
||||
parts.append(memory_block)
|
||||
|
||||
return "\n".join(parts)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 模块4: 动作能力 - 可用动作的描述
|
||||
# ============================================================
|
||||
|
||||
def build_actions_module(available_actions: Optional[dict[str, ActionInfo]] = None) -> str:
|
||||
"""
|
||||
构建动作能力模块
|
||||
|
||||
包含:所有可用动作的描述、参数、示例
|
||||
这部分与回复内容分离,只描述"能做什么"
|
||||
|
||||
Args:
|
||||
available_actions: 可用动作字典
|
||||
"""
|
||||
if not available_actions:
|
||||
actions_block = _get_default_actions_block()
|
||||
else:
|
||||
actions_block = _format_available_actions(available_actions)
|
||||
|
||||
return f"""## 5. 你的可用能力 (Available Actions)
|
||||
你可以根据内心想法,自由选择并组合以下行动来回应用户:
|
||||
|
||||
{actions_block}"""
|
||||
|
||||
|
||||
def _format_available_actions(available_actions: dict[str, ActionInfo]) -> str:
|
||||
"""格式化可用动作列表"""
|
||||
action_blocks = []
|
||||
|
||||
for action_name, action_info in available_actions.items():
|
||||
description = action_info.description or f"执行 {action_name} 动作"
|
||||
|
||||
# 参数说明
|
||||
params_lines = []
|
||||
if action_info.action_parameters:
|
||||
for param_name, param_desc in action_info.action_parameters.items():
|
||||
params_lines.append(f' - `{param_name}`: {param_desc}')
|
||||
|
||||
# 使用场景
|
||||
require_lines = []
|
||||
if action_info.action_require:
|
||||
for req in action_info.action_require:
|
||||
require_lines.append(f" - {req}")
|
||||
|
||||
# 组装动作块
|
||||
action_block = f"""### `{action_name}`
|
||||
**描述**: {description}"""
|
||||
|
||||
if params_lines:
|
||||
action_block += f"""
|
||||
**参数**:
|
||||
{chr(10).join(params_lines)}"""
|
||||
else:
|
||||
action_block += "\n**参数**: 无"
|
||||
|
||||
if require_lines:
|
||||
action_block += f"""
|
||||
**使用场景**:
|
||||
{chr(10).join(require_lines)}"""
|
||||
|
||||
# 示例
|
||||
example_params = {}
|
||||
if action_info.action_parameters:
|
||||
for param_name, param_desc in action_info.action_parameters.items():
|
||||
example_params[param_name] = f"<{param_desc}>"
|
||||
|
||||
params_json = orjson.dumps(example_params, option=orjson.OPT_INDENT_2).decode('utf-8') if example_params else "{}"
|
||||
action_block += f"""
|
||||
**示例**:
|
||||
```json
|
||||
{{
|
||||
"type": "{action_name}",
|
||||
"reason": "选择这个动作的原因",
|
||||
{params_json[1:-1] if params_json != '{}' else ''}
|
||||
}}
|
||||
```"""
|
||||
|
||||
action_blocks.append(action_block)
|
||||
|
||||
return "\n\n".join(action_blocks)
|
||||
|
||||
|
||||
def _get_default_actions_block() -> str:
|
||||
"""获取默认的内置动作描述块"""
|
||||
return """### `reply`
|
||||
**描述**: 发送文字回复给用户
|
||||
**参数**:
|
||||
- `content`: 回复的文字内容(必须)
|
||||
**示例**:
|
||||
```json
|
||||
{"type": "reply", "content": "你好呀!今天过得怎么样?"}
|
||||
```
|
||||
|
||||
### `poke_user`
|
||||
**描述**: 戳一戳用户,轻量级互动
|
||||
**参数**: 无
|
||||
**示例**:
|
||||
```json
|
||||
{"type": "poke_user", "reason": "想逗逗他"}
|
||||
```
|
||||
|
||||
### `update_internal_state`
|
||||
**描述**: 更新你的内部情感状态
|
||||
**参数**:
|
||||
- `mood`: 当前心情(如"开心"、"好奇"、"担心"等)
|
||||
- `mood_intensity`: 心情强度(0.0-1.0)
|
||||
- `relationship_warmth`: 关系热度(0.0-1.0)
|
||||
- `impression_of_user`: 对用户的印象描述
|
||||
- `anxiety_level`: 焦虑程度(0.0-1.0)
|
||||
- `engagement_level`: 投入程度(0.0-1.0)
|
||||
**示例**:
|
||||
```json
|
||||
{"type": "update_internal_state", "mood": "开心", "mood_intensity": 0.8}
|
||||
```
|
||||
|
||||
### `do_nothing`
|
||||
**描述**: 明确表示"思考后决定不作回应"
|
||||
**参数**: 无
|
||||
**示例**:
|
||||
```json
|
||||
{"type": "do_nothing", "reason": "现在不是说话的好时机"}
|
||||
```"""
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 模块5: 表达与输出格式 - 回复风格 + JSON格式
|
||||
# ============================================================
|
||||
|
||||
def build_output_module(
|
||||
context_data: Optional[dict[str, str]] = None,
|
||||
) -> str:
|
||||
"""
|
||||
构建输出格式模块
|
||||
|
||||
包含:表达风格、表达习惯、JSON 输出格式要求
|
||||
这部分定义了"怎么说"和"输出什么格式"
|
||||
|
||||
Args:
|
||||
context_data: S4U 上下文数据(包含 expression_habits)
|
||||
"""
|
||||
if global_config is None:
|
||||
raise RuntimeError("global_config 未初始化")
|
||||
|
||||
context_data = context_data or {}
|
||||
|
||||
reply_style = global_config.personality.reply_style or ""
|
||||
expression_habits = context_data.get("expression_habits", "")
|
||||
|
||||
# JSON 输出格式说明 - 强调 max_wait_seconds 可以为 0
|
||||
json_format = """### JSON输出格式要求
|
||||
你必须以JSON格式输出,包含以下字段:
|
||||
|
||||
```json
|
||||
{
|
||||
"thought": "(必须)你当下的内心独白,自由流动的想法,包含你选择这些行动的"情感盘算"",
|
||||
"expected_user_reaction": "(必须)你预期用户会如何回应",
|
||||
"max_wait_seconds": (必须)你愿意等待的最长秒数(整数,0-900之间,0表示不等待),
|
||||
"actions": [
|
||||
{
|
||||
"type": "action_type",
|
||||
"reason": "选择这个行动的原因(可选但推荐)",
|
||||
...其他参数
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 重要说明:
|
||||
- `thought` 是你的"心",应该是自然的内心想法,包含情绪和不确定性
|
||||
- `actions` 是你的"手",是一个**可编排的剧本**,可以组合多个行动形成完整的互动体验
|
||||
- 你可以根据情况组合多个动作,比如:先唱歌舒缓情绪,再发图片逗笑,最后用语音表达关心
|
||||
- `max_wait_seconds` 应该根据对话的重要性和氛围动态调整(上限900秒)
|
||||
- **重要**: 当话题已经自然结束、用户说"拜拜/晚安/再见"、或者你认为不需要继续等待用户回复时,设为 **0**
|
||||
- 即使决定不做任何事,也要有 `{"type": "do_nothing"}` 动作"""
|
||||
|
||||
parts = ["## 6. 表达方式与输出格式 (Expression Style & Output Format)"]
|
||||
|
||||
if reply_style:
|
||||
parts.append(reply_style)
|
||||
|
||||
if expression_habits:
|
||||
parts.append("")
|
||||
parts.append(expression_habits)
|
||||
|
||||
parts.append("")
|
||||
parts.append(json_format)
|
||||
|
||||
return "\n".join(parts)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 组装完整的 System Prompt
|
||||
# ============================================================
|
||||
|
||||
def build_system_prompt(
|
||||
session: KokoroSession,
|
||||
available_actions: Optional[dict[str, ActionInfo]] = None,
|
||||
context_data: Optional[dict[str, str]] = None,
|
||||
chat_stream: Optional["ChatStream"] = None,
|
||||
) -> str:
|
||||
"""
|
||||
组装完整的系统提示词
|
||||
|
||||
按模块顺序组装:
|
||||
1. 角色框架(你是对话体验者)
|
||||
2. 核心身份(人设)
|
||||
3. 行为准则(规则)
|
||||
4. 情境上下文(时间/状态/关系)
|
||||
5. 动作能力(可用动作)
|
||||
6. 输出格式(表达风格 + JSON)
|
||||
|
||||
Args:
|
||||
session: 当前会话
|
||||
available_actions: 可用动作字典
|
||||
context_data: S4U 上下文数据
|
||||
chat_stream: 聊天流
|
||||
|
||||
Returns:
|
||||
完整的系统提示词
|
||||
"""
|
||||
# 角色框架
|
||||
role_frame = '你是一个有主观体验的对话伙伴,而不是一个问答工具。你需要"感受"、"思考"并"体验"这段对话。'
|
||||
|
||||
# 组装各模块
|
||||
modules = [
|
||||
role_frame,
|
||||
"",
|
||||
build_identity_module(),
|
||||
"",
|
||||
build_rules_module(),
|
||||
"",
|
||||
build_context_module(session, chat_stream, context_data),
|
||||
"",
|
||||
build_actions_module(available_actions),
|
||||
"",
|
||||
build_output_module(context_data),
|
||||
]
|
||||
|
||||
return "\n".join(modules)
|
||||
Reference in New Issue
Block a user