下面的代码基于前面的内容: https://blog.csdn.net/DavidSoCool/article/details/160857790
文档:https://java2ai.com/docs/frameworks/agent-framework/advanced/multi-agent
下面内容分为四大块:顺序执行\并发执行\路由
systemPrompt 定义路由决策的整体框架,instruction 提供具体的路由指导。
一 顺序执行(Sequential Agent)
功能描述:A根据用户的输入生成谜语,B根据A出的谜语猜答案.
代码:
1.新增SequentialAgentConfig.java
package com.david.springalibabareactagentdemo.config;
import com.alibaba.cloud.ai.graph.agent.ReactAgent;
import com.alibaba.cloud.ai.graph.agent.flow.agent.SequentialAgent;
import com.alibaba.cloud.ai.graph.exception.GraphRunnerException;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.deepseek.DeepSeekChatModel;
import org.springframework.ai.deepseek.api.DeepSeekApi;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.List;
@Configuration
public class SequentialAgentConfig {
@Value("${spring.ai.deepseek.api-key}")
private String apiKey;
@Bean
public SequentialAgent sequentialAgent() {
// 创建 ChatModel
DeepSeekApi deepSeekApi = DeepSeekApi.builder()
.apiKey(apiKey)
.build();
ChatModel chatModel = DeepSeekChatModel.builder()
.deepSeekApi(deepSeekApi)
.build();
// 创建专业化的子Agent
ReactAgent riddleAgent = ReactAgent.builder()
.name("riddle_agent")
.model(chatModel)
.description("出谜语Agent")
.instruction("""
你是一个谜语专家,擅长出谜语。请根据用户的输入出谜语:{input}。
最终只返回谜语,不要包含答案信息。
""")
// 设置为 false 可以让子 Agent 专注于自己的任务,不受父流程复杂上下文的影响。
.includeContents(false)
.outputKey("riddle")
.build();
ReactAgent guessAgent = ReactAgent.builder()
.name("guess_agent")
.model(chatModel)
.description("猜谜语Agent")
.instruction("""
你是一个猜谜语专家,擅长对猜谜语,待猜的谜语:
{riddle}
最终只返回谜语答案,不要包含任何信息。
""")
// 设置为 false 可以让子 Agent 专注于自己的任务,不受父流程复杂上下文的影响。
.includeContents(false)
.outputKey("riddle_answer")
.build();
// 创建顺序Agent
SequentialAgent sequentialAgent = SequentialAgent.builder()
.name("blog_agent")
.description("根据用户给定的描述出一个谜语,然后让猜谜语的Agent进行猜谜")
.subAgents(List.of(riddleAgent, guessAgent))
.build();
return sequentialAgent;
}
}
2.新增接口及调用
/**
* 测试顺序执行多个Agent
*
* @param message
* @return
* @throws GraphRunnerException
*/
@RequestMapping("/sequentialAgentChat")
public Map<String, String> sequentialAgentChat(@RequestParam(value = "message") String message) throws GraphRunnerException {
Map<String, String> map = new HashMap<>();
// 使用
Optional<OverAllState> result = sequentialAgent.invoke(message);
if (result.isPresent()) {
OverAllState state = result.get();
// 访问第一个Agent的输出
state.value("riddle").ifPresent(riddle -> {
if (riddle instanceof AssistantMessage) {
String riddleStr = ((AssistantMessage) riddle).getText();
System.out.println("谜语: " + riddleStr);
map.put("谜语", riddleStr);
}
});
// 访问第二个Agent的输出
state.value("riddle_answer").ifPresent(riddleAnswer -> {
if (riddleAnswer instanceof AssistantMessage) {
String riddleAnswerStr = ((AssistantMessage) riddleAnswer).getText();
System.out.println("答案: " + riddleAnswerStr);
map.put("猜的答案", riddleAnswerStr);
}
});
}
return map;
}
3.启动服务并测试

这里可以看到日志输出的结果,大部分还是可以才对的,从猜奇异果有多种叫法的结果可以看出includeContents =false也是生效的,子Agent的上下文是隔离的

