1
This commit is contained in:
@@ -26,9 +26,9 @@ use crate::{
|
||||
prompt::runtime_chat::{
|
||||
NPC_CHAT_TURN_REPLY_SYSTEM_PROMPT, NPC_CHAT_TURN_SUGGESTION_SYSTEM_PROMPT,
|
||||
NpcChatTurnPromptInput, build_deterministic_chat_suggestions,
|
||||
build_deterministic_npc_reply, build_fallback_function_suggestions,
|
||||
build_fallback_npc_chat_suggestions, build_npc_chat_turn_reply_prompt,
|
||||
build_npc_chat_turn_suggestion_prompt,
|
||||
build_deterministic_hostile_breakoff_reply, build_deterministic_npc_reply,
|
||||
build_fallback_function_suggestions, build_fallback_npc_chat_suggestions,
|
||||
build_npc_chat_turn_reply_prompt, build_npc_chat_turn_suggestion_prompt,
|
||||
},
|
||||
request_context::RequestContext,
|
||||
state::AppState,
|
||||
@@ -137,16 +137,26 @@ pub async fn stream_runtime_npc_chat_turn(
|
||||
let (npc_reply, suggestions, function_suggestions, force_exit) = match llm_result {
|
||||
Some(result) => result,
|
||||
None => {
|
||||
let npc_reply = build_deterministic_npc_reply(
|
||||
npc_name.as_str(),
|
||||
player_message.as_str(),
|
||||
payload.npc_initiates_conversation,
|
||||
);
|
||||
let force_exit = should_force_chat_exit(payload.chat_directive.as_ref())
|
||||
|| should_hostile_chat_breakoff_deterministically(
|
||||
let deterministic_hostile_breakoff =
|
||||
should_hostile_chat_breakoff_deterministically(
|
||||
player_message.as_str(),
|
||||
payload.chat_directive.as_ref(),
|
||||
Some(&payload.npc_state),
|
||||
);
|
||||
let force_exit = should_force_chat_exit(payload.chat_directive.as_ref())
|
||||
|| deterministic_hostile_breakoff;
|
||||
let npc_reply = if deterministic_hostile_breakoff {
|
||||
build_deterministic_hostile_breakoff_reply(
|
||||
npc_name.as_str(),
|
||||
player_message.as_str(),
|
||||
)
|
||||
} else {
|
||||
build_deterministic_npc_reply(
|
||||
npc_name.as_str(),
|
||||
player_message.as_str(),
|
||||
payload.npc_initiates_conversation,
|
||||
)
|
||||
};
|
||||
let suggestions = if force_exit {
|
||||
Vec::new()
|
||||
} else {
|
||||
@@ -272,6 +282,7 @@ where
|
||||
|| should_hostile_chat_breakoff_deterministically(
|
||||
payload.player_message.as_str(),
|
||||
payload.chat_directive.as_ref(),
|
||||
Some(&payload.npc_state),
|
||||
);
|
||||
|
||||
if force_exit {
|
||||
@@ -618,6 +629,7 @@ fn is_hostile_model_chat(chat_directive: Option<&Value>) -> bool {
|
||||
fn should_hostile_chat_breakoff_deterministically(
|
||||
player_message: &str,
|
||||
chat_directive: Option<&Value>,
|
||||
npc_state: Option<&Value>,
|
||||
) -> bool {
|
||||
if !is_hostile_model_chat(chat_directive) {
|
||||
return false;
|
||||
@@ -631,6 +643,14 @@ fn should_hostile_chat_breakoff_deterministically(
|
||||
return true;
|
||||
}
|
||||
|
||||
// 中文注释:模型建议不可用时,后端兜底仍按敌对聊天口径避免负面挑衅被拖成闲聊。
|
||||
if npc_state
|
||||
.and_then(|state| read_number_field(state, "chattedCount"))
|
||||
.is_some_and(|chatted_count| chatted_count >= 4.0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
let hostile_break_words = [
|
||||
"动手",
|
||||
"开战",
|
||||
@@ -640,6 +660,18 @@ fn should_hostile_chat_breakoff_deterministically(
|
||||
"闭嘴",
|
||||
"少废话",
|
||||
"别挡路",
|
||||
"废话",
|
||||
"威胁",
|
||||
"找死",
|
||||
"送死",
|
||||
"住口",
|
||||
"让开",
|
||||
"滚开",
|
||||
"不退",
|
||||
"不会退",
|
||||
"别装",
|
||||
"骗子",
|
||||
"叛徒",
|
||||
];
|
||||
count_keyword_matches(player_message, &hostile_break_words) > 0
|
||||
}
|
||||
@@ -812,6 +844,51 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hostile_chat_breakoff_fallback_triggers_on_negative_words() {
|
||||
let chat_directive = json!({
|
||||
"terminationMode": "hostile_model",
|
||||
"isHostileChat": true,
|
||||
});
|
||||
let npc_state = json!({ "chattedCount": 1 });
|
||||
|
||||
assert!(should_hostile_chat_breakoff_deterministically(
|
||||
"少废话,让开,不然现在就动手。",
|
||||
Some(&chat_directive),
|
||||
Some(&npc_state),
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hostile_chat_breakoff_fallback_triggers_after_four_turns() {
|
||||
let chat_directive = json!({
|
||||
"terminationMode": "hostile_model",
|
||||
"isHostileChat": true,
|
||||
});
|
||||
let npc_state = json!({ "chattedCount": 4 });
|
||||
|
||||
assert!(should_hostile_chat_breakoff_deterministically(
|
||||
"我还想再问一个问题。",
|
||||
Some(&chat_directive),
|
||||
Some(&npc_state),
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hostile_chat_breakoff_fallback_ignores_non_hostile_chat() {
|
||||
let chat_directive = json!({
|
||||
"terminationMode": "none",
|
||||
"isHostileChat": false,
|
||||
});
|
||||
let npc_state = json!({ "chattedCount": 6 });
|
||||
|
||||
assert!(!should_hostile_chat_breakoff_deterministically(
|
||||
"少废话,让开。",
|
||||
Some(&chat_directive),
|
||||
Some(&npc_state),
|
||||
));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn npc_chat_turn_prefers_request_snapshot_over_persisted_session() {
|
||||
let state = AppState::new(AppConfig::default()).expect("state should build");
|
||||
|
||||
Reference in New Issue
Block a user