fix: adapt deepseek custom models and harden todo/tool gating

This commit is contained in:
JOJO 2026-04-24 18:31:03 +08:00
parent 1b27b3ffa2
commit 070d77c070
11 changed files with 306 additions and 26 deletions

2
.gitignore vendored
View File

@ -23,6 +23,7 @@ sub_agent/tasks/
sub_agent/data/
sub_agent/logs/
sub_agent/project/
sub_agent_results/
# Misc
*.pid
@ -32,6 +33,7 @@ static/dist/
*.tsbuildinfo
user_upload/
skills/
ax_probe_chrome.py
# Ignore docs and runtime pid
webapp.pid

View File

@ -25,21 +25,22 @@
"enable_thinking": true
}
},
"extra_parameter": {}
"extra_parameter": {},
"model_description": "你的基础模型是 Kimi-k2.6(测试别名),底层与 Kimi-k2.5 一致,并通过 thinking 参数开启/关闭思考能力。"
},
{
"model_name": "kimi-k2.7",
"description": "Kimi-k2.7(测试模型),仅快速模式",
"model_name": "DeepSeek-V4-Flash Max",
"description": "DeepSeek V4 Flashreasoning_effort=max支持快速/思考/深度思考",
"visible": true,
"url": "${API_BASE_KIMI_OFFICIAL}",
"apikey": "${API_KEY_KIMI_OFFICIAL}",
"url": "https://api.deepseek.com",
"apikey": "${API_KEY_DEEPSEEK}",
"multimodal": "none",
"reasoning_capability": "fast",
"context_window": 256000,
"max_output_tokens": 32768,
"reasoning_capability": "fast,thinking",
"context_window": 1000000,
"max_output_tokens": 384000,
"thinkmode_status": {
"type": "param_toggle",
"model_id": "kimi-k2.5",
"model_id": "deepseek-v4-flash",
"fast_extra_parameter": {
"thinking": {
"type": "disabled"
@ -47,11 +48,97 @@
},
"thinking_extra_parameter": {
"thinking": {
"type": "disabled"
}
"type": "enabled"
},
"reasoning_effort": "max"
}
},
"extra_parameter": {}
"extra_parameter": {},
"model_description": "你是DeepSeek-V4-Flash一个快捷、高效的通用模型具备接近旗舰级的推理能力在简单到中等复杂度任务中表现出色并支持 1M 上下文,适合追求响应速度与成本效率的使用场景。"
},
{
"model_name": "DeepSeek-V4-Flah High",
"description": "DeepSeek V4 Flashreasoning_effort=high支持快速/思考/深度思考",
"visible": true,
"url": "https://api.deepseek.com",
"apikey": "${API_KEY_DEEPSEEK}",
"multimodal": "none",
"reasoning_capability": "fast,thinking",
"context_window": 1000000,
"max_output_tokens": 384000,
"thinkmode_status": {
"type": "param_toggle",
"model_id": "deepseek-v4-flash",
"fast_extra_parameter": {
"thinking": {
"type": "disabled"
}
},
"thinking_extra_parameter": {
"thinking": {
"type": "enabled"
},
"reasoning_effort": "high"
}
},
"extra_parameter": {},
"model_description": "你是DeepSeek-V4-Flash一个快捷、高效的通用模型具备接近旗舰级的推理能力在简单到中等复杂度任务中表现出色并支持 1M 上下文,适合追求响应速度与成本效率的使用场景。"
},
{
"model_name": "DeepSeek-V4-Pro Max",
"description": "DeepSeek V4 Proreasoning_effort=max支持快速/思考/深度思考",
"visible": true,
"url": "https://api.deepseek.com",
"apikey": "${API_KEY_DEEPSEEK}",
"multimodal": "none",
"reasoning_capability": "fast,thinking",
"context_window": 1000000,
"max_output_tokens": 384000,
"thinkmode_status": {
"type": "param_toggle",
"model_id": "deepseek-v4-pro",
"fast_extra_parameter": {
"thinking": {
"type": "disabled"
}
},
"thinking_extra_parameter": {
"thinking": {
"type": "enabled"
},
"reasoning_effort": "max"
}
},
"extra_parameter": {},
"model_description": "你是DeepSeek-V4-Pro一个面向复杂任务的高性能通用模型具备突出的 Agent 能力、丰富的世界知识和顶级推理表现在编程、数学、STEM 与复杂问题分析场景中表现尤为出色,并支持 1M 上下文。"
},
{
"model_name": "DeepSeek-V4-Pro High",
"description": "DeepSeek V4 Proreasoning_effort=high支持快速/思考/深度思考",
"visible": true,
"url": "https://api.deepseek.com",
"apikey": "${API_KEY_DEEPSEEK}",
"multimodal": "none",
"reasoning_capability": "fast,thinking",
"context_window": 1000000,
"max_output_tokens": 384000,
"thinkmode_status": {
"type": "param_toggle",
"model_id": "deepseek-v4-pro",
"fast_extra_parameter": {
"thinking": {
"type": "disabled"
}
},
"thinking_extra_parameter": {
"thinking": {
"type": "enabled"
},
"reasoning_effort": "high"
}
},
"extra_parameter": {},
"model_description": "你是DeepSeek-V4-Pro一个面向复杂任务的高性能通用模型具备突出的 Agent 能力、丰富的世界知识和顶级推理表现在编程、数学、STEM 与复杂问题分析场景中表现尤为出色,并支持 1M 上下文。"
}
]
}