二 并行执行(Parallel Agent)
功能描述:A根据用户的输入生成菜名,B根据用户输入生成植物学名.
代码:
1.新增ParallelAgentConfig.java
package com.david.springalibabareactagentdemo.config;
import com.alibaba.cloud.ai.graph.agent.ReactAgent;
import com.alibaba.cloud.ai.graph.agent.flow.agent.ParallelAgent;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.deepseek.DeepSeekChatModel;
import org.springframework.ai.deepseek.api.DeepSeekApi;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.List;
@Configuration
public class ParallelAgentConfig {
@Value("${spring.ai.deepseek.api-key}")
private String apiKey;
@Bean
public ParallelAgent parallelAgent() {
// 创建 ChatModel
DeepSeekApi deepSeekApi = DeepSeekApi.builder()
.apiKey(apiKey)
.build();
ChatModel chatModel = DeepSeekChatModel.builder()
.deepSeekApi(deepSeekApi)
.build();
// 创建多个专业化Agent
ReactAgent cookAgent = ReactAgent.builder()
.name("cook_agent")
.model(chatModel)
.description("专业厨师的AI助手")
.instruction("""
你是一个擅长做饭的厨师,擅长根据食材输出可以做的菜。
用户会给你食材:{input},你只需根据食材输出可以做的菜名,不要包含任何信息。
""")
.outputKey("cook_result")
// 设置为 false 可以让子 Agent 专注于自己的任务,不受父流程复杂上下文的影响。
.includeContents(false)
.build();
ReactAgent plantAgent = ReactAgent.builder()
.name("plant_agent")
.model(chatModel)
.description("专业植物学家的AI助手")
.instruction("""
你是一个专业的植物学家,擅长植物学。
"用户会给你的植物是:{input},你只需要输出植物学名,不要包含任何信息。
""")
.outputKey("plant_result")
// 设置为 false 可以让子 Agent 专注于自己的任务,不受父流程复杂上下文的影响。
.includeContents(false)
.build();
// 创建并行Agent
ParallelAgent parallelAgent = ParallelAgent.builder()
.name("parallel_creative_agent")
.description("并行执行多个任务,包括输出菜名和植物学名")
.mergeOutputKey("merged_results")
.subAgents(List.of(cookAgent, plantAgent))
.mergeStrategy(new ParallelAgent.DefaultMergeStrategy())
.build();
return parallelAgent;
}
}
2.新增接口及调用
/**
* 测试并行执行多个Agent
*
* @param message
* @return
* @throws GraphRunnerException
*/
@RequestMapping("/parallelAgentChat")
public Map<String, String> parallelAgentChat(@RequestParam(value = "message") String message) throws GraphRunnerException {
Map<String, String> map = new HashMap<>();
// 使用
Optional<OverAllState> result = parallelAgent.invoke(message);
System.out.println("输入: " + message);
map.put("输入", message);
if (result.isPresent()) {
OverAllState state = result.get();
// 访问各个Agent的输出
state.value("cook_result").ifPresent(r -> {
if (r instanceof AssistantMessage) {
String text = ((AssistantMessage) r).getText();
System.out.println("cook_result: " + text);
map.put("cook_result", text);
}
});
state.value("plant_result").ifPresent(r ->{
if (r instanceof AssistantMessage) {
String text = ((AssistantMessage) r).getText();
System.out.println("plant_result: " + text);
map.put("plant_result", text);
}
});
// 访问合并后的结果
state.value("merged_results").ifPresent(r -> {
System.out.println("merged_results: " + r);
map.put("merged_results", r.toString());
});
}
return map;
}
3.启动服务并测试

