flzxsqc-demo 1.4.21 → 1.4.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent-backend/pom.xml +15 -0
- package/agent-backend/src/main/java/com/example/agent/application/harness/requirements/SkillQuestionRequirementService.java +77 -7
- package/agent-backend/src/main/java/com/example/agent/application/service/AgentWaitingFlowService.java +46 -0
- package/agent-backend/src/main/java/com/example/agent/application/service/report/MarkdownReportDocxConverter.java +713 -0
- package/agent-backend/src/main/java/com/example/agent/application/service/report/ReportDownloadService.java +109 -0
- package/agent-backend/src/main/java/com/example/agent/infrastructure/llm/DummyLlmClient.java +55 -12
- package/agent-backend/src/main/java/com/example/agent/interfaces/rest/AgentApiExceptionHandler.java +16 -0
- package/agent-backend/src/main/java/com/example/agent/interfaces/rest/AgentSessionController.java +20 -0
- package/agent-backend/src/main/resources/application.yml +2 -0
- package/agent-backend/src/test/java/com/example/agent/application/harness/requirements/SkillQuestionRequirementServiceTest.java +22 -0
- package/agent-backend/src/test/java/com/example/agent/application/service/AgentTurnRunnerIntegrationTest.java +2 -1
- package/agent-backend/src/test/java/com/example/agent/application/service/AgentWaitingFlowServiceTest.java +69 -0
- package/agent-backend/src/test/java/com/example/agent/application/service/report/MarkdownReportDocxConverterTest.java +119 -0
- package/agent-backend/src/test/java/com/example/agent/application/service/report/ReportDownloadServiceTest.java +74 -0
- package/agent-backend/src/test/java/com/example/agent/infrastructure/llm/DummyLlmClientTest.java +4 -3
- package/agent-backend/src/test/java/com/example/agent/interfaces/rest/AgentSessionReportDownloadControllerTest.java +93 -0
- package/agent-front-react/doc/analysis-progress-bubble-company-migration.md +394 -0
- package/agent-front-react/src/api/client.ts +35 -0
- package/agent-front-react/src/pages/AnalysisTaskPage.tsx +35 -7
- package/agent-front-react/src/pages/analysis-task/ChatComposer.tsx +13 -0
- package/agent-front-react/src/pages/analysis-task/TaskConversationTab.tsx +50 -20
- package/agent-front-react/src/pages/analysis-task/TaskReportTab.tsx +12 -0
- package/agent-front-react/src/pages/analysis-task/analysisTaskUtils.tsx +0 -22
- package/agent-front-react/src/pages/analysis-task/useAnalysisProgress.ts +275 -0
- package/agent-front-react/src/styles.css +80 -1
- package/package.json +1 -1
package/agent-backend/pom.xml
CHANGED
|
@@ -71,6 +71,21 @@
|
|
|
71
71
|
<groupId>com.fasterxml.jackson.dataformat</groupId>
|
|
72
72
|
<artifactId>jackson-dataformat-yaml</artifactId>
|
|
73
73
|
</dependency>
|
|
74
|
+
<dependency>
|
|
75
|
+
<groupId>org.commonmark</groupId>
|
|
76
|
+
<artifactId>commonmark</artifactId>
|
|
77
|
+
<version>0.21.0</version>
|
|
78
|
+
</dependency>
|
|
79
|
+
<dependency>
|
|
80
|
+
<groupId>org.commonmark</groupId>
|
|
81
|
+
<artifactId>commonmark-ext-gfm-tables</artifactId>
|
|
82
|
+
<version>0.21.0</version>
|
|
83
|
+
</dependency>
|
|
84
|
+
<dependency>
|
|
85
|
+
<groupId>org.apache.poi</groupId>
|
|
86
|
+
<artifactId>poi-ooxml</artifactId>
|
|
87
|
+
<version>5.5.1</version>
|
|
88
|
+
</dependency>
|
|
74
89
|
<!-- Optional: logback and log4j binding -->
|
|
75
90
|
<dependency>
|
|
76
91
|
<groupId>org.springframework.boot</groupId>
|
|
@@ -79,12 +79,19 @@ public class SkillQuestionRequirementService {
|
|
|
79
79
|
}
|
|
80
80
|
boolean needsConfirmation = Boolean.TRUE.equals(template.get("confirmation_required")) || hasDefaultRequiringConfirmation(elements, defaultsUsed);
|
|
81
81
|
if (needsConfirmation) {
|
|
82
|
-
return RequirementCheckResult.needsConfirmation(confirmationQuestion(extracted), extracted, defaultsUsed);
|
|
82
|
+
return RequirementCheckResult.needsConfirmation(confirmationQuestion(extracted, defaultsUsed, primarySkill), extracted, defaultsUsed);
|
|
83
83
|
}
|
|
84
84
|
extracted.put("confirmed", true);
|
|
85
85
|
return RequirementCheckResult.ready(extracted);
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
public String analysisSummary(RequirementCheckResult check, SkillDefinition primarySkill) {
|
|
89
|
+
if (check == null) {
|
|
90
|
+
return "已完成原始需求分析,将按原始需求开始后续分析。";
|
|
91
|
+
}
|
|
92
|
+
return analysisSummary(check.requirements(), check.defaultsUsed(), check.missing(), primarySkill);
|
|
93
|
+
}
|
|
94
|
+
|
|
88
95
|
private Map<String, Object> extractWithLlm(String query, SkillDefinition primarySkill, Map<String, Object> template) {
|
|
89
96
|
ChatMessage system = new ChatMessage();
|
|
90
97
|
system.setRole(ChatRole.SYSTEM);
|
|
@@ -168,15 +175,78 @@ public class SkillQuestionRequirementService {
|
|
|
168
175
|
return false;
|
|
169
176
|
}
|
|
170
177
|
|
|
171
|
-
private String confirmationQuestion(Map<String, Object> extracted
|
|
172
|
-
|
|
178
|
+
private String confirmationQuestion(Map<String, Object> extracted,
|
|
179
|
+
List<Map<String, Object>> defaultsUsed,
|
|
180
|
+
SkillDefinition primarySkill) {
|
|
181
|
+
return analysisSummary(extracted, defaultsUsed, new ArrayList<String>(), primarySkill)
|
|
182
|
+
+ "\n请确认是否继续?";
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
private String analysisSummary(Map<String, Object> extracted,
|
|
186
|
+
List<Map<String, Object>> defaultsUsed,
|
|
187
|
+
List<String> missing,
|
|
188
|
+
SkillDefinition primarySkill) {
|
|
189
|
+
StringBuilder builder = new StringBuilder("已完成原始需求分析,将按以下要素开始后续分析:");
|
|
173
190
|
appendAnalysisTargets(builder, extracted);
|
|
174
|
-
builder.append("\n- 时间范围:").append(
|
|
175
|
-
builder.append("\n- 数据范围:").append(
|
|
176
|
-
builder.append("\n- 输出形式:").append(
|
|
191
|
+
builder.append("\n- 时间范围:").append(displayValue(extracted.get("time_range")));
|
|
192
|
+
builder.append("\n- 数据范围:").append(displayValue(extracted.get("data_scope")));
|
|
193
|
+
builder.append("\n- 输出形式:").append(displayValue(extracted.get("output_type")));
|
|
194
|
+
appendDefaultsUsed(builder, defaultsUsed, primarySkill);
|
|
195
|
+
appendMissingElements(builder, missing, primarySkill);
|
|
177
196
|
return builder.toString();
|
|
178
197
|
}
|
|
179
198
|
|
|
199
|
+
private void appendDefaultsUsed(StringBuilder builder,
|
|
200
|
+
List<Map<String, Object>> defaultsUsed,
|
|
201
|
+
SkillDefinition primarySkill) {
|
|
202
|
+
if (defaultsUsed == null || defaultsUsed.isEmpty()) {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
List<String> values = new ArrayList<String>();
|
|
206
|
+
for (Map<String, Object> used : defaultsUsed) {
|
|
207
|
+
String key = stringValue(used.get("key"));
|
|
208
|
+
values.add(elementName(primarySkill, key) + "=" + displayValue(used.get("value")));
|
|
209
|
+
}
|
|
210
|
+
builder.append("\n- 已采用默认值:").append(String.join("、", values));
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
private void appendMissingElements(StringBuilder builder,
|
|
214
|
+
List<String> missing,
|
|
215
|
+
SkillDefinition primarySkill) {
|
|
216
|
+
if (missing == null || missing.isEmpty()) {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
List<String> names = new ArrayList<String>();
|
|
220
|
+
for (String key : missing) {
|
|
221
|
+
names.add(elementName(primarySkill, key));
|
|
222
|
+
}
|
|
223
|
+
builder.append("\n- 未识别项:").append(String.join("、", names));
|
|
224
|
+
builder.append("(将以原始用户需求作为分析目标继续)");
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
private String elementName(SkillDefinition primarySkill, String key) {
|
|
228
|
+
Map<String, Object> template = primarySkill == null ? null : primarySkill.getQuestionRequirements();
|
|
229
|
+
Object rawElements = template == null ? null : template.get("required_elements");
|
|
230
|
+
if (rawElements instanceof List) {
|
|
231
|
+
for (Object item : (List<?>) rawElements) {
|
|
232
|
+
if (!(item instanceof Map)) {
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
Map<?, ?> element = (Map<?, ?>) item;
|
|
236
|
+
if (key != null && key.equals(stringValue(element.get("key")))) {
|
|
237
|
+
String name = stringValue(element.get("name"));
|
|
238
|
+
return name == null ? key : name;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return key == null ? "未知要素" : key;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
private String displayValue(Object value) {
|
|
246
|
+
String text = stringValue(value);
|
|
247
|
+
return text == null || text.trim().isEmpty() ? "未识别" : text;
|
|
248
|
+
}
|
|
249
|
+
|
|
180
250
|
private void normalizeAnalysisTargets(Map<String, Object> extracted, String query) {
|
|
181
251
|
List<String> targets = targetList(extracted.get("analysis_targets"));
|
|
182
252
|
if (targets.isEmpty() && looksLikeMultiTargetRequest(query)) {
|
|
@@ -308,7 +378,7 @@ public class SkillQuestionRequirementService {
|
|
|
308
378
|
builder.append("\n- 分析目标:").append(targets.get(0));
|
|
309
379
|
return;
|
|
310
380
|
}
|
|
311
|
-
builder.append("\n- 分析目标:").append(
|
|
381
|
+
builder.append("\n- 分析目标:").append(displayValue(extracted.get("analysis_target")));
|
|
312
382
|
}
|
|
313
383
|
|
|
314
384
|
private List<String> targetList(Object raw) {
|
|
@@ -15,6 +15,7 @@ import com.example.agent.domain.model.SkillDefinition;
|
|
|
15
15
|
import com.example.agent.domain.model.chat.ChatMessage;
|
|
16
16
|
import com.example.agent.domain.model.chat.ChatRole;
|
|
17
17
|
import lombok.RequiredArgsConstructor;
|
|
18
|
+
import org.springframework.beans.factory.annotation.Value;
|
|
18
19
|
import org.springframework.stereotype.Service;
|
|
19
20
|
|
|
20
21
|
import java.time.Instant;
|
|
@@ -40,6 +41,9 @@ public class AgentWaitingFlowService {
|
|
|
40
41
|
private final HarnessWorkflowService harnessWorkflowService;
|
|
41
42
|
private final HarnessMetricsService harnessMetricsService;
|
|
42
43
|
|
|
44
|
+
@Value("${agent.analysis.requirement-feedback.interaction-enabled:false}")
|
|
45
|
+
private boolean requirementFeedbackInteractionEnabled;
|
|
46
|
+
|
|
43
47
|
public AgentSession askForZeroDataLoopConfirmation(AgentSession session, AgentTurn turn) {
|
|
44
48
|
CompletionDecision decision = finalAnswerGuardService.zeroDataLoopDecision(session);
|
|
45
49
|
if (decision == null) {
|
|
@@ -175,6 +179,11 @@ public class AgentWaitingFlowService {
|
|
|
175
179
|
requirementSkill,
|
|
176
180
|
session.getGlobalContext()
|
|
177
181
|
);
|
|
182
|
+
// This kill switch bypasses only the up-front requirement prompt; later safety waits remain unchanged.
|
|
183
|
+
if (!requirementFeedbackInteractionEnabled) {
|
|
184
|
+
autoContinueQuestionRequirements(session, turn, requirementSkill, check);
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
178
187
|
if (!check.needsUserInput()) {
|
|
179
188
|
putAnalysisRequirements(session, check.requirements());
|
|
180
189
|
harnessWorkflowService.finished(session.getSessionId(), turn.getTurnId(), HarnessPhase.QUESTION_REQUIREMENT_VALIDATION, "SUCCEEDED");
|
|
@@ -208,6 +217,39 @@ public class AgentWaitingFlowService {
|
|
|
208
217
|
return session;
|
|
209
218
|
}
|
|
210
219
|
|
|
220
|
+
private void autoContinueQuestionRequirements(AgentSession session,
|
|
221
|
+
AgentTurn turn,
|
|
222
|
+
SkillDefinition requirementSkill,
|
|
223
|
+
SkillQuestionRequirementService.RequirementCheckResult check) {
|
|
224
|
+
Map<String, Object> requirements = check.requirements() == null
|
|
225
|
+
? new LinkedHashMap<String, Object>()
|
|
226
|
+
: new LinkedHashMap<String, Object>(check.requirements());
|
|
227
|
+
if (isBlank(requirements.get("analysis_target"))) {
|
|
228
|
+
requirements.put("analysis_target", session.getQuery());
|
|
229
|
+
}
|
|
230
|
+
requirements.put("confirmed", true);
|
|
231
|
+
putAnalysisRequirements(session, requirements);
|
|
232
|
+
|
|
233
|
+
String summary = skillQuestionRequirementService.analysisSummary(check, requirementSkill);
|
|
234
|
+
ChatMessage assistant = new ChatMessage();
|
|
235
|
+
assistant.setRole(ChatRole.ASSISTANT);
|
|
236
|
+
assistant.setContent(summary);
|
|
237
|
+
session.getTranscript().add(assistant);
|
|
238
|
+
session.setLatestAnswer(summary);
|
|
239
|
+
sessionStore.save(session);
|
|
240
|
+
runtimeEventBus.publish(session.getSessionId(), turn.getTurnId(), RuntimeEventType.ASSISTANT_MESSAGE, payload(
|
|
241
|
+
"content", summary,
|
|
242
|
+
"messageType", "REQUIREMENT_ANALYSIS",
|
|
243
|
+
"terminal", false
|
|
244
|
+
));
|
|
245
|
+
harnessWorkflowService.finished(
|
|
246
|
+
session.getSessionId(),
|
|
247
|
+
turn.getTurnId(),
|
|
248
|
+
HarnessPhase.QUESTION_REQUIREMENT_VALIDATION,
|
|
249
|
+
"AUTO_CONTINUED"
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
|
|
211
253
|
private SkillDefinition questionRequirementSkill(List<SkillDefinition> skills) {
|
|
212
254
|
for (SkillDefinition skill : skills) {
|
|
213
255
|
if (skill == null || skill.getQuestionRequirements() == null) {
|
|
@@ -317,6 +359,10 @@ public class AgentWaitingFlowService {
|
|
|
317
359
|
session.setGlobalContext(globalContext);
|
|
318
360
|
}
|
|
319
361
|
|
|
362
|
+
private boolean isBlank(Object value) {
|
|
363
|
+
return value == null || String.valueOf(value).trim().isEmpty();
|
|
364
|
+
}
|
|
365
|
+
|
|
320
366
|
private Map<String, Object> payload(Object... items) {
|
|
321
367
|
Map<String, Object> payload = new HashMap<String, Object>();
|
|
322
368
|
for (int i = 0; i + 1 < items.length; i += 2) {
|