Skip to content

Commit 2d8d766

Browse files
committed
add orchestrator workflow
1 parent 38f94dd commit 2d8d766

File tree

9 files changed

+288
-0
lines changed

9 files changed

+288
-0
lines changed

spring-ai-agent/pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
<modules>
2020
<module>spring-ai-workflow</module>
21+
<module>spring-ai-agent-orchestrator</module>
2122
</modules>
2223

2324
</project>
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
2+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
3+
<modelVersion>4.0.0</modelVersion>
4+
<parent>
5+
<groupId>com.glmapper</groupId>
6+
<artifactId>spring-ai-agent</artifactId>
7+
<version>0.0.1</version>
8+
</parent>
9+
10+
<artifactId>spring-ai-agent-orchestrator</artifactId>
11+
<packaging>jar</packaging>
12+
13+
<name>spring-ai-agent-orchestrator</name>
14+
15+
<dependencies>
16+
<dependency>
17+
<groupId>org.springframework.ai</groupId>
18+
<artifactId>spring-ai-starter-model-openai</artifactId>
19+
</dependency>
20+
<dependency>
21+
<groupId>org.springframework.boot</groupId>
22+
<artifactId>spring-boot-starter-test</artifactId>
23+
<scope>test</scope>
24+
</dependency>
25+
</dependencies>
26+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.glmapper.ai.workflow;
2+
3+
import org.springframework.boot.SpringApplication;
4+
import org.springframework.boot.autoconfigure.SpringBootApplication;
5+
6+
/**
7+
* Hello world!
8+
*/
9+
@SpringBootApplication
10+
public class OrchestratorWorkersWorkflowApplication {
11+
public static void main(String[] args) {
12+
SpringApplication.run(OrchestratorWorkersWorkflowApplication.class, args);
13+
}
14+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package com.glmapper.ai.workflow.config;
2+
3+
import org.springframework.ai.chat.client.ChatClient;
4+
import org.springframework.ai.openai.OpenAiChatModel;
5+
import org.springframework.context.annotation.Bean;
6+
import org.springframework.context.annotation.Configuration;
7+
8+
9+
/**
10+
* @Classname OpenaiChatClientConfigs
11+
* @Description 注入 ChatClient
12+
*
13+
* @Date 2025/6/10 09:23
14+
* @Created by Gepeng18
15+
*/
16+
@Configuration
17+
public class OpenaiChatClientConfigs {
18+
19+
/**
20+
* 注入ChatClient
21+
*
22+
* @param chatModel
23+
* @return
24+
*/
25+
@Bean
26+
public ChatClient chatClient(OpenAiChatModel chatModel) {
27+
return ChatClient.builder(chatModel)
28+
.build();
29+
}
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
package com.glmapper.ai.workflow.workflow;
2+
3+
import com.fasterxml.jackson.core.type.TypeReference;
4+
import com.fasterxml.jackson.databind.ObjectMapper;
5+
import com.glmapper.ai.workflow.workflow.model.WorkflowResponse;
6+
import org.springframework.ai.chat.client.ChatClient;
7+
import org.springframework.ai.chat.messages.SystemMessage;
8+
import org.springframework.ai.chat.messages.UserMessage;
9+
import org.springframework.ai.chat.prompt.Prompt;
10+
import org.springframework.beans.factory.annotation.Autowired;
11+
import org.springframework.stereotype.Component;
12+
13+
import java.util.Arrays;
14+
import java.util.List;
15+
import java.util.concurrent.CompletableFuture;
16+
import java.util.stream.Collectors;
17+
18+
/**
19+
* @Classname OrchestratorWorkersWorkflow
20+
* @Description Orchestrator 用大模型拆解任务,Worker 并行处理,合并结果
21+
* @Date 2025/6/12 20:19
22+
* @Created by glmapper
23+
*/
24+
25+
@Component
26+
public class OrchestratorWorkersWorkflow {
27+
28+
@Autowired
29+
private ChatClient chatClient;
30+
31+
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
32+
33+
public WorkflowResponse process(String taskDescription) {
34+
try {
35+
// 1. 用大模型拆解任务
36+
LlmSubtaskResult subtaskResult = callLlmForSubtasks(taskDescription);
37+
List<String> subtasks = subtaskResult.subtasks;
38+
String analysis = subtaskResult.analysis;
39+
if (subtasks == null || subtasks.isEmpty()) {
40+
return WorkflowResponse.builder()
41+
.success(false)
42+
.errorMessage("大模型未能拆解出子任务")
43+
.analysis(analysis)
44+
.subtasks(subtasks)
45+
.build();
46+
}
47+
48+
// 2. Workers process subtasks in parallel
49+
List<String> workerResponses = subtasks.stream()
50+
.map(subtask -> CompletableFuture.supplyAsync(() -> workerProcess(subtask)))
51+
.collect(Collectors.toList())
52+
.stream()
53+
.map(CompletableFuture::join)
54+
.collect(Collectors.toList());
55+
56+
// 3. Results are combined into final response
57+
String combined = String.join("\n", workerResponses);
58+
return WorkflowResponse.builder()
59+
.content(combined)
60+
.success(true)
61+
.analysis(analysis)
62+
.subtasks(subtasks)
63+
.workerResponses(workerResponses)
64+
.build();
65+
} catch (Exception e) {
66+
return WorkflowResponse.builder()
67+
.success(false)
68+
.errorMessage("Orchestrator/Worker 执行失败: " + e.getMessage())
69+
.build();
70+
}
71+
}
72+
73+
// 用大模型拆解任务,返回原始分析和子任务列表
74+
private LlmSubtaskResult callLlmForSubtasks(String taskDescription) throws Exception {
75+
List messages = List.of(new SystemMessage("你是一个任务拆解专家。请将用户输入的复杂任务描述拆解为若干可以独立执行的子任务,输出格式为 JSON 数组,每个元素为一个子任务字符串。"), new UserMessage(taskDescription));
76+
Prompt prompt = new Prompt(messages);
77+
String modelResult = chatClient.prompt(prompt).call().content();
78+
// 解析为 List<String>
79+
List<String> subtasks;
80+
try {
81+
subtasks = OBJECT_MAPPER.readValue(modelResult, new TypeReference<List<String>>() {
82+
});
83+
} catch (Exception e) {
84+
// 容错:如果模型返回不是严格 JSON 数组,尝试简单分割
85+
subtasks = Arrays.stream(modelResult.split("[。,.,\n]"))
86+
.map(String::trim)
87+
.filter(s -> !s.isEmpty())
88+
.collect(Collectors.toList());
89+
}
90+
return new LlmSubtaskResult(modelResult, subtasks);
91+
}
92+
93+
// 内部类:封装大模型分析结果
94+
private record LlmSubtaskResult(String analysis, List<String> subtasks) {
95+
}
96+
97+
private String workerProcess(String subtask) {
98+
try {
99+
// 你可以根据业务自定义 system prompt
100+
String systemPrompt = "你是一个高效的AI助手,请认真完成以下子任务:";
101+
List messages = List.of(new SystemMessage(systemPrompt), new UserMessage(subtask));
102+
Prompt prompt = new Prompt(messages);
103+
// 直接用 chatClient 让大模型"执行"子任务
104+
return chatClient.prompt(prompt).call().content();
105+
} catch (Exception e) {
106+
// 失败时返回错误信息,便于排查
107+
return "[Worker Error] " + e.getMessage();
108+
}
109+
}
110+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.glmapper.ai.workflow.workflow.model;
2+
3+
import lombok.Builder;
4+
import lombok.Data;
5+
6+
/**
7+
* @Classname WorkflowRequest
8+
* @Description 用户请求
9+
*
10+
* @Date 2025/6/10 11:23
11+
* @Created by Gepeng18
12+
*/
13+
@Data
14+
@Builder
15+
public class WorkflowRequest {
16+
17+
/**
18+
* 请求内容
19+
*/
20+
private String question;
21+
22+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package com.glmapper.ai.workflow.workflow.model;
2+
3+
import lombok.Builder;
4+
import lombok.Data;
5+
6+
/**
7+
* @Classname WorkflowResponse
8+
* @Description 工作流响应
9+
*
10+
* @Date 2025/6/10 14:32
11+
* @Created by Gepeng18
12+
*/
13+
@Data
14+
@Builder
15+
public class WorkflowResponse {
16+
17+
/**
18+
* 大模型对任务的分析/拆解原始输出
19+
*/
20+
private String analysis;
21+
22+
/**
23+
* 拆解出的所有子任务
24+
*/
25+
private java.util.List<String> subtasks;
26+
27+
/**
28+
* 每个 worker 的输出结果
29+
*/
30+
private java.util.List<String> workerResponses;
31+
32+
/**
33+
* 最终合成的内容(如汇总、总结、最终答案等)
34+
*/
35+
private String content;
36+
37+
/**
38+
* 总体是否成功
39+
*/
40+
private boolean success;
41+
42+
/**
43+
* 错误信息(如有)
44+
*/
45+
private String errorMessage;
46+
47+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
server:
2+
port: 8080
3+
spring:
4+
application:
5+
name: spring-ai-workflow-orchestrator
6+
ai:
7+
openai:
8+
api-key: ${OPENAI_API_KEY}
9+
base-url: https://api.deepseek.com
10+
chat:
11+
options:
12+
model: deepseek-chat
13+
temperature: 0.7
14+
completions-path: /v1/chat/completions
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.glmapper.ai.workflow;
2+
3+
import com.glmapper.ai.workflow.workflow.OrchestratorWorkersWorkflow;
4+
import com.glmapper.ai.workflow.workflow.model.WorkflowResponse;
5+
import org.junit.jupiter.api.Test;
6+
import org.springframework.beans.factory.annotation.Autowired;
7+
import org.springframework.boot.test.context.SpringBootTest;
8+
9+
/**
10+
* Unit test for simple App.
11+
*/
12+
@SpringBootTest(classes = OrchestratorWorkersWorkflowApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
13+
public class OrchestratorWorkersWorkflowTest {
14+
15+
@Autowired
16+
private OrchestratorWorkersWorkflow orchestratorWorkersWorkflow;
17+
18+
@Test
19+
public void test() {
20+
WorkflowResponse response = orchestratorWorkersWorkflow.process("Generate both technical and user-friendly documentation for a REST API endpoint");
21+
System.out.println("Analysis: " + response.getAnalysis());
22+
System.out.println("Worker Outputs: " + response.getWorkerResponses());
23+
}
24+
}

0 commit comments

Comments
 (0)