mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-15 03:55:09 +08:00
fix(goal): emit goal_evaluated on every GoalEvaluationNode skip path
This commit is contained in:
parent
81915ccfae
commit
7d7ea99747
@ -90,6 +90,13 @@ public class GoalEvaluationNode implements NodeAction {
|
|||||||
return Map.of();
|
return Map.of();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Every skip path below emits a goal_evaluated event with a reason
|
||||||
|
// so the frontend can flip the breathing-halo state back off. The
|
||||||
|
// chat composable's `message_complete` handler optimistically sets
|
||||||
|
// evaluating=true; without a balancing event the ring would stay
|
||||||
|
// in that state forever after e.g. a max-iterations turn.
|
||||||
|
Long goalIdForEvents = (goalOpt.get() instanceof GoalEntity ge) ? ge.getId() : null;
|
||||||
|
|
||||||
// ReAct path: FinalAnswerNode wrote a canonical finishReason that
|
// ReAct path: FinalAnswerNode wrote a canonical finishReason that
|
||||||
// determines whether this turn counts. Plan-Execute usually doesn't
|
// determines whether this turn counts. Plan-Execute usually doesn't
|
||||||
// set finishReason on the happy path, so we only enforce these
|
// set finishReason on the happy path, so we only enforce these
|
||||||
@ -105,12 +112,14 @@ public class GoalEvaluationNode implements NodeAction {
|
|||||||
log.debug("[GoalEvaluationNode] skipping evaluation (REACT finishReason={})", fr);
|
log.debug("[GoalEvaluationNode] skipping evaluation (REACT finishReason={})", fr);
|
||||||
return MateClawStateAccessor.output()
|
return MateClawStateAccessor.output()
|
||||||
.goalEvaluatedThisRun(true)
|
.goalEvaluatedThisRun(true)
|
||||||
|
.events(List.of(skippedEvent(goalIdForEvents, "react_finish_reason:" + fr)))
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (accessor.awaitingApproval()) {
|
if (accessor.awaitingApproval()) {
|
||||||
return MateClawStateAccessor.output()
|
return MateClawStateAccessor.output()
|
||||||
.goalEvaluatedThisRun(true)
|
.goalEvaluatedThisRun(true)
|
||||||
|
.events(List.of(skippedEvent(goalIdForEvents, "awaiting_approval")))
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,6 +128,7 @@ public class GoalEvaluationNode implements NodeAction {
|
|||||||
log.warn("[GoalEvaluationNode] ACTIVE_GOAL is not a GoalEntity: {}", goalObj.getClass());
|
log.warn("[GoalEvaluationNode] ACTIVE_GOAL is not a GoalEntity: {}", goalObj.getClass());
|
||||||
return MateClawStateAccessor.output()
|
return MateClawStateAccessor.output()
|
||||||
.goalEvaluatedThisRun(true)
|
.goalEvaluatedThisRun(true)
|
||||||
|
.events(List.of(skippedEvent(null, "non_goal_entity")))
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,6 +137,7 @@ public class GoalEvaluationNode implements NodeAction {
|
|||||||
log.warn("[GoalEvaluationNode] terminalAnswer empty (flavor={}); skipping evaluation", flavor);
|
log.warn("[GoalEvaluationNode] terminalAnswer empty (flavor={}); skipping evaluation", flavor);
|
||||||
return MateClawStateAccessor.output()
|
return MateClawStateAccessor.output()
|
||||||
.goalEvaluatedThisRun(true)
|
.goalEvaluatedThisRun(true)
|
||||||
|
.events(List.of(skippedEvent(goal.getId(), "empty_terminal_answer")))
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,6 +174,7 @@ public class GoalEvaluationNode implements NodeAction {
|
|||||||
return MateClawStateAccessor.output()
|
return MateClawStateAccessor.output()
|
||||||
.goalEvaluationResult(GoalEvaluationResult.fallback("node_exception").toMap())
|
.goalEvaluationResult(GoalEvaluationResult.fallback("node_exception").toMap())
|
||||||
.goalEvaluatedThisRun(true)
|
.goalEvaluatedThisRun(true)
|
||||||
|
.events(List.of(skippedEvent(goal.getId(), "evaluator_or_persist_failed")))
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -203,6 +215,7 @@ public class GoalEvaluationNode implements NodeAction {
|
|||||||
return MateClawStateAccessor.output()
|
return MateClawStateAccessor.output()
|
||||||
.goalEvaluationResult(result.toMap())
|
.goalEvaluationResult(result.toMap())
|
||||||
.goalEvaluatedThisRun(true)
|
.goalEvaluatedThisRun(true)
|
||||||
|
.events(List.of(skippedEvent(refreshed.getId(), "terminal_write_failed")))
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -278,4 +291,18 @@ public class GoalEvaluationNode implements NodeAction {
|
|||||||
private static GraphEventPublisher.GraphEvent goalEvent(String type, Map<String, Object> data) {
|
private static GraphEventPublisher.GraphEvent goalEvent(String type, Map<String, Object> data) {
|
||||||
return new GraphEventPublisher.GraphEvent(type, Map.copyOf(data), System.currentTimeMillis());
|
return new GraphEventPublisher.GraphEvent(type, Map.copyOf(data), System.currentTimeMillis());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a goal_evaluated event for skip paths so the frontend can
|
||||||
|
* unconditionally flip its "evaluating" flag off after every turn that
|
||||||
|
* has an active goal — even when the evaluator never ran. The reason
|
||||||
|
* field lets us tell apart "normal continue" from "skipped because of
|
||||||
|
* max iterations" in logs / future telemetry without ambiguity.
|
||||||
|
*/
|
||||||
|
private static GraphEventPublisher.GraphEvent skippedEvent(Long goalId, String reason) {
|
||||||
|
return goalEvent("goal_evaluated", Map.of(
|
||||||
|
"goalId", goalId == null ? "" : String.valueOf(goalId),
|
||||||
|
"skipped", true,
|
||||||
|
"reason", reason == null ? "" : reason));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user