mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-13 03:13:41 +08:00
feat(goal): persist checklist end-to-end + GoalResponse wire DTO
This commit is contained in:
parent
b65887f93d
commit
0fc8579a3e
@ -25,9 +25,8 @@ import java.util.Optional;
|
||||
/**
|
||||
* Sits between FinalAnswerNode (or PlanSummaryNode) and the graph END.
|
||||
*
|
||||
* <p>Per RFC 48 v3 §3.3, evaluation runs on a settled terminal answer so
|
||||
* upstream finishReason / evidence checks are already authoritative. The
|
||||
* node:
|
||||
* <p>Evaluation runs on a settled terminal answer so upstream finishReason /
|
||||
* evidence checks are already authoritative. The node:
|
||||
* <ol>
|
||||
* <li>Bails out for the "this turn shouldn't count" finishReasons
|
||||
* (evidence_insufficient, stopped, error_fallback, return_direct,
|
||||
@ -50,8 +49,8 @@ public class GoalEvaluationNode implements NodeAction {
|
||||
private final GoalFollowupService followupService;
|
||||
private final GoalService goalService;
|
||||
private final GoalProperties properties;
|
||||
private final ConversationWindowManager windowManager; // unused PR2, kept for PR5
|
||||
private final ConversationService conversationService; // unused PR2, kept for PR5
|
||||
private final ConversationWindowManager windowManager; // reserved for evaluator context windowing
|
||||
private final ConversationService conversationService; // reserved for evaluator context lookups
|
||||
private final GraphFlavor flavor;
|
||||
|
||||
public GoalEvaluationNode(GoalEvaluationService evaluationService,
|
||||
@ -72,7 +71,7 @@ public class GoalEvaluationNode implements NodeAction {
|
||||
|
||||
@Override
|
||||
public Map<String, Object> apply(OverAllState state) throws Exception {
|
||||
// Master kill switch — node stays inert until PR5 flips this.
|
||||
// Master kill switch — when disabled the node stays inert.
|
||||
if (!properties.isEnabled()) {
|
||||
return Map.of();
|
||||
}
|
||||
@ -187,29 +186,31 @@ public class GoalEvaluationNode implements NodeAction {
|
||||
// and abort the streamed answer the user already sees.
|
||||
try {
|
||||
if (result.completed() || result.score() >= 0.95) {
|
||||
goalService.markCompleted(refreshed.getId(), result);
|
||||
GoalEntity completed = goalService.markCompleted(refreshed.getId(), result);
|
||||
return MateClawStateAccessor.output()
|
||||
.goalEvaluationResult(result.toMap())
|
||||
.goalEvaluatedThisRun(true)
|
||||
.events(List.of(goalEvent("goal_completed", Map.of(
|
||||
"goalId", String.valueOf(refreshed.getId()),
|
||||
"score", result.score()))))
|
||||
"goalId", String.valueOf(completed.getId()),
|
||||
"score", result.score(),
|
||||
"goal", goalService.toResponse(completed)))))
|
||||
.build();
|
||||
}
|
||||
|
||||
if (goalService.isBudgetExhausted(refreshed)) {
|
||||
String reason = goalService.exhaustionReason(refreshed);
|
||||
goalService.markExhausted(refreshed.getId(), reason);
|
||||
GoalEntity exhausted = goalService.markExhausted(refreshed.getId(), reason);
|
||||
return MateClawStateAccessor.output()
|
||||
.goalEvaluationResult(result.toMap())
|
||||
.goalEvaluatedThisRun(true)
|
||||
.events(List.of(goalEvent("goal_exhausted", Map.of(
|
||||
"goalId", String.valueOf(refreshed.getId()),
|
||||
"turnsUsed", refreshed.getTurnsUsed(),
|
||||
"agentLlmCallsUsed", refreshed.getAgentLlmCallsUsed(),
|
||||
"evalLlmCallsUsed", refreshed.getEvalLlmCallsUsed(),
|
||||
"totalLlmCallsUsed", refreshed.totalLlmCallsUsed(),
|
||||
"reason", reason))))
|
||||
"goalId", String.valueOf(exhausted.getId()),
|
||||
"turnsUsed", exhausted.getTurnsUsed(),
|
||||
"agentLlmCallsUsed", exhausted.getAgentLlmCallsUsed(),
|
||||
"evalLlmCallsUsed", exhausted.getEvalLlmCallsUsed(),
|
||||
"totalLlmCallsUsed", exhausted.totalLlmCallsUsed(),
|
||||
"reason", reason,
|
||||
"goal", goalService.toResponse(exhausted)))))
|
||||
.build();
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
@ -270,7 +271,8 @@ public class GoalEvaluationNode implements NodeAction {
|
||||
.needsToolCall(false)
|
||||
.events(List.of(goalEvent("goal_followup", Map.of(
|
||||
"goalId", String.valueOf(refreshed.getId()),
|
||||
"prompt", followup.get()))));
|
||||
"prompt", followup.get(),
|
||||
"goal", goalService.toResponse(refreshed)))));
|
||||
|
||||
if (flavor == GraphFlavor.REACT) {
|
||||
// ReAct: append the followup as a fresh user message via the
|
||||
@ -309,7 +311,8 @@ public class GoalEvaluationNode implements NodeAction {
|
||||
.events(List.of(goalEvent("goal_evaluated", Map.of(
|
||||
"goalId", String.valueOf(refreshed.getId()),
|
||||
"score", result.score(),
|
||||
"gap", result.gap() == null ? "" : result.gap()))))
|
||||
"gap", result.gap() == null ? "" : result.gap(),
|
||||
"goal", goalService.toResponse(refreshed)))))
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
@ -18,6 +18,7 @@ import vip.mate.exception.MateClawException;
|
||||
import vip.mate.goal.model.GoalCreateRequest;
|
||||
import vip.mate.goal.model.GoalEntity;
|
||||
import vip.mate.goal.model.GoalEventEntity;
|
||||
import vip.mate.goal.model.GoalResponse;
|
||||
import vip.mate.goal.model.GoalUpdateRequest;
|
||||
import vip.mate.goal.service.GoalService;
|
||||
import vip.mate.workspace.conversation.ConversationService;
|
||||
@ -51,7 +52,7 @@ public class GoalController {
|
||||
|
||||
@Operation(summary = "Create a persistent goal for a conversation")
|
||||
@PostMapping
|
||||
public R<GoalEntity> create(@RequestBody GoalCreateRequest req, Authentication auth) {
|
||||
public R<GoalResponse> create(@RequestBody GoalCreateRequest req, Authentication auth) {
|
||||
String username = currentUsername(auth);
|
||||
requireOwner(req.getConversationId(), username);
|
||||
// Derive agentId/workspaceId from the conversation itself so the
|
||||
@ -71,22 +72,22 @@ public class GoalController {
|
||||
req.setAgentId(conv.getAgentId());
|
||||
req.setWorkspaceId(conv.getWorkspaceId() != null ? conv.getWorkspaceId() : 1L);
|
||||
GoalEntity g = goalService.create(req, username);
|
||||
return R.ok(g);
|
||||
return R.ok(goalService.toResponse(g));
|
||||
}
|
||||
|
||||
@Operation(summary = "Get the active goal bound to a conversation (or null)")
|
||||
@GetMapping("/by-conversation/{conversationId}")
|
||||
public R<GoalEntity> findActive(@PathVariable String conversationId, Authentication auth) {
|
||||
public R<GoalResponse> findActive(@PathVariable String conversationId, Authentication auth) {
|
||||
requireOwner(conversationId, currentUsername(auth));
|
||||
return R.ok(goalService.findActiveByConversation(conversationId));
|
||||
return R.ok(goalService.toResponse(goalService.findActiveByConversation(conversationId)));
|
||||
}
|
||||
|
||||
@Operation(summary = "Get goal detail by id")
|
||||
@GetMapping("/{id}")
|
||||
public R<GoalEntity> get(@PathVariable Long id, Authentication auth) {
|
||||
public R<GoalResponse> get(@PathVariable Long id, Authentication auth) {
|
||||
GoalEntity g = goalService.getById(id);
|
||||
requireOwner(g.getConversationId(), currentUsername(auth));
|
||||
return R.ok(g);
|
||||
return R.ok(goalService.toResponse(g));
|
||||
}
|
||||
|
||||
@Operation(summary = "Get the event timeline for a goal")
|
||||
@ -101,61 +102,61 @@ public class GoalController {
|
||||
|
||||
@Operation(summary = "List goals (optionally filtered by status)")
|
||||
@GetMapping
|
||||
public R<List<GoalEntity>> list(@RequestParam(required = false) String status,
|
||||
public R<List<GoalResponse>> list(@RequestParam(required = false) String status,
|
||||
@RequestParam(defaultValue = "50") int limit,
|
||||
Authentication auth) {
|
||||
// List is owner-scoped — only your own goals are visible.
|
||||
return R.ok(goalService.list(status, currentUsername(auth), limit));
|
||||
return R.ok(goalService.toResponseList(goalService.list(status, currentUsername(auth), limit)));
|
||||
}
|
||||
|
||||
@Operation(summary = "Sparse update of a non-terminal goal")
|
||||
@PatchMapping("/{id}")
|
||||
public R<GoalEntity> update(@PathVariable Long id,
|
||||
public R<GoalResponse> update(@PathVariable Long id,
|
||||
@RequestBody GoalUpdateRequest req,
|
||||
Authentication auth) {
|
||||
GoalEntity g = goalService.getById(id);
|
||||
String username = currentUsername(auth);
|
||||
requireOwner(g.getConversationId(), username);
|
||||
return R.ok(goalService.update(id, req, username));
|
||||
return R.ok(goalService.toResponse(goalService.update(id, req, username)));
|
||||
}
|
||||
|
||||
@Operation(summary = "Pause an active goal")
|
||||
@PostMapping("/{id}/pause")
|
||||
public R<GoalEntity> pause(@PathVariable Long id, Authentication auth) {
|
||||
public R<GoalResponse> pause(@PathVariable Long id, Authentication auth) {
|
||||
GoalEntity g = goalService.getById(id);
|
||||
String username = currentUsername(auth);
|
||||
requireOwner(g.getConversationId(), username);
|
||||
return R.ok(goalService.pause(id, username));
|
||||
return R.ok(goalService.toResponse(goalService.pause(id, username)));
|
||||
}
|
||||
|
||||
@Operation(summary = "Resume a paused goal")
|
||||
@PostMapping("/{id}/resume")
|
||||
public R<GoalEntity> resume(@PathVariable Long id, Authentication auth) {
|
||||
public R<GoalResponse> resume(@PathVariable Long id, Authentication auth) {
|
||||
GoalEntity g = goalService.getById(id);
|
||||
String username = currentUsername(auth);
|
||||
requireOwner(g.getConversationId(), username);
|
||||
return R.ok(goalService.resume(id, username));
|
||||
return R.ok(goalService.toResponse(goalService.resume(id, username)));
|
||||
}
|
||||
|
||||
@Operation(summary = "Abandon a goal (terminal)")
|
||||
@PostMapping("/{id}/abandon")
|
||||
public R<GoalEntity> abandon(@PathVariable Long id, Authentication auth) {
|
||||
public R<GoalResponse> abandon(@PathVariable Long id, Authentication auth) {
|
||||
GoalEntity g = goalService.getById(id);
|
||||
String username = currentUsername(auth);
|
||||
requireOwner(g.getConversationId(), username);
|
||||
return R.ok(goalService.abandon(id, username));
|
||||
return R.ok(goalService.toResponse(goalService.abandon(id, username)));
|
||||
}
|
||||
|
||||
@Operation(summary = "Append a sub-criterion to an active goal")
|
||||
@PostMapping("/{id}/criteria")
|
||||
public R<GoalEntity> addCriterion(@PathVariable Long id,
|
||||
public R<GoalResponse> addCriterion(@PathVariable Long id,
|
||||
@RequestBody Map<String, String> body,
|
||||
Authentication auth) {
|
||||
GoalEntity g = goalService.getById(id);
|
||||
String username = currentUsername(auth);
|
||||
requireOwner(g.getConversationId(), username);
|
||||
String criterion = body != null ? body.get("criterion") : null;
|
||||
return R.ok(goalService.appendCriterion(id, criterion, username));
|
||||
return R.ok(goalService.toResponse(goalService.appendCriterion(id, criterion, username)));
|
||||
}
|
||||
|
||||
// ==================== Helpers ====================
|
||||
|
||||
@ -0,0 +1,54 @@
|
||||
package vip.mate.goal.model;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Outward-facing shape of a goal. Identical to {@link GoalEntity} except the
|
||||
* checklist is the parsed {@code List<GoalCriterion>} array rather than the
|
||||
* raw JSON String stored in the column — so REST responses and SSE payloads
|
||||
* always carry {@code criteria} as an array, never a string.
|
||||
*
|
||||
* <p>{@code criteria} is never null on the wire: a missing / unparseable
|
||||
* column maps to an empty list.
|
||||
*/
|
||||
@Data
|
||||
public class GoalResponse {
|
||||
|
||||
private Long id;
|
||||
private String conversationId;
|
||||
private Long agentId;
|
||||
private Long workspaceId;
|
||||
private String createdBy;
|
||||
|
||||
private String title;
|
||||
private String description;
|
||||
private String exitCriteria;
|
||||
private String successCheckPrompt;
|
||||
|
||||
private GoalStatus status;
|
||||
|
||||
private Integer turnBudget;
|
||||
private Integer turnsUsed;
|
||||
private Integer llmCallBudget;
|
||||
private Integer agentLlmCallsUsed;
|
||||
private Integer evalLlmCallsUsed;
|
||||
private int totalLlmCallsUsed;
|
||||
|
||||
private String progressSummary;
|
||||
private Double completionScore;
|
||||
private LocalDateTime lastEvaluationAt;
|
||||
|
||||
private Boolean autoFollowupEnabled;
|
||||
private Integer followupCooldownSeconds;
|
||||
private LocalDateTime lastFollowupAt;
|
||||
|
||||
private Integer version;
|
||||
private LocalDateTime createTime;
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
/** Parsed checklist; empty (never null) when the column is null/unparseable. */
|
||||
private List<GoalCriterion> criteria;
|
||||
}
|
||||
@ -4,13 +4,14 @@ import vip.mate.goal.model.GoalCreateRequest;
|
||||
import vip.mate.goal.model.GoalEntity;
|
||||
import vip.mate.goal.model.GoalEvaluationResult;
|
||||
import vip.mate.goal.model.GoalEventEntity;
|
||||
import vip.mate.goal.model.GoalResponse;
|
||||
import vip.mate.goal.model.GoalUpdateRequest;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Persistent goal service — CRUD, status transitions, and bookkeeping
|
||||
* called from {@code GoalEvaluationNode} (PR2).
|
||||
* called from {@code GoalEvaluationNode}.
|
||||
*
|
||||
* <p>Concurrency model: writes use a per-row {@code WHERE version=?}
|
||||
* compare-and-set. On conflict the service retries up to 3 times before
|
||||
@ -78,4 +79,17 @@ public interface GoalService {
|
||||
|
||||
/** Append a sub-criterion without restarting the goal. */
|
||||
GoalEntity appendCriterion(Long id, String criterion, String username);
|
||||
|
||||
// ==================== Response mapping ====================
|
||||
|
||||
/**
|
||||
* Map an entity to its outward-facing form: {@code criteria} becomes a
|
||||
* parsed {@code List<GoalCriterion>} array (empty when null/unparseable),
|
||||
* never the raw JSON String. Use at every REST return point and SSE
|
||||
* payload so clients never see the string form.
|
||||
*/
|
||||
GoalResponse toResponse(GoalEntity entity);
|
||||
|
||||
/** Convenience: {@link #toResponse} over a list. */
|
||||
List<GoalResponse> toResponseList(List<GoalEntity> entities);
|
||||
}
|
||||
|
||||
@ -14,8 +14,10 @@ import vip.mate.audit.service.AuditEventService;
|
||||
import vip.mate.exception.MateClawException;
|
||||
import vip.mate.goal.config.GoalProperties;
|
||||
import vip.mate.goal.model.GoalCreateRequest;
|
||||
import vip.mate.goal.model.GoalCriteriaCodec;
|
||||
import vip.mate.goal.model.GoalCriterion;
|
||||
import vip.mate.goal.model.GoalEntity;
|
||||
import vip.mate.goal.model.GoalResponse;
|
||||
import vip.mate.goal.model.GoalEvaluationResult;
|
||||
import vip.mate.goal.model.GoalEventEntity;
|
||||
import vip.mate.goal.model.GoalEventType;
|
||||
@ -283,6 +285,18 @@ public class GoalServiceImpl implements GoalService {
|
||||
w.set(GoalEntity::getCompletionScore, result.score())
|
||||
.set(GoalEntity::getProgressSummary, result.gap());
|
||||
}
|
||||
// Snapshot the checklist as fully satisfied. Idempotent for the
|
||||
// auto path (recordEvaluation already merged all-passed); required
|
||||
// for manual completion, which has no preceding verdict.
|
||||
List<GoalCriterion> existing = GoalCriteriaCodec.parse(fresh.getCriteria(), objectMapper);
|
||||
if (!existing.isEmpty()) {
|
||||
List<GoalCriterion> allPassed = existing.stream()
|
||||
.map(c -> c.passed() ? c : new GoalCriterion(c.id(), c.text(), true,
|
||||
c.evidence() == null || c.evidence().isBlank()
|
||||
? "marked complete" : c.evidence()))
|
||||
.toList();
|
||||
w.set(GoalEntity::getCriteria, GoalCriteriaCodec.serialize(allPassed, objectMapper));
|
||||
}
|
||||
bumpVersionAndTime(w);
|
||||
return w;
|
||||
});
|
||||
@ -290,6 +304,7 @@ public class GoalServiceImpl implements GoalService {
|
||||
detail.put("finalScore", result != null ? result.score() : null);
|
||||
detail.put("agentLlmCallsUsed", g.getAgentLlmCallsUsed());
|
||||
detail.put("evalLlmCallsUsed", g.getEvalLlmCallsUsed());
|
||||
detail.put("criteria", GoalCriteriaCodec.parse(g.getCriteria(), objectMapper));
|
||||
writeEvent(id, GoalEventType.COMPLETED, null, detail);
|
||||
recordAudit("goal.completed", g, detail);
|
||||
|
||||
@ -349,7 +364,7 @@ public class GoalServiceImpl implements GoalService {
|
||||
int agentDelta = Math.max(0, agentLlmCallsDelta);
|
||||
int evalDelta = Math.max(0, evalLlmCallsDelta);
|
||||
|
||||
retryOptimistic(id, "recordEvaluation", fresh -> {
|
||||
GoalEntity g = retryOptimistic(id, "recordEvaluation", fresh -> {
|
||||
if (fresh.getStatus().isTerminal()) return null; // ignore late evaluations
|
||||
LambdaUpdateWrapper<GoalEntity> w = baseLockedUpdate(fresh)
|
||||
.setSql("turns_used = turns_used + 1")
|
||||
@ -359,6 +374,13 @@ public class GoalServiceImpl implements GoalService {
|
||||
if (result != null) {
|
||||
w.set(GoalEntity::getCompletionScore, result.score())
|
||||
.set(GoalEntity::getProgressSummary, result.gap());
|
||||
// Persist the checklist by carrier: bootstrap writes the fresh
|
||||
// draft; verdict merges the per-criterion delta into the
|
||||
// current list (re-read on the locked `fresh` to avoid races).
|
||||
String criteriaJson = nextCriteriaJson(fresh, result);
|
||||
if (criteriaJson != null) {
|
||||
w.set(GoalEntity::getCriteria, criteriaJson);
|
||||
}
|
||||
}
|
||||
bumpVersionAndTime(w);
|
||||
return w;
|
||||
@ -374,9 +396,33 @@ public class GoalServiceImpl implements GoalService {
|
||||
}
|
||||
detail.put("agentLlmCallsDelta", agentDelta);
|
||||
detail.put("evalLlmCallsDelta", evalDelta);
|
||||
// Full checklist (array) so the timeline / SSE consumer never sees the
|
||||
// raw String column or has to reconstruct from the per-round delta.
|
||||
detail.put("criteria", GoalCriteriaCodec.parse(g.getCriteria(), objectMapper));
|
||||
writeEvent(id, GoalEventType.EVALUATED, null, detail);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the next criteria JSON for a record-evaluation write, or
|
||||
* {@code null} when the result carries no checklist change. Bootstrap
|
||||
* results replace the list with the freshly derived draft; verdict
|
||||
* results merge their per-criterion delta into the locked-row list.
|
||||
*/
|
||||
private String nextCriteriaJson(GoalEntity fresh, GoalEvaluationResult result) {
|
||||
if (result.bootstrapCriteria() != null && !result.bootstrapCriteria().isEmpty()) {
|
||||
return GoalCriteriaCodec.serialize(result.bootstrapCriteria(), objectMapper);
|
||||
}
|
||||
if (result.criterionVerdicts() != null && !result.criterionVerdicts().isEmpty()) {
|
||||
List<GoalCriterion> existing = GoalCriteriaCodec.parse(fresh.getCriteria(), objectMapper);
|
||||
if (existing.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return GoalCriteriaCodec.serialize(
|
||||
GoalCriteriaCodec.merge(existing, result.criterionVerdicts()), objectMapper);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBudgetExhausted(GoalEntity goal) {
|
||||
int turns = goal.getTurnsUsed() != null ? goal.getTurnsUsed() : 0;
|
||||
@ -451,31 +497,62 @@ public class GoalServiceImpl implements GoalService {
|
||||
if (raw == null || raw.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
List<GoalCriterion> out = new java.util.ArrayList<>(raw.size());
|
||||
int n = 1;
|
||||
List<GoalCriterion> kept = new java.util.ArrayList<>(raw.size());
|
||||
for (GoalCriterion c : raw) {
|
||||
if (c == null || c.text() == null || c.text().isBlank()) {
|
||||
continue;
|
||||
if (c != null && c.text() != null && !c.text().isBlank()) {
|
||||
kept.add(new GoalCriterion("", c.text().trim(), false, ""));
|
||||
}
|
||||
out.add(new GoalCriterion("C" + n, c.text().trim(), false, ""));
|
||||
n++;
|
||||
}
|
||||
return out.isEmpty() ? null : out;
|
||||
return kept.isEmpty() ? null : GoalCriteriaCodec.reindex(kept);
|
||||
}
|
||||
|
||||
/** Serialize a checklist to JSON text, or {@code null} for a null list. */
|
||||
private String serializeCriteria(List<GoalCriterion> criteria) {
|
||||
if (criteria == null) {
|
||||
return GoalCriteriaCodec.serialize(criteria, objectMapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GoalResponse toResponse(GoalEntity e) {
|
||||
if (e == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return objectMapper.writeValueAsString(criteria);
|
||||
} catch (JsonProcessingException e) {
|
||||
// Should never happen for a plain record list; fail soft to NULL
|
||||
// (bootstrap path) rather than aborting goal creation.
|
||||
log.warn("[Goal] failed to serialize criteria, storing null: {}", e.getMessage());
|
||||
return null;
|
||||
GoalResponse r = new GoalResponse();
|
||||
r.setId(e.getId());
|
||||
r.setConversationId(e.getConversationId());
|
||||
r.setAgentId(e.getAgentId());
|
||||
r.setWorkspaceId(e.getWorkspaceId());
|
||||
r.setCreatedBy(e.getCreatedBy());
|
||||
r.setTitle(e.getTitle());
|
||||
r.setDescription(e.getDescription());
|
||||
r.setExitCriteria(e.getExitCriteria());
|
||||
r.setSuccessCheckPrompt(e.getSuccessCheckPrompt());
|
||||
r.setStatus(e.getStatus());
|
||||
r.setTurnBudget(e.getTurnBudget());
|
||||
r.setTurnsUsed(e.getTurnsUsed());
|
||||
r.setLlmCallBudget(e.getLlmCallBudget());
|
||||
r.setAgentLlmCallsUsed(e.getAgentLlmCallsUsed());
|
||||
r.setEvalLlmCallsUsed(e.getEvalLlmCallsUsed());
|
||||
r.setTotalLlmCallsUsed(e.totalLlmCallsUsed());
|
||||
r.setProgressSummary(e.getProgressSummary());
|
||||
r.setCompletionScore(e.getCompletionScore());
|
||||
r.setLastEvaluationAt(e.getLastEvaluationAt());
|
||||
r.setAutoFollowupEnabled(e.getAutoFollowupEnabled());
|
||||
r.setFollowupCooldownSeconds(e.getFollowupCooldownSeconds());
|
||||
r.setLastFollowupAt(e.getLastFollowupAt());
|
||||
r.setVersion(e.getVersion());
|
||||
r.setCreateTime(e.getCreateTime());
|
||||
r.setUpdateTime(e.getUpdateTime());
|
||||
// Always an array; empty when the column is null/unparseable.
|
||||
r.setCriteria(GoalCriteriaCodec.parse(e.getCriteria(), objectMapper));
|
||||
return r;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<GoalResponse> toResponseList(List<GoalEntity> entities) {
|
||||
if (entities == null) {
|
||||
return List.of();
|
||||
}
|
||||
return entities.stream().map(this::toResponse).toList();
|
||||
}
|
||||
|
||||
private void validateCreate(GoalCreateRequest req) {
|
||||
|
||||
@ -246,7 +246,7 @@ public class GoalManagementTool {
|
||||
streamTracker.broadcastObject(conversationId, eventName, Map.of(
|
||||
"goalId", String.valueOf(goal.getId()),
|
||||
"conversationId", conversationId,
|
||||
"goal", goal));
|
||||
"goal", goalService.toResponse(goal)));
|
||||
} catch (Exception e) {
|
||||
log.debug("[GoalManagementTool] broadcast {} failed: {}", eventName, e.getMessage());
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user