Merge branch 'codex/backend-rewrite-spacetimedb' of http://82.157.175.59:3000/GenarrativeAI/Genarrative into codex/backend-rewrite-spacetimedb
This commit is contained in:
@@ -3,6 +3,9 @@
|
||||
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
default-members = [
|
||||
"crates/api-server",
|
||||
]
|
||||
members = [
|
||||
"crates/api-server",
|
||||
"crates/module-ai",
|
||||
|
||||
@@ -233,6 +233,7 @@ pub async fn submit_big_fish_message(
|
||||
BigFishAgentTurnRequest {
|
||||
llm_client: state.llm_client(),
|
||||
session: &submitted_session,
|
||||
quick_fill_requested: payload.quick_fill_requested.unwrap_or(false),
|
||||
},
|
||||
move |text| {
|
||||
draft_sink.persist_visible_text_async(text);
|
||||
@@ -321,6 +322,7 @@ pub async fn stream_big_fish_message(
|
||||
.map_err(|error| {
|
||||
big_fish_error_response(&request_context, map_big_fish_client_error(error))
|
||||
})?;
|
||||
let quick_fill_requested = payload.quick_fill_requested.unwrap_or(false);
|
||||
let mut draft_writer = AiGenerationDraftWriter::new(AiGenerationDraftContext::new(
|
||||
"big_fish",
|
||||
owner_user_id.as_str(),
|
||||
@@ -346,6 +348,7 @@ pub async fn stream_big_fish_message(
|
||||
BigFishAgentTurnRequest {
|
||||
llm_client: state.llm_client(),
|
||||
session: &submitted_session,
|
||||
quick_fill_requested,
|
||||
},
|
||||
|text| {
|
||||
draft_sink.persist_visible_text_async(text);
|
||||
|
||||
@@ -9,11 +9,13 @@ use spacetime_client::{
|
||||
use crate::creation_agent_anchor_templates::{
|
||||
get_creation_agent_anchor_template, render_anchor_question_block,
|
||||
};
|
||||
use crate::creation_agent_chat::render_quick_fill_extra_rules;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct BigFishAgentTurnRequest<'a> {
|
||||
pub llm_client: Option<&'a LlmClient>,
|
||||
pub session: &'a BigFishSessionRecord,
|
||||
pub quick_fill_requested: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -115,7 +117,7 @@ where
|
||||
let llm_client = request
|
||||
.llm_client
|
||||
.ok_or_else(|| BigFishAgentTurnError::new("当前模型不可用,请稍后重试。"))?;
|
||||
let prompt = build_big_fish_agent_prompt(request.session);
|
||||
let prompt = build_big_fish_agent_prompt(request.session, request.quick_fill_requested);
|
||||
let mut latest_reply_text = String::new();
|
||||
let response = llm_client
|
||||
.stream_text(
|
||||
@@ -146,7 +148,11 @@ where
|
||||
Ok(BigFishAgentTurnResult {
|
||||
assistant_reply_text: output.reply_text,
|
||||
stage: BigFishCreationStage::CollectingAnchors.as_str().to_string(),
|
||||
progress_percent: output.progress_percent.min(100),
|
||||
progress_percent: if request.quick_fill_requested {
|
||||
100
|
||||
} else {
|
||||
output.progress_percent.min(100)
|
||||
},
|
||||
anchor_pack_json: serde_json::to_string(&output.next_anchor_pack)
|
||||
.unwrap_or_else(|_| "{}".to_string()),
|
||||
error_message: None,
|
||||
@@ -193,15 +199,33 @@ pub(crate) fn build_failed_finalize_record_input(
|
||||
}
|
||||
}
|
||||
|
||||
fn build_big_fish_agent_prompt(session: &BigFishSessionRecord) -> String {
|
||||
fn build_big_fish_agent_prompt(
|
||||
session: &BigFishSessionRecord,
|
||||
quick_fill_requested: bool,
|
||||
) -> String {
|
||||
let anchor_question_block = get_creation_agent_anchor_template("big_fish")
|
||||
.map(render_anchor_question_block)
|
||||
.unwrap_or_else(|| "模板目标:收束成可玩的竖屏大鱼吃小鱼玩法草稿。".to_string());
|
||||
let quick_fill_rules = if quick_fill_requested {
|
||||
format!(
|
||||
"\n\n{}",
|
||||
render_quick_fill_extra_rules(
|
||||
"当前玩法方向里的成长、生态、风险节奏等缺失关键词",
|
||||
"不要要求用户再提供等级、鱼群、场景或节奏信息",
|
||||
"输出完整 nextAnchorPack,直接补齐 value 为空或 status 为 missing 的项",
|
||||
"生成结果页",
|
||||
)
|
||||
)
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
format!(
|
||||
"{anchor_question_block}\n\n当前是第 {turn} 轮,当前进度 {progress}% 。\n\n当前 anchor pack:\n{anchor_pack}\n\n最近聊天记录:\n{chat_history}\n\n{contract}",
|
||||
"{anchor_question_block}{quick_fill_rules}\n\n当前是第 {turn} 轮,当前进度 {progress}% 。\n\n是否要求自动补充剩余关键字:{quick_fill_requested_text}\n\n当前 anchor pack:\n{anchor_pack}\n\n最近聊天记录:\n{chat_history}\n\n{contract}",
|
||||
anchor_question_block = anchor_question_block,
|
||||
quick_fill_rules = quick_fill_rules,
|
||||
turn = session.current_turn.saturating_add(1),
|
||||
progress = session.progress_percent,
|
||||
quick_fill_requested_text = if quick_fill_requested { "是" } else { "否" },
|
||||
anchor_pack = serialize_record_anchor_pack(&session.anchor_pack),
|
||||
chat_history =
|
||||
serde_json::to_string_pretty(&build_chat_history(session.messages.as_slice()))
|
||||
@@ -399,3 +423,70 @@ fn extract_reply_text_from_partial_json(text: &str) -> Option<String> {
|
||||
Some(result)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::build_big_fish_agent_prompt;
|
||||
|
||||
fn anchor_item(
|
||||
key: &str,
|
||||
label: &str,
|
||||
value: &str,
|
||||
status: &str,
|
||||
) -> spacetime_client::BigFishAnchorItemRecord {
|
||||
spacetime_client::BigFishAnchorItemRecord {
|
||||
key: key.to_string(),
|
||||
label: label.to_string(),
|
||||
value: value.to_string(),
|
||||
status: status.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn empty_session_record() -> spacetime_client::BigFishSessionRecord {
|
||||
spacetime_client::BigFishSessionRecord {
|
||||
session_id: "big-fish-session-test".to_string(),
|
||||
current_turn: 2,
|
||||
progress_percent: 60,
|
||||
stage: "collecting_anchors".to_string(),
|
||||
anchor_pack: spacetime_client::BigFishAnchorPackRecord {
|
||||
gameplay_promise: anchor_item(
|
||||
"gameplayPromise",
|
||||
"玩法承诺",
|
||||
"微光小鱼逆袭深海巨兽",
|
||||
"confirmed",
|
||||
),
|
||||
ecology_visual_theme: anchor_item(
|
||||
"ecologyVisualTheme",
|
||||
"生态视觉主题",
|
||||
"幽蓝珊瑚海沟",
|
||||
"confirmed",
|
||||
),
|
||||
growth_ladder: anchor_item("growthLadder", "成长阶梯", "", "missing"),
|
||||
risk_tempo: anchor_item("riskTempo", "风险节奏", "", "missing"),
|
||||
},
|
||||
draft: None,
|
||||
asset_slots: Vec::new(),
|
||||
asset_coverage: spacetime_client::BigFishAssetCoverageRecord {
|
||||
level_main_image_ready_count: 0,
|
||||
level_motion_ready_count: 0,
|
||||
background_ready: false,
|
||||
required_level_count: 8,
|
||||
publish_ready: false,
|
||||
blockers: Vec::new(),
|
||||
},
|
||||
messages: Vec::new(),
|
||||
last_assistant_reply: None,
|
||||
publish_ready: false,
|
||||
updated_at: "2026-04-24T10:00:00.000Z".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn quick_fill_prompt_forbids_follow_up_questions() {
|
||||
let prompt = build_big_fish_agent_prompt(&empty_session_record(), true);
|
||||
|
||||
assert!(prompt.contains("用户刚刚主动要求你自动补充剩余关键字"));
|
||||
assert!(prompt.contains("不要再继续提问"));
|
||||
assert!(prompt.contains("progressPercent 直接输出为 100"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3432,11 +3432,6 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub(crate) fn find_motion_template(id: &str) -> Option<&'static MotionTemplate> {
|
||||
BUILT_IN_MOTION_TEMPLATES
|
||||
.iter()
|
||||
.find(|template| template.id == id.trim())
|
||||
}
|
||||
fn resolve_character_animation_model_uses_strategy_specific_field() {
|
||||
let payload = CharacterAnimationGenerateRequest {
|
||||
character_id: "hero".to_string(),
|
||||
|
||||
25
server-rs/crates/api-server/src/creation_agent_chat.rs
Normal file
25
server-rs/crates/api-server/src/creation_agent_chat.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
/// 共创聊天中“补充剩余关键字”的统一提示规则。
|
||||
///
|
||||
/// RPG、拼图、大鱼吃小鱼都通过聊天补充设定;这里集中维护点击补全后必须进入
|
||||
/// 自行补齐、不可继续追问、进度推进到可提交状态的公共约束,避免各入口各写一套。
|
||||
pub(crate) fn render_quick_fill_extra_rules(
|
||||
acceptance_scope: &str,
|
||||
forbidden_follow_up: &str,
|
||||
completion_target: &str,
|
||||
submit_hint: &str,
|
||||
) -> String {
|
||||
format!(
|
||||
r#"用户刚刚主动要求你自动补充剩余关键字。
|
||||
|
||||
这表示用户接受你基于当前方向自行补完仍缺失的关键设定:{acceptance_scope}
|
||||
|
||||
本轮要求:
|
||||
1. 不要再继续提问
|
||||
2. {forbidden_follow_up}
|
||||
3. 必须保留已有已确认内容,并直接补齐缺失或仍为空的关键项
|
||||
4. 对你自行推断补齐的项,应标记或表达为系统推断;已有明确内容继续保持确认或锁定状态
|
||||
5. progressPercent 直接输出为 100
|
||||
6. {completion_target}
|
||||
7. replyText 只做简短完成说明,引导用户可以{submit_hint},不能出现问号"#,
|
||||
)
|
||||
}
|
||||
@@ -2297,24 +2297,6 @@ fn has_custom_world_scene_act(profile: Option<&Map<String, Value>>) -> bool {
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn ensure_non_empty(
|
||||
request_context: &RequestContext,
|
||||
value: &str,
|
||||
field_name: &str,
|
||||
) -> Result<(), Response> {
|
||||
if value.trim().is_empty() {
|
||||
return Err(custom_world_error_response(
|
||||
request_context,
|
||||
AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({
|
||||
"provider": "custom-world-agent",
|
||||
"message": format!("{field_name} is required"),
|
||||
})),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn map_custom_world_publish_gate_response(
|
||||
gate: CustomWorldPublishGateRecord,
|
||||
) -> CustomWorldPublishGateResponse {
|
||||
@@ -2476,6 +2458,24 @@ fn custom_world_error_response(request_context: &RequestContext, error: AppError
|
||||
error.into_response_with_context(Some(request_context))
|
||||
}
|
||||
|
||||
fn ensure_non_empty(
|
||||
request_context: &RequestContext,
|
||||
value: &str,
|
||||
field_name: &str,
|
||||
) -> Result<(), Response> {
|
||||
if value.trim().is_empty() {
|
||||
return Err(custom_world_error_response(
|
||||
request_context,
|
||||
AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({
|
||||
"provider": "custom-world",
|
||||
"message": format!("{field_name} is required"),
|
||||
})),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn custom_world_sse_json_event(event_name: &str, payload: Value) -> Result<Event, AppError> {
|
||||
Event::default()
|
||||
.event(event_name)
|
||||
|
||||
@@ -7,11 +7,12 @@ use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Value as JsonValue, json};
|
||||
|
||||
use crate::custom_world_rpg_draft_prompts::{
|
||||
BASE_SYSTEM_PROMPT, GLOBAL_HARD_RULES, OUTPUT_CONTRACT_REMINDER, QUICK_FILL_EXTRA_RULES,
|
||||
BASE_SYSTEM_PROMPT, GLOBAL_HARD_RULES, OUTPUT_CONTRACT_REMINDER,
|
||||
STATE_INFERENCE_OUTPUT_CONTRACT, STATE_INFERENCE_SYSTEM_PROMPT,
|
||||
extract_reply_text_from_partial_json, mode_rules, parse_conversation_mode, parse_drift_risk,
|
||||
parse_json_response_text, parse_user_input_signal, render_chat_history_context,
|
||||
render_current_anchor_context, render_dynamic_state_context, user_signal_rules,
|
||||
parse_json_response_text, parse_user_input_signal, quick_fill_extra_rules,
|
||||
render_chat_history_context, render_current_anchor_context, render_dynamic_state_context,
|
||||
user_signal_rules,
|
||||
};
|
||||
use spacetime_client::{
|
||||
CustomWorldAgentMessageFinalizeRecordInput, CustomWorldAgentMessageRecord,
|
||||
@@ -799,7 +800,7 @@ fn build_eight_anchor_single_turn_prompt(
|
||||
user_signal_rules(dynamic_state.user_input_signal).to_string(),
|
||||
];
|
||||
if quick_fill_requested {
|
||||
blocks.push(QUICK_FILL_EXTRA_RULES.to_string());
|
||||
blocks.push(quick_fill_extra_rules());
|
||||
}
|
||||
blocks.push(render_dynamic_state_context(dynamic_state));
|
||||
blocks.push(render_current_anchor_context(current_anchor_content));
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::creation_agent_chat::render_quick_fill_extra_rules;
|
||||
use crate::custom_world_agent_turn::{
|
||||
EightAnchorContent, PromptConversationMode, PromptDriftRisk, PromptDynamicState,
|
||||
PromptUserInputSignal,
|
||||
@@ -41,15 +42,14 @@ pub(crate) const GLOBAL_HARD_RULES: &str = r#"全局硬约束:
|
||||
11. 你输出的 JSON 必须可以被直接解析。
|
||||
12. 输出字段顺序必须固定为:replyText、progressPercent、nextAnchorContent。"#;
|
||||
|
||||
pub(crate) const QUICK_FILL_EXTRA_RULES: &str = r#"用户刚刚主动要求你自动补全剩余设定。
|
||||
|
||||
这表示用户接受你基于当前方向自动补完剩余设定。
|
||||
|
||||
本轮要求:
|
||||
1. 不要再继续提问
|
||||
2. 直接输出一版尽量完整的设定结构
|
||||
3. progressPercent 直接输出为 100
|
||||
4. replyText 要告诉用户现在可以进入“生成游戏设定草稿”"#;
|
||||
pub(crate) fn quick_fill_extra_rules() -> String {
|
||||
render_quick_fill_extra_rules(
|
||||
"当前 RPG 世界方向里的剩余设定",
|
||||
"不要要求用户再提供世界观、角色、冲突或禁忌信息",
|
||||
"直接输出一版尽量完整的设定结构",
|
||||
"进入“生成游戏设定草稿”",
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) const STATE_INFERENCE_SYSTEM_PROMPT: &str = r#"你是正式生成世界设定前的一步“创作状态识别器”。
|
||||
你的职责不是直接生成新设定,而是先判断:下一轮正式生成应该用什么推进策略,尤其要判断 replyText 应该更偏确认、吸收、收束、纠偏,还是启发式提问。
|
||||
|
||||
@@ -16,6 +16,7 @@ mod character_animation_assets;
|
||||
mod character_visual_assets;
|
||||
mod config;
|
||||
mod creation_agent_anchor_templates;
|
||||
mod creation_agent_chat;
|
||||
mod custom_world;
|
||||
mod custom_world_agent_entities;
|
||||
mod custom_world_agent_turn;
|
||||
|
||||
@@ -213,6 +213,7 @@ pub async fn submit_puzzle_agent_message(
|
||||
PuzzleAgentTurnRequest {
|
||||
llm_client: state.llm_client(),
|
||||
session: &submitted_session,
|
||||
quick_fill_requested: payload.quick_fill_requested.unwrap_or(false),
|
||||
},
|
||||
|_| {},
|
||||
)
|
||||
@@ -278,6 +279,7 @@ pub async fn stream_puzzle_agent_message(
|
||||
)?;
|
||||
|
||||
let owner_user_id = authenticated.claims().user_id().to_string();
|
||||
let quick_fill_requested = payload.quick_fill_requested.unwrap_or(false);
|
||||
let session = state
|
||||
.spacetime_client()
|
||||
.submit_puzzle_agent_message(PuzzleAgentMessageSubmitRecordInput {
|
||||
@@ -315,6 +317,7 @@ pub async fn stream_puzzle_agent_message(
|
||||
PuzzleAgentTurnRequest {
|
||||
llm_client: state.llm_client(),
|
||||
session: &session,
|
||||
quick_fill_requested,
|
||||
},
|
||||
move |text| {
|
||||
let _ = reply_tx.send(text.to_string());
|
||||
|
||||
@@ -9,11 +9,13 @@ use spacetime_client::{
|
||||
use crate::creation_agent_anchor_templates::{
|
||||
get_creation_agent_anchor_template, render_anchor_question_block,
|
||||
};
|
||||
use crate::creation_agent_chat::render_quick_fill_extra_rules;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct PuzzleAgentTurnRequest<'a> {
|
||||
pub llm_client: Option<&'a LlmClient>,
|
||||
pub session: &'a PuzzleAgentSessionRecord,
|
||||
pub quick_fill_requested: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -122,7 +124,7 @@ where
|
||||
.llm_client
|
||||
.ok_or_else(|| PuzzleAgentTurnError::new("当前模型不可用,请稍后重试。"))?;
|
||||
|
||||
let prompt = build_puzzle_agent_prompt(request.session);
|
||||
let prompt = build_puzzle_agent_prompt(request.session, request.quick_fill_requested);
|
||||
let mut latest_reply_text = String::new();
|
||||
let response = llm_client
|
||||
.stream_text(
|
||||
@@ -155,7 +157,11 @@ where
|
||||
stage: resolve_puzzle_agent_stage(output.progress_percent)
|
||||
.as_str()
|
||||
.to_string(),
|
||||
progress_percent: output.progress_percent,
|
||||
progress_percent: if request.quick_fill_requested {
|
||||
100
|
||||
} else {
|
||||
output.progress_percent
|
||||
},
|
||||
anchor_pack_json: serde_json::to_string(&output.next_anchor_pack).unwrap_or_else(|_| {
|
||||
serde_json::to_string(&empty_anchor_pack()).unwrap_or_else(|_| "{}".to_string())
|
||||
}),
|
||||
@@ -207,15 +213,33 @@ pub(crate) fn build_failed_finalize_record_input(
|
||||
}
|
||||
}
|
||||
|
||||
fn build_puzzle_agent_prompt(session: &PuzzleAgentSessionRecord) -> String {
|
||||
fn build_puzzle_agent_prompt(
|
||||
session: &PuzzleAgentSessionRecord,
|
||||
quick_fill_requested: bool,
|
||||
) -> String {
|
||||
let anchor_question_block = get_creation_agent_anchor_template("puzzle")
|
||||
.map(render_anchor_question_block)
|
||||
.unwrap_or_else(|| "模板目标:收束成可以发布为拼图关卡的视觉方案。".to_string());
|
||||
let quick_fill_rules = if quick_fill_requested {
|
||||
format!(
|
||||
"\n\n{}",
|
||||
render_quick_fill_extra_rules(
|
||||
"当前题材方向里的拼图关键词",
|
||||
"不要要求用户再提供素材、风格或禁忌",
|
||||
"输出完整 nextAnchorPack,直接补齐 value 为空或 status 为 missing 的项",
|
||||
"生成结果页",
|
||||
)
|
||||
)
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
format!(
|
||||
"{anchor_question_block}\n\n当前是第 {turn} 轮,当前进度 {progress}% 。\n\n当前 anchor pack:\n{anchor_pack}\n\n最近聊天记录:\n{chat_history}\n\n{contract}",
|
||||
"{anchor_question_block}{quick_fill_rules}\n\n当前是第 {turn} 轮,当前进度 {progress}% 。\n\n是否要求自动补充剩余关键字:{quick_fill_requested_text}\n\n当前 anchor pack:\n{anchor_pack}\n\n最近聊天记录:\n{chat_history}\n\n{contract}",
|
||||
anchor_question_block = anchor_question_block,
|
||||
quick_fill_rules = quick_fill_rules,
|
||||
turn = session.current_turn.saturating_add(1),
|
||||
progress = session.progress_percent,
|
||||
quick_fill_requested_text = if quick_fill_requested { "是" } else { "否" },
|
||||
anchor_pack = serde_json::to_string_pretty(&map_record_anchor_pack(&session.anchor_pack))
|
||||
.unwrap_or_else(|_| "{}".to_string()),
|
||||
chat_history =
|
||||
@@ -430,7 +454,57 @@ mod tests {
|
||||
use module_puzzle::PuzzleAnchorStatus;
|
||||
use serde_json::json;
|
||||
|
||||
use super::{extract_reply_text_from_partial_json, parse_model_output};
|
||||
use super::{
|
||||
build_puzzle_agent_prompt, extract_reply_text_from_partial_json, parse_model_output,
|
||||
};
|
||||
|
||||
fn empty_session_record() -> spacetime_client::PuzzleAgentSessionRecord {
|
||||
spacetime_client::PuzzleAgentSessionRecord {
|
||||
session_id: "puzzle-session-test".to_string(),
|
||||
current_turn: 2,
|
||||
progress_percent: 60,
|
||||
stage: "collecting_anchors".to_string(),
|
||||
anchor_pack: spacetime_client::PuzzleAnchorPackRecord {
|
||||
theme_promise: spacetime_client::PuzzleAnchorItemRecord {
|
||||
key: "themePromise".to_string(),
|
||||
label: "题材承诺".to_string(),
|
||||
value: "雨夜猫咪遗迹".to_string(),
|
||||
status: "confirmed".to_string(),
|
||||
},
|
||||
visual_subject: spacetime_client::PuzzleAnchorItemRecord {
|
||||
key: "visualSubject".to_string(),
|
||||
label: "画面主体".to_string(),
|
||||
value: String::new(),
|
||||
status: "missing".to_string(),
|
||||
},
|
||||
visual_mood: spacetime_client::PuzzleAnchorItemRecord {
|
||||
key: "visualMood".to_string(),
|
||||
label: "视觉气质".to_string(),
|
||||
value: String::new(),
|
||||
status: "missing".to_string(),
|
||||
},
|
||||
composition_hooks: spacetime_client::PuzzleAnchorItemRecord {
|
||||
key: "compositionHooks".to_string(),
|
||||
label: "拼图记忆点".to_string(),
|
||||
value: String::new(),
|
||||
status: "missing".to_string(),
|
||||
},
|
||||
tags_and_forbidden: spacetime_client::PuzzleAnchorItemRecord {
|
||||
key: "tagsAndForbidden".to_string(),
|
||||
label: "标签与禁忌".to_string(),
|
||||
value: String::new(),
|
||||
status: "missing".to_string(),
|
||||
},
|
||||
},
|
||||
draft: None,
|
||||
messages: Vec::new(),
|
||||
last_assistant_reply: None,
|
||||
published_profile_id: None,
|
||||
suggested_actions: Vec::new(),
|
||||
result_preview: None,
|
||||
updated_at: "2026-04-24T10:00:00.000Z".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extract_reply_text_from_partial_json_preserves_chinese_characters() {
|
||||
@@ -496,4 +570,13 @@ mod tests {
|
||||
"雨夜、猫咪、神庙遗迹;禁止文字水印"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn quick_fill_prompt_forbids_follow_up_questions() {
|
||||
let prompt = build_puzzle_agent_prompt(&empty_session_record(), true);
|
||||
|
||||
assert!(prompt.contains("用户刚刚主动要求你自动补充剩余关键字"));
|
||||
assert!(prompt.contains("不要再继续提问"));
|
||||
assert!(prompt.contains("progressPercent 直接输出为 100"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1775,6 +1775,8 @@ mod tests {
|
||||
let error = validate_custom_world_profile_upsert_input(&CustomWorldProfileUpsertInput {
|
||||
profile_id: "cwprof_001".to_string(),
|
||||
owner_user_id: "user_001".to_string(),
|
||||
public_work_code: None,
|
||||
author_public_user_code: None,
|
||||
source_agent_session_id: None,
|
||||
world_name: "裂潮边城".to_string(),
|
||||
subtitle: "港口余烬".to_string(),
|
||||
|
||||
@@ -12,6 +12,8 @@ pub struct CreateBigFishSessionRequest {
|
||||
pub struct SendBigFishMessageRequest {
|
||||
pub client_message_id: String,
|
||||
pub text: String,
|
||||
#[serde(default)]
|
||||
pub quick_fill_requested: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
|
||||
@@ -12,6 +12,8 @@ pub struct CreatePuzzleAgentSessionRequest {
|
||||
pub struct SendPuzzleAgentMessageRequest {
|
||||
pub client_message_id: String,
|
||||
pub text: String,
|
||||
#[serde(default)]
|
||||
pub quick_fill_requested: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
|
||||
@@ -1573,6 +1573,9 @@ fn deserialize_run(value: &str) -> Result<PuzzleRunSnapshot, String> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use module_puzzle::{
|
||||
build_generated_candidates, empty_anchor_pack, recommendation_score, tag_similarity_score,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn puzzle_json_round_trip_keeps_snapshot_shape() {
|
||||
|
||||
Reference in New Issue
Block a user