mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-16 20:34:39 +08:00
fix: derive goal progress from the current merged checklist
This commit is contained in:
parent
c024e2d9f0
commit
6a00c3869d
@ -473,8 +473,6 @@ public class GoalServiceImpl implements GoalService {
|
|||||||
// persistent pause/input boundary established while the call ran.
|
// persistent pause/input boundary established while the call ran.
|
||||||
if (result != null && (!Boolean.TRUE.equals(fresh.getPersistentExecution())
|
if (result != null && (!Boolean.TRUE.equals(fresh.getPersistentExecution())
|
||||||
|| fresh.getStatus() == GoalStatus.ACTIVE)) {
|
|| fresh.getStatus() == GoalStatus.ACTIVE)) {
|
||||||
w.set(GoalEntity::getCompletionScore, result.score())
|
|
||||||
.set(GoalEntity::getProgressSummary, result.gap());
|
|
||||||
// Persist the checklist by carrier: bootstrap writes the fresh
|
// Persist the checklist by carrier: bootstrap writes the fresh
|
||||||
// draft; verdict merges the per-criterion delta into the
|
// draft; verdict merges the per-criterion delta into the
|
||||||
// current list (re-read on the locked `fresh` to avoid races).
|
// current list (re-read on the locked `fresh` to avoid races).
|
||||||
@ -482,6 +480,21 @@ public class GoalServiceImpl implements GoalService {
|
|||||||
if (criteriaJson != null) {
|
if (criteriaJson != null) {
|
||||||
w.set(GoalEntity::getCriteria, criteriaJson);
|
w.set(GoalEntity::getCriteria, criteriaJson);
|
||||||
}
|
}
|
||||||
|
List<GoalCriterion> current = GoalCriteriaCodec.parse(
|
||||||
|
criteriaJson != null ? criteriaJson : fresh.getCriteria(), objectMapper);
|
||||||
|
if (!current.isEmpty() && !GoalEvaluationResult.DECISION_FALLBACK.equals(result.decision())) {
|
||||||
|
// The model may have seen fewer criteria. Derive the public
|
||||||
|
// projection from the same fresh checklist this CAS writes.
|
||||||
|
List<GoalCriterion> remaining = GoalCriteriaCodec.remaining(current);
|
||||||
|
double score = (double) (current.size() - remaining.size()) / current.size();
|
||||||
|
String gap = remaining.isEmpty() ? "" : "Still missing: " + remaining.stream()
|
||||||
|
.map(GoalCriterion::text).collect(java.util.stream.Collectors.joining("; "));
|
||||||
|
w.set(GoalEntity::getCompletionScore, score)
|
||||||
|
.set(GoalEntity::getProgressSummary, gap);
|
||||||
|
} else {
|
||||||
|
w.set(GoalEntity::getCompletionScore, result.score())
|
||||||
|
.set(GoalEntity::getProgressSummary, result.gap());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
bumpVersionAndTime(w);
|
bumpVersionAndTime(w);
|
||||||
return w;
|
return w;
|
||||||
@ -489,9 +502,11 @@ public class GoalServiceImpl implements GoalService {
|
|||||||
|
|
||||||
Map<String, Object> detail = new LinkedHashMap<>();
|
Map<String, Object> detail = new LinkedHashMap<>();
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
detail.put("completionScore", result.score());
|
detail.put("completionScore", g.getCompletionScore());
|
||||||
detail.put("gap", result.gap());
|
detail.put("gap", g.getProgressSummary());
|
||||||
detail.put("decision", result.decision());
|
detail.put("decision", result.decision());
|
||||||
|
detail.put("evaluatorScore", result.score());
|
||||||
|
detail.put("evaluatorGap", result.gap());
|
||||||
detail.put("evaluatorModel", result.evaluatorModel());
|
detail.put("evaluatorModel", result.evaluatorModel());
|
||||||
detail.put("latencyMs", result.latencyMs());
|
detail.put("latencyMs", result.latencyMs());
|
||||||
}
|
}
|
||||||
|
|||||||
@ -95,6 +95,30 @@ class GoalPersistenceIntegrationTest {
|
|||||||
assertEquals(2, saved.getAgentLlmCallsUsed());
|
assertEquals(2, saved.getAgentLlmCallsUsed());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void lateVerdictProjectsProgressFromCurrentChecklist() throws Exception {
|
||||||
|
GoalEntity created = goalService.create(req("late-verdict-current-progress", "prepare a report"), "alice");
|
||||||
|
goalService.appendCriterion(created.getId(), "write the report", "alice");
|
||||||
|
// The model evaluated only C1. A second condition commits before the result.
|
||||||
|
goalService.appendCriterion(created.getId(), "include an appendix", "alice");
|
||||||
|
var delayed = new vip.mate.goal.model.GoalEvaluationResult(1.0, "", "completed", true,
|
||||||
|
"fixture", 1, 0, java.util.List.of(
|
||||||
|
new vip.mate.goal.model.GoalChecklistVerdict.CriterionVerdict("C1", true, "report written")), null);
|
||||||
|
goalService.recordEvaluation(created.getId(), delayed, 2, 1);
|
||||||
|
GoalEntity saved = goalService.getById(created.getId());
|
||||||
|
assertEquals(0.5, saved.getCompletionScore());
|
||||||
|
var event = goalService.listEvents(created.getId(), 20).stream()
|
||||||
|
.filter(e -> "evaluated".equals(e.getEventType())).findFirst().orElseThrow();
|
||||||
|
var detail = new com.fasterxml.jackson.databind.ObjectMapper().readTree(event.getDetailJson());
|
||||||
|
assertEquals(0.5, detail.get("completionScore").asDouble());
|
||||||
|
assertEquals(1.0, detail.get("evaluatorScore").asDouble());
|
||||||
|
org.junit.jupiter.api.Assertions.assertTrue(detail.get("gap").asText().contains("include an appendix"));
|
||||||
|
org.junit.jupiter.api.Assertions.assertTrue(saved.getProgressSummary().contains("include an appendix"));
|
||||||
|
assertEquals(GoalStatus.ACTIVE, saved.getStatus());
|
||||||
|
assertEquals(1, saved.getEvalLlmCallsUsed());
|
||||||
|
assertThrows(MateClawException.class, () -> goalService.markEvaluatedCompleted(created.getId(), delayed));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("GoalStatus values persist as lowercase literals — load-bearing for uk_agent_goal_active_conv")
|
@DisplayName("GoalStatus values persist as lowercase literals — load-bearing for uk_agent_goal_active_conv")
|
||||||
void status_persistsAsLowercaseString() {
|
void status_persistsAsLowercaseString() {
|
||||||
|
|||||||
@ -665,6 +665,31 @@ class GoalServiceTest {
|
|||||||
assertFalse(setsProperty(writes.getAllValues().get(1), "criteria"), "retry must not replace newly established criteria");
|
assertFalse(setsProperty(writes.getAllValues().get(1), "criteria"), "retry must not replace newly established criteria");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void verdictProgressRecomputesAfterCasConflict() {
|
||||||
|
GoalEntity original = persisted(1L, GoalStatus.ACTIVE);
|
||||||
|
original.setCriteria("[{\"id\":\"C1\",\"text\":\"report\",\"passed\":false,\"evidence\":\"\"}]");
|
||||||
|
GoalEntity fresh = persisted(1L, GoalStatus.ACTIVE);
|
||||||
|
fresh.setVersion(1);
|
||||||
|
fresh.setCriteria("[{\"id\":\"C1\",\"text\":\"report\",\"passed\":false,\"evidence\":\"\"},"
|
||||||
|
+ "{\"id\":\"C2\",\"text\":\"appendix\",\"passed\":false,\"evidence\":\"\"}]");
|
||||||
|
when(goalMapper.selectById(1L)).thenReturn(original, original, fresh, fresh);
|
||||||
|
when(goalMapper.update(any(), any(LambdaUpdateWrapper.class))).thenReturn(0, 1);
|
||||||
|
var result = new GoalEvaluationResult(1.0, "", "completed", true, "fixture", 1, 0,
|
||||||
|
java.util.List.of(new vip.mate.goal.model.GoalChecklistVerdict.CriterionVerdict(
|
||||||
|
"C1", true, "report written")), null);
|
||||||
|
service.recordEvaluation(1L, result, 2, 1);
|
||||||
|
ArgumentCaptor<LambdaUpdateWrapper> writes = ArgumentCaptor.forClass(LambdaUpdateWrapper.class);
|
||||||
|
verify(goalMapper, times(2)).update(any(), writes.capture());
|
||||||
|
var first = writes.getAllValues().getFirst();
|
||||||
|
var retry = writes.getAllValues().getLast();
|
||||||
|
assertTrue(setsProperty(first, "completionScore"));
|
||||||
|
assertTrue(setsProperty(retry, "completionScore"));
|
||||||
|
assertTrue(first.getParamNameValuePairs().containsValue(1.0));
|
||||||
|
assertTrue(retry.getParamNameValuePairs().containsValue(0.5));
|
||||||
|
assertTrue(retry.getParamNameValuePairs().containsValue("Still missing: appendix"));
|
||||||
|
}
|
||||||
|
|
||||||
// ==================== criteria checklist ====================
|
// ==================== criteria checklist ====================
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user