diff --git a/mateclaw-server/src/main/java/vip/mate/agent/AgentGraphBuilder.java b/mateclaw-server/src/main/java/vip/mate/agent/AgentGraphBuilder.java index f9e16cc2..36065c9b 100644 --- a/mateclaw-server/src/main/java/vip/mate/agent/AgentGraphBuilder.java +++ b/mateclaw-server/src/main/java/vip/mate/agent/AgentGraphBuilder.java @@ -88,6 +88,11 @@ public class AgentGraphBuilder { @org.springframework.beans.factory.annotation.Value( "${mateclaw.skill.disclosure.load-skill-tool.enabled:true}") private boolean loadSkillToolEnabled; + + /** Escape hatch: when false, the final answer is sent verbatim without Markdown normalization. */ + @org.springframework.beans.factory.annotation.Value( + "${mate.agent.markdown-normalize-enabled:true}") + private boolean markdownNormalizeEnabled; private final ConversationService conversationService; private final ModelConfigService modelConfigService; private final ModelProviderService modelProviderService; @@ -809,7 +814,7 @@ public class AgentGraphBuilder { SummarizingNode summarizingNode = new SummarizingNode(chatModel, streamingHelper, streamTracker); LimitExceededNode limitExceededNode = new LimitExceededNode( chatModel, observationProcessor, streamingHelper, i18nService, progressLedgerService); - FinalAnswerNode finalAnswerNode = new FinalAnswerNode(generatedFileCache); + FinalAnswerNode finalAnswerNode = new FinalAnswerNode(generatedFileCache, markdownNormalizeEnabled); KeyStrategyFactory keyStrategyFactory = KeyStrategy.builder() // 输入字段 diff --git a/mateclaw-server/src/main/java/vip/mate/agent/graph/node/FinalAnswerNode.java b/mateclaw-server/src/main/java/vip/mate/agent/graph/node/FinalAnswerNode.java index ea478714..0db1585d 100644 --- a/mateclaw-server/src/main/java/vip/mate/agent/graph/node/FinalAnswerNode.java +++ b/mateclaw-server/src/main/java/vip/mate/agent/graph/node/FinalAnswerNode.java @@ -40,12 +40,25 @@ public class FinalAnswerNode implements NodeAction { */ private final GeneratedFileCache generatedFileCache; + /** + * Kill-switch for the deterministic Markdown cleanup applied to the answer + * body. {@code true} (default) runs {@link MarkdownNormalizer}; set to + * {@code false} to surface model output verbatim if a normalization edge + * case ever mangles a legitimate answer in production. + */ + private final boolean markdownNormalizeEnabled; + public FinalAnswerNode() { - this(null); + this(null, true); } public FinalAnswerNode(GeneratedFileCache generatedFileCache) { + this(generatedFileCache, true); + } + + public FinalAnswerNode(GeneratedFileCache generatedFileCache, boolean markdownNormalizeEnabled) { this.generatedFileCache = generatedFileCache; + this.markdownNormalizeEnabled = markdownNormalizeEnabled; } @Override @@ -176,8 +189,11 @@ public class FinalAnswerNode implements NodeAction { // unaligned table pipes) that prompt rules fail to prevent; this fixes the // mechanical defects before the answer is persisted / sent to channels. // Verbatim tool output (RETURN_DIRECT) and approval-wait paths return early - // above and are intentionally left untouched. - finalAnswer = MarkdownNormalizer.normalize(finalAnswer); + // above and are intentionally left untouched. Gated so operators can turn + // the rewrite off (mate.agent.markdown-normalize-enabled=false). + if (markdownNormalizeEnabled) { + finalAnswer = MarkdownNormalizer.normalize(finalAnswer); + } // Build the event list. Always carries the finish_reason event so // downstream consumers (memory gate, channel accumulator, message diff --git a/mateclaw-server/src/main/resources/application.yml b/mateclaw-server/src/main/resources/application.yml index 9c8b6255..d064b00c 100644 --- a/mateclaw-server/src/main/resources/application.yml +++ b/mateclaw-server/src/main/resources/application.yml @@ -222,6 +222,10 @@ mateclaw: # MateClaw Agent 配置 mate: agent: + # Deterministic Markdown cleanup of the final answer (heading spaces, glued + # ---, table pipe alignment) before persistence / channel delivery. Set to + # false to pass model output through verbatim. + markdown-normalize-enabled: true graph: observation: # 与 GraphObservationProperties.java 默认值对齐,参考 openclaw token-budget 设计