fix(model): 修复重启后进入对话模型回变为默认模型
对话级隔离后存在四处模型覆盖/错存路径: 1. 对话级 terminal 绑定加载用 restore_model=False,重启后新建的 对话级 terminal 丢失对话保存的模型 -> 绑定加载恢复模型 2. /api/model 把工作区级 terminal 的陈旧 current_conversation_id 当目标保存,模型写到错误对话或丢失(且用陈旧 history 全量保存 有覆盖新消息风险)-> 仅当请求显式携带 conversation_id 且与 terminal 当前对话一致时才持久化 3. 前端切换模型不携带 conversation_id -> handleModelSelect 带上 4. _apply_workspace_personalization_preferences 把 session 全局模型 /个性化默认模型覆盖到对话级 terminal -> 对话级 terminal 跳过, 其模型由绑定加载权威确定
This commit is contained in:
parent
a4eb688814
commit
1289c31dfe
@ -35,7 +35,11 @@ class WebTerminal(MainTerminal):
|
||||
对话级 terminal(_bound_conversation_id 非空):加载绑定的对话;
|
||||
加载失败不 fallback 到“最近对话”(避免串对话),保持无对话状态,
|
||||
由任务执行链路的 ensure_conversation_loaded 显式处理。
|
||||
工作区级 terminal(未绑定):保持原有“最近对话”行为。
|
||||
对话级 terminal 只服务绑定对话,必须恢复该对话保存的模型
|
||||
(restore_model=True),否则重启后新建的对话级 terminal 会用默认
|
||||
模型跑任务/回写 metadata,把用户切换的模型覆盖掉。
|
||||
工作区级 terminal(未绑定):保持原有“最近对话”行为,且刻意不恢复
|
||||
模型(restore_model=False),避免 /new 页面显示旧对话的模型。
|
||||
"""
|
||||
if self.context_manager.current_conversation_id:
|
||||
return
|
||||
@ -44,7 +48,7 @@ class WebTerminal(MainTerminal):
|
||||
if bound_id:
|
||||
if not str(bound_id).startswith("conv_"):
|
||||
bound_id = f"conv_{bound_id}"
|
||||
result = self.load_conversation(bound_id, restore_model=False)
|
||||
result = self.load_conversation(bound_id, restore_model=True)
|
||||
if result.get("success"):
|
||||
print(f"[WebTerminal] 已加载绑定对话: {bound_id}")
|
||||
else:
|
||||
|
||||
@ -164,23 +164,38 @@ def update_model(terminal: WebTerminal, workspace: UserWorkspace, username: str)
|
||||
session["run_mode"] = terminal.run_mode
|
||||
session["thinking_mode"] = terminal.thinking_mode
|
||||
|
||||
# 更新当前对话元数据
|
||||
# 更新对话元数据:仅当请求显式携带 conversation_id 时持久化。
|
||||
# 对话级隔离后,未带 conversation_id 时拿到的是工作区级 terminal,
|
||||
# 其 current_conversation_id 可能是陈旧对话(安全导航/切换后),
|
||||
# 盲目保存会把模型写到错误对话,或用陈旧 history 覆盖新消息。
|
||||
# /new 页面(无 conversation_id)不持久化,新对话在首个任务时
|
||||
# 由前端随任务传入的 model_key 落盘。
|
||||
requested_cid = str(data.get("conversation_id") or "").strip()
|
||||
if requested_cid and not requested_cid.startswith("conv_"):
|
||||
requested_cid = f"conv_{requested_cid}"
|
||||
ctx = terminal.context_manager
|
||||
if ctx.current_conversation_id:
|
||||
try:
|
||||
ctx.conversation_manager.save_conversation(
|
||||
conversation_id=ctx.current_conversation_id,
|
||||
messages=ctx.conversation_history,
|
||||
project_path=str(ctx.project_path),
|
||||
todo_list=ctx.todo_list,
|
||||
thinking_mode=terminal.thinking_mode,
|
||||
run_mode=terminal.run_mode,
|
||||
model_key=terminal.model_key,
|
||||
has_images=getattr(ctx, "has_images", False),
|
||||
has_videos=getattr(ctx, "has_videos", False)
|
||||
current_cid = getattr(ctx, "current_conversation_id", None)
|
||||
if requested_cid and current_cid:
|
||||
if current_cid == requested_cid:
|
||||
try:
|
||||
ctx.conversation_manager.save_conversation(
|
||||
conversation_id=current_cid,
|
||||
messages=ctx.conversation_history,
|
||||
project_path=str(ctx.project_path),
|
||||
todo_list=ctx.todo_list,
|
||||
thinking_mode=terminal.thinking_mode,
|
||||
run_mode=terminal.run_mode,
|
||||
model_key=terminal.model_key,
|
||||
has_images=getattr(ctx, "has_images", False),
|
||||
has_videos=getattr(ctx, "has_videos", False)
|
||||
)
|
||||
except Exception as exc:
|
||||
print(f"[API] 保存模型到对话失败: {exc}")
|
||||
else:
|
||||
print(
|
||||
f"[API] 跳过模型保存:请求对话 {requested_cid} "
|
||||
f"与 terminal 当前对话 {current_cid} 不一致"
|
||||
)
|
||||
except Exception as exc:
|
||||
print(f"[API] 保存模型到对话失败: {exc}")
|
||||
|
||||
status = terminal.get_status()
|
||||
socketio.emit('status_update', status, room=f"user_{username}")
|
||||
|
||||
@ -122,16 +122,28 @@ def _apply_workspace_personalization_preferences(terminal: WebTerminal, workspac
|
||||
if isinstance(raw_session_model, str) and raw_session_model.strip():
|
||||
session_model = raw_session_model.strip()
|
||||
|
||||
# 对话级 terminal(_bound_conversation_id)的模型由绑定加载权威恢复
|
||||
# (对话文件 metadata.model_key),session 级模型是全局的最后选择,
|
||||
# 不能覆盖到某个具体对话的 terminal 上,否则重启后进入对话会被
|
||||
# session 里的其它模型回写(模型回变默认的 bug)。
|
||||
is_conversation_bound = bool(getattr(terminal, "_bound_conversation_id", None))
|
||||
|
||||
# default_model 是“新会话初始偏好”,不能在每次 /api/status、任务创建、
|
||||
# 加载资源时覆盖用户已经在当前会话里手动切换的模型。
|
||||
if session_model and session_model != getattr(terminal, "model_key", None):
|
||||
if (
|
||||
session_model
|
||||
and not is_conversation_bound
|
||||
and session_model != getattr(terminal, "model_key", None)
|
||||
):
|
||||
try:
|
||||
terminal.set_model(session_model)
|
||||
except Exception as exc:
|
||||
debug_log(f"[Personalization] 恢复会话模型失败: {session_model} ({exc})")
|
||||
|
||||
apply_default_model = not bool(session_model) and not bool(
|
||||
getattr(terminal, "_workspace_default_model_applied", False)
|
||||
apply_default_model = (
|
||||
not is_conversation_bound
|
||||
and not bool(session_model)
|
||||
and not bool(getattr(terminal, "_workspace_default_model_applied", False))
|
||||
)
|
||||
terminal.apply_personalization_preferences(config, apply_default_model=apply_default_model)
|
||||
if apply_default_model:
|
||||
|
||||
@ -72,10 +72,16 @@ export const modelMethods = {
|
||||
}
|
||||
const prev = this.currentModelKey;
|
||||
try {
|
||||
// 携带当前对话 id:对话级隔离后,后端需要把模型设置到该对话专属的
|
||||
// 对话级 terminal 并保存到正确的对话文件,避免写到工作区级 terminal
|
||||
// 的陈旧对话(否则重启后模型回变为默认)。
|
||||
const resp = await fetch('/api/model', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ model_key: key })
|
||||
body: JSON.stringify({
|
||||
model_key: key,
|
||||
conversation_id: this.currentConversationId || undefined
|
||||
})
|
||||
});
|
||||
const payload = await resp.json();
|
||||
if (!resp.ok || !payload.success) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user