refactor: use sequential agent for chat workflow

This commit is contained in:
zhuyongxin
2026-07-03 18:03:06 +08:00
parent 6919092b83
commit f01866c1a2
2 changed files with 30 additions and 73 deletions
@@ -3,7 +3,7 @@ package com.superbiz.agent.service;
import com.alibaba.cloud.ai.graph.OverAllState;
import com.alibaba.cloud.ai.graph.RunnableConfig;
import com.alibaba.cloud.ai.graph.agent.ReactAgent;
import com.alibaba.cloud.ai.graph.agent.flow.agent.SupervisorAgent;
import com.alibaba.cloud.ai.graph.agent.flow.agent.SequentialAgent;
import com.alibaba.cloud.ai.graph.exception.GraphRunnerException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -358,7 +358,7 @@ public class ChatService {
}
/**
* 多 Agent 复杂对话执行(Planner + Executor + Supervisor)
* 多 Agent 复杂对话执行(Planner -> Executor -> Verifier)
*/
public ChatResult executeChatComplex(ChatModel chatModel, ToolCallback[] toolCallbacks,
String question, List<Map<String, String>> history) throws GraphRunnerException {
@@ -395,18 +395,16 @@ public class ChatService {
ReactAgent executor = buildChatExecutorAgent(chatModel, toolCallbacks, history, retryContext);
ReactAgent verifier = buildChatVerifierAgent(chatModel);
SupervisorAgent supervisor = SupervisorAgent.builder()
.name("chat_supervisor")
.description("负责按单轮顺序调度 Planner、Executor、Verifier 的多 Agent 控制器")
.model(chatModel)
.systemPrompt(buildSupervisorPrompt(round))
SequentialAgent workflow = SequentialAgent.builder()
.name("chat_workflow")
.description("按固定顺序执行 Planner、Executor、Verifier 的多 Agent 工作流")
.subAgents(List.of(planner, executor, verifier))
.build();
String supervisorInput = buildSupervisorInput(question, retryContext);
Optional<OverAllState> stateOptional = supervisor.invoke(supervisorInput, config);
String workflowInput = buildWorkflowInput(question, retryContext);
Optional<OverAllState> stateOptional = workflow.invoke(workflowInput, config);
if (stateOptional.isEmpty()) {
finalDecision = buildVerifierFallbackDecision(round, "supervisor 未返回有效状态");
finalDecision = buildVerifierFallbackDecision(round, "workflow 未返回有效状态");
answer = buildLowConfidenceOutput(answer, finalDecision);
persistVerifierEvaluation(session, finalDecision, round);
break;
@@ -420,7 +418,7 @@ public class ChatService {
verifierOutput = invokeVerifierFallback(verifier, question, round, config);
}
finalDecision = parseVerifierDecision(verifierOutput, round);
logger.debug("Supervisor round {} finished: plannerPlanLength={}, answerLength={}, verifierOutputLength={}",
logger.debug("Sequential workflow round {} finished: plannerPlanLength={}, answerLength={}, verifierOutputLength={}",
round,
plannerPlan != null ? plannerPlan.length() : 0,
answer != null ? answer.length() : 0,
@@ -580,20 +578,20 @@ public class ChatService {
return session;
}
private String buildSupervisorInput(String question, String retryContext) {
private String buildWorkflowInput(String question, String retryContext) {
StringBuilder input = new StringBuilder();
input.append("请按 supervisor 系统提示完成本轮 Planner -> Executor -> Verifier 编排。\n\n");
input.append("请按固定工作流完成本轮 Planner -> Executor -> Verifier。\n\n");
input.append("--- 用户问题 ---\n").append(question);
if (retryContext != null && !retryContext.isBlank()) {
input.append("\n\n--- retry_context ---\n").append(retryContext);
}
input.append("\n\n完成 verifier 后立即 FINISH,不要额外生成最终答案。");
input.append("\n\nVerifier 完成后由外层代码读取 verifier_output 并决定最终用户输出。");
return input.toString();
}
private String invokeVerifierFallback(ReactAgent verifier, String question, int round, RunnableConfig config) {
try {
logger.warn("Supervisor round {} finished without verifier_output, invoking chat_verifier fallback", round);
logger.warn("Sequential workflow round {} finished without verifier_output, invoking chat_verifier fallback", round);
return verifier.call("请基于 executor_final_answer 和 tool_trace_summary 输出 verifier JSON。原始问题:" + question, config)
.getText();
} catch (Exception e) {
@@ -685,21 +683,6 @@ public class ChatService {
return new VerifierDecision("LOW_CONFID", 0.0, 0, List.of(), rationale, round);
}
private String buildSupervisorPrompt(int round) {
return """
你是一个多 Agent 调度器。每一轮必须严格按顺序完成以下动作:
1. 先调用 chat_planner 生成执行计划
2. 再调用 chat_executor 执行计划并形成最终答案
3. 最后调用 chat_verifier 对 executor 最终答案做事实核查
规则:
- 本轮只允许完成一次 Planner -> Executor -> Verifier 链路
- Verifier 完成后立即停止,不要继续调用任何 Agent
- 不要自己编造答案,最终用户输出由外层代码根据 verifier_output 决定
- 当前是第 %d 轮,保持单轮内顺序稳定
""".formatted(round);
}
private String extractStateText(Optional<OverAllState> stateOptional, String key) {
if (stateOptional.isEmpty()) {
return null;
@@ -31,10 +31,10 @@ import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
class ChatServiceSupervisorAgentTest {
class ChatServiceSequentialAgentTest {
@Test
void executeChatComplexInvokesSupervisorFlow() throws Exception {
void executeChatComplexInvokesSequentialWorkflow() throws Exception {
ChatService chatService = createChatService();
ScriptedChatModel chatModel = new ScriptedChatModel();
@@ -43,12 +43,12 @@ class ChatServiceSupervisorAgentTest {
new ToolCallback[0],
"请分析订单支付超时的原因,并给出修复建议",
List.of(),
"supervisor-test-session"
"sequential-test-session"
);
assertEquals("EXECUTOR_FINAL_ANSWER", result.answer());
assertEquals("supervisor-test-session", result.sessionId());
assertEquals(List.of("chat_planner", "chat_executor", "chat_verifier", "FINISH"), chatModel.decisions);
assertEquals("sequential-test-session", result.sessionId());
assertEquals(List.of("chat_planner", "chat_executor", "chat_verifier"), chatModel.agentCalls);
assertTrue(chatModel.sawVerifierPrompt);
}
@@ -78,47 +78,28 @@ class ChatServiceSupervisorAgentTest {
new ToolCallback[0],
"请分析订单支付超时的原因,并给出修复建议",
List.of(),
"supervisor-low-confidence-session"
"sequential-low-confidence-session"
);
assertTrue(result.answer().contains("EXECUTOR_FINAL_ANSWER"));
assertEquals(List.of("chat_planner", "chat_executor", "chat_verifier", "FINISH"), chatModel.decisions);
assertEquals(List.of("chat_planner", "chat_executor", "chat_verifier"), chatModel.agentCalls);
}
@Test
void executeChatComplexInvokesVerifierFallbackWhenSupervisorSkipsVerifier() throws Exception {
void executeChatComplexRunsPlannerExecutorVerifierInFixedOrder() throws Exception {
ChatService chatService = createChatService();
ScriptedChatModel chatModel = new ScriptedChatModel(
List.of("chat_planner", "chat_executor", "FINISH"),
"""
{
"verdict": "PASS",
"groundedness_score": 1.0,
"critical_fact_count": 1,
"facts_checked": [
{
"fact": "executor answer generated",
"is_critical": true,
"verification": "direct_evidence",
"detail": "covered by fallback verifier",
"evidence_refs": []
}
],
"rationale": "fallback verifier pass"
}
"""
);
ScriptedChatModel chatModel = new ScriptedChatModel();
ChatService.ChatResult result = chatService.executeChatComplex(
chatModel,
new ToolCallback[0],
"请分析订单支付超时的原因,并给出修复建议",
List.of(),
"supervisor-verifier-fallback-session"
"sequential-workflow-session"
);
assertEquals("EXECUTOR_FINAL_ANSWER", result.answer());
assertEquals(List.of("chat_planner", "chat_executor", "FINISH"), chatModel.decisions);
assertEquals(List.of("chat_planner", "chat_executor", "chat_verifier"), chatModel.agentCalls);
assertTrue(chatModel.sawVerifierPrompt);
}
@@ -190,15 +171,13 @@ class ChatServiceSupervisorAgentTest {
}
private static final class ScriptedChatModel implements ChatModel {
private final List<String> decisionScript;
private final java.util.ArrayList<String> decisions = new java.util.ArrayList<>();
private int decisionIndex;
private final java.util.ArrayList<String> agentCalls = new java.util.ArrayList<>();
private String promptText = "";
private boolean sawVerifierPrompt;
private final String verifierOutput;
private ScriptedChatModel() {
this(List.of("chat_planner", "chat_executor", "chat_verifier", "FINISH"), """
this("""
{
"verdict": "PASS",
"groundedness_score": 1.0,
@@ -218,11 +197,6 @@ class ChatServiceSupervisorAgentTest {
}
private ScriptedChatModel(String verifierOutput) {
this(List.of("chat_planner", "chat_executor", "chat_verifier", "FINISH"), verifierOutput);
}
private ScriptedChatModel(List<String> decisionScript, String verifierOutput) {
this.decisionScript = decisionScript;
this.verifierOutput = verifierOutput;
}
@@ -230,14 +204,14 @@ class ChatServiceSupervisorAgentTest {
public ChatResponse call(Prompt prompt) {
promptText = prompt.getContents();
String text;
if (promptText.contains("Available options:")) {
text = "{\"agent\":\"" + decisionScript.get(decisionIndex++) + "\"}";
decisions.add(text.substring(10, text.length() - 2));
} else if (promptText.contains("PLANNER_TEST_PROMPT")) {
if (promptText.contains("PLANNER_TEST_PROMPT")) {
agentCalls.add("chat_planner");
text = "PLANNER_PLAN";
} else if (promptText.contains("EXECUTOR_TEST_PROMPT")) {
agentCalls.add("chat_executor");
text = "EXECUTOR_FINAL_ANSWER";
} else if (promptText.contains("VERIFIER_TEST_PROMPT")) {
agentCalls.add("chat_verifier");
sawVerifierPrompt = true;
text = verifierOutput;
} else {