三 路由(LlmRoutingAgent)
功能描述: 根据用户的输入路由到不同职责的Agent
为了提高路由的准确性,需要注意以下几点:
a. 提供清晰明确的Agent描述, 子Agent的 description 非常重要,它告诉LLM何时应该选择该Agent
b.明确Agent的职责边界
c. 使用不同领域的Agent避免重叠
d. 使用systemPrompt 用于设置路由决策的系统提示
代码:
1.新增RoutingAgentConfig.java
package com.david.springalibabareactagentdemo.config;
import com.alibaba.cloud.ai.graph.agent.ReactAgent;
import com.alibaba.cloud.ai.graph.agent.flow.agent.LlmRoutingAgent;
import com.alibaba.cloud.ai.graph.agent.flow.agent.ParallelAgent;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.deepseek.DeepSeekChatModel;
import org.springframework.ai.deepseek.api.DeepSeekApi;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.List;
@Configuration
public class RoutingAgentConfig {
@Value("${spring.ai.deepseek.api-key}")
private String apiKey;
final String ROUTING_SYSTEM_PROMPT = """
你是一个智能的内容路由Agent,负责根据用户需求将任务路由到最合适的专家Agent。
## 你的职责
1. 仔细分析用户输入的意图和需求
2. 根据任务特性,选择最合适的专家Agent
3. 确保路由决策准确、高效
## 可用的子Agent及其职责
### cook_agent
- **功能**: 擅长做饭的厨师,擅长根据蔬菜输出可以做的菜品
- **适用场景**:
* 用户需要知道如何烹饪
* 用户需要知道如何食用
- **输出**: cook_result
### plant_agent
- **功能**: 专业的植物学家,擅长植物学
- **适用场景**:
* 用户需要了解这个植物
- **输出**: plant_result
## 决策规则
1. **烹饪任务**: 如果用户需要知道如何烹饪,选择 cook_agent
2. **植物任务**: 如果用户需要了解植物,选择 plant_agent
## 响应格式
只返回Agent名称(cook_agent、plant_agent),不要包含其他解释。
""";
@Bean
public LlmRoutingAgent llmRoutingAgent() {
// 创建 ChatModel
DeepSeekApi deepSeekApi = DeepSeekApi.builder()
.apiKey(apiKey)
.build();
ChatModel chatModel = DeepSeekChatModel.builder()
.deepSeekApi(deepSeekApi)
.build();
// 创建专业化的子Agent
ReactAgent cookAgent = ReactAgent.builder()
.name("cook_agent")
.model(chatModel)
.description("专业厨师的AI助手")
.instruction("""
你是一个擅长做饭的厨师,擅长根据蔬菜输出可以做的菜品。
用户会给你蔬菜:{input},你只需根据蔬菜输出制作的菜品,不要包含任何信息。
""")
.outputKey("cook_result")
// 设置为 false 可以让子 Agent 专注于自己的任务,不受父流程复杂上下文的影响。
.includeContents(false)
.build();
ReactAgent plantAgent = ReactAgent.builder()
.name("plant_agent")
.model(chatModel)
.description("专业植物学家的AI助手")
.instruction("""
你是一个专业的植物学家,擅长植物学。
"用户会给你的植物是:{input},你只需要输出植物简介,不要包含任何信息。
""")
.outputKey("plant_result")
// 设置为 false 可以让子 Agent 专注于自己的任务,不受父流程复杂上下文的影响。
.includeContents(false)
.build();
// 创建路由Agent
LlmRoutingAgent routingAgent = LlmRoutingAgent.builder()
.name("content_routing_agent")
.description("根据用户需求智能路由到合适的专家Agent")
.model(chatModel)
.subAgents(List.of(cookAgent, plantAgent))
.systemPrompt(ROUTING_SYSTEM_PROMPT)
.build();
return routingAgent;
}
}
2.新增接口及调用
/**
* 测试路由Agent
*
* @param message
* @return
* @throws GraphRunnerException
*/
@RequestMapping("/llmRoutingAgentChat")
public Map<String, String> llmRoutingAgentChat(@RequestParam(value = "message") String message) throws GraphRunnerException {
Map<String, String> map = new HashMap<>();
// 使用
Optional<OverAllState> result = llmRoutingAgent.invoke(message);
log.info("输入: " + message);
map.put("输入", message);
if (result.isPresent()) {
OverAllState state = result.get();
List<AbstractMessage> messageList = (List<AbstractMessage>) state.data().get("messages");
// messageList不为空获取最后一条消息
if (messageList != null && !messageList.isEmpty()) {
AbstractMessage lastMessage = messageList.get(messageList.size() - 1);
String response = lastMessage.getText();
log.info("result: " + response);
map.put("result", response);
}
}
return map;
}
3.启动服务并测试
从控制台的日志可以看出,是正确路由到了指定的Agent

输入"介绍下西兰花"

输入"西兰花怎么吃"


536

被折叠的 条评论
为什么被折叠?



