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 非空):加载绑定的对话;
|
对话级 terminal(_bound_conversation_id 非空):加载绑定的对话;
|
||||||
加载失败不 fallback 到“最近对话”(避免串对话),保持无对话状态,
|
加载失败不 fallback 到“最近对话”(避免串对话),保持无对话状态,
|
||||||
由任务执行链路的 ensure_conversation_loaded 显式处理。
|
由任务执行链路的 ensure_conversation_loaded 显式处理。
|
||||||
工作区级 terminal(未绑定):保持原有“最近对话”行为。
|
对话级 terminal 只服务绑定对话,必须恢复该对话保存的模型
|
||||||
|
(restore_model=True),否则重启后新建的对话级 terminal 会用默认
|
||||||
|
模型跑任务/回写 metadata,把用户切换的模型覆盖掉。
|
||||||
|
工作区级 terminal(未绑定):保持原有“最近对话”行为,且刻意不恢复
|
||||||
|
模型(restore_model=False),避免 /new 页面显示旧对话的模型。
|
||||||
"""
|
"""
|
||||||
if self.context_manager.current_conversation_id:
|
if self.context_manager.current_conversation_id:
|
||||||
return
|
return
|
||||||
@ -44,7 +48,7 @@ class WebTerminal(MainTerminal):
|
|||||||
if bound_id:
|
if bound_id:
|
||||||
if not str(bound_id).startswith("conv_"):
|
if not str(bound_id).startswith("conv_"):
|
||||||
bound_id = f"conv_{bound_id}"
|
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"):
|
if result.get("success"):
|
||||||
print(f"[WebTerminal] 已加载绑定对话: {bound_id}")
|
print(f"[WebTerminal] 已加载绑定对话: {bound_id}")
|
||||||
else:
|
else:
|
||||||
|
|||||||
@ -164,23 +164,38 @@ def update_model(terminal: WebTerminal, workspace: UserWorkspace, username: str)
|
|||||||
session["run_mode"] = terminal.run_mode
|
session["run_mode"] = terminal.run_mode
|
||||||
session["thinking_mode"] = terminal.thinking_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
|
ctx = terminal.context_manager
|
||||||
if ctx.current_conversation_id:
|
current_cid = getattr(ctx, "current_conversation_id", None)
|
||||||
try:
|
if requested_cid and current_cid:
|
||||||
ctx.conversation_manager.save_conversation(
|
if current_cid == requested_cid:
|
||||||
conversation_id=ctx.current_conversation_id,
|
try:
|
||||||
messages=ctx.conversation_history,
|
ctx.conversation_manager.save_conversation(
|
||||||
project_path=str(ctx.project_path),
|
conversation_id=current_cid,
|
||||||
todo_list=ctx.todo_list,
|
messages=ctx.conversation_history,
|
||||||
thinking_mode=terminal.thinking_mode,
|
project_path=str(ctx.project_path),
|
||||||
run_mode=terminal.run_mode,
|
todo_list=ctx.todo_list,
|
||||||
model_key=terminal.model_key,
|
thinking_mode=terminal.thinking_mode,
|
||||||
has_images=getattr(ctx, "has_images", False),
|
run_mode=terminal.run_mode,
|
||||||
has_videos=getattr(ctx, "has_videos", False)
|
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()
|
status = terminal.get_status()
|
||||||
socketio.emit('status_update', status, room=f"user_{username}")
|
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():
|
if isinstance(raw_session_model, str) and raw_session_model.strip():
|
||||||
session_model = 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、任务创建、
|
# 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:
|
try:
|
||||||
terminal.set_model(session_model)
|
terminal.set_model(session_model)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
debug_log(f"[Personalization] 恢复会话模型失败: {session_model} ({exc})")
|
debug_log(f"[Personalization] 恢复会话模型失败: {session_model} ({exc})")
|
||||||
|
|
||||||
apply_default_model = not bool(session_model) and not bool(
|
apply_default_model = (
|
||||||
getattr(terminal, "_workspace_default_model_applied", False)
|
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)
|
terminal.apply_personalization_preferences(config, apply_default_model=apply_default_model)
|
||||||
if apply_default_model:
|
if apply_default_model:
|
||||||
|
|||||||
@ -72,10 +72,16 @@ export const modelMethods = {
|
|||||||
}
|
}
|
||||||
const prev = this.currentModelKey;
|
const prev = this.currentModelKey;
|
||||||
try {
|
try {
|
||||||
|
// 携带当前对话 id:对话级隔离后,后端需要把模型设置到该对话专属的
|
||||||
|
// 对话级 terminal 并保存到正确的对话文件,避免写到工作区级 terminal
|
||||||
|
// 的陈旧对话(否则重启后模型回变为默认)。
|
||||||
const resp = await fetch('/api/model', {
|
const resp = await fetch('/api/model', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
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();
|
const payload = await resp.json();
|
||||||
if (!resp.ok || !payload.success) {
|
if (!resp.ok || !payload.success) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user