Skip to content

Commit f07e266

Browse files
Structure output to enhance the result of data collection (alibaba#1672)
* feat(controller): refactor saveToVersionHistory method to handle plan ID extraction and JSON parsing feat(model): add autogenerated ID field to PlanTemplate entity feat(interface): add getPlanId and setPlanId methods to PlanInterface fix(yml): enable H2 console for easier database access * 修复前端bug * build ui * javaformat --------- Co-authored-by: rainerWJY <answeropensource@alibabacloud.com>
1 parent 0635fdd commit f07e266

File tree

17 files changed

+150
-48
lines changed

17 files changed

+150
-48
lines changed

spring-ai-alibaba-jmanus/src/main/java/com/alibaba/cloud/ai/example/manus/planning/controller/PlanTemplateController.java

Lines changed: 88 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ public ResponseEntity<Map<String, Object>> generatePlan(@RequestBody Map<String,
140140
}
141141

142142
// Save to version history
143-
PlanTemplateService.VersionSaveResult saveResult = saveToVersionHistory(planTemplateId, planJson);
143+
PlanTemplateService.VersionSaveResult saveResult = saveToVersionHistory(planJson);
144144

145145
// Return plan data
146146
Map<String, Object> response = new HashMap<>();
@@ -288,32 +288,71 @@ private ResponseEntity<Map<String, Object>> executePlanByTemplateIdInternal(Stri
288288

289289
/**
290290
* Save version history
291-
* @param planId Plan ID
292291
* @param planJson Plan JSON data
293292
* @return Save result
294293
*/
295-
private PlanTemplateService.VersionSaveResult saveToVersionHistory(String planId, String planJson) {
296-
// Extract title from JSON
297-
String title = planTemplateService.extractTitleFromPlan(planJson);
294+
private PlanTemplateService.VersionSaveResult saveToVersionHistory(String planJson) {
295+
try {
296+
// Parse JSON to extract planTemplateId and title
297+
ObjectMapper mapper = new ObjectMapper();
298+
PlanInterface planData = mapper.readValue(planJson, PlanInterface.class);
298299

299-
// Check if the plan exists
300-
PlanTemplate template = planTemplateService.getPlanTemplate(planId);
301-
if (template == null) {
302-
// If it doesn't exist, create a new plan
303-
planTemplateService.savePlanTemplate(planId, title, "User request to generate plan: " + planId, planJson);
304-
logger.info("New plan created: {}", planId);
305-
return new PlanTemplateService.VersionSaveResult(true, false, "New plan created", 0);
306-
}
307-
else {
308-
// If it exists, save a new version
309-
PlanTemplateService.VersionSaveResult result = planTemplateService.saveToVersionHistory(planId, planJson);
310-
if (result.isSaved()) {
311-
logger.info("New version of plan {} saved", planId, result.getVersionIndex());
300+
String planTemplateId = planData.getRootPlanId();
301+
if (planTemplateId == null || planTemplateId.trim().isEmpty()) {
302+
planTemplateId = planData.getCurrentPlanId();
303+
}
304+
305+
// Check if planTemplateId is empty or starts with "new-", then generate a new
306+
// one
307+
if (planTemplateId == null || planTemplateId.trim().isEmpty() || planTemplateId.startsWith("new-")) {
308+
String newPlanTemplateId = planIdDispatcher.generatePlanTemplateId();
309+
logger.info(
310+
"Original planTemplateId '{}' is empty or starts with 'new-', generated new planTemplateId: {}",
311+
planTemplateId, newPlanTemplateId);
312+
313+
// Update the plan object with new ID
314+
planData.setCurrentPlanId(newPlanTemplateId);
315+
planData.setRootPlanId(newPlanTemplateId);
316+
317+
// Re-serialize the updated plan object to JSON
318+
planJson = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(planData);
319+
planTemplateId = newPlanTemplateId;
320+
}
321+
322+
String title = planData.getTitle();
323+
324+
if (planTemplateId == null || planTemplateId.trim().isEmpty()) {
325+
throw new IllegalArgumentException("Plan ID cannot be found in JSON");
326+
}
327+
if (title == null || title.trim().isEmpty()) {
328+
title = "Untitled Plan";
329+
}
330+
331+
// Check if the plan exists
332+
PlanTemplate template = planTemplateService.getPlanTemplate(planTemplateId);
333+
if (template == null) {
334+
// If it doesn't exist, create a new plan
335+
planTemplateService.savePlanTemplate(planTemplateId, title,
336+
"User request to generate plan: " + planTemplateId, planJson);
337+
logger.info("New plan created: {}", planTemplateId);
338+
return new PlanTemplateService.VersionSaveResult(true, false, "New plan created", 0);
312339
}
313340
else {
314-
logger.info("Plan {} is the same, no new version saved", planId);
341+
// If it exists, save a new version
342+
PlanTemplateService.VersionSaveResult result = planTemplateService.saveToVersionHistory(planTemplateId,
343+
planJson);
344+
if (result.isSaved()) {
345+
logger.info("New version of plan {} saved", planTemplateId, result.getVersionIndex());
346+
}
347+
else {
348+
logger.info("Plan {} is the same, no new version saved", planTemplateId);
349+
}
350+
return result;
315351
}
316-
return result;
352+
}
353+
catch (Exception e) {
354+
logger.error("Failed to parse plan JSON", e);
355+
throw new RuntimeException("Failed to save version history: " + e.getMessage());
317356
}
318357
}
319358

@@ -324,20 +363,42 @@ private PlanTemplateService.VersionSaveResult saveToVersionHistory(String planId
324363
*/
325364
@PostMapping("/save")
326365
public ResponseEntity<Map<String, Object>> savePlan(@RequestBody Map<String, String> request) {
327-
String planId = request.get("planId");
328366
String planJson = request.get("planJson");
329367

330-
if (planId == null || planId.trim().isEmpty()) {
331-
return ResponseEntity.badRequest().body(Map.of("error", "Plan ID cannot be empty"));
332-
}
333-
334368
if (planJson == null || planJson.trim().isEmpty()) {
335369
return ResponseEntity.badRequest().body(Map.of("error", "Plan data cannot be empty"));
336370
}
337371

338372
try {
373+
// Parse JSON to get planId
374+
ObjectMapper mapper = new ObjectMapper();
375+
PlanInterface planData = mapper.readValue(planJson, PlanInterface.class);
376+
String planId = planData.getCurrentPlanId();
377+
if (planId == null) {
378+
planId = planData.getRootPlanId();
379+
}
380+
381+
// Check if planId is empty or starts with "new-", then generate a new one
382+
if (planId == null || planId.trim().isEmpty() || planId.startsWith("new-")) {
383+
String newPlanId = planIdDispatcher.generatePlanTemplateId();
384+
logger.info("Original planId '{}' is empty or starts with 'new-', generated new planId: {}", planId,
385+
newPlanId);
386+
387+
// Update the plan object with new ID
388+
planData.setCurrentPlanId(newPlanId);
389+
planData.setRootPlanId(newPlanId);
390+
391+
// Re-serialize the updated plan object to JSON
392+
planJson = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(planData);
393+
planId = newPlanId;
394+
}
395+
396+
if (planId == null || planId.trim().isEmpty()) {
397+
return ResponseEntity.badRequest().body(Map.of("error", "Plan ID cannot be found in JSON"));
398+
}
399+
339400
// Save to version history
340-
PlanTemplateService.VersionSaveResult saveResult = saveToVersionHistory(planId, planJson);
401+
PlanTemplateService.VersionSaveResult saveResult = saveToVersionHistory(planJson);
341402

342403
// Calculate version count
343404
List<String> versions = planTemplateService.getPlanVersions(planId);
@@ -543,7 +604,7 @@ public ResponseEntity<Map<String, Object>> updatePlanTemplate(@RequestBody Map<S
543604
}
544605

545606
// Save to version history
546-
PlanTemplateService.VersionSaveResult saveResult = saveToVersionHistory(planId, planJson);
607+
PlanTemplateService.VersionSaveResult saveResult = saveToVersionHistory(planJson);
547608

548609
// Return plan data
549610
Map<String, Object> response = new HashMap<>();

spring-ai-alibaba-jmanus/src/main/java/com/alibaba/cloud/ai/example/manus/planning/model/po/PlanTemplate.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919

2020
import jakarta.persistence.Column;
2121
import jakarta.persistence.Entity;
22+
import jakarta.persistence.GeneratedValue;
23+
import jakarta.persistence.GenerationType;
2224
import jakarta.persistence.Id;
2325
import jakarta.persistence.Table;
2426

@@ -31,7 +33,11 @@
3133
public class PlanTemplate {
3234

3335
@Id
34-
@Column(name = "plan_template_id", length = 50)
36+
@GeneratedValue(strategy = GenerationType.IDENTITY)
37+
@Column(name = "id")
38+
private Long id;
39+
40+
@Column(name = "plan_template_id", length = 50, unique = true, nullable = false)
3541
private String planTemplateId;
3642

3743
@Column(name = "title", length = 255)
@@ -59,6 +65,14 @@ public PlanTemplate(String planTemplateId, String title, String userRequest) {
5965
}
6066

6167
// Getters and setters
68+
public Long getId() {
69+
return id;
70+
}
71+
72+
public void setId(Long id) {
73+
this.id = id;
74+
}
75+
6276
public String getPlanTemplateId() {
6377
return planTemplateId;
6478
}

spring-ai-alibaba-jmanus/src/main/java/com/alibaba/cloud/ai/example/manus/planning/model/vo/AbstractExecutionPlan.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,6 @@ public void setPlanId(String planId) {
9191
this.rootPlanId = planId;
9292
}
9393

94-
@JsonIgnore
9594
public String getPlanId() {
9695
return rootPlanId;
9796
}

spring-ai-alibaba-jmanus/src/main/java/com/alibaba/cloud/ai/example/manus/planning/model/vo/PlanInterface.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,10 @@ public interface PlanInterface {
162162
*/
163163
String getPlanExecutionStateStringFormat(boolean onlyCompletedAndFirstInProgress);
164164

165+
public void setPlanId(String planId);
166+
167+
public String getPlanId();
168+
165169
/**
166170
* 更新所有步骤的索引,从0开始递增。 Update the indices of all steps, starting from 0.
167171
*/

spring-ai-alibaba-jmanus/src/main/java/com/alibaba/cloud/ai/example/manus/tool/PlanningTool.java

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ public static class PlanningInput {
4848

4949
private List<String> steps;
5050

51+
private String terminateColumns;
52+
5153
public PlanningInput() {
5254
}
5355

@@ -56,6 +58,7 @@ public PlanningInput(String command, String planId, String title, List<String> s
5658
this.planId = planId;
5759
this.title = title;
5860
this.steps = steps;
61+
this.terminateColumns = null;
5962
}
6063

6164
public String getCommand() {
@@ -90,6 +93,14 @@ public void setSteps(List<String> steps) {
9093
this.steps = steps;
9194
}
9295

96+
public String getTerminateColumns() {
97+
return terminateColumns;
98+
}
99+
100+
public void setTerminateColumns(String terminateColumns) {
101+
this.terminateColumns = terminateColumns;
102+
}
103+
93104
}
94105

95106
public String getCurrentPlanId() {
@@ -122,6 +133,11 @@ public ExecutionPlan getCurrentPlan() {
122133
"type": "string"
123134
}
124135
}
136+
,
137+
"terminateColumns": {
138+
"description": "Terminate structure output columns for all steps (optional, will be applied to every step)",
139+
"type": "string"
140+
}
125141
},
126142
"required": [
127143
"command",
@@ -158,7 +174,7 @@ public ToolExecuteResult run(PlanningInput input) {
158174
List<String> steps = input.getSteps();
159175

160176
return switch (command) {
161-
case "create" -> createPlan(planId, title, steps);
177+
case "create" -> createPlan(planId, title, steps, input.getTerminateColumns());
162178
// case "update" -> updatePlan(planId, title, steps);
163179
// case "get" -> getPlan(planId);
164180
// case "mark_step" -> markStep(planId, stepIndex, stepStatus, stepNotes);
@@ -182,18 +198,23 @@ private ExecutionStep createExecutionStep(String step, int index) {
182198
return executionStep;
183199
}
184200

185-
public ToolExecuteResult createPlan(String planId, String title, List<String> steps) {
201+
public ToolExecuteResult createPlan(String planId, String title, List<String> steps, String terminateColumns) {
186202
if (title == null || steps == null || steps.isEmpty()) {
187203
log.info("Missing required parameters when creating plan: planId={}, title={}, steps={}", planId, title,
188204
steps);
189205
return new ToolExecuteResult("Required parameters missing");
190206
}
191207

192208
ExecutionPlan plan = new ExecutionPlan(planId, planId, title);
193-
// Use the new createExecutionStep method to create and add steps
209+
194210
int index = 0;
195211
for (String step : steps) {
196-
plan.addStep(createExecutionStep(step, index++));
212+
ExecutionStep execStep = createExecutionStep(step, index);
213+
if (terminateColumns != null && !terminateColumns.isEmpty()) {
214+
execStep.setTerminateColumns(terminateColumns);
215+
}
216+
plan.addStep(execStep);
217+
index++;
197218
}
198219

199220
this.currentPlan = plan;

spring-ai-alibaba-jmanus/src/main/resources/application-h2.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ spring:
66
password: $FSD#@!@#!#$!12341234
77
h2:
88
console:
9-
enabled: false
9+
enabled: true
1010
path: /h2-console
1111
settings:
1212
web-allow-others: true
Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

spring-ai-alibaba-jmanus/src/main/resources/static/ui/assets/index-DcaLeeQk.css renamed to spring-ai-alibaba-jmanus/src/main/resources/static/ui/assets/index-CKOWj43e.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

spring-ai-alibaba-jmanus/src/main/resources/static/ui/assets/index-Br5u3Y3R.js renamed to spring-ai-alibaba-jmanus/src/main/resources/static/ui/assets/index-CbxL5R-z.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

spring-ai-alibaba-jmanus/src/main/resources/static/ui/assets/index-b8HjBFKN.js renamed to spring-ai-alibaba-jmanus/src/main/resources/static/ui/assets/index-Ck66gNC3.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)