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:
matevip 2026-04-27 08:17:17 +08:00
parent 0476447ab6
commit cc3c9a8618
10 changed files with 72 additions and 17 deletions

View File

@ -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);

View File

@ -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;

View File

@ -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();

View File

@ -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) ====================

View File

@ -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) ====================

View File

@ -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优先展示 ====================

View File

@ -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优先展示 ====================

View File

@ -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;

View File

@ -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;

View File

@ -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()
})