修复策划 Agent 模型选择链路

保存策划会话的模型目录 ID并交由 api-server 解析

移除官方直连路径的固定模型并补齐 AGC 客户端标记
This commit is contained in:
2026-09-11 05:51:35 +00:00
parent 8444dee9e9
commit f2f786d69b
6 changed files with 37 additions and 11 deletions
@@ -490,6 +490,9 @@ async fn request_design_provider(
}
let config = load_game_creator_app_config()?;
let mut llm = resolve_game_creator_llm_config_for_agent(&config, "design-agent");
if !session.model_id.trim().is_empty() {
llm.model = session.model_id.clone();
}
// 此循环统一处理流中断与 HTTP 瞬态错误,避免与传输重试相乘。
let max_retries = llm.max_retries;
llm.max_retries = 0;
@@ -761,7 +764,10 @@ pub(crate) async fn continue_design_agent_at(
if read_planning_session_v2(root)?.is_some() {
return Err("此项目包含旧策划会话,请查看原有记录或在新项目开始五阶段策划".into());
}
new_design_session(&project_id)
new_design_session(
&project_id,
&load_game_creator_app_config()?.selected_model_id,
)
}
};
if session.project_id != project_id {
@@ -940,7 +946,7 @@ mod tests {
let temp = tempfile::tempdir().expect("tempdir");
let root = temp.path();
concept_artifacts(root);
let mut session = new_design_session("project");
let mut session = new_design_session("project", "quality");
session.turn = Some(DesignTurn {
id: "turn-1".into(),
pending: true,
@@ -987,7 +993,7 @@ mod tests {
#[test]
fn text_cannot_approve_without_event() {
let mut session = new_design_session("project");
let mut session = new_design_session("project", "quality");
let err = prepare_design_input(
&mut session,
"turn-1",
@@ -653,7 +653,7 @@ mod tests {
#[test]
fn phase_context_injects_current_skill_only() {
let resources = DesignResources::new(pack_root()).expect("pack");
let mut session = new_design_session("project");
let mut session = new_design_session("project", "quality");
let concept = resources.phase_context(&session);
assert!(concept.contains("skills.concept"));
assert!(!concept.contains("skills.top_design"));
@@ -677,7 +677,7 @@ mod tests {
copy_dir(&pack_root(), &dest).expect("copy pack");
fs::remove_file(dest.join("resources/skills/concept.md")).expect("remove");
let resources = DesignResources::new(dest).expect("pack without concept skill");
let session = new_design_session("project");
let session = new_design_session("project", "quality");
let context = resources.phase_context(&session);
assert!(!context.contains("skills.concept"));
assert!(context.contains("project/00_concept/design.md"));
@@ -45,6 +45,9 @@ pub(crate) struct DesignSession {
pub(crate) engine: String,
pub(crate) session_id: String,
pub(crate) project_id: String,
/// 入口选择的 AGC 模型目录 ID;同一会话内保持稳定,不保存上游真实模型名。
#[serde(default)]
pub(crate) model_id: String,
pub(crate) current_phase: String,
#[serde(default)]
pub(crate) approved_phases: Vec<String>,
@@ -130,13 +133,17 @@ pub(crate) fn design_phase_required_artifacts() -> BTreeMap<String, Vec<String>>
])
}
pub(crate) fn new_design_session(project_id: impl Into<String>) -> DesignSession {
pub(crate) fn new_design_session(
project_id: impl Into<String>,
model_id: impl Into<String>,
) -> DesignSession {
let now = unix_timestamp();
DesignSession {
schema_version: DESIGN_SESSION_SCHEMA_VERSION.to_string(),
engine: DESIGN_SESSION_ENGINE.to_string(),
session_id: Uuid::new_v4().to_string(),
project_id: project_id.into(),
model_id: model_id.into(),
current_phase: DESIGN_PHASES[0].to_string(),
approved_phases: Vec::new(),
pending_approval: None,
@@ -214,7 +221,7 @@ pub(crate) fn ensure_design_session(
}
return Ok(session);
}
let session = new_design_session(project_id.to_string());
let session = new_design_session(project_id.to_string(), "quality");
write_design_session(root, &session)?;
Ok(session)
}
@@ -321,7 +328,7 @@ mod tests {
#[test]
fn phase_artifact_check_includes_previous_phases() {
let root = tempfile::tempdir().expect("tempdir");
let mut session = new_design_session("project");
let mut session = new_design_session("project", "quality");
fs::create_dir_all(root.path().join(".workspace/project/00_concept")).expect("mkdir");
fs::write(
root.path().join(".workspace/project/00_concept/design.md"),
@@ -347,7 +354,7 @@ mod tests {
#[test]
fn rejecting_approval_keeps_phase_and_clears_request() {
let root = tempfile::tempdir().expect("tempdir");
let mut session = new_design_session("project");
let mut session = new_design_session("project", "quality");
fs::create_dir_all(root.path().join(".workspace/project/00_concept")).expect("mkdir");
fs::write(
root.path().join(".workspace/project/00_concept/design.md"),
@@ -93,7 +93,6 @@ fn user_selected_path_is_authorized(path: &Path, is_directory: bool) -> bool {
}
pub(crate) const OFFICIAL_LLM_ROUTER_BASE_URL: &str = "https://router.genarrative.world/v1";
pub(crate) const OFFICIAL_LLM_ROUTER_MODEL: &str = "gpt-6-astra";
pub(crate) const GAME_CREATOR_LLM_AGENT_REASONING_EFFORT_DEFAULTS: [(&str, &str); 21] = [
(GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID, "high"),
@@ -208,11 +207,12 @@ fn build_game_creator_official_platform_llm_config(
LlmProvider::OpenAiCompatible,
proxy_base_url,
session.access_token,
OFFICIAL_LLM_ROUTER_MODEL.to_string(),
llm.model.clone(),
llm.request_timeout_ms,
llm.max_retries,
llm.retry_backoff_ms,
)
.map(|config| config.with_agc_client_marker(true))
.map_err(|error| format!("官方 LLM Router 代理配置无效:{error}"))
}