mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-13 03:13:41 +08:00
fix(ux): preserve in-flight turn on tab switch + raise max_iterations cap to 100
Three small but high-impact fixes that all surfaced together while
verifying the long-form generation flow.
1. ChatConsole onBeforeUnmount no longer kills the backend turn.
Previously, switching tabs / route navigation / any cause that
unmounted the chat view called stopChatGeneration(), which POSTs
/chat/{cid}/stop and aborts the in-flight LLM call. The user
reported a turn dying mid-generation just from switching pages.
Replaced with resetForNewConversation() — front-end SSE disconnect
only, no /stop. Backend keeps running; pollActivity / status probe
reconnects on return. Aligns with the existing comment in
selectConversation: "let A's backend agent run continue running."
2. Agent max_iterations raised 25 → 100 with a hard ceiling.
The previous 25-step ceiling caused LimitExceededNode to fire on
substantive multi-tool tasks (document generation + image conversion
+ retry loops). 100 matches QwenPaw's _MAX_MAX_ITERATIONS upper
bound. New plumbing:
- BaseAgent.MAX_ITERATIONS_HARD_CEILING = 100 public constant
- BaseAgent default field 25 → 100 (Java-side fallback)
- AgentGraphBuilder clamps any per-agent DB override to the
ceiling at runtime; if the row holds 200, runtime sees 100 and
a WARN is logged with the original value.
- V47 migration (h2 + mysql) idempotently bumps the three default
seeded agents (1000000001, 1000000002, 1000000003) only if they
still hold the old defaults (25 / 20). User-customized values
are not touched.
- data-en/zh/-mysql-en/-mysql-zh seed files updated to 100 for
fresh installs.
3. DocxRenderTool tells the LLM not to prepend a host to the URL.
DeepSeek and Claude have both been observed wrapping the
/api/v1/files/generated/{id} relative path returned by renderDocx
into an absolute URL with a hallucinated domain (e.g.
https://ai-tools-system.com/...), breaking the download link in
the rendered chat bubble. The tool's return string now appends an
explicit "must use the relative path verbatim, do not add any
https:// or http:// prefix" instruction, which Claude and
DeepSeek both honor.
This commit is contained in:
parent
0476447ab6
commit
cc3c9a8618
@ -194,7 +194,15 @@ public class AgentGraphBuilder {
|
||||
// 内置搜索作为首选,search 工具作为补充/兜底
|
||||
log.info("内置搜索已开启 (provider={}),search 工具保留作为补充通道", provider.getProviderId());
|
||||
}
|
||||
int maxIter = entity.getMaxIterations() != null ? entity.getMaxIterations() : 25;
|
||||
// Default 100 if DB row leaves max_iterations null; clamp per-agent overrides
|
||||
// to the hard ceiling (BaseAgent.MAX_ITERATIONS_HARD_CEILING) so a misconfigured
|
||||
// row can never push an unbounded loop. Aligned with QwenPaw's 1..100 range.
|
||||
int rawMaxIter = entity.getMaxIterations() != null ? entity.getMaxIterations() : 100;
|
||||
int maxIter = Math.max(1, Math.min(rawMaxIter, BaseAgent.MAX_ITERATIONS_HARD_CEILING));
|
||||
if (maxIter != rawMaxIter) {
|
||||
log.warn("Agent {} max_iterations={} clamped to {} (1..{})",
|
||||
entity.getId(), rawMaxIter, maxIter, BaseAgent.MAX_ITERATIONS_HARD_CEILING);
|
||||
}
|
||||
|
||||
String enhancedPrompt = buildEnhancedPrompt(entity, builtinSearchEnabled);
|
||||
|
||||
|
||||
@ -43,8 +43,13 @@ public abstract class BaseAgent {
|
||||
/** 系统提示词 */
|
||||
protected String systemPrompt;
|
||||
|
||||
/** 最大工具调用迭代次数 */
|
||||
protected int maxIterations = 25;
|
||||
/**
|
||||
* Max ReAct iterations (one reasoning + action + observation step counts as one).
|
||||
* Default 100, hard ceiling 100 (enforced in AgentGraphBuilder so per-agent DB
|
||||
* overrides cannot exceed it). Aligned with QwenPaw's _MAX_MAX_ITERATIONS.
|
||||
*/
|
||||
public static final int MAX_ITERATIONS_HARD_CEILING = 100;
|
||||
protected int maxIterations = 100;
|
||||
|
||||
/** 工作区活动目录(限制文件工具访问范围,为空不限制) */
|
||||
protected String workspaceBasePath;
|
||||
|
||||
@ -70,7 +70,13 @@ public class DocxRenderTool {
|
||||
displayName, bytes.length, elapsed, id);
|
||||
|
||||
String url = "/api/v1/files/generated/" + id;
|
||||
return "文档已生成:[" + displayName + "](" + url + ")(链接 10 分钟内有效)";
|
||||
// Explicit instruction to suppress LLM hallucinating an absolute host.
|
||||
// DeepSeek/Claude have been observed prepending placeholder domains
|
||||
// (e.g. https://ai-tools-system.com) when echoing the URL back to the user,
|
||||
// breaking the download link. Repeat the path verbatim with no host.
|
||||
return "文档已生成:[" + displayName + "](" + url + ")(链接 10 分钟内有效)。\n"
|
||||
+ "重要:回答用户时**必须**使用上述相对路径 `" + url + "`,"
|
||||
+ "**不要**添加任何 https://、http:// 域名前缀,前端会自动拼接当前主机。";
|
||||
} catch (Exception e) {
|
||||
log.error("[DocxRender] render failed for {}: {}", displayName, e.getMessage(), e);
|
||||
return "渲染失败:" + e.getMessage();
|
||||
|
||||
@ -10,21 +10,21 @@ MERGE INTO mate_agent (id, name, description, agent_type, system_prompt, model_n
|
||||
KEY (id)
|
||||
VALUES (1000000001, 'MateClaw Assistant', 'Default AI assistant with ReAct mode and tool calling', 'react',
|
||||
'You are MateClaw, an intelligent AI assistant. You can help users answer questions, analyze data, and execute tasks. Please respond professionally and in a friendly manner.',
|
||||
NULL, 25, TRUE, '🤖', 'default,assistant', NOW(), NOW(), 0);
|
||||
NULL, 100, TRUE, '🤖', 'default,assistant', NOW(), NOW(), 0);
|
||||
|
||||
-- Default Agent: Task Planner (Plan-Execute mode)
|
||||
MERGE INTO mate_agent (id, name, description, agent_type, system_prompt, model_name, max_iterations, enabled, icon, tags, create_time, update_time, deleted)
|
||||
KEY (id)
|
||||
VALUES (1000000002, 'Task Planner', 'Task planning assistant for complex multi-step tasks', 'plan_execute',
|
||||
'You are a professional task planning and execution assistant. You excel at breaking complex goals into executable steps and completing them systematically.',
|
||||
NULL, 20, TRUE, '📋', 'planning,task', NOW(), NOW(), 0);
|
||||
NULL, 100, TRUE, '📋', 'planning,task', NOW(), NOW(), 0);
|
||||
|
||||
-- StateGraph ReAct Agent (StateGraph architecture)
|
||||
MERGE INTO mate_agent (id, name, description, agent_type, system_prompt, model_name, max_iterations, enabled, icon, tags, create_time, update_time, deleted)
|
||||
KEY (id)
|
||||
VALUES (1000000003, 'StateGraph ReAct', 'StateGraph-based ReAct Agent with explicit reasoning loops and tool calling', 'react',
|
||||
'You are an intelligent assistant based on the StateGraph architecture. You can use tools to help users solve problems. Please respond professionally and in a friendly manner.',
|
||||
NULL, 25, TRUE, '🔄', 'react,stategraph,tools', NOW(), NOW(), 0);
|
||||
NULL, 100, TRUE, '🔄', 'react,stategraph,tools', NOW(), NOW(), 0);
|
||||
|
||||
-- ==================== Local Model Providers (displayed first) ====================
|
||||
|
||||
|
||||
@ -9,21 +9,21 @@ ON DUPLICATE KEY UPDATE username=VALUES(username), password=VALUES(password), ni
|
||||
INSERT INTO mate_agent (id, name, description, agent_type, system_prompt, model_name, max_iterations, enabled, icon, tags, create_time, update_time, deleted)
|
||||
VALUES (1000000001, 'MateClaw Assistant', 'Default AI assistant with ReAct mode and tool calling', 'react',
|
||||
'You are MateClaw, an intelligent AI assistant. You can help users answer questions, analyze data, and execute tasks. Please respond professionally and in a friendly manner.',
|
||||
NULL, 25, TRUE, '🤖', 'default,assistant', NOW(), NOW(), 0)
|
||||
NULL, 100, TRUE, '🤖', 'default,assistant', NOW(), NOW(), 0)
|
||||
ON DUPLICATE KEY UPDATE name=VALUES(name), description=VALUES(description), agent_type=VALUES(agent_type), system_prompt=VALUES(system_prompt), model_name=VALUES(model_name), max_iterations=VALUES(max_iterations), enabled=VALUES(enabled), icon=VALUES(icon), tags=VALUES(tags), update_time=VALUES(update_time), deleted=VALUES(deleted);
|
||||
|
||||
-- Default Agent: Task Planner (Plan-Execute mode)
|
||||
INSERT INTO mate_agent (id, name, description, agent_type, system_prompt, model_name, max_iterations, enabled, icon, tags, create_time, update_time, deleted)
|
||||
VALUES (1000000002, 'Task Planner', 'Task planning assistant for complex multi-step tasks', 'plan_execute',
|
||||
'You are a professional task planning and execution assistant. You excel at breaking complex goals into executable steps and completing them systematically.',
|
||||
NULL, 20, TRUE, '📋', 'planning,task', NOW(), NOW(), 0)
|
||||
NULL, 100, TRUE, '📋', 'planning,task', NOW(), NOW(), 0)
|
||||
ON DUPLICATE KEY UPDATE name=VALUES(name), description=VALUES(description), agent_type=VALUES(agent_type), system_prompt=VALUES(system_prompt), model_name=VALUES(model_name), max_iterations=VALUES(max_iterations), enabled=VALUES(enabled), icon=VALUES(icon), tags=VALUES(tags), update_time=VALUES(update_time), deleted=VALUES(deleted);
|
||||
|
||||
-- StateGraph ReAct Agent (StateGraph architecture)
|
||||
INSERT INTO mate_agent (id, name, description, agent_type, system_prompt, model_name, max_iterations, enabled, icon, tags, create_time, update_time, deleted)
|
||||
VALUES (1000000003, 'StateGraph ReAct', 'StateGraph-based ReAct Agent with explicit reasoning loops and tool calling', 'react',
|
||||
'You are an intelligent assistant based on the StateGraph architecture. You can use tools to help users solve problems. Please respond professionally and in a friendly manner.',
|
||||
NULL, 25, TRUE, '🔄', 'react,stategraph,tools', NOW(), NOW(), 0)
|
||||
NULL, 100, TRUE, '🔄', 'react,stategraph,tools', NOW(), NOW(), 0)
|
||||
ON DUPLICATE KEY UPDATE name=VALUES(name), description=VALUES(description), agent_type=VALUES(agent_type), system_prompt=VALUES(system_prompt), model_name=VALUES(model_name), max_iterations=VALUES(max_iterations), enabled=VALUES(enabled), icon=VALUES(icon), tags=VALUES(tags), update_time=VALUES(update_time), deleted=VALUES(deleted);
|
||||
|
||||
-- ==================== Local Model Providers (displayed first) ====================
|
||||
|
||||
@ -9,21 +9,21 @@ ON DUPLICATE KEY UPDATE username=VALUES(username), password=VALUES(password), ni
|
||||
INSERT INTO mate_agent (id, name, description, agent_type, system_prompt, model_name, max_iterations, enabled, icon, tags, create_time, update_time, deleted)
|
||||
VALUES (1000000001, 'MateClaw Assistant', '默认 AI 助手,基于 ReAct 模式,支持工具调用', 'react',
|
||||
'你是 MateClaw,一个智能 AI 助手。你可以帮助用户回答问题、分析数据、执行任务。请用中文回复,保持专业、友好的态度。',
|
||||
NULL, 25, TRUE, '🤖', 'default,assistant', NOW(), NOW(), 0)
|
||||
NULL, 100, TRUE, '🤖', 'default,assistant', NOW(), NOW(), 0)
|
||||
ON DUPLICATE KEY UPDATE name=VALUES(name), description=VALUES(description), agent_type=VALUES(agent_type), system_prompt=VALUES(system_prompt), model_name=VALUES(model_name), max_iterations=VALUES(max_iterations), enabled=VALUES(enabled), icon=VALUES(icon), tags=VALUES(tags), update_time=VALUES(update_time), deleted=VALUES(deleted);
|
||||
|
||||
-- 默认 Agent:任务规划助手(Plan-Execute 模式)
|
||||
INSERT INTO mate_agent (id, name, description, agent_type, system_prompt, model_name, max_iterations, enabled, icon, tags, create_time, update_time, deleted)
|
||||
VALUES (1000000002, 'Task Planner', '任务规划助手,适合复杂多步骤任务', 'plan_execute',
|
||||
'你是一个专业的任务规划和执行助手。你擅长将复杂目标分解为可执行的步骤,并逐步完成。请用中文回复。',
|
||||
NULL, 20, TRUE, '📋', 'planning,task', NOW(), NOW(), 0)
|
||||
NULL, 100, TRUE, '📋', 'planning,task', NOW(), NOW(), 0)
|
||||
ON DUPLICATE KEY UPDATE name=VALUES(name), description=VALUES(description), agent_type=VALUES(agent_type), system_prompt=VALUES(system_prompt), model_name=VALUES(model_name), max_iterations=VALUES(max_iterations), enabled=VALUES(enabled), icon=VALUES(icon), tags=VALUES(tags), update_time=VALUES(update_time), deleted=VALUES(deleted);
|
||||
|
||||
-- StateGraph ReAct Agent(支持 StateGraph 架构)
|
||||
INSERT INTO mate_agent (id, name, description, agent_type, system_prompt, model_name, max_iterations, enabled, icon, tags, create_time, update_time, deleted)
|
||||
VALUES (1000000003, 'StateGraph ReAct', '基于 StateGraph 的 ReAct Agent,支持显式推理循环和工具调用', 'react',
|
||||
'你是基于 StateGraph 架构的智能助手。你可以使用工具来帮助用户解决问题。请用中文回复,保持专业、友好的态度。',
|
||||
NULL, 25, TRUE, '🔄', 'react,stategraph,tools', NOW(), NOW(), 0)
|
||||
NULL, 100, TRUE, '🔄', 'react,stategraph,tools', NOW(), NOW(), 0)
|
||||
ON DUPLICATE KEY UPDATE name=VALUES(name), description=VALUES(description), agent_type=VALUES(agent_type), system_prompt=VALUES(system_prompt), model_name=VALUES(model_name), max_iterations=VALUES(max_iterations), enabled=VALUES(enabled), icon=VALUES(icon), tags=VALUES(tags), update_time=VALUES(update_time), deleted=VALUES(deleted);
|
||||
|
||||
-- ==================== 本地模型 Provider(优先展示) ====================
|
||||
|
||||
@ -10,21 +10,21 @@ MERGE INTO mate_agent (id, name, description, agent_type, system_prompt, model_n
|
||||
KEY (id)
|
||||
VALUES (1000000001, 'MateClaw Assistant', '默认 AI 助手,基于 ReAct 模式,支持工具调用', 'react',
|
||||
'你是 MateClaw,一个智能 AI 助手。你可以帮助用户回答问题、分析数据、执行任务。请用中文回复,保持专业、友好的态度。',
|
||||
NULL, 25, TRUE, '🤖', 'default,assistant', NOW(), NOW(), 0);
|
||||
NULL, 100, TRUE, '🤖', 'default,assistant', NOW(), NOW(), 0);
|
||||
|
||||
-- 默认 Agent:任务规划助手(Plan-Execute 模式)
|
||||
MERGE INTO mate_agent (id, name, description, agent_type, system_prompt, model_name, max_iterations, enabled, icon, tags, create_time, update_time, deleted)
|
||||
KEY (id)
|
||||
VALUES (1000000002, 'Task Planner', '任务规划助手,适合复杂多步骤任务', 'plan_execute',
|
||||
'你是一个专业的任务规划和执行助手。你擅长将复杂目标分解为可执行的步骤,并逐步完成。请用中文回复。',
|
||||
NULL, 20, TRUE, '📋', 'planning,task', NOW(), NOW(), 0);
|
||||
NULL, 100, TRUE, '📋', 'planning,task', NOW(), NOW(), 0);
|
||||
|
||||
-- StateGraph ReAct Agent(支持 StateGraph 架构)
|
||||
MERGE INTO mate_agent (id, name, description, agent_type, system_prompt, model_name, max_iterations, enabled, icon, tags, create_time, update_time, deleted)
|
||||
KEY (id)
|
||||
VALUES (1000000003, 'StateGraph ReAct', '基于 StateGraph 的 ReAct Agent,支持显式推理循环和工具调用', 'react',
|
||||
'你是基于 StateGraph 架构的智能助手。你可以使用工具来帮助用户解决问题。请用中文回复,保持专业、友好的态度。',
|
||||
NULL, 25, TRUE, '🔄', 'react,stategraph,tools', NOW(), NOW(), 0);
|
||||
NULL, 100, TRUE, '🔄', 'react,stategraph,tools', NOW(), NOW(), 0);
|
||||
|
||||
-- ==================== 本地模型 Provider(优先展示) ====================
|
||||
|
||||
|
||||
@ -0,0 +1,16 @@
|
||||
-- V47: Bump default agents' max_iterations to 100 (QwenPaw-style hard ceiling).
|
||||
--
|
||||
-- The previous defaults (25 for ReAct, 20 for plan-execute) ran the LimitExceededNode
|
||||
-- too eagerly on substantive multi-tool tasks (e.g. document generation with image
|
||||
-- conversion). New default is 100, matching QwenPaw's _MAX_MAX_ITERATIONS upper bound.
|
||||
-- AgentGraphBuilder still clamps any per-agent override to MAX_ITERATIONS_HARD_CEILING
|
||||
-- at runtime, so a user-configured 200 will be silently capped to 100.
|
||||
--
|
||||
-- Idempotent: only updates rows that still hold the old defaults, so user-customized
|
||||
-- agents are not touched.
|
||||
|
||||
UPDATE mate_agent SET max_iterations = 100
|
||||
WHERE id IN (1000000001, 1000000003) AND max_iterations = 25;
|
||||
|
||||
UPDATE mate_agent SET max_iterations = 100
|
||||
WHERE id = 1000000002 AND max_iterations = 20;
|
||||
@ -0,0 +1,16 @@
|
||||
-- V47: Bump default agents' max_iterations to 100 (QwenPaw-style hard ceiling).
|
||||
--
|
||||
-- The previous defaults (25 for ReAct, 20 for plan-execute) ran the LimitExceededNode
|
||||
-- too eagerly on substantive multi-tool tasks (e.g. document generation with image
|
||||
-- conversion). New default is 100, matching QwenPaw's _MAX_MAX_ITERATIONS upper bound.
|
||||
-- AgentGraphBuilder still clamps any per-agent override to MAX_ITERATIONS_HARD_CEILING
|
||||
-- at runtime, so a user-configured 200 will be silently capped to 100.
|
||||
--
|
||||
-- Idempotent: only updates rows that still hold the old defaults, so user-customized
|
||||
-- agents are not touched.
|
||||
|
||||
UPDATE mate_agent SET max_iterations = 100
|
||||
WHERE id IN (1000000001, 1000000003) AND max_iterations = 25;
|
||||
|
||||
UPDATE mate_agent SET max_iterations = 100
|
||||
WHERE id = 1000000002 AND max_iterations = 20;
|
||||
@ -798,7 +798,11 @@ onBeforeUnmount(() => {
|
||||
clearInterval(activityPollTimer)
|
||||
activityPollTimer = null
|
||||
}
|
||||
stopChatGeneration()
|
||||
// Switching tabs / route changes / mouse-detach unmount this component, but the
|
||||
// backend agent should keep running so the user can reconnect later. Use
|
||||
// resetForNewConversation (front-end SSE disconnect only) instead of
|
||||
// stopChatGeneration which would POST /stop and abort the in-flight turn.
|
||||
resetForNewConversation()
|
||||
// 释放所有附件的 ObjectURL,防止内存泄漏
|
||||
revokeAllPreviewUrls()
|
||||
})
|
||||
|
||||
Loading…
Reference in New Issue
Block a user