diff --git a/mateclaw-server/src/main/java/vip/mate/goal/service/GoalEvaluationService.java b/mateclaw-server/src/main/java/vip/mate/goal/service/GoalEvaluationService.java index 1252d477..a75167ef 100644 --- a/mateclaw-server/src/main/java/vip/mate/goal/service/GoalEvaluationService.java +++ b/mateclaw-server/src/main/java/vip/mate/goal/service/GoalEvaluationService.java @@ -96,6 +96,7 @@ public class GoalEvaluationService implements Evaluator { // choose the last value of an ambiguous model-produced JSON key. ObjectMapper evaluatorJson = objectMapper.copy() .disable(com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .enable(com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_TRAILING_TOKENS) .enable(com.fasterxml.jackson.core.JsonParser.Feature.STRICT_DUPLICATE_DETECTION); this.draftConverter = new BeanOutputConverter<>(GoalCriteriaDraft.class, evaluatorJson); this.verdictConverter = new BeanOutputConverter<>(GoalChecklistVerdict.class, evaluatorJson); diff --git a/mateclaw-server/src/test/java/vip/mate/goal/service/GoalEvaluationServiceTest.java b/mateclaw-server/src/test/java/vip/mate/goal/service/GoalEvaluationServiceTest.java index 6c3bdd4c..0892e61d 100644 --- a/mateclaw-server/src/test/java/vip/mate/goal/service/GoalEvaluationServiceTest.java +++ b/mateclaw-server/src/test/java/vip/mate/goal/service/GoalEvaluationServiceTest.java @@ -299,6 +299,27 @@ class GoalEvaluationServiceTest { } } + @Test + void aSecondStructuredResultCannotBeIgnoredAfterACompletionVerdict() { + stubChatResponse("{\"criterionVerdicts\":[{\"id\":\"C1\",\"passed\":true,\"evidence\":\"ready\"}," + + "{\"id\":\"C2\",\"passed\":true,\"evidence\":\"ready\"}]} " + + "{\"criterionVerdicts\":[{\"id\":\"C1\",\"passed\":false,\"evidence\":\"not ready\"}]}"); + var result = svc.evaluate(goalWithCriteria(), List.of(), "finished"); + assertEquals(GoalEvaluationResult.DECISION_FALLBACK, result.decision()); + assertFalse(result.completed()); + assertEquals(1, result.llmCallsConsumed()); + assertTrue(result.criterionVerdicts().isEmpty()); + } + + @Test + void bootstrapCannotIgnoreTrailingContradictoryContent() { + stubChatResponse("{\"criteria\":[{\"text\":\"deliver report\"}]} {\"criteria\":[]}"); + var result = svc.evaluate(goal(), List.of(), "finished"); + assertEquals(GoalEvaluationResult.DECISION_FALLBACK, result.decision()); + assertEquals(1, result.llmCallsConsumed()); + assertNull(result.bootstrapCriteria()); + } + // ==================== Parser tolerance ==================== @Test