feat(kokoro_flow_chatter): 添加活动流格式配置及上下文构建功能,修复分离模式失效的问题
This commit is contained in:
@@ -995,6 +995,27 @@ class KokoroFlowChatterWaitingConfig(ValidatedConfigBase):
|
||||
)
|
||||
|
||||
|
||||
class KokoroFlowChatterPromptConfig(ValidatedConfigBase):
|
||||
"""Kokoro Flow Chatter 提示词/上下文构建配置"""
|
||||
|
||||
activity_stream_format: Literal["narrative", "table", "both"] = Field(
|
||||
default="narrative",
|
||||
description='活动流格式: "narrative"(线性叙事) / "table"(结构化表格) / "both"(两者都输出)',
|
||||
)
|
||||
max_activity_entries: int = Field(
|
||||
default=30,
|
||||
ge=0,
|
||||
le=200,
|
||||
description="活动流最多保留条数(越大越完整,但token越高)",
|
||||
)
|
||||
max_entry_length: int = Field(
|
||||
default=500,
|
||||
ge=0,
|
||||
le=5000,
|
||||
description="活动流单条最大字符数(用于裁剪,避免单条过长拖垮上下文)",
|
||||
)
|
||||
|
||||
|
||||
class KokoroFlowChatterConfig(ValidatedConfigBase):
|
||||
"""
|
||||
Kokoro Flow Chatter 配置类 - 私聊专用心流对话系统
|
||||
@@ -1031,6 +1052,11 @@ class KokoroFlowChatterConfig(ValidatedConfigBase):
|
||||
description="自定义KFC决策行为指导提示词(unified影响整体,split仅影响planner)",
|
||||
)
|
||||
|
||||
prompt: KokoroFlowChatterPromptConfig = Field(
|
||||
default_factory=KokoroFlowChatterPromptConfig,
|
||||
description="提示词/上下文构建配置(活动流格式、裁剪等)",
|
||||
)
|
||||
|
||||
waiting: KokoroFlowChatterWaitingConfig = Field(
|
||||
default_factory=KokoroFlowChatterWaitingConfig,
|
||||
description="等待策略配置(默认等待时间、倍率等)",
|
||||
|
||||
@@ -223,6 +223,9 @@ class KokoroFlowChatter(BaseChatter):
|
||||
exec_results.append(result)
|
||||
if result.get("success") and action.type in ("kfc_reply", "respond"):
|
||||
has_reply = True
|
||||
reply_text = (result.get("reply_text") or "").strip()
|
||||
if reply_text:
|
||||
action.params["content"] = reply_text
|
||||
|
||||
# 11. 记录 Bot 规划到 mental_log
|
||||
session.add_bot_planning(
|
||||
@@ -336,6 +339,12 @@ class KokoroFlowChatter(BaseChatter):
|
||||
# 为 kfc_reply 动作注入回复生成所需的上下文
|
||||
for action in plan_response.actions:
|
||||
if action.type == "kfc_reply":
|
||||
# 分离模式下 Planner 不应直接生成回复内容;即使模型输出了 content,也应忽略
|
||||
if "content" in action.params and action.params.get("content"):
|
||||
logger.warning(
|
||||
"[KFC] Split模式下Planner输出了kfc_reply.content,已忽略(由Replyer生成)"
|
||||
)
|
||||
action.params.pop("content", None)
|
||||
action.params["user_id"] = user_id
|
||||
action.params["user_name"] = user_name
|
||||
action.params["thought"] = plan_response.thought
|
||||
|
||||
@@ -90,6 +90,12 @@ class PromptConfig:
|
||||
# 每条记录最大字符数
|
||||
max_entry_length: int = 500
|
||||
|
||||
# 活动流格式:narrative(线性叙事)/ table(结构化表格)/ both(两者都给)
|
||||
# - narrative: 更自然,但信息密度较低,长时更容易丢细节
|
||||
# - table: 更高信息密度,便于模型对齐字段、检索与对比
|
||||
# - both: 调试/对照用,token 更高
|
||||
activity_stream_format: str = "narrative"
|
||||
|
||||
# 是否包含人物关系信息
|
||||
include_relation: bool = True
|
||||
|
||||
@@ -236,6 +242,11 @@ def load_config() -> KokoroFlowChatterConfig:
|
||||
config.prompt = PromptConfig(
|
||||
max_activity_entries=getattr(pmt_cfg, "max_activity_entries", 30),
|
||||
max_entry_length=getattr(pmt_cfg, "max_entry_length", 500),
|
||||
activity_stream_format=getattr(
|
||||
pmt_cfg,
|
||||
"activity_stream_format",
|
||||
getattr(pmt_cfg, "activity_format", "narrative"),
|
||||
),
|
||||
include_relation=getattr(pmt_cfg, "include_relation", True),
|
||||
include_memory=getattr(pmt_cfg, "include_memory", True),
|
||||
)
|
||||
|
||||
@@ -456,6 +456,11 @@ class ProactiveThinker:
|
||||
# 分离模式下需要注入上下文信息
|
||||
for action in plan_response.actions:
|
||||
if action.type == "kfc_reply":
|
||||
if "content" in action.params and action.params.get("content"):
|
||||
logger.warning(
|
||||
"[KFC ProactiveThinker] Split模式下Planner输出了kfc_reply.content,已忽略(由Replyer生成)"
|
||||
)
|
||||
action.params.pop("content", None)
|
||||
action.params["user_id"] = session.user_id
|
||||
action.params["user_name"] = user_name
|
||||
action.params["thought"] = plan_response.thought
|
||||
@@ -495,7 +500,7 @@ class ProactiveThinker:
|
||||
|
||||
# 执行动作(回复生成在 Action.execute() 中完成)
|
||||
for action in plan_response.actions:
|
||||
await action_manager.execute_action(
|
||||
result = await action_manager.execute_action(
|
||||
action_name=action.type,
|
||||
chat_id=session.stream_id,
|
||||
target_message=None,
|
||||
@@ -504,6 +509,10 @@ class ProactiveThinker:
|
||||
thinking_id=None,
|
||||
log_prefix="[KFC ProactiveThinker]",
|
||||
)
|
||||
if result.get("success") and action.type in ("kfc_reply", "respond"):
|
||||
reply_text = (result.get("reply_text") or "").strip()
|
||||
if reply_text:
|
||||
action.params["content"] = reply_text
|
||||
|
||||
# 🎯 只有真正发送了消息才增加追问计数(do_nothing 不算追问)
|
||||
has_reply_action = any(
|
||||
@@ -703,6 +712,11 @@ class ProactiveThinker:
|
||||
if self._mode == KFCMode.SPLIT:
|
||||
for action in plan_response.actions:
|
||||
if action.type == "kfc_reply":
|
||||
if "content" in action.params and action.params.get("content"):
|
||||
logger.warning(
|
||||
"[KFC ProactiveThinker] Split模式下Planner输出了kfc_reply.content,已忽略(由Replyer生成)"
|
||||
)
|
||||
action.params.pop("content", None)
|
||||
action.params["user_id"] = session.user_id
|
||||
action.params["user_name"] = user_name
|
||||
action.params["thought"] = plan_response.thought
|
||||
@@ -735,7 +749,7 @@ class ProactiveThinker:
|
||||
|
||||
# 执行动作(回复生成在 Action.execute() 中完成)
|
||||
for action in plan_response.actions:
|
||||
await action_manager.execute_action(
|
||||
result = await action_manager.execute_action(
|
||||
action_name=action.type,
|
||||
chat_id=session.stream_id,
|
||||
target_message=None,
|
||||
@@ -744,6 +758,10 @@ class ProactiveThinker:
|
||||
thinking_id=None,
|
||||
log_prefix="[KFC ProactiveThinker]",
|
||||
)
|
||||
if result.get("success") and action.type in ("kfc_reply", "respond"):
|
||||
reply_text = (result.get("reply_text") or "").strip()
|
||||
if reply_text:
|
||||
action.params["content"] = reply_text
|
||||
|
||||
# 记录到 mental_log
|
||||
session.add_bot_planning(
|
||||
|
||||
@@ -284,6 +284,42 @@ class PromptBuilder:
|
||||
|
||||
return ""
|
||||
|
||||
def _build_last_bot_action_block(self, session: KokoroSession | None) -> str:
|
||||
"""
|
||||
构建“最近一次Bot动作/发言”块(用于插入到当前情况里)
|
||||
|
||||
目的:让模型在决策时能显式参考“我刚刚做过什么/说过什么”,降低长上下文里漏细节的概率。
|
||||
"""
|
||||
if not session or not getattr(session, "mental_log", None):
|
||||
return ""
|
||||
|
||||
last_planning_entry: MentalLogEntry | None = None
|
||||
for entry in reversed(session.mental_log):
|
||||
if entry.event_type == EventType.BOT_PLANNING:
|
||||
last_planning_entry = entry
|
||||
break
|
||||
|
||||
if not last_planning_entry:
|
||||
return ""
|
||||
|
||||
actions_desc = self._format_actions(last_planning_entry.actions)
|
||||
|
||||
last_message = ""
|
||||
for action in last_planning_entry.actions:
|
||||
if action.get("type") == "kfc_reply":
|
||||
content = (action.get("content") or "").strip()
|
||||
if content:
|
||||
last_message = content
|
||||
|
||||
if last_message and len(last_message) > 80:
|
||||
last_message = last_message[:80] + "..."
|
||||
|
||||
lines = [f"你最近一次执行的动作是:{actions_desc}"]
|
||||
if last_message:
|
||||
lines.append(f"你上一次发出的消息是:「{last_message}」")
|
||||
|
||||
return "\n".join(lines) + "\n\n"
|
||||
|
||||
async def _build_context_data(
|
||||
self,
|
||||
user_name: str,
|
||||
@@ -541,14 +577,39 @@ class PromptBuilder:
|
||||
构建活动流
|
||||
|
||||
将 mental_log 中的事件按时间顺序转换为线性叙事
|
||||
使用统一的 prompt 模板
|
||||
支持线性叙事或结构化表格两种格式(可通过配置切换)
|
||||
"""
|
||||
entries = session.get_recent_entries(limit=30)
|
||||
from ..config import get_config
|
||||
|
||||
kfc_config = get_config()
|
||||
prompt_cfg = getattr(kfc_config, "prompt", None)
|
||||
max_entries = getattr(prompt_cfg, "max_activity_entries", 30) if prompt_cfg else 30
|
||||
max_entry_length = getattr(prompt_cfg, "max_entry_length", 500) if prompt_cfg else 500
|
||||
stream_format = (
|
||||
getattr(prompt_cfg, "activity_stream_format", "narrative") if prompt_cfg else "narrative"
|
||||
)
|
||||
|
||||
entries = session.get_recent_entries(limit=max_entries)
|
||||
if not entries:
|
||||
return ""
|
||||
|
||||
parts = []
|
||||
stream_format = (stream_format or "narrative").strip().lower()
|
||||
if stream_format == "table":
|
||||
return self._build_activity_stream_table(entries, user_name, max_entry_length)
|
||||
if stream_format == "both":
|
||||
table = self._build_activity_stream_table(entries, user_name, max_entry_length)
|
||||
narrative = await self._build_activity_stream_narrative(entries, user_name)
|
||||
return "\n\n".join([p for p in (table, narrative) if p])
|
||||
|
||||
return await self._build_activity_stream_narrative(entries, user_name)
|
||||
|
||||
async def _build_activity_stream_narrative(
|
||||
self,
|
||||
entries: list[MentalLogEntry],
|
||||
user_name: str,
|
||||
) -> str:
|
||||
"""构建线性叙事活动流(旧格式)"""
|
||||
parts: list[str] = []
|
||||
for entry in entries:
|
||||
part = await self._format_entry(entry, user_name)
|
||||
if part:
|
||||
@@ -556,6 +617,95 @@ class PromptBuilder:
|
||||
|
||||
return "\n\n".join(parts)
|
||||
|
||||
def _build_activity_stream_table(
|
||||
self,
|
||||
entries: list[MentalLogEntry],
|
||||
user_name: str,
|
||||
max_cell_length: int = 500,
|
||||
) -> str:
|
||||
"""
|
||||
构建结构化表格活动流(更高信息密度)
|
||||
|
||||
统一列:序号 / 时间 / 事件类型 / 内容 / 想法 / 行动 / 结果
|
||||
"""
|
||||
|
||||
def truncate(text: str, limit: int) -> str:
|
||||
if not text:
|
||||
return ""
|
||||
if limit <= 0:
|
||||
return text
|
||||
text = text.strip()
|
||||
return text if len(text) <= limit else (text[: max(0, limit - 1)] + "…")
|
||||
|
||||
def md_cell(value: str) -> str:
|
||||
value = (value or "").replace("\r\n", "\n").replace("\n", "<br>")
|
||||
value = value.replace("|", "\\|")
|
||||
return truncate(value, max_cell_length)
|
||||
|
||||
event_type_alias = {
|
||||
EventType.USER_MESSAGE: "用户消息",
|
||||
EventType.BOT_PLANNING: "你的决策",
|
||||
EventType.WAITING_UPDATE: "等待中",
|
||||
EventType.PROACTIVE_TRIGGER: "主动触发",
|
||||
}
|
||||
|
||||
header = ["#", "时间", "类型", "内容", "想法", "行动", "结果"]
|
||||
lines = [
|
||||
"|" + "|".join(header) + "|",
|
||||
"|" + "|".join(["---"] * len(header)) + "|",
|
||||
]
|
||||
|
||||
for idx, entry in enumerate(entries, 1):
|
||||
time_str = entry.get_time_str()
|
||||
type_str = event_type_alias.get(entry.event_type, str(entry.event_type))
|
||||
|
||||
content = ""
|
||||
thought = ""
|
||||
action = ""
|
||||
result = ""
|
||||
|
||||
if entry.event_type == EventType.USER_MESSAGE:
|
||||
content = entry.content
|
||||
reply_status = entry.metadata.get("reply_status")
|
||||
if reply_status in ("in_time", "late"):
|
||||
elapsed_min = entry.metadata.get("elapsed_seconds", 0) / 60
|
||||
max_wait_min = entry.metadata.get("max_wait_seconds", 0) / 60
|
||||
status_cn = "及时" if reply_status == "in_time" else "迟到"
|
||||
result = f"回复{status_cn}(等{elapsed_min:.1f}/{max_wait_min:.1f}分钟)"
|
||||
|
||||
elif entry.event_type == EventType.BOT_PLANNING:
|
||||
thought = entry.thought or "(无)"
|
||||
action = self._format_actions(entry.actions)
|
||||
if entry.max_wait_seconds > 0:
|
||||
wait_min = entry.max_wait_seconds / 60
|
||||
expected = entry.expected_reaction or "(无)"
|
||||
result = f"等待≤{wait_min:.1f}分钟;期待={expected}"
|
||||
else:
|
||||
result = "不等待"
|
||||
|
||||
elif entry.event_type == EventType.WAITING_UPDATE:
|
||||
thought = entry.waiting_thought or "还在等…"
|
||||
elapsed_min = entry.elapsed_seconds / 60
|
||||
mood = (entry.mood or "").strip()
|
||||
result = f"已等{elapsed_min:.1f}分钟" + (f";心情={mood}" if mood else "")
|
||||
|
||||
elif entry.event_type == EventType.PROACTIVE_TRIGGER:
|
||||
silence = entry.metadata.get("silence_duration", "一段时间")
|
||||
result = f"沉默{silence}"
|
||||
|
||||
row = [
|
||||
str(idx),
|
||||
md_cell(time_str),
|
||||
md_cell(type_str),
|
||||
md_cell(content),
|
||||
md_cell(thought),
|
||||
md_cell(action),
|
||||
md_cell(result),
|
||||
]
|
||||
lines.append("|" + "|".join(row) + "|")
|
||||
|
||||
return "(结构化活动流表;按时间顺序)\n" + "\n".join(lines)
|
||||
|
||||
async def _format_entry(self, entry: MentalLogEntry, user_name: str) -> str:
|
||||
"""格式化单个活动日志条目"""
|
||||
|
||||
@@ -661,6 +811,7 @@ class PromptBuilder:
|
||||
) -> str:
|
||||
"""构建当前情况描述"""
|
||||
current_time = datetime.now().strftime("%Y年%m月%d日 %H:%M")
|
||||
last_action_block = self._build_last_bot_action_block(session)
|
||||
|
||||
# 如果之前没有设置等待时间(max_wait_seconds == 0),视为 new_message
|
||||
if situation_type in ("reply_in_time", "reply_late"):
|
||||
@@ -674,6 +825,7 @@ class PromptBuilder:
|
||||
return await global_prompt_manager.format_prompt(
|
||||
PROMPT_NAMES["situation_new_message"],
|
||||
current_time=current_time,
|
||||
last_action_block=last_action_block,
|
||||
user_name=user_name,
|
||||
latest_message=latest_message,
|
||||
)
|
||||
@@ -685,6 +837,7 @@ class PromptBuilder:
|
||||
return await global_prompt_manager.format_prompt(
|
||||
PROMPT_NAMES["situation_reply_in_time"],
|
||||
current_time=current_time,
|
||||
last_action_block=last_action_block,
|
||||
user_name=user_name,
|
||||
elapsed_minutes=elapsed / 60,
|
||||
max_wait_minutes=max_wait / 60,
|
||||
@@ -698,6 +851,7 @@ class PromptBuilder:
|
||||
return await global_prompt_manager.format_prompt(
|
||||
PROMPT_NAMES["situation_reply_late"],
|
||||
current_time=current_time,
|
||||
last_action_block=last_action_block,
|
||||
user_name=user_name,
|
||||
elapsed_minutes=elapsed / 60,
|
||||
max_wait_minutes=max_wait / 60,
|
||||
@@ -743,6 +897,7 @@ class PromptBuilder:
|
||||
return await global_prompt_manager.format_prompt(
|
||||
PROMPT_NAMES["situation_timeout"],
|
||||
current_time=current_time,
|
||||
last_action_block=last_action_block,
|
||||
user_name=user_name,
|
||||
elapsed_minutes=elapsed / 60,
|
||||
max_wait_minutes=max_wait / 60,
|
||||
@@ -756,6 +911,7 @@ class PromptBuilder:
|
||||
return await global_prompt_manager.format_prompt(
|
||||
PROMPT_NAMES["situation_proactive"],
|
||||
current_time=current_time,
|
||||
last_action_block=last_action_block,
|
||||
user_name=user_name,
|
||||
silence_duration=silence,
|
||||
trigger_reason=trigger_reason,
|
||||
@@ -766,6 +922,7 @@ class PromptBuilder:
|
||||
PROMPT_NAMES["situation_new_message"],
|
||||
current_time=current_time,
|
||||
user_name=user_name,
|
||||
last_action_block=last_action_block,
|
||||
)
|
||||
|
||||
def _build_actions_block(self, available_actions: dict | None) -> str:
|
||||
@@ -926,15 +1083,17 @@ class PromptBuilder:
|
||||
"""
|
||||
from datetime import datetime
|
||||
current_time = datetime.now().strftime("%Y年%m月%d日 %H:%M")
|
||||
last_action_block = self._build_last_bot_action_block(session)
|
||||
|
||||
if situation_type == "new_message":
|
||||
return f"现在是 {current_time}。{user_name} 刚给你发了消息。"
|
||||
return f"现在是 {current_time}。\n\n{last_action_block}{user_name} 刚给你发了消息。"
|
||||
|
||||
elif situation_type == "reply_in_time":
|
||||
elapsed = session.waiting_config.get_elapsed_seconds()
|
||||
max_wait = session.waiting_config.max_wait_seconds
|
||||
return (
|
||||
f"现在是 {current_time}。\n"
|
||||
f"现在是 {current_time}。\n\n"
|
||||
f"{last_action_block}"
|
||||
f"你之前发了消息后在等 {user_name} 的回复。"
|
||||
f"等了大约 {elapsed / 60:.1f} 分钟(你原本打算最多等 {max_wait / 60:.1f} 分钟)。"
|
||||
f"现在 {user_name} 回复了!"
|
||||
@@ -944,7 +1103,8 @@ class PromptBuilder:
|
||||
elapsed = session.waiting_config.get_elapsed_seconds()
|
||||
max_wait = session.waiting_config.max_wait_seconds
|
||||
return (
|
||||
f"现在是 {current_time}。\n"
|
||||
f"现在是 {current_time}。\n\n"
|
||||
f"{last_action_block}"
|
||||
f"你之前发了消息后在等 {user_name} 的回复。"
|
||||
f"你原本打算最多等 {max_wait / 60:.1f} 分钟,但实际等了 {elapsed / 60:.1f} 分钟才收到回复。"
|
||||
f"虽然有点迟,但 {user_name} 终于回复了。"
|
||||
@@ -954,7 +1114,8 @@ class PromptBuilder:
|
||||
elapsed = session.waiting_config.get_elapsed_seconds()
|
||||
max_wait = session.waiting_config.max_wait_seconds
|
||||
return (
|
||||
f"现在是 {current_time}。\n"
|
||||
f"现在是 {current_time}。\n\n"
|
||||
f"{last_action_block}"
|
||||
f"你之前发了消息后一直在等 {user_name} 的回复。"
|
||||
f"你原本打算最多等 {max_wait / 60:.1f} 分钟,现在已经等了 {elapsed / 60:.1f} 分钟了,对方还是没回。"
|
||||
f"你决定主动说点什么。"
|
||||
@@ -963,13 +1124,14 @@ class PromptBuilder:
|
||||
elif situation_type == "proactive":
|
||||
silence = extra_context.get("silence_duration", "一段时间")
|
||||
return (
|
||||
f"现在是 {current_time}。\n"
|
||||
f"现在是 {current_time}。\n\n"
|
||||
f"{last_action_block}"
|
||||
f"你和 {user_name} 已经有一段时间没聊天了(沉默了 {silence})。"
|
||||
f"你决定主动找 {user_name} 聊点什么。"
|
||||
)
|
||||
|
||||
# 默认
|
||||
return f"现在是 {current_time}。"
|
||||
return f"现在是 {current_time}。\n\n{last_action_block}".rstrip()
|
||||
|
||||
async def _build_reply_context(
|
||||
self,
|
||||
|
||||
@@ -34,7 +34,7 @@ kfc_MAIN_PROMPT = Prompt(
|
||||
{tool_info}
|
||||
|
||||
# 你们之间最近的活动记录
|
||||
以下是你和 {user_name} 最近的互动历史,按时间顺序记录了你们的对话和你的心理活动:
|
||||
以下是你和 {user_name} 最近的互动历史,按时间顺序记录了你们的对话和你的心理活动(可能是线性叙事或结构化表格):
|
||||
{activity_stream}
|
||||
|
||||
# 聊天历史总览
|
||||
@@ -69,7 +69,7 @@ kfc_OUTPUT_FORMAT = Prompt(
|
||||
{{"type": "动作名称", ...动作参数}}
|
||||
],
|
||||
"expected_reaction": "你期待对方的反应是什么",
|
||||
- `max_wait_seconds`:预估的等待时间(秒),请根据对话节奏来判断。通常你应该设置为0避免总是等待显得聒噪,但是当你觉得你需要等待对方回复时,可以设置一个合理的等待时间。
|
||||
"max_wait_seconds": 0
|
||||
}}
|
||||
```
|
||||
|
||||
@@ -93,7 +93,7 @@ kfc_SITUATION_NEW_MESSAGE = Prompt(
|
||||
name="kfc_situation_new_message",
|
||||
template="""现在是 {current_time}。
|
||||
|
||||
{user_name} 刚刚给你发了消息:「{latest_message}」
|
||||
{last_action_block}{user_name} 刚刚给你发了消息:「{latest_message}」
|
||||
|
||||
这是一次新的对话发起(不是对你之前消息的回复)。
|
||||
|
||||
@@ -108,7 +108,7 @@ kfc_SITUATION_REPLY_IN_TIME = Prompt(
|
||||
name="kfc_situation_reply_in_time",
|
||||
template="""现在是 {current_time}。
|
||||
|
||||
你之前发了消息后一直在等 {user_name} 的回复。
|
||||
{last_action_block}你之前发了消息后一直在等 {user_name} 的回复。
|
||||
等了大约 {elapsed_minutes:.1f} 分钟(你原本打算最多等 {max_wait_minutes:.1f} 分钟)。
|
||||
现在 {user_name} 回复了:「{latest_message}」
|
||||
|
||||
@@ -119,7 +119,7 @@ kfc_SITUATION_REPLY_LATE = Prompt(
|
||||
name="kfc_situation_reply_late",
|
||||
template="""现在是 {current_time}。
|
||||
|
||||
你之前发了消息后在等 {user_name} 的回复。
|
||||
{last_action_block}你之前发了消息后在等 {user_name} 的回复。
|
||||
你原本打算最多等 {max_wait_minutes:.1f} 分钟,但实际等了 {elapsed_minutes:.1f} 分钟才收到回复。
|
||||
虽然有点迟,但 {user_name} 终于回复了:「{latest_message}」
|
||||
|
||||
@@ -130,7 +130,7 @@ kfc_SITUATION_TIMEOUT = Prompt(
|
||||
name="kfc_situation_timeout",
|
||||
template="""现在是 {current_time}。
|
||||
|
||||
你之前发了消息后一直在等 {user_name} 的回复。
|
||||
{last_action_block}你之前发了消息后一直在等 {user_name} 的回复。
|
||||
你原本打算最多等 {max_wait_minutes:.1f} 分钟,现在已经等了 {elapsed_minutes:.1f} 分钟了,对方还是没回。
|
||||
你当时期待的反应是:"{expected_reaction}"
|
||||
{timeout_context}
|
||||
@@ -161,7 +161,7 @@ kfc_SITUATION_PROACTIVE = Prompt(
|
||||
name="kfc_situation_proactive",
|
||||
template="""现在是 {current_time}。
|
||||
|
||||
你和 {user_name} 已经有一段时间没聊天了(沉默了 {silence_duration})。
|
||||
{last_action_block}你和 {user_name} 已经有一段时间没聊天了(沉默了 {silence_duration})。
|
||||
{trigger_reason}
|
||||
|
||||
你在想要不要主动找 {user_name} 聊点什么。
|
||||
@@ -251,7 +251,7 @@ kfc_PLANNER_OUTPUT_FORMAT = Prompt(
|
||||
{{"type": "动作名称", ...动作参数}}
|
||||
],
|
||||
"expected_reaction": "你期待对方的反应是什么",
|
||||
- `max_wait_seconds`:预估的等待时间(秒),请根据对话节奏来判断。通常你应该设置为0避免总是等待显得聒噪,但是当你觉得你需要等待对方回复时,可以设置一个合理的等待时间。
|
||||
"max_wait_seconds": 0
|
||||
}}
|
||||
```
|
||||
|
||||
@@ -264,6 +264,7 @@ kfc_PLANNER_OUTPUT_FORMAT = Prompt(
|
||||
|
||||
### 注意事项
|
||||
- 动作参数直接写在动作对象里,不需要 `action_data` 包装
|
||||
- **分离模式规则**:Planner 阶段禁止输出 `kfc_reply.content`(就算写了也会被系统忽略,回复内容由 Replyer 单独生成)
|
||||
- 即使什么都不想做,也放一个 `{{"type": "do_nothing"}}`
|
||||
- 可以组合多个动作,比如先发消息再发表情""",
|
||||
)
|
||||
@@ -406,7 +407,7 @@ kfc_UNIFIED_OUTPUT_FORMAT = Prompt(
|
||||
{{"type": "kfc_reply", "content": "你的回复内容"}}
|
||||
],
|
||||
"expected_reaction": "你期待对方的反应是什么",
|
||||
- `max_wait_seconds`:预估的等待时间(秒),请根据对话节奏来判断。通常你应该设置为0避免总是等待显得聒噪,但是当你觉得你需要等待对方回复时,可以设置一个合理的等待时间。
|
||||
"max_wait_seconds": 0
|
||||
}}
|
||||
```
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[inner]
|
||||
version = "8.0.0"
|
||||
version = "8.0.1"
|
||||
|
||||
#----以下是给开发人员阅读的,如果你只是部署了MoFox-Bot,不需要阅读----
|
||||
#如果你想要修改配置文件,请递增version的值
|
||||
@@ -638,6 +638,20 @@ enable_continuous_thinking = true # 是否在等待期间启用心理活动更
|
||||
# 留空则不生效
|
||||
custom_decision_prompt = ""
|
||||
|
||||
# --- 提示词/上下文构建配置 ---
|
||||
[kokoro_flow_chatter.prompt]
|
||||
# 活动流格式(你们之间最近发生的事)
|
||||
# - "narrative": 线性叙事(更自然,但信息密度较低,长时更容易丢细节)
|
||||
# - "table": 结构化表格(更高信息密度、更利于模型对齐字段;推荐)
|
||||
# - "both": 同时输出表格 + 叙事(对照/调试用,token 更高)
|
||||
activity_stream_format = "table"
|
||||
|
||||
# 活动流最多保留条数(越大越完整,但 token 越高)
|
||||
max_activity_entries = 5
|
||||
|
||||
# 表格单元格/叙事单条的最大字符数(用于裁剪,避免某条过长拖垮上下文)
|
||||
max_entry_length = 500
|
||||
|
||||
# --- 等待策略 ---
|
||||
[kokoro_flow_chatter.waiting]
|
||||
default_max_wait_seconds = 300 # LLM 未给出等待时间时的默认值
|
||||
|
||||
Reference in New Issue
Block a user