View File

@ -25,21 +25,22 @@
"enable_thinking": true
}
},
"extra_parameter": {}
"extra_parameter": {},
"model_description": "你的基础模型是 Kimi-k2.6(测试别名),底层与 Kimi-k2.5 一致,并通过 thinking 参数开启/关闭思考能力。"
},
{
"model_name": "kimi-k2.7",
"description": "Kimi-k2.7(测试模型),仅快速模式",
"model_name": "DeepSeek-V4-Flash Max",
"description": "DeepSeek V4 Flashreasoning_effort=max支持快速/思考/深度思考",
"visible": true,
"url": "${API_BASE_KIMI_OFFICIAL}",
"apikey": "${API_KEY_KIMI_OFFICIAL}",
"url": "https://api.deepseek.com",
"apikey": "${API_KEY_DEEPSEEK}",
"multimodal": "none",
"reasoning_capability": "fast",
"context_window": 256000,
"max_output_tokens": 32768,
"reasoning_capability": "fast,thinking",
"context_window": 1000000,
"max_output_tokens": 384000,
"thinkmode_status": {
"type": "param_toggle",
"model_id": "kimi-k2.5",
"model_id": "deepseek-v4-flash",
"fast_extra_parameter": {
"thinking": {
"type": "disabled"
@ -47,11 +48,97 @@
},
"thinking_extra_parameter": {
"thinking": {
"type": "disabled"
}
"type": "enabled"
},
"reasoning_effort": "max"
}
},
"extra_parameter": {}
"extra_parameter": {},
"model_description": "你是DeepSeek-V4-Flash一个快捷、高效的通用模型具备接近旗舰级的推理能力在简单到中等复杂度任务中表现出色并支持 1M 上下文,适合追求响应速度与成本效率的使用场景。"
},
{
"model_name": "DeepSeek-V4-Flah High",
"description": "DeepSeek V4 Flashreasoning_effort=high支持快速/思考/深度思考",
"visible": true,
"url": "https://api.deepseek.com",
"apikey": "${API_KEY_DEEPSEEK}",
"multimodal": "none",
"reasoning_capability": "fast,thinking",
"context_window": 1000000,
"max_output_tokens": 384000,
"thinkmode_status": {
"type": "param_toggle",
"model_id": "deepseek-v4-flash",
"fast_extra_parameter": {
"thinking": {
"type": "disabled"
}
},
"thinking_extra_parameter": {
"thinking": {
"type": "enabled"
},
"reasoning_effort": "high"
}
},
"extra_parameter": {},
"model_description": "你是DeepSeek-V4-Flash一个快捷、高效的通用模型具备接近旗舰级的推理能力在简单到中等复杂度任务中表现出色并支持 1M 上下文,适合追求响应速度与成本效率的使用场景。"
},
{
"model_name": "DeepSeek-V4-Pro Max",
"description": "DeepSeek V4 Proreasoning_effort=max支持快速/思考/深度思考",
"visible": true,
"url": "https://api.deepseek.com",
"apikey": "${API_KEY_DEEPSEEK}",
"multimodal": "none",
"reasoning_capability": "fast,thinking",
"context_window": 1000000,
"max_output_tokens": 384000,
"thinkmode_status": {
"type": "param_toggle",
"model_id": "deepseek-v4-pro",
"fast_extra_parameter": {
"thinking": {
"type": "disabled"
}
},
"thinking_extra_parameter": {
"thinking": {
"type": "enabled"
},
"reasoning_effort": "max"
}
},
"extra_parameter": {},
"model_description": "你是DeepSeek-V4-Pro一个面向复杂任务的高性能通用模型具备突出的 Agent 能力、丰富的世界知识和顶级推理表现在编程、数学、STEM 与复杂问题分析场景中表现尤为出色,并支持 1M 上下文。"
},
{
"model_name": "DeepSeek-V4-Pro High",
"description": "DeepSeek V4 Proreasoning_effort=high支持快速/思考/深度思考",
"visible": true,
"url": "https://api.deepseek.com",
"apikey": "${API_KEY_DEEPSEEK}",
"multimodal": "none",
"reasoning_capability": "fast,thinking",
"context_window": 1000000,
"max_output_tokens": 384000,
"thinkmode_status": {
"type": "param_toggle",
"model_id": "deepseek-v4-pro",
"fast_extra_parameter": {
"thinking": {
"type": "disabled"
}
},
"thinking_extra_parameter": {
"thinking": {
"type": "enabled"
},
"reasoning_effort": "high"
}
},
"extra_parameter": {},
"model_description": "你是DeepSeek-V4-Pro一个面向复杂任务的高性能通用模型具备突出的 Agent 能力、丰富的世界知识和顶级推理表现在编程、数学、STEM 与复杂问题分析场景中表现尤为出色,并支持 1M 上下文。"
}
]
}

View File

@ -400,6 +400,8 @@ def _build_custom_profile(item: Dict[str, Any]) -> Optional[Dict[str, Any]]:
profile: Dict[str, Any] = {
"name": key,
"description": str(item.get("description") or ""),
"model_description": str(item.get("model_description") or ""),
"is_custom_model": True,
"hidden": not bool(item.get("visible", True)),
"multimodal": _normalize_multimodal(item.get("multimodal")),
"context_window": context_window,
@ -496,8 +498,23 @@ def get_model_prompt_replacements(key: str) -> dict:
"""获取模型相关的提示词替换字段,若缺失则回退到 Kimi 版本。"""
fallback = MODEL_PROMPT_OVERRIDES.get("kimi", {})
overrides = MODEL_PROMPT_OVERRIDES.get(key) or {}
custom_model_description = ""
is_custom_model = False
try:
custom_profile = get_registered_model_profiles().get(key) or {}
custom_model_description = str(custom_profile.get("model_description") or "")
is_custom_model = bool(custom_profile.get("is_custom_model"))
except Exception:
custom_model_description = ""
is_custom_model = False
if is_custom_model:
return {
"model_description": custom_model_description or overrides.get("model_description") or fallback.get("model_description") or "",
"thinking_model_line": "",
"deep_thinking_line": "",
}
return {
"model_description": overrides.get("model_description") or fallback.get("model_description") or "",
"model_description": custom_model_description or overrides.get("model_description") or fallback.get("model_description") or "",
"thinking_model_line": overrides.get("thinking_model_line") or fallback.get("thinking_model_line") or "",
"deep_thinking_line": overrides.get("deep_thinking_line") or fallback.get("deep_thinking_line") or ""
}

View File

@ -143,6 +143,7 @@
3. **诚实守信**:做不到的事情坦白说,不编造
4. **用户友好**:用简单的语言解释复杂的操作
5. **正确执行**:主动确认细节,获得明确许可后再开始
6. **沟通原则**:使用用户使用的语言进行思考和交流
## 8. 个性化配置

View File

@ -161,6 +161,7 @@
3. **诚实守信**:做不到的事情坦白说,不编造
4. **用户友好**:用简单的语言解释复杂的操作
5. **正确执行**:主动确认细节,获得明确许可后再开始
6. **沟通原则**:使用用户使用的语言进行思考和交流
## 8. 个性化配置

View File

@ -151,6 +151,16 @@ async def _wait_for_tool_approval(*, approval_id: str, username: str, timeout_se
async def execute_tool_calls(*, web_terminal, tool_calls, sender, messages, client_sid: str, username: str, iteration: int, conversation_id: Optional[str], last_tool_call_time: float, process_sub_agent_updates, process_background_command_updates, maybe_mark_failure_from_message, mark_force_thinking, get_stop_flag, clear_stop_flag, workspace=None):
previous_tool_loop_active = getattr(web_terminal, "_tool_loop_active", False)
web_terminal._tool_loop_active = True
allowed_tool_names = set()
try:
defined_tools = web_terminal.define_tools() or []
for tool in defined_tools:
name = ((tool or {}).get("function") or {}).get("name")
if isinstance(name, str) and name:
allowed_tool_names.add(name)
except Exception as exc:
debug_log(f"构建工具白名单失败(降级继续): {exc}")
# 执行每个工具
for tool_call in tool_calls:
# 检查停止标志
@ -310,6 +320,49 @@ async def execute_tool_calls(*, web_terminal, tool_calls, sender, messages, clie
})
continue
# 严格校验:只允许执行当前回合明确下发给模型的工具。
# 某些模型会“幻觉”调用未下发工具(如 view_image必须在执行层拦截。
if allowed_tool_names and function_name not in allowed_tool_names:
if function_name == "view_image" and "vlm_analyze" in allowed_tool_names:
if not arguments.get("prompt"):
arguments["prompt"] = "请详细分析这张图片的内容,包括关键文字、主体对象与场景信息。"
debug_log(
"工具名自动纠正: view_image -> vlm_analyze (模型未获授权 view_image)"
)
function_name = "vlm_analyze"
else:
denied_message = (
f"工具 {function_name} 不在当前模型可用工具列表中,已拒绝执行。"
)
denied_payload = {
"success": False,
"status": "denied",
"code": "tool_not_allowed",
"tool": function_name,
"message": denied_message,
}
sender('update_action', {
'preparing_id': tool_call_id,
'status': 'completed',
'result': denied_payload,
'message': denied_message,
'conversation_id': conversation_id
})
denied_content = json.dumps(denied_payload, ensure_ascii=False)
web_terminal.context_manager.add_conversation(
"tool",
denied_content,
tool_call_id=tool_call_id,
name=function_name
)
messages.append({
"role": "tool",
"tool_call_id": tool_call_id,
"name": function_name,
"content": denied_content
})
continue
debug_log(f"执行工具: {function_name} (ID: {tool_call_id})")
permission_eval = web_terminal.evaluate_tool_permission(function_name, arguments)

View File

@ -65,6 +65,8 @@ Terminal 工具提供了一个可以持续存在的终端会话,命令在其
- top、htop
- 任何需要完整 TTY 的程序
# 简单的交互程序 如只需要输入输出内容的py,node程序等可以使用
## 三步工作流
使用 terminal 工具的标准流程:

View File

@ -91,6 +91,11 @@ export async function mounted() {
}
export function beforeUnmount() {
if (this._todoRefreshTimer) {
clearTimeout(this._todoRefreshTimer);
this._todoRefreshTimer = null;
}
// 停止任务轮询
try {
const { useTaskStore } = require('../stores/task');

View File

@ -44,6 +44,20 @@ function isEmptyAssistantPlaceholderMessage(message: any): boolean {
* REST API
*/
export const taskPollingMethods = {
scheduleTodoListRefresh(delayMs = 120) {
const delay = Number.isFinite(delayMs) ? Math.max(0, Number(delayMs)) : 120;
if (this._todoRefreshTimer) {
clearTimeout(this._todoRefreshTimer);
this._todoRefreshTimer = null;
}
this._todoRefreshTimer = setTimeout(() => {
this._todoRefreshTimer = null;
if (typeof this.fetchTodoList === 'function') {
Promise.resolve(this.fetchTodoList()).catch(() => {});
}
}, delay);
},
cleanupTrailingEmptyAssistantPlaceholder(reason = 'unspecified') {
if (!Array.isArray(this.messages) || !this.messages.length) {
return false;
@ -912,6 +926,13 @@ export const taskPollingMethods = {
targetAction.tool.content = data.content;
}
// 待办工具执行完成后主动刷新左侧待办列表(网页端不再依赖 websocket todo_updated
if (data.status === 'completed') {
const toolName = String(targetAction?.tool?.name || '').toLowerCase();
if (toolName.startsWith('todo_') || toolName === 'todo_create' || toolName === 'todo_update_task') {
this.scheduleTodoListRefresh(80);
}
}
this.$forceUpdate();
this.conditionalScrollToBottom();
@ -1063,6 +1084,7 @@ export const taskPollingMethods = {
this.updateCurrentContextTokens();
}, 500);
}
this.scheduleTodoListRefresh(100);
const pendingToolsAfter =
typeof this.hasPendingToolActions === 'function' ? this.hasPendingToolActions() : null;
jsonDebug('handleTaskComplete:after', {
@ -1099,6 +1121,7 @@ export const taskPollingMethods = {
if (typeof this.clearPendingTools === 'function') {
this.clearPendingTools('task_stopped');
}
this.scheduleTodoListRefresh(100);
this.$forceUpdate();
this.clearTaskState();

View File

@ -1600,7 +1600,6 @@ export const uiMethods = {
}
const focusPromise = this.focusFetchFiles();
const todoPromise = this.fileFetchTodoList();
let treePromise: Promise<any> | null = null;
const isHostMode = statusData?.container?.mode === 'host';
this.versioningHostMode = !!isHostMode;
@ -1672,6 +1671,9 @@ export const uiMethods = {
}
}
// 待办数据依赖当前会话上下文,放在会话恢复/切换之后拉取,避免初始化早期拿到空快照
const todoPromise = this.fileFetchTodoList();
// 等待其他加载项完成(允许部分失败不阻塞模式切换)
const pendingPromises = [focusPromise, todoPromise];
if (treePromise) {