From eabd8f393ab27bcd1a2ef7c8be95934efc076c76 Mon Sep 17 00:00:00 2001 From: Linghong Date: Sat, 5 Sep 2026 06:07:32 +0000 Subject: [PATCH] =?UTF-8?q?=E5=B0=86=E7=AD=96=E5=88=92=E5=86=B3=E7=AD=96?= =?UTF-8?q?=E8=BD=AE=E6=AC=A1=E4=BA=A4=E7=94=B1Runtime=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 移除Provider输入中的 decisions.round 字段 由Runtime按决策顺序生成持久化轮次 放宽GDD实际门禁并同步模型数量上限 --- .../runtime_protocol/planning_policy_v2.rs | 49 +++++------ .../runtime_protocol/planning_storage.rs | 88 ++++++++----------- 2 files changed, 59 insertions(+), 78 deletions(-) diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/planning_policy_v2.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/planning_policy_v2.rs index 9ab61b837..f9f5ac6d1 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/planning_policy_v2.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/planning_policy_v2.rs @@ -40,7 +40,6 @@ pub(crate) struct PlanningGddDecisionInputV2 { pub state: String, #[serde(default)] pub answer_source: Option, - pub round: u32, pub answer_summary: String, } @@ -252,7 +251,7 @@ pub(crate) fn planning_v2_function_tools() -> Vec "keywords": { "type": "array", "description": "风格关键词", - "minItems": 3, + "minItems": 1, "maxItems": 5, "items": {"type": "string"} }, @@ -265,8 +264,8 @@ pub(crate) fn planning_v2_function_tools() -> Vec "pillars": { "type": "array", "description": "游戏支柱", - "minItems": 2, - "maxItems": 4, + "minItems": 1, + "maxItems": 5, "items": { "type": "object", "additionalProperties": false, @@ -282,8 +281,8 @@ pub(crate) fn planning_v2_function_tools() -> Vec "coreLoop": { "type": "array", "description": "单局核心循环步骤", - "minItems": 4, - "maxItems": 8, + "minItems": 1, + "maxItems": 7, "items": {"type": "string"} }, "targetUsers": { @@ -305,8 +304,8 @@ pub(crate) fn planning_v2_function_tools() -> Vec "mvpSystems": { "type": "array", "description": "MVP 必须有的系统", - "minItems": 3, - "maxItems": 6, + "minItems": 1, + "maxItems": 7, "items": { "type": "object", "additionalProperties": false, @@ -329,8 +328,8 @@ pub(crate) fn planning_v2_function_tools() -> Vec "outOfScope": { "type": "array", "description": "暂不做的内容", - "minItems": 1, - "maxItems": 12, + "minItems": 0, + "maxItems": 10, "items": {"type": "string"} }, "creatorTips": { @@ -373,15 +372,10 @@ pub(crate) fn planning_v2_function_tools() -> Vec "type": "string", "description": "来源标记,如 user_freeform、user_choice、agent_inferred" }, - "round": { - "type": "integer", - "minimum": 0, - "description": "对应回合,初始需求为 0" - }, "answerSummary": {"type": "string", "description": "结论摘要"} }, "description": "决定内容", - "required": ["topic", "state", "round", "answerSummary"] + "required": ["topic", "state", "answerSummary"] } }, "prototypeValidationItems": { @@ -598,7 +592,7 @@ fn validate_v2_decision_content( answer_source: Option, answer_summary: &str, ) -> Result<(), String> { - validate_text(topic, &format!("decisions[{index}].topic"), 1, 120) + validate_text(topic, &format!("decisions[{index}].topic"), 1, 400) .map_err(|error| error.to_string())?; normalize_v2_decision_state(state)?; normalize_answer_source(state, answer_source)?; @@ -628,7 +622,7 @@ fn validate_v2_prototype_content( ("observation", observation), ("passCriterion", pass_criterion), ] { - validate_text(value, &format!("prototypeValidationItems.{label}"), 1, 400) + validate_text(value, &format!("prototypeValidationItems.{label}"), 1, 800) .map_err(|error| error.to_string())?; } Ok(()) @@ -654,8 +648,8 @@ fn validate_v2_decision_inputs( .iter() .filter(|decision| decision.state.trim() == "prototype_pending") .count(); - if prototype_items.len() > 3 || prototype_count != prototype_items.len() { - return Err("PLANNING_INVALID_GDD: prototypeValidationItems 必须逐项对应 prototype_pending 决定且最多 3 项".to_string()); + if prototype_items.len() > 16 || prototype_count != prototype_items.len() { + return Err("PLANNING_INVALID_GDD: prototypeValidationItems 必须逐项对应 prototype_pending 决定且最多 16 项".to_string()); } for item in prototype_items { validate_v2_prototype_content( @@ -692,8 +686,8 @@ fn validate_v2_durable_decisions( } } let mut item_ids = std::collections::BTreeSet::new(); - if prototype_ids.len() != prototype_items.len() { - return Err("PLANNING_INVALID_GDD: prototypeValidationItems 必须逐项对应 prototype_pending 决定且最多 3 项".to_string()); + if prototype_ids.len() != prototype_items.len() || prototype_items.len() > 16 { + return Err("PLANNING_INVALID_GDD: prototypeValidationItems 必须逐项对应 prototype_pending 决定且最多 16 项".to_string()); } for item in prototype_items { if !item_ids.insert(item.id.as_str()) || !prototype_ids.contains(item.id.as_str()) { @@ -735,7 +729,9 @@ fn build_gdd_v2( topic: value.topic, state: normalize_v2_decision_state(&value.state)?, answer_source: normalize_answer_source(&value.state, value.answer_source)?, - round: value.round, + // round 是 Runtime 元数据:首项代表初始需求,其余决策按 + // 输出顺序归入当前最多 3 轮的澄清区间,不接受模型自行指定。 + round: (index as u32).min(3), answer_summary: value.answer_summary, basis: None, }) @@ -1619,7 +1615,7 @@ mod tests { "outOfScope": ["多人联机"], "creatorTips": {"doFirst": "先做一张可走完的地图", "deferForNow": "暂缓复杂成长线", "howToVerify": "观察玩家是否能说出选择后果", "expandWhen": "连续三局都能理解后再扩展"} }, - "decisions": [{"topic": "初始需求", "state": "confirmed", "round": 0, "answerSummary": "做一个短局守夜策略游戏"}], + "decisions": [{"topic": "初始需求", "state": "confirmed", "answerSummary": "做一个短局守夜策略游戏"}], "prototypeValidationItems": [] }) } @@ -1697,6 +1693,7 @@ mod tests { .as_object() .expect("decision properties"); assert!(!decision_properties.contains_key("id")); + assert!(!decision_properties.contains_key("round")); assert!(!gdd_properties["decisions"]["items"]["required"] .as_array() .expect("decision required") @@ -1717,8 +1714,8 @@ mod tests { fn runtime_assigns_decision_and_prototype_ids() { let mut value = sample_gdd_value(); value["decisions"] = serde_json::json!([ - {"topic": "初始需求", "state": "confirmed", "round": 0, "answerSummary": "做一个短局守夜策略游戏"}, - {"topic": "核心回路", "state": "prototype_pending", "round": 1, "answerSummary": "验证核心回路"} + {"topic": "初始需求", "state": "confirmed", "answerSummary": "做一个短局守夜策略游戏"}, + {"topic": "核心回路", "state": "prototype_pending", "answerSummary": "验证核心回路"} ]); value["prototypeValidationItems"] = serde_json::json!([ {"question": "玩家是否理解核心回路?", "microPrototype": "做一个最小交互原型", "observation": "观察玩家行为", "passCriterion": "多数玩家完成目标"} diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/planning_storage.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/planning_storage.rs index bb151f7a2..c3d7fb40c 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/planning_storage.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/planning_storage.rs @@ -118,7 +118,7 @@ pub(crate) const PLAN_FAST_GDD_PATH: &str = "game/fast_gdd.md"; pub(crate) const PLAN_FAST_GDD_MAX_BYTES: usize = 128 * 1024; /// 单条决定 answerSummary 的上限(`initial-request` 除外)。 -pub(crate) const PLAN_DECISION_ANSWER_SUMMARY_MAX_CHARS: usize = 400; +pub(crate) const PLAN_DECISION_ANSWER_SUMMARY_MAX_CHARS: usize = 800; /// `initial-request` 那条决定的 answerSummary 上限,也就是立项策划入口原始需求的上限。 /// @@ -660,9 +660,9 @@ fn validate_plan_platform_facts(value: &PlanPlatformFacts) -> Result<(), Plannin pub(crate) fn validate_plan_game(game: &PlanGddGame) -> Result<(), PlanningStorageError> { validate_text(&game.title, "game.title", 1, 80)?; - validate_text(&game.genre.primary, "game.genre.primary", 1, 40)?; + validate_text(&game.genre.primary, "game.genre.primary", 1, 80)?; if let Some(fusion) = &game.genre.fusion { - validate_text(fusion, "game.genre.fusion", 1, 40)?; + validate_text(fusion, "game.genre.fusion", 1, 80)?; } validate_text( &game.art_style.visual_type, @@ -670,52 +670,44 @@ pub(crate) fn validate_plan_game(game: &PlanGddGame) -> Result<(), PlanningStora 1, 80, )?; - if !(3..=5).contains(&game.art_style.keywords.len()) { - return Err(invalid("game.artStyle.keywords 必须有 3~5 项")); + if !(1..=6).contains(&game.art_style.keywords.len()) { + return Err(invalid("game.artStyle.keywords 必须有 1~6 项")); } - validate_unique( - game.art_style.keywords.iter().map(String::as_str), - "game.artStyle.keywords", - )?; for (index, keyword) in game.art_style.keywords.iter().enumerate() { - validate_text(keyword, &format!("game.artStyle.keywords[{index}]"), 1, 32)?; + validate_text(keyword, &format!("game.artStyle.keywords[{index}]"), 1, 64)?; } validate_text( &game.art_style.mood_and_color, "game.artStyle.moodAndColor", 1, - 400, + 1000, )?; validate_text( &game.art_style.mvp_art_boundary, "game.artStyle.mvpArtBoundary", 1, - 400, + 1200, )?; // 设计约定:Runtime 接受范围为 10~160,模型 schema 提示范围收紧为 25~90; // 两者有意不对称,用于避免过短概念同时保留对已有/人工 GDD 的兼容,不是缺陷或 bug。 validate_text(&game.one_liner, "game.oneLiner", 10, 160)?; - if !(2..=4).contains(&game.pillars.len()) { - return Err(invalid("game.pillars 必须有 2~4 条")); + if !(1..=6).contains(&game.pillars.len()) { + return Err(invalid("game.pillars 必须有 1~6 条")); } - validate_unique( - game.pillars.iter().map(|item| item.name.as_str()), - "game.pillars.name", - )?; for (index, pillar) in game.pillars.iter().enumerate() { - validate_text(&pillar.name, &format!("game.pillars[{index}].name"), 1, 40)?; + validate_text(&pillar.name, &format!("game.pillars[{index}].name"), 1, 80)?; validate_text( &pillar.player_feel, &format!("game.pillars[{index}].playerFeel"), 1, - 240, + 400, )?; validate_text( &pillar.mechanism, &format!("game.pillars[{index}].mechanism"), 1, - 240, + 400, )?; validate_decision_state(&pillar.decision_state)?; if pillar.basis.is_some() { @@ -723,33 +715,33 @@ pub(crate) fn validate_plan_game(game: &PlanGddGame) -> Result<(), PlanningStora } } - if !(4..=8).contains(&game.core_loop.len()) { - return Err(invalid("game.coreLoop 必须有 4~8 步")); + if !(1..=8).contains(&game.core_loop.len()) { + return Err(invalid("game.coreLoop 必须有 1~8 步")); } for (index, step) in game.core_loop.iter().enumerate() { - validate_text(step, &format!("game.coreLoop[{index}]"), 1, 120)?; + validate_text(step, &format!("game.coreLoop[{index}]"), 1, 400)?; } validate_text( &game.target_users.core_users, "game.targetUsers.coreUsers", 1, - 240, + 400, )?; validate_text( &game.target_users.preferences, "game.targetUsers.preferences", 1, - 240, + 400, )?; validate_text( &game.target_users.session_length, "game.targetUsers.sessionLength", 1, - 240, + 400, )?; - if game.target_users.reference_games.len() > 5 { - return Err(invalid("game.targetUsers.referenceGames 最多 5 项")); + if game.target_users.reference_games.len() > 8 { + return Err(invalid("game.targetUsers.referenceGames 最多 8 项")); } for (index, reference) in game.target_users.reference_games.iter().enumerate() { validate_text( @@ -761,50 +753,42 @@ pub(crate) fn validate_plan_game(game: &PlanGddGame) -> Result<(), PlanningStora } validate_plan_platform_facts(&game.platform_facts)?; - if !(3..=6).contains(&game.mvp_systems.len()) { - return Err(invalid("game.mvpSystems 必须有 3~6 项")); + if !(1..=8).contains(&game.mvp_systems.len()) { + return Err(invalid("game.mvpSystems 必须有 1~8 项")); } - validate_unique( - game.mvp_systems.iter().map(|item| item.system.as_str()), - "game.mvpSystems.system", - )?; for (index, system) in game.mvp_systems.iter().enumerate() { validate_text( &system.system, &format!("game.mvpSystems[{index}].system"), 1, - 40, + 80, )?; validate_text( &system.minimal_function, &format!("game.mvpSystems[{index}].minimalFunction"), 1, - 240, + 400, )?; validate_text( &system.why_required, &format!("game.mvpSystems[{index}].whyRequired"), 1, - 240, + 400, )?; validate_text( &system.verify_method, &format!("game.mvpSystems[{index}].verifyMethod"), 1, - 240, + 400, )?; validate_decision_state(&system.decision_state)?; if system.basis.is_some() { return Err(invalid("v1 的 mvpSystem.basis 必须为 null")); } } - if !(1..=12).contains(&game.out_of_scope.len()) { - return Err(invalid("game.outOfScope 必须有 1~12 项")); + if game.out_of_scope.len() > 12 { + return Err(invalid("game.outOfScope 必须有 0~12 项")); } - validate_unique( - game.out_of_scope.iter().map(String::as_str), - "game.outOfScope", - )?; for (index, item) in game.out_of_scope.iter().enumerate() { validate_text(item, &format!("game.outOfScope[{index}]"), 1, 80)?; } @@ -812,25 +796,25 @@ pub(crate) fn validate_plan_game(game: &PlanGddGame) -> Result<(), PlanningStora &game.creator_tips.do_first, "game.creatorTips.doFirst", 1, - 400, + 1000, )?; validate_text( &game.creator_tips.defer_for_now, "game.creatorTips.deferForNow", 1, - 400, + 1000, )?; validate_text( &game.creator_tips.how_to_verify, "game.creatorTips.howToVerify", 1, - 400, + 1000, )?; validate_text( &game.creator_tips.expand_when, "game.creatorTips.expandWhen", 1, - 400, + 1000, )?; Ok(()) } @@ -934,8 +918,8 @@ fn validate_decisions( prototype_decisions.insert(decision.id.as_str()); } } - if prototype_items.len() > 3 { - return Err(invalid("prototypeValidationItems 最多 3 项")); + if prototype_items.len() > 16 { + return Err(invalid("prototypeValidationItems 最多 16 项")); } validate_unique( prototype_items.iter().map(|item| item.id.as_str()), @@ -1954,7 +1938,7 @@ fn validate_plan_session_shape(value: &PlanSessionV1) -> Result<(), PlanningStor &decision.topic, &format!("session.decisionsSummary[{index}].topic"), 1, - 80, + 400, )?; validate_decision_state(&decision.state)?; validate_answer_source(&decision.answer_source)?;