From 47d3f81f7ed9a27b5d4a2dfa20d57a47b342088f Mon Sep 17 00:00:00 2001 From: wangjx <631099904@qq.com> Date: Wed, 11 Feb 2026 23:18:01 +0800 Subject: [PATCH 1/6] feat: add subagent personal assistant --- pom.xml | 3 +- .../pom.xml | 60 ++++++++ .../alibaba/ai/example/agent/HITLHelper.java | 79 ++++++++++ .../SubAgentPersonalAssistantApplication.java | 11 ++ .../ai/example/agent/config/AgentConfig.java | 136 ++++++++++++++++++ .../PersonalAssistantController.java | 134 +++++++++++++++++ .../agent/model/AvailableTimeInfo.java | 45 ++++++ .../ai/example/agent/model/CalendarInfo.java | 57 ++++++++ .../ai/example/agent/model/EmailInfo.java | 45 ++++++ .../ai/example/agent/model/UserInfo.java | 42 ++++++ .../agent/tool/CreateCalendarEventTool.java | 43 ++++++ .../ai/example/agent/tool/DateTimeTools.java | 24 ++++ .../agent/tool/GetAvailableTimeSlotsTool.java | 40 ++++++ .../example/agent/tool/GetUserDataTool.java | 68 +++++++++ .../ai/example/agent/tool/SendEmailTool.java | 52 +++++++ .../src/main/resources/application.yml | 11 ++ 16 files changed, 849 insertions(+), 1 deletion(-) create mode 100644 spring-ai-alibaba-agent-example/subagent-personal-assistant-example/pom.xml create mode 100644 spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/HITLHelper.java create mode 100644 spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/SubAgentPersonalAssistantApplication.java create mode 100644 spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/config/AgentConfig.java create mode 100644 spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/controller/PersonalAssistantController.java create mode 100644 spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/model/AvailableTimeInfo.java create mode 100644 spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/model/CalendarInfo.java create mode 100644 spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/model/EmailInfo.java create mode 100644 spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/model/UserInfo.java create mode 100644 spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/tool/CreateCalendarEventTool.java create mode 100644 spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/tool/DateTimeTools.java create mode 100644 spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/tool/GetAvailableTimeSlotsTool.java create mode 100644 spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/tool/GetUserDataTool.java create mode 100644 spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/tool/SendEmailTool.java create mode 100644 spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/resources/application.yml diff --git a/pom.xml b/pom.xml index 722ef359..1966d8d9 100644 --- a/pom.xml +++ b/pom.xml @@ -79,7 +79,8 @@ spring-ai-alibaba-bailian-example spring-ai-alibaba-evaluation-example spring-ai-alibaba-mem0-example - + spring-ai-alibaba-agent-example/subagent-personal-assistant-example + diff --git a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/pom.xml b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/pom.xml new file mode 100644 index 00000000..c78c3b99 --- /dev/null +++ b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/pom.xml @@ -0,0 +1,60 @@ + + + 4.0.0 + + com.alibaba.cloud.ai + spring-ai-alibaba-examples + 1.0.0 + ../../pom.xml + + + subagent-personal-assistant-example + Spring AI Alibaba SubAgent Personal Assistant Example + Multi Agent Personal Assistant + + + 21 + 21 + UTF-8 + + + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.springframework.boot + spring-boot-starter-web + + + + com.alibaba.cloud.ai + spring-ai-alibaba-agent-framework + + + + com.alibaba.cloud.ai + spring-ai-alibaba-starter-dashscope + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + \ No newline at end of file diff --git a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/HITLHelper.java b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/HITLHelper.java new file mode 100644 index 00000000..972209cd --- /dev/null +++ b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/HITLHelper.java @@ -0,0 +1,79 @@ +package com.cloud.alibaba.ai.example.agent; + +import com.alibaba.cloud.ai.graph.action.InterruptionMetadata; + +public class HITLHelper { + /** + * 批准所有工具调用 + */ + public static InterruptionMetadata approveAll(InterruptionMetadata interruptionMetadata) { + InterruptionMetadata.Builder builder = InterruptionMetadata.builder() + .nodeId(interruptionMetadata.node()) + .state(interruptionMetadata.state()); + + interruptionMetadata.toolFeedbacks().forEach(toolFeedback -> { + builder.addToolFeedback( + InterruptionMetadata.ToolFeedback.builder(toolFeedback) + .result(InterruptionMetadata.ToolFeedback.FeedbackResult.APPROVED) + .description("Agree to tool execution.") + .build() + ); + }); + + return builder.build(); + } + + /** + * 拒绝所有工具调用 + */ + public static InterruptionMetadata rejectAll( + InterruptionMetadata interruptionMetadata, + String reason) { + InterruptionMetadata.Builder builder = InterruptionMetadata.builder() + .nodeId(interruptionMetadata.node()) + .state(interruptionMetadata.state()); + + interruptionMetadata.toolFeedbacks().forEach(toolFeedback -> { + builder.addToolFeedback( + InterruptionMetadata.ToolFeedback.builder(toolFeedback) + .result(InterruptionMetadata.ToolFeedback.FeedbackResult.REJECTED) + .description(reason) + .build() + ); + }); + + return builder.build(); + } + + /** + * 编辑特定工具的参数 + */ + public static InterruptionMetadata editTool( + InterruptionMetadata interruptionMetadata, + String toolName, + String newArguments) { + InterruptionMetadata.Builder builder = InterruptionMetadata.builder() + .nodeId(interruptionMetadata.node()) + .state(interruptionMetadata.state()); + + interruptionMetadata.toolFeedbacks().forEach(toolFeedback -> { + if (toolFeedback.getName().equals(toolName)) { + builder.addToolFeedback( + InterruptionMetadata.ToolFeedback.builder(toolFeedback) + .arguments(newArguments) + .result(InterruptionMetadata.ToolFeedback.FeedbackResult.EDITED) + .build() + ); + } else { + builder.addToolFeedback( + InterruptionMetadata.ToolFeedback.builder(toolFeedback) + .result(InterruptionMetadata.ToolFeedback.FeedbackResult.APPROVED) + .build() + ); + } + }); + + return builder.build(); + } + +} diff --git a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/SubAgentPersonalAssistantApplication.java b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/SubAgentPersonalAssistantApplication.java new file mode 100644 index 00000000..356d48bf --- /dev/null +++ b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/SubAgentPersonalAssistantApplication.java @@ -0,0 +1,11 @@ +package com.cloud.alibaba.ai.example.agent; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class SubAgentPersonalAssistantApplication { + public static void main(String[] args) { + SpringApplication.run(SubAgentPersonalAssistantApplication.class, args); + } +} \ No newline at end of file diff --git a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/config/AgentConfig.java b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/config/AgentConfig.java new file mode 100644 index 00000000..e5c7e579 --- /dev/null +++ b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/config/AgentConfig.java @@ -0,0 +1,136 @@ +package com.cloud.alibaba.ai.example.agent.config; + +import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel; +import com.alibaba.cloud.ai.graph.agent.AgentTool; +import com.alibaba.cloud.ai.graph.agent.ReactAgent; +import com.alibaba.cloud.ai.graph.agent.hook.hip.HumanInTheLoopHook; +import com.alibaba.cloud.ai.graph.agent.hook.hip.ToolConfig; +import com.alibaba.cloud.ai.graph.checkpoint.savers.MemorySaver; +import com.cloud.alibaba.ai.example.agent.model.UserInfo; +import com.cloud.alibaba.ai.example.agent.tool.*; +import org.springframework.ai.tool.ToolCallback; +import org.springframework.ai.tool.function.FunctionToolCallback; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.List; + + +@Configuration +public class AgentConfig { + + + private final static String CALENDAR_AGENT_PROMPT = """ + You are a calendar scheduling assistant. + Parse natural language scheduling requests (e.g., 'next Tuesday at 2pm') + into proper ISO datetime formats. + Use get_available_time_slots to check availability when needed. + Use create_calendar_event to schedule events. + Always confirm what was scheduled in your final response. + """; + + private final static String EMAIL_AGENT_PROMPT = """ + You are an email assistant. + Compose professional emails based on natural language requests. + Extract recipient information and craft appropriate subject lines and body text. + Use send_email to send the message. + Always confirm what was sent in your final response. + """; + + private final static String SUPERVISOR_PROMPT = """ + You are a helpful personal assistant. + You can schedule calendar events and send emails. + Break down user requests into appropriate tool calls and coordinate the results. + When a request involves multiple actions, use multiple tools in sequence. + """; + + private final DashScopeChatModel dashScopeChatModel; + + public AgentConfig(DashScopeChatModel dashScopeChatModel) { + this.dashScopeChatModel = dashScopeChatModel; + } + + @Bean("reactAgent") + public ReactAgent reactAgent() { + // 配置检查点保存器(人工介入需要检查点来处理中断) + MemorySaver memorySaver = new MemorySaver(); + ToolCallback calendarAgent = AgentTool.getFunctionToolCallback(calendarAgent()); + ToolCallback emailAgent = AgentTool.getFunctionToolCallback(emailAgent()); + FunctionToolCallback getUser = FunctionToolCallback.builder("get_user_email_tool", new GetUserDataTool()) + .description("You can provide the functionality to retrieve a user's email address by their username, and to obtain all user names within a department by specifying the department name.") + .inputType(UserInfo.class) + .build(); + + return ReactAgent.builder() + .name("supervisor_agent") + .model(dashScopeChatModel) + .systemPrompt(SUPERVISOR_PROMPT) + .hooks(createHumanInTheLoopHook()) + .tools(List.of(calendarAgent, emailAgent, getUser)) + //.tools(getUser) + .saver(memorySaver) + .build(); + } + + @Bean("emailAgent") + public ReactAgent emailAgent() { + + String instruction = + """ + Send emails using natural language. + + Use this when the user wants to send notifications, reminders, or any email + communication. Handles recipient extraction, subject generation, and email + composition. + + Input: Natural language email request (e.g., 'send them a reminder about + the meeting') + """; + // 创建 Agent + return ReactAgent.builder() + .name("emailAgent") + .model(dashScopeChatModel) + .tools(List.of(new SendEmailTool().toolCallback())) + .systemPrompt(EMAIL_AGENT_PROMPT) + .instruction(instruction) + .inputType(String.class) + .hooks(createHumanInTheLoopHook()) + .build(); + } + + @Bean("calendarAgent") + public ReactAgent calendarAgent() { + + String instruction = """ + Schedule calendar events using natural language. + + Use this when the user wants to create, modify, or check calendar appointments. + Handles date/time parsing, availability checking, and event creation. + + Input: Natural language scheduling request (e.g., 'meeting with design team + next Tuesday at 2pm') + """; + + // 创建 Agent + return ReactAgent.builder() + .name("calendarAgent") + .model(dashScopeChatModel) + .tools(List.of(new CreateCalendarEventTool().toolCallback(), new GetAvailableTimeSlotsTool().toolCallback(), new DateTimeTools().toolCallback())) + .systemPrompt(CALENDAR_AGENT_PROMPT) + .instruction(instruction) + .inputType(String.class) + .build(); + + } + + private HumanInTheLoopHook createHumanInTheLoopHook() { + // 创建人工介入Hook + return HumanInTheLoopHook.builder() + .approvalOn("calendarAgent", ToolConfig.builder() + .description("Calendar event pending approval") + .build()) + .approvalOn("emailAgent", ToolConfig.builder() + .description("Outbound email pending approval") + .build()).build(); + } +} diff --git a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/controller/PersonalAssistantController.java b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/controller/PersonalAssistantController.java new file mode 100644 index 00000000..76f08acd --- /dev/null +++ b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/controller/PersonalAssistantController.java @@ -0,0 +1,134 @@ +package com.cloud.alibaba.ai.example.agent.controller; + +import com.alibaba.cloud.ai.graph.NodeOutput; +import com.alibaba.cloud.ai.graph.OverAllState; +import com.alibaba.cloud.ai.graph.RunnableConfig; +import com.alibaba.cloud.ai.graph.action.InterruptionMetadata; +import com.alibaba.cloud.ai.graph.agent.ReactAgent; +import com.alibaba.cloud.ai.graph.exception.GraphRunnerException; +import com.alibaba.cloud.ai.graph.streaming.OutputType; +import com.alibaba.cloud.ai.graph.streaming.StreamingOutput; +import com.alibaba.fastjson.JSON; +import com.cloud.alibaba.ai.example.agent.HITLHelper; +import org.springframework.ai.chat.messages.Message; +import org.springframework.ai.chat.messages.ToolResponseMessage; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.core.scheduler.Schedulers; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; + +@RestController +public class PersonalAssistantController { + + private static final Map ALL_STATE_MAP = new ConcurrentHashMap(); + private static final Map> TOOL_FEEDBACK_MAP = new ConcurrentHashMap<>(); + + @Autowired + private ReactAgent reactAgent; + + + @GetMapping(value = "/react/agent/supervisorAgent", produces = MediaType.TEXT_EVENT_STREAM_VALUE) + public Flux supervisorAgentTest(String query, String threadId, String nodeId) throws GraphRunnerException { + RunnableConfig config; + if (nodeId != null && ALL_STATE_MAP.containsKey(nodeId)) { + System.out.println("人工介入开始..."); + // 人工介入利用检查点机制。 + // 你必须提供线程ID以将执行与会话线程关联, + // 以便可以暂停和恢复对话(人工审查所需)。 + OverAllState overAllState = ALL_STATE_MAP.get(nodeId); + InterruptionMetadata metadata = InterruptionMetadata.builder(nodeId, overAllState).toolFeedbacks(TOOL_FEEDBACK_MAP.get(nodeId)).build(); + InterruptionMetadata approvalMetadata = HITLHelper.approveAll(metadata); + // 使用批准决策恢复执行 + config = RunnableConfig.builder() + .threadId(threadId) // 相同的线程ID + .addHumanFeedback(approvalMetadata) + .build(); + ALL_STATE_MAP.remove(nodeId); + TOOL_FEEDBACK_MAP.remove(nodeId); + return resumeStreamingWithHack(config); + } else { + + //String threadId = "user-session-123"; + config = RunnableConfig.builder() + .threadId(threadId) + .build(); + } + return reactAgent.stream(query, config) + .doOnNext(this::println); + + } + + public Flux resumeStreamingWithHack(RunnableConfig config) { + + return Mono.fromCallable(() -> { + // 使用 invokeAndGetOutput 恢复(同步阻塞) + Optional result = reactAgent.invokeAndGetOutput("", config); + if (result.isPresent()) { + return extractMessageFromState(result.get()); + } + return ""; + }) + .subscribeOn(Schedulers.boundedElastic()) + .flux() + .concatMap(Flux::just); + } + + private NodeOutput extractMessageFromState(NodeOutput nodeOutput) { + OverAllState state = nodeOutput.state(); + List messages = (List) state.data().get("messages"); + // 解析 messages 列表获取最后一条 AI 消息... + Message lastMessage = messages.get(messages.size() - 1); + System.out.println(lastMessage); + return nodeOutput; + } + + private void println(NodeOutput nodeOutput) { + if (nodeOutput instanceof StreamingOutput streamingOutput) { + String node = streamingOutput.node(); + Message message = streamingOutput.message(); + if (message == null) { + return; + } + if ("_AGENT_MODEL_".equals(node)) { + System.out.print(message.getText()); + } + if ("_AGENT_TOOL_".equals(node)) { + ToolResponseMessage responseMessage = (ToolResponseMessage) message; + List responses = responseMessage.getResponses(); + System.out.println("_AGENT_TOOL_"); + for (ToolResponseMessage.ToolResponse respons : responses) { + String string = respons.responseData(); + System.out.println("id: " + respons.id()); + System.out.println("name: " + respons.name()); + System.out.println("responseData: " + string); + + } + } + } else if (nodeOutput instanceof InterruptionMetadata interruptionMetadata) { + System.out.println("检测到中断,需要人工审批"); + List toolFeedbacks = + interruptionMetadata.toolFeedbacks(); + + for (InterruptionMetadata.ToolFeedback feedback : toolFeedbacks) { + System.out.println("工具: " + feedback.getName()); + System.out.println("参数: " + feedback.getArguments()); + System.out.println("描述: " + feedback.getDescription()); + } + String node = interruptionMetadata.node(); + OverAllState state = interruptionMetadata.state(); + System.out.println("参数 node: " + node); + ALL_STATE_MAP.put(node, state); + TOOL_FEEDBACK_MAP.put(node, toolFeedbacks); + } else { + System.out.println("其它:" + JSON.toJSONString(nodeOutput)); + } + } +} diff --git a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/model/AvailableTimeInfo.java b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/model/AvailableTimeInfo.java new file mode 100644 index 00000000..0c5cf7c2 --- /dev/null +++ b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/model/AvailableTimeInfo.java @@ -0,0 +1,45 @@ +package com.cloud.alibaba.ai.example.agent.model; + +import java.util.List; + +public class AvailableTimeInfo { + /** + * 日期信息,表示可用时间的具体日期 + */ + private String date; + + /** + * 持续时间(分钟),表示该时间段的长度 + */ + private Integer durationMinutes; + + /** + * 参与者列表,包含所有参与此时间段的人员信息 + */ + private List attendees; + + public List getAttendees() { + return attendees; + } + + public void setAttendees(List attendees) { + this.attendees = attendees; + } + + public String getDate() { + return date; + } + + public void setDate(String date) { + this.date = date; + } + + public Integer getDurationMinutes() { + return durationMinutes; + } + + public void setDurationMinutes(Integer durationMinutes) { + this.durationMinutes = durationMinutes; + } +} + diff --git a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/model/CalendarInfo.java b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/model/CalendarInfo.java new file mode 100644 index 00000000..f37bda9e --- /dev/null +++ b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/model/CalendarInfo.java @@ -0,0 +1,57 @@ +package com.cloud.alibaba.ai.example.agent.model; + +import java.util.List; + +public class CalendarInfo { + /** + * 日历事件的标题 + */ + private String title; + + /** + * 日历事件的开始时间 + */ + private String startTime; + + /** + * 日历事件的结束时间 + */ + private String endTime; + + /** + * 日历事件的参与者列表 + */ + private List attendees; + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getStartTime() { + return startTime; + } + + public void setStartTime(String startTime) { + this.startTime = startTime; + } + + public String getEndTime() { + return endTime; + } + + public void setEndTime(String endTime) { + this.endTime = endTime; + } + + public List getAttendees() { + return attendees; + } + + public void setAttendees(List attendees) { + this.attendees = attendees; + } +} diff --git a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/model/EmailInfo.java b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/model/EmailInfo.java new file mode 100644 index 00000000..36446046 --- /dev/null +++ b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/model/EmailInfo.java @@ -0,0 +1,45 @@ +package com.cloud.alibaba.ai.example.agent.model; + +import java.util.List; + + +public class EmailInfo { + /** + * 收件人邮箱地址列表 + */ + private List to; + + /** + * 邮件主题 + */ + private String subject; + + /** + * 邮件正文内容 + */ + private String body; + + public List getTo() { + return to; + } + + public void setTo(List to) { + this.to = to; + } + + public String getSubject() { + return subject; + } + + public void setSubject(String subject) { + this.subject = subject; + } + + public String getBody() { + return body; + } + + public void setBody(String body) { + this.body = body; + } +} diff --git a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/model/UserInfo.java b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/model/UserInfo.java new file mode 100644 index 00000000..0bf5db75 --- /dev/null +++ b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/model/UserInfo.java @@ -0,0 +1,42 @@ +package com.cloud.alibaba.ai.example.agent.model; + +public class UserInfo { + /** + * 用户名称 + */ + private String userName; + + /** + * 用户邮箱 + */ + private String email; + + /** + * 部门名称 + */ + private String departmentName; + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getDepartmentName() { + return departmentName; + } + + public void setDepartmentName(String departmentName) { + this.departmentName = departmentName; + } +} diff --git a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/tool/CreateCalendarEventTool.java b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/tool/CreateCalendarEventTool.java new file mode 100644 index 00000000..50d58fe6 --- /dev/null +++ b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/tool/CreateCalendarEventTool.java @@ -0,0 +1,43 @@ +package com.cloud.alibaba.ai.example.agent.tool; + +import com.cloud.alibaba.ai.example.agent.model.CalendarInfo; +import com.cloud.alibaba.ai.example.agent.model.UserInfo; +import org.springframework.ai.chat.model.ToolContext; +import org.springframework.ai.tool.ToolCallback; +import org.springframework.ai.tool.function.FunctionToolCallback; + +import java.util.List; +import java.util.function.BiFunction; + +public class CreateCalendarEventTool implements BiFunction { + @Override + public String apply(CalendarInfo calendarInfo, ToolContext toolContext) { + // 参数解析 + String title = calendarInfo.getTitle(); + String startTime =calendarInfo.getStartTime(); + String endTime = calendarInfo.getEndTime(); + @SuppressWarnings("unchecked") + List attendees =calendarInfo.getAttendees(); + // 验证时间格式(简化版) + if (!isValidIsoDateTime(startTime) || !isValidIsoDateTime(endTime)) { + return "Error: Invalid ISO datetime format"; + } + // 模拟创建事件 + return String.format("Event created: %s from %s to %s with %d attendees", + title, startTime, endTime, attendees.size()); + } + + + private boolean isValidIsoDateTime(String datetime) { + // 简单验证 ISO 8601 格式(实际应使用更严格的验证) + return datetime != null && datetime.matches("\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}"); + } + + public ToolCallback toolCallback() { + return FunctionToolCallback.builder("create_calendar_event", this) + .description("create_calendar_event") + .inputType(CalendarInfo.class) + .build(); + + } +} diff --git a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/tool/DateTimeTools.java b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/tool/DateTimeTools.java new file mode 100644 index 00000000..8abc4c31 --- /dev/null +++ b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/tool/DateTimeTools.java @@ -0,0 +1,24 @@ +package com.cloud.alibaba.ai.example.agent.tool; + +import org.springframework.ai.chat.model.ToolContext; +import org.springframework.ai.tool.ToolCallback; +import org.springframework.ai.tool.function.FunctionToolCallback; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Map; +import java.util.function.BiFunction; + +public class DateTimeTools implements BiFunction, ToolContext, String> { + @Override + public String apply(Map map, ToolContext toolContext) { + return LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")); + } + + public ToolCallback toolCallback() { + return FunctionToolCallback.builder("getCurrentDateTime", this) + .description("get_current_date_time") + .inputType(Map.class) + .build(); + } +} diff --git a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/tool/GetAvailableTimeSlotsTool.java b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/tool/GetAvailableTimeSlotsTool.java new file mode 100644 index 00000000..a7467149 --- /dev/null +++ b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/tool/GetAvailableTimeSlotsTool.java @@ -0,0 +1,40 @@ +package com.cloud.alibaba.ai.example.agent.tool; + +import com.cloud.alibaba.ai.example.agent.model.AvailableTimeInfo; +import org.springframework.ai.chat.model.ToolContext; +import org.springframework.ai.tool.ToolCallback; +import org.springframework.ai.tool.function.FunctionToolCallback; + +import java.util.List; +import java.util.Map; +import java.util.function.BiFunction; + +public class GetAvailableTimeSlotsTool implements BiFunction { + @Override + public String apply(AvailableTimeInfo args, ToolContext toolContext) { + // 参数解析 + @SuppressWarnings("unchecked") + List attendees = (List) args.getAttendees(); + String date = (String) args.getDate(); + int durationMinutes = (Integer) args.getDurationMinutes(); + // 验证日期格式(简化版) + if (!isValidIsoDate(date)) { + return "Error: Invalid ISO date format"; + } + // 模拟查询可用时间槽 + List timeSlots = List.of("09:00", "14:00", "16:00"); + return String.format("Available time slots for %s: %s", date, String.join(", ", timeSlots)); + } + + private boolean isValidIsoDate(String date) { + // 简单验证 ISO 8601 日期格式 + return date != null && date.matches("\\d{4}-\\d{2}-\\d{2}"); + } + + public ToolCallback toolCallback() { + return FunctionToolCallback.builder("get_available_time_slots", this) + .description("get_available_time_slots") + .inputType(AvailableTimeInfo.class) + .build(); + } +} diff --git a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/tool/GetUserDataTool.java b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/tool/GetUserDataTool.java new file mode 100644 index 00000000..fad31c7a --- /dev/null +++ b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/tool/GetUserDataTool.java @@ -0,0 +1,68 @@ +package com.cloud.alibaba.ai.example.agent.tool; + +import com.alibaba.fastjson.JSON; +import com.cloud.alibaba.ai.example.agent.model.UserInfo; +import org.springframework.ai.chat.model.ToolContext; +import org.springframework.ai.tool.ToolCallback; +import org.springframework.ai.tool.function.FunctionToolCallback; +import org.springframework.util.StringUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiFunction; + +public class GetUserDataTool implements BiFunction { + + private static List USER_INFO_LIST = new ArrayList<>(); + + static { + UserInfo userInfo = new UserInfo(); + userInfo.setUserName("张三"); + userInfo.setEmail("zhangsan@agent.cn"); + userInfo.setDepartmentName("研发部"); + USER_INFO_LIST.add(userInfo); + UserInfo userInfo1 = new UserInfo(); + userInfo1.setUserName("李四"); + userInfo1.setEmail("lisi@agent.cn"); + userInfo1.setDepartmentName("研发部"); + USER_INFO_LIST.add(userInfo1); + + UserInfo userInfo2 = new UserInfo(); + userInfo2.setUserName("王五"); + userInfo2.setEmail("wangwu@agent.cn"); + userInfo2.setDepartmentName("设计团队"); + + USER_INFO_LIST.add(userInfo2); + + } + + @Override + public String apply(UserInfo userInfo, ToolContext toolContext) { + String userName = userInfo.getUserName(); + + if (StringUtils.hasLength(userName)) { + List userInfos = USER_INFO_LIST.stream().filter(f -> f.getUserName().equals(userName)).toList(); + if (userInfos != null && !userInfos.isEmpty()) { + String str = JSON.toJSONString(userInfos); + return String.format("Available user list for %s", str); + } + } + String department = userInfo.getDepartmentName(); + + if (StringUtils.hasLength(department)) { + List userInfos = USER_INFO_LIST.stream().filter(f -> f.getDepartmentName().equals(department)).toList(); + if (userInfos != null && !userInfos.isEmpty()) { + String str = JSON.toJSONString(userInfos); + return String.format("Available user list for %s", str); + } + } + return ""; + } + + public ToolCallback toolCallback() { + return FunctionToolCallback.builder("get_user_email_tool", this) + .description("You can provide the functionality to retrieve a user's email address by their username, and to obtain all user names within a department by specifying the department name.") + .inputType(UserInfo.class) + .build(); + } +} diff --git a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/tool/SendEmailTool.java b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/tool/SendEmailTool.java new file mode 100644 index 00000000..0c1130c0 --- /dev/null +++ b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/tool/SendEmailTool.java @@ -0,0 +1,52 @@ +package com.cloud.alibaba.ai.example.agent.tool; + +import com.cloud.alibaba.ai.example.agent.model.EmailInfo; +import org.springframework.ai.chat.model.ToolContext; +import org.springframework.ai.tool.ToolCallback; +import org.springframework.ai.tool.function.FunctionToolCallback; + +import java.util.List; +import java.util.function.BiFunction; + +public class SendEmailTool implements BiFunction { + @Override + public String apply(EmailInfo args, ToolContext toolContext) { + // 参数解析 + @SuppressWarnings("unchecked") + List to = (List) args.getTo(); + String subject = (String) args.getSubject(); + String body = (String) args.getBody(); + // 验证邮箱格式(简化版) + for (String email : to) { + if (!isValidEmail(email)) { + return "Error: Invalid email address: " + email; + } + } + // 模拟发送邮件 + System.out.println("Sending email..."); + String format = String.format("Email sent to %s - Subject: %s", String.join(", ", to), subject); + System.out.println( format); + return format; + } + private boolean isValidEmail(String email) { + // 简单验证邮箱格式(实际应使用更严格的验证) + return email != null && email.matches("^[\\w.-]+@[\\w.-]+\\.[a-zA-Z]{2,}$"); + } + + public ToolCallback toolCallback() { + return FunctionToolCallback.builder("send_email",this) + .description(""" + Send emails using natural language. + + Use this when the user wants to send notifications, reminders, or any email + communication. Handles recipient extraction, subject generation, and email + composition. + + Input: Natural language email request (e.g., 'send them a reminder about + the meeting + """) + .inputType(EmailInfo.class) + .build(); + + } +} diff --git a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/resources/application.yml b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/resources/application.yml new file mode 100644 index 00000000..fb187374 --- /dev/null +++ b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/resources/application.yml @@ -0,0 +1,11 @@ + spring: + application: + name: subagent-personal-assistant-examplee + ai: + dashscope: + api-key: ${DASHSCOPE_API_KEY} + chat: + options: + model: 'qwen3-max-2026-01-23' + server: + port: 8083 From 2168b2cc548297cbdc539f5ee2685c43a7fdf4b7 Mon Sep 17 00:00:00 2001 From: wangjx Date: Thu, 12 Feb 2026 17:53:30 +0800 Subject: [PATCH 2/6] feat: add subagent personal assistant --- .../ai/example/agent/config/AgentConfig.java | 26 +++++++------------ .../PersonalAssistantController.java | 24 ++++------------- .../ai/example/agent/tool/SendEmailTool.java | 6 ++--- 3 files changed, 17 insertions(+), 39 deletions(-) diff --git a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/config/AgentConfig.java b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/config/AgentConfig.java index e5c7e579..208a7df0 100644 --- a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/config/AgentConfig.java +++ b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/config/AgentConfig.java @@ -30,18 +30,18 @@ Parse natural language scheduling requests (e.g., 'next Tuesday at 2pm') """; private final static String EMAIL_AGENT_PROMPT = """ - You are an email assistant. - Compose professional emails based on natural language requests. - Extract recipient information and craft appropriate subject lines and body text. - Use send_email to send the message. - Always confirm what was sent in your final response. + You are an email assistant. + Compose professional emails based on natural language requests. + Extract recipient information and craft appropriate subject lines and body text. + Use send_email to send the message. + Always confirm what was sent in your final response. """; private final static String SUPERVISOR_PROMPT = """ - You are a helpful personal assistant. - You can schedule calendar events and send emails. - Break down user requests into appropriate tool calls and coordinate the results. - When a request involves multiple actions, use multiple tools in sequence. + You are a helpful personal assistant. + You can schedule calendar events and send emails. + Break down user requests into appropriate tool calls and coordinate the results. + When a request involves multiple actions, use multiple tools in sequence. """; private final DashScopeChatModel dashScopeChatModel; @@ -67,7 +67,6 @@ public ReactAgent reactAgent() { .systemPrompt(SUPERVISOR_PROMPT) .hooks(createHumanInTheLoopHook()) .tools(List.of(calendarAgent, emailAgent, getUser)) - //.tools(getUser) .saver(memorySaver) .build(); } @@ -77,12 +76,10 @@ public ReactAgent emailAgent() { String instruction = """ - Send emails using natural language. - + Send emails using natural language. Use this when the user wants to send notifications, reminders, or any email communication. Handles recipient extraction, subject generation, and email composition. - Input: Natural language email request (e.g., 'send them a reminder about the meeting') """; @@ -94,7 +91,6 @@ public ReactAgent emailAgent() { .systemPrompt(EMAIL_AGENT_PROMPT) .instruction(instruction) .inputType(String.class) - .hooks(createHumanInTheLoopHook()) .build(); } @@ -103,10 +99,8 @@ public ReactAgent calendarAgent() { String instruction = """ Schedule calendar events using natural language. - Use this when the user wants to create, modify, or check calendar appointments. Handles date/time parsing, availability checking, and event creation. - Input: Natural language scheduling request (e.g., 'meeting with design team next Tuesday at 2pm') """; diff --git a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/controller/PersonalAssistantController.java b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/controller/PersonalAssistantController.java index 76f08acd..6c43f52f 100644 --- a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/controller/PersonalAssistantController.java +++ b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/controller/PersonalAssistantController.java @@ -6,7 +6,6 @@ import com.alibaba.cloud.ai.graph.action.InterruptionMetadata; import com.alibaba.cloud.ai.graph.agent.ReactAgent; import com.alibaba.cloud.ai.graph.exception.GraphRunnerException; -import com.alibaba.cloud.ai.graph.streaming.OutputType; import com.alibaba.cloud.ai.graph.streaming.StreamingOutput; import com.alibaba.fastjson.JSON; import com.cloud.alibaba.ai.example.agent.HITLHelper; @@ -28,7 +27,6 @@ @RestController public class PersonalAssistantController { - private static final Map ALL_STATE_MAP = new ConcurrentHashMap(); private static final Map> TOOL_FEEDBACK_MAP = new ConcurrentHashMap<>(); @Autowired @@ -38,20 +36,18 @@ public class PersonalAssistantController { @GetMapping(value = "/react/agent/supervisorAgent", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public Flux supervisorAgentTest(String query, String threadId, String nodeId) throws GraphRunnerException { RunnableConfig config; - if (nodeId != null && ALL_STATE_MAP.containsKey(nodeId)) { + if (nodeId != null && TOOL_FEEDBACK_MAP.containsKey(nodeId)) { System.out.println("人工介入开始..."); // 人工介入利用检查点机制。 // 你必须提供线程ID以将执行与会话线程关联, // 以便可以暂停和恢复对话(人工审查所需)。 - OverAllState overAllState = ALL_STATE_MAP.get(nodeId); - InterruptionMetadata metadata = InterruptionMetadata.builder(nodeId, overAllState).toolFeedbacks(TOOL_FEEDBACK_MAP.get(nodeId)).build(); + InterruptionMetadata metadata = InterruptionMetadata.builder().toolFeedbacks(TOOL_FEEDBACK_MAP.get(nodeId)).build(); InterruptionMetadata approvalMetadata = HITLHelper.approveAll(metadata); // 使用批准决策恢复执行 config = RunnableConfig.builder() .threadId(threadId) // 相同的线程ID .addHumanFeedback(approvalMetadata) .build(); - ALL_STATE_MAP.remove(nodeId); TOOL_FEEDBACK_MAP.remove(nodeId); return resumeStreamingWithHack(config); } else { @@ -72,7 +68,8 @@ public Flux resumeStreamingWithHack(RunnableConfig config) { // 使用 invokeAndGetOutput 恢复(同步阻塞) Optional result = reactAgent.invokeAndGetOutput("", config); if (result.isPresent()) { - return extractMessageFromState(result.get()); + println(result.get()); + return result.get(); } return ""; }) @@ -80,16 +77,6 @@ public Flux resumeStreamingWithHack(RunnableConfig config) { .flux() .concatMap(Flux::just); } - - private NodeOutput extractMessageFromState(NodeOutput nodeOutput) { - OverAllState state = nodeOutput.state(); - List messages = (List) state.data().get("messages"); - // 解析 messages 列表获取最后一条 AI 消息... - Message lastMessage = messages.get(messages.size() - 1); - System.out.println(lastMessage); - return nodeOutput; - } - private void println(NodeOutput nodeOutput) { if (nodeOutput instanceof StreamingOutput streamingOutput) { String node = streamingOutput.node(); @@ -124,8 +111,7 @@ private void println(NodeOutput nodeOutput) { } String node = interruptionMetadata.node(); OverAllState state = interruptionMetadata.state(); - System.out.println("参数 node: " + node); - ALL_STATE_MAP.put(node, state); + System.out.println("检测到中断,等待人工介入... node: " + node); TOOL_FEEDBACK_MAP.put(node, toolFeedbacks); } else { System.out.println("其它:" + JSON.toJSONString(nodeOutput)); diff --git a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/tool/SendEmailTool.java b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/tool/SendEmailTool.java index 0c1130c0..5c911dd3 100644 --- a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/tool/SendEmailTool.java +++ b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/tool/SendEmailTool.java @@ -23,10 +23,8 @@ public String apply(EmailInfo args, ToolContext toolContext) { } } // 模拟发送邮件 - System.out.println("Sending email..."); - String format = String.format("Email sent to %s - Subject: %s", String.join(", ", to), subject); - System.out.println( format); - return format; + System.out.printf("Email sent to %s - Subject: %s%n body: %s", String.join(", ", to), subject,body); + return String.format("Email sent to %s - Subject: %s", String.join(", ", to), subject); } private boolean isValidEmail(String email) { // 简单验证邮箱格式(实际应使用更严格的验证) From 889131bbc1a96d98efecab8547d461f9d1804c69 Mon Sep 17 00:00:00 2001 From: wangjx Date: Fri, 13 Feb 2026 14:36:54 +0800 Subject: [PATCH 3/6] feat: add subagent personal assistant --- .../README.md | 110 ++++++++++++++++++ .../alibaba/ai/example/agent/HITLHelper.java | 15 +++ .../SubAgentPersonalAssistantApplication.java | 19 +++ .../ai/example/agent/config/AgentConfig.java | 48 +++++--- .../PersonalAssistantController.java | 58 ++++++--- .../agent/model/AvailableTimeInfo.java | 21 ++++ .../ai/example/agent/model/CalendarInfo.java | 21 ++++ .../ai/example/agent/model/EmailInfo.java | 22 +++- .../ai/example/agent/model/UserInfo.java | 21 ++++ ...sTool.java => AvailableTimeSlotsTool.java} | 40 ++++++- .../agent/tool/CreateCalendarEventTool.java | 43 +++++-- .../ai/example/agent/tool/DateTimeTools.java | 26 ++++- .../ai/example/agent/tool/SendEmailTool.java | 44 +++++-- ...GetUserDataTool.java => UserDataTool.java} | 48 ++++++-- .../src/main/resources/application.yml | 4 +- 15 files changed, 472 insertions(+), 68 deletions(-) create mode 100644 spring-ai-alibaba-agent-example/subagent-personal-assistant-example/README.md rename spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/tool/{GetAvailableTimeSlotsTool.java => AvailableTimeSlotsTool.java} (52%) rename spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/tool/{GetUserDataTool.java => UserDataTool.java} (54%) diff --git a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/README.md b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/README.md new file mode 100644 index 00000000..359ef18f --- /dev/null +++ b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/README.md @@ -0,0 +1,110 @@ +# Spring AI Alibaba SQL Agent Example + +本示例展示如何使用 Spring AI Alibaba 的 ReactAgent 框架构建一个多智能体监督者模式系统,通过主智能体协调多个子智能体,实现日历代理与邮件发送功能。 +## 功能特性 + +- **多智能体**: Subagent使用Tool Calling 模式被 Supervisor Agen调用。 +- **人工介入**: 通过在Supervisor Agent配置hooks加入中断功能。 + +## 快速开始 + +### 前置条件 + +- Java 17+ +- Maven 3.6+ +- DashScope API Key + +### 运行步骤 + +1. **设置 API Key** + + ```bash + export DASHSCOPE_API_KEY=your-api-key + ``` + +2. **构建项目** + + ```bash + cd subagent-personal-assistant-example + mvn clean package -DskipTests + ``` + +3. **运行应用** + + ```bash + mvn spring-boot:run + ``` + +4. **访问API** + + ``` shell + curl --location 'http://127.0.0.1:8080/react/agent/supervisorAgent?query=Schedule%20a%20meeting%20with%20the%20design%20team%20next%20Tuesday%20at%202pm%20for%201%20hour%2C%20and%20send%20them%20an%20email%20reminder%20about%20reviewing%20the%20new%20mockups.&threadId=user-session-124&nodeId=_AGENT_HOOK_HITL' \ +--header 'Content-Type: application/json' \ +--header 'Accept: text/event-stream' \ +--data '' + ```` +## Agent说明 +* supervisor_agent: 监督者智能体,负责协调多个子智能体,实现日历代理与邮件发送功能。 +* calendar_agent: 日历日程助理,检查可用时间和安排日历事件。 +* email_agent: 创建邮件主题和邮件发送。 +## 工具说明 + +| 工具名称 | 功能 | 输入参数 | +|---------|-------------------|-----------------------| +| `get_user_email_tool` | 获取用户邮箱 | UserInfo | +| `get_current_date_time` | 获取当前日期和时间 | 无 | +| `get_available_time_slots` | 获取可用时间 slots |AvailableTimeInfo | +| `create_calendar_event` | 创建日历事件 | CalendarInfo | +| `send_email` | 发送邮件 | EmailInfo | + +## 示例对话 + +``` +用户: Schedule a meeting with the design team next Tuesday at 2pm for 1 hour, and send them an email reminder about reviewing the new mockups. + +Agent: +I'll help you schedule a meeting with the design team and send them an email reminder. Let me break this down into steps: + +1. First, I need to get the email addresses of the design team members +2. Then schedule the calendar event for next Tuesday at 2pm for 1 hour +3. Finally, send an email reminder about reviewing the new mockups + +Let me start by getting the design team members' information: + +================================= Tool Message ================================= + +id: call_e06f221fa93b4e8ea77e62d0 +name: get_user_email_tool +responseData: "Available user list for [{\"departmentName\":\"design(设计团队)\",\"email\":\"wangwu@agent.cn\",\"userName\":\"wangwu(王五)\"}]" +Now I have the design team member information. I'll schedule the meeting for next Tuesday at 2pm for 1 hour with Wang Wu (王五) from the design team: + +检测到中断,需要人工审批 +工具: calendar_agent +参数: {"input": "Schedule a meeting with wangwu@agent.cn next Tuesday at 2pm for 1 hour. Subject: Design Team Meeting - Mockup Review"} +描述: The AI is requesting to use the tool: calendar_agent. +Description: Calendar event pending approval +With the following arguments: {"input": "Schedule a meeting with wangwu@agent.cn next Tuesday at 2pm for 1 hour. Subject: Design Team Meeting - Mockup Review"} +Do you approve? +检测到中断,等待人工介入... node: _AGENT_HOOK_HITL +人工介入开始... +检测到中断,需要人工审批 +工具: email_agent +参数: {"input": "Send an email to wangwu@agent.cn with subject 'Reminder: Design Team Meeting - Mockup Review' and content 'Hi Wang Wu, This is a reminder about our upcoming meeting next Tuesday (February 17, 2026) at 2:00 PM to review the new mockups. Please come prepared with your feedback. Looking forward to our discussion!'"} +描述: The AI is requesting to use the tool: email_agent. +Description: Outbound email pending approval +With the following arguments: {"input": "Send an email to wangwu@agent.cn with subject 'Reminder: Design Team Meeting - Mockup Review' and content 'Hi Wang Wu, This is a reminder about our upcoming meeting next Tuesday (February 17, 2026) at 2:00 PM to review the new mockups. Please come prepared with your feedback. Looking forward to our discussion!'"} +Do you approve? +检测到中断,等待人工介入... node: _AGENT_HOOK_HITL +人工介入开始... +Email sent to wangwu@agent.cn - Subject: Reminder: Design Team Meeting - Mockup Review + body: Hi Wang Wu, This is a reminder about our upcoming meeting next Tuesday (February 17, 2026) at 2:00 PM to review the new mockups. Please come prepared with your feedback. Looking forward to our discussion! +``` +## 注意事项 +1. 当检测到人工接入时,需要带着nodeId重新发起请求。 +2. 中断恢复是阻塞调用,等待时间较长。 +3. 当前模型使用的qwen3-max-2026-01-23,模型不同可能导致示例结果有所偏差。 + +## 相关链接 + +- [Spring AI Alibaba 文档](https://java2ai.com) +- [Spring AI Alibaba GitHub](https://github.com/alibaba/spring-ai-alibaba) \ No newline at end of file diff --git a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/HITLHelper.java b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/HITLHelper.java index 972209cd..ea7733d0 100644 --- a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/HITLHelper.java +++ b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/HITLHelper.java @@ -1,3 +1,18 @@ +/* + * Copyright 2026-2027 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.cloud.alibaba.ai.example.agent; import com.alibaba.cloud.ai.graph.action.InterruptionMetadata; diff --git a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/SubAgentPersonalAssistantApplication.java b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/SubAgentPersonalAssistantApplication.java index 356d48bf..dd797775 100644 --- a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/SubAgentPersonalAssistantApplication.java +++ b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/SubAgentPersonalAssistantApplication.java @@ -1,8 +1,27 @@ +/* + * Copyright 2026-2027 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.cloud.alibaba.ai.example.agent; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +/** + * @author wangjx + * @since 2026-02-13 + */ @SpringBootApplication public class SubAgentPersonalAssistantApplication { public static void main(String[] args) { diff --git a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/config/AgentConfig.java b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/config/AgentConfig.java index 208a7df0..1e68072b 100644 --- a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/config/AgentConfig.java +++ b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/config/AgentConfig.java @@ -1,3 +1,19 @@ +/* + * Copyright 2026-2027 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.cloud.alibaba.ai.example.agent.config; import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel; @@ -6,16 +22,21 @@ import com.alibaba.cloud.ai.graph.agent.hook.hip.HumanInTheLoopHook; import com.alibaba.cloud.ai.graph.agent.hook.hip.ToolConfig; import com.alibaba.cloud.ai.graph.checkpoint.savers.MemorySaver; -import com.cloud.alibaba.ai.example.agent.model.UserInfo; import com.cloud.alibaba.ai.example.agent.tool.*; import org.springframework.ai.tool.ToolCallback; -import org.springframework.ai.tool.function.FunctionToolCallback; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.List; - +/** + * Configuration class for setting up AI agents used in the personal assistant application. + * This class defines and configures various agents including calendar scheduling, + * email composition, and a supervisor agent that coordinates between them. + * + * @author wangjx + * @since 2026-02-13 + */ @Configuration public class AgentConfig { @@ -50,28 +71,25 @@ public AgentConfig(DashScopeChatModel dashScopeChatModel) { this.dashScopeChatModel = dashScopeChatModel; } - @Bean("reactAgent") + @Bean("supervisorAgent") public ReactAgent reactAgent() { // 配置检查点保存器(人工介入需要检查点来处理中断) MemorySaver memorySaver = new MemorySaver(); ToolCallback calendarAgent = AgentTool.getFunctionToolCallback(calendarAgent()); ToolCallback emailAgent = AgentTool.getFunctionToolCallback(emailAgent()); - FunctionToolCallback getUser = FunctionToolCallback.builder("get_user_email_tool", new GetUserDataTool()) - .description("You can provide the functionality to retrieve a user's email address by their username, and to obtain all user names within a department by specifying the department name.") - .inputType(UserInfo.class) - .build(); + return ReactAgent.builder() .name("supervisor_agent") .model(dashScopeChatModel) .systemPrompt(SUPERVISOR_PROMPT) .hooks(createHumanInTheLoopHook()) - .tools(List.of(calendarAgent, emailAgent, getUser)) + .tools(List.of(calendarAgent, emailAgent, new UserDataTool().toolCallback())) .saver(memorySaver) .build(); } - @Bean("emailAgent") + public ReactAgent emailAgent() { String instruction = @@ -85,7 +103,7 @@ public ReactAgent emailAgent() { """; // 创建 Agent return ReactAgent.builder() - .name("emailAgent") + .name("email_agent") .model(dashScopeChatModel) .tools(List.of(new SendEmailTool().toolCallback())) .systemPrompt(EMAIL_AGENT_PROMPT) @@ -107,9 +125,9 @@ public ReactAgent calendarAgent() { // 创建 Agent return ReactAgent.builder() - .name("calendarAgent") + .name("calendar_agent") .model(dashScopeChatModel) - .tools(List.of(new CreateCalendarEventTool().toolCallback(), new GetAvailableTimeSlotsTool().toolCallback(), new DateTimeTools().toolCallback())) + .tools(List.of(new CreateCalendarEventTool().toolCallback(), new AvailableTimeSlotsTool().toolCallback(), new DateTimeTools().toolCallback())) .systemPrompt(CALENDAR_AGENT_PROMPT) .instruction(instruction) .inputType(String.class) @@ -120,10 +138,10 @@ public ReactAgent calendarAgent() { private HumanInTheLoopHook createHumanInTheLoopHook() { // 创建人工介入Hook return HumanInTheLoopHook.builder() - .approvalOn("calendarAgent", ToolConfig.builder() + .approvalOn("calendar_agent", ToolConfig.builder() .description("Calendar event pending approval") .build()) - .approvalOn("emailAgent", ToolConfig.builder() + .approvalOn("email_agent", ToolConfig.builder() .description("Outbound email pending approval") .build()).build(); } diff --git a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/controller/PersonalAssistantController.java b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/controller/PersonalAssistantController.java index 6c43f52f..50827735 100644 --- a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/controller/PersonalAssistantController.java +++ b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/controller/PersonalAssistantController.java @@ -1,3 +1,19 @@ +/* + * Copyright 2026-2027 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.cloud.alibaba.ai.example.agent.controller; import com.alibaba.cloud.ai.graph.NodeOutput; @@ -7,11 +23,11 @@ import com.alibaba.cloud.ai.graph.agent.ReactAgent; import com.alibaba.cloud.ai.graph.exception.GraphRunnerException; import com.alibaba.cloud.ai.graph.streaming.StreamingOutput; -import com.alibaba.fastjson.JSON; import com.cloud.alibaba.ai.example.agent.HITLHelper; import org.springframework.ai.chat.messages.Message; import org.springframework.ai.chat.messages.ToolResponseMessage; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @@ -24,40 +40,58 @@ import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; +/** + * REST controller for managing personal assistant functionalities. + * This controller handles requests related to the supervisor agent, + * including streaming responses and human-in-the-loop interventions. + * + * @author wangjx + * @since 2026-02-13 + */ @RestController public class PersonalAssistantController { private static final Map> TOOL_FEEDBACK_MAP = new ConcurrentHashMap<>(); @Autowired - private ReactAgent reactAgent; + @Qualifier("supervisorAgent") + private ReactAgent supervisorAgent; + /** + * Handles GET requests to the supervisor agent endpoint. + * Supports both regular streaming and human-in-the-loop interventions. + * + * @param query the user's query string + * @param threadId the session thread identifier + * @param nodeId the node identifier for human intervention + * @return a Flux stream of responses from the supervisor agent + * @throws GraphRunnerException if there's an error during graph execution + */ @GetMapping(value = "/react/agent/supervisorAgent", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public Flux supervisorAgentTest(String query, String threadId, String nodeId) throws GraphRunnerException { RunnableConfig config; if (nodeId != null && TOOL_FEEDBACK_MAP.containsKey(nodeId)) { System.out.println("人工介入开始..."); - // 人工介入利用检查点机制。 - // 你必须提供线程ID以将执行与会话线程关联, - // 以便可以暂停和恢复对话(人工审查所需)。 + // Human intervention using checkpoint mechanism. + // You must provide a thread ID to associate execution with a session thread, + // so that conversations can be paused and resumed (required for human review). InterruptionMetadata metadata = InterruptionMetadata.builder().toolFeedbacks(TOOL_FEEDBACK_MAP.get(nodeId)).build(); InterruptionMetadata approvalMetadata = HITLHelper.approveAll(metadata); - // 使用批准决策恢复执行 + // Resume execution using approval decision config = RunnableConfig.builder() - .threadId(threadId) // 相同的线程ID + .threadId(threadId) // Same thread ID .addHumanFeedback(approvalMetadata) .build(); TOOL_FEEDBACK_MAP.remove(nodeId); return resumeStreamingWithHack(config); } else { - //String threadId = "user-session-123"; config = RunnableConfig.builder() .threadId(threadId) .build(); } - return reactAgent.stream(query, config) + return supervisorAgent.stream(query, config) .doOnNext(this::println); } @@ -66,7 +100,7 @@ public Flux resumeStreamingWithHack(RunnableConfig config) { return Mono.fromCallable(() -> { // 使用 invokeAndGetOutput 恢复(同步阻塞) - Optional result = reactAgent.invokeAndGetOutput("", config); + Optional result = supervisorAgent.invokeAndGetOutput("", config); if (result.isPresent()) { println(result.get()); return result.get(); @@ -90,7 +124,7 @@ private void println(NodeOutput nodeOutput) { if ("_AGENT_TOOL_".equals(node)) { ToolResponseMessage responseMessage = (ToolResponseMessage) message; List responses = responseMessage.getResponses(); - System.out.println("_AGENT_TOOL_"); + System.out.println("================================= Tool Message =================================\n"); for (ToolResponseMessage.ToolResponse respons : responses) { String string = respons.responseData(); System.out.println("id: " + respons.id()); @@ -113,8 +147,6 @@ private void println(NodeOutput nodeOutput) { OverAllState state = interruptionMetadata.state(); System.out.println("检测到中断,等待人工介入... node: " + node); TOOL_FEEDBACK_MAP.put(node, toolFeedbacks); - } else { - System.out.println("其它:" + JSON.toJSONString(nodeOutput)); } } } diff --git a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/model/AvailableTimeInfo.java b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/model/AvailableTimeInfo.java index 0c5cf7c2..6fb1a054 100644 --- a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/model/AvailableTimeInfo.java +++ b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/model/AvailableTimeInfo.java @@ -1,7 +1,28 @@ +/* + * Copyright 2026-2027 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.cloud.alibaba.ai.example.agent.model; import java.util.List; +/** + * + * @author wangjx + * @since 2026-02-13 + * */ public class AvailableTimeInfo { /** * 日期信息,表示可用时间的具体日期 diff --git a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/model/CalendarInfo.java b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/model/CalendarInfo.java index f37bda9e..dbc80fb6 100644 --- a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/model/CalendarInfo.java +++ b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/model/CalendarInfo.java @@ -1,7 +1,28 @@ +/* + * Copyright 2026-2027 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.cloud.alibaba.ai.example.agent.model; import java.util.List; +/** + * + * @author wangjx + * @since 2026-02-13 + * */ public class CalendarInfo { /** * 日历事件的标题 diff --git a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/model/EmailInfo.java b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/model/EmailInfo.java index 36446046..c7f05806 100644 --- a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/model/EmailInfo.java +++ b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/model/EmailInfo.java @@ -1,8 +1,28 @@ +/* + * Copyright 2026-2027 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.cloud.alibaba.ai.example.agent.model; import java.util.List; - +/** + * + * @author wangjx + * @since 2026-02-13 + * */ public class EmailInfo { /** * 收件人邮箱地址列表 diff --git a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/model/UserInfo.java b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/model/UserInfo.java index 0bf5db75..dc3196b8 100644 --- a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/model/UserInfo.java +++ b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/model/UserInfo.java @@ -1,5 +1,26 @@ +/* + * Copyright 2026-2027 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.cloud.alibaba.ai.example.agent.model; +/** + * + * @author wangjx + * @since 2026-02-13 + * */ public class UserInfo { /** * 用户名称 diff --git a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/tool/GetAvailableTimeSlotsTool.java b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/tool/AvailableTimeSlotsTool.java similarity index 52% rename from spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/tool/GetAvailableTimeSlotsTool.java rename to spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/tool/AvailableTimeSlotsTool.java index a7467149..a1e4a65f 100644 --- a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/tool/GetAvailableTimeSlotsTool.java +++ b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/tool/AvailableTimeSlotsTool.java @@ -1,3 +1,18 @@ +/* + * Copyright 2026-2027 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.cloud.alibaba.ai.example.agent.tool; import com.cloud.alibaba.ai.example.agent.model.AvailableTimeInfo; @@ -6,31 +21,44 @@ import org.springframework.ai.tool.function.FunctionToolCallback; import java.util.List; -import java.util.Map; import java.util.function.BiFunction; -public class GetAvailableTimeSlotsTool implements BiFunction { +/** + * A tool that retrieves available time slots for scheduling meetings. + * This tool implements the BiFunction interface to process input parameters + * and return formatted time slot information. + * + * @author wangjx + * @since 2026-02-13 + */ +public class AvailableTimeSlotsTool implements BiFunction { + + @Override public String apply(AvailableTimeInfo args, ToolContext toolContext) { - // 参数解析 + // Parse input parameters @SuppressWarnings("unchecked") List attendees = (List) args.getAttendees(); String date = (String) args.getDate(); int durationMinutes = (Integer) args.getDurationMinutes(); - // 验证日期格式(简化版) + + // Validate date format (simplified version) if (!isValidIsoDate(date)) { return "Error: Invalid ISO date format"; } - // 模拟查询可用时间槽 + + // Simulate querying available time slots List timeSlots = List.of("09:00", "14:00", "16:00"); return String.format("Available time slots for %s: %s", date, String.join(", ", timeSlots)); } + private boolean isValidIsoDate(String date) { - // 简单验证 ISO 8601 日期格式 + // Simple validation of ISO 8601 date format return date != null && date.matches("\\d{4}-\\d{2}-\\d{2}"); } + public ToolCallback toolCallback() { return FunctionToolCallback.builder("get_available_time_slots", this) .description("get_available_time_slots") diff --git a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/tool/CreateCalendarEventTool.java b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/tool/CreateCalendarEventTool.java index 50d58fe6..23bcffa0 100644 --- a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/tool/CreateCalendarEventTool.java +++ b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/tool/CreateCalendarEventTool.java @@ -1,7 +1,22 @@ +/* + * Copyright 2026-2027 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.cloud.alibaba.ai.example.agent.tool; import com.cloud.alibaba.ai.example.agent.model.CalendarInfo; -import com.cloud.alibaba.ai.example.agent.model.UserInfo; import org.springframework.ai.chat.model.ToolContext; import org.springframework.ai.tool.ToolCallback; import org.springframework.ai.tool.function.FunctionToolCallback; @@ -9,35 +24,45 @@ import java.util.List; import java.util.function.BiFunction; +/** + * A tool for creating calendar events. + * This class implements BiFunction to process CalendarInfo and ToolContext, + * validating input data and simulating event creation. + * + * @author wangjx + * @since 2026-02-13 + */ public class CreateCalendarEventTool implements BiFunction { + + @Override public String apply(CalendarInfo calendarInfo, ToolContext toolContext) { - // 参数解析 + // Parse parameters String title = calendarInfo.getTitle(); - String startTime =calendarInfo.getStartTime(); + String startTime = calendarInfo.getStartTime(); String endTime = calendarInfo.getEndTime(); @SuppressWarnings("unchecked") - List attendees =calendarInfo.getAttendees(); - // 验证时间格式(简化版) + List attendees = calendarInfo.getAttendees(); + + // Validate time format (simplified version) if (!isValidIsoDateTime(startTime) || !isValidIsoDateTime(endTime)) { return "Error: Invalid ISO datetime format"; } - // 模拟创建事件 + + // Simulate event creation return String.format("Event created: %s from %s to %s with %d attendees", title, startTime, endTime, attendees.size()); } private boolean isValidIsoDateTime(String datetime) { - // 简单验证 ISO 8601 格式(实际应使用更严格的验证) + // Simple validation of ISO 8601 format (should use stricter validation in production) return datetime != null && datetime.matches("\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}"); } - public ToolCallback toolCallback() { return FunctionToolCallback.builder("create_calendar_event", this) .description("create_calendar_event") .inputType(CalendarInfo.class) .build(); - } } diff --git a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/tool/DateTimeTools.java b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/tool/DateTimeTools.java index 8abc4c31..3cb03652 100644 --- a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/tool/DateTimeTools.java +++ b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/tool/DateTimeTools.java @@ -1,3 +1,18 @@ +/* + * Copyright 2026-2027 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.cloud.alibaba.ai.example.agent.tool; import org.springframework.ai.chat.model.ToolContext; @@ -9,14 +24,23 @@ import java.util.Map; import java.util.function.BiFunction; +/** + * Utility class for handling date and time operations. + * Provides functionality to retrieve the current date and time. + * + * @author wangjx + * @since 2026-02-13 + */ public class DateTimeTools implements BiFunction, ToolContext, String> { + + @Override public String apply(Map map, ToolContext toolContext) { return LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")); } public ToolCallback toolCallback() { - return FunctionToolCallback.builder("getCurrentDateTime", this) + return FunctionToolCallback.builder("getCurrentDateTime", this) .description("get_current_date_time") .inputType(Map.class) .build(); diff --git a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/tool/SendEmailTool.java b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/tool/SendEmailTool.java index 5c911dd3..85ac8cdc 100644 --- a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/tool/SendEmailTool.java +++ b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/tool/SendEmailTool.java @@ -1,3 +1,18 @@ +/* + * Copyright 2026-2027 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.cloud.alibaba.ai.example.agent.tool; import com.cloud.alibaba.ai.example.agent.model.EmailInfo; @@ -8,6 +23,16 @@ import java.util.List; import java.util.function.BiFunction; +/** + * A tool for sending emails based on natural language input. + * This class implements the BiFunction interface to process email information + * and simulate email sending functionality. + * The tool validates email addresses, formats the email content, + * and provides a callback mechanism for integration with AI agents. + * + * @author wangjx + * @since 2026-02-13 + */ public class SendEmailTool implements BiFunction { @Override public String apply(EmailInfo args, ToolContext toolContext) { @@ -23,28 +48,29 @@ public String apply(EmailInfo args, ToolContext toolContext) { } } // 模拟发送邮件 - System.out.printf("Email sent to %s - Subject: %s%n body: %s", String.join(", ", to), subject,body); + System.out.printf("Email sent to %s - Subject: %s%n body: %s", String.join(", ", to), subject, body); return String.format("Email sent to %s - Subject: %s", String.join(", ", to), subject); } + + private boolean isValidEmail(String email) { // 简单验证邮箱格式(实际应使用更严格的验证) return email != null && email.matches("^[\\w.-]+@[\\w.-]+\\.[a-zA-Z]{2,}$"); } public ToolCallback toolCallback() { - return FunctionToolCallback.builder("send_email",this) + return FunctionToolCallback.builder("send_email", this) .description(""" Send emails using natural language. - - Use this when the user wants to send notifications, reminders, or any email - communication. Handles recipient extraction, subject generation, and email - composition. - - Input: Natural language email request (e.g., 'send them a reminder about - the meeting + Use this when the user wants to send notifications, reminders, or any email + communication. Handles recipient extraction, subject generation, and email + composition. + Input: Natural language email request (e.g., 'send them a reminder about + the meeting """) .inputType(EmailInfo.class) .build(); } + } diff --git a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/tool/GetUserDataTool.java b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/tool/UserDataTool.java similarity index 54% rename from spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/tool/GetUserDataTool.java rename to spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/tool/UserDataTool.java index fad31c7a..d72f518b 100644 --- a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/tool/GetUserDataTool.java +++ b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/tool/UserDataTool.java @@ -1,3 +1,19 @@ +/* + * Copyright 2026-2027 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.cloud.alibaba.ai.example.agent.tool; import com.alibaba.fastjson.JSON; @@ -11,26 +27,34 @@ import java.util.List; import java.util.function.BiFunction; -public class GetUserDataTool implements BiFunction { +/** + * A tool class that provides functionality to retrieve user information based on username or department. + * It implements BiFunction interface to process UserInfo and ToolContext inputs and return relevant user data as a JSON string. + * The class contains a predefined list of users and supports filtering by username or department name. + * + * @author wangjx + * @since 2026-02-13 + */ +public class UserDataTool implements BiFunction { - private static List USER_INFO_LIST = new ArrayList<>(); + private static final List USER_INFO_LIST = new ArrayList<>(); static { UserInfo userInfo = new UserInfo(); - userInfo.setUserName("张三"); + userInfo.setUserName("zhangsan(张三)"); userInfo.setEmail("zhangsan@agent.cn"); - userInfo.setDepartmentName("研发部"); + userInfo.setDepartmentName("development(研发部)"); USER_INFO_LIST.add(userInfo); UserInfo userInfo1 = new UserInfo(); - userInfo1.setUserName("李四"); + userInfo1.setUserName("lisi(李四)"); userInfo1.setEmail("lisi@agent.cn"); - userInfo1.setDepartmentName("研发部"); + userInfo1.setDepartmentName("development(研发部)"); USER_INFO_LIST.add(userInfo1); UserInfo userInfo2 = new UserInfo(); - userInfo2.setUserName("王五"); + userInfo2.setUserName("wangwu(王五)"); userInfo2.setEmail("wangwu@agent.cn"); - userInfo2.setDepartmentName("设计团队"); + userInfo2.setDepartmentName("design(设计团队)"); USER_INFO_LIST.add(userInfo2); @@ -41,8 +65,8 @@ public String apply(UserInfo userInfo, ToolContext toolContext) { String userName = userInfo.getUserName(); if (StringUtils.hasLength(userName)) { - List userInfos = USER_INFO_LIST.stream().filter(f -> f.getUserName().equals(userName)).toList(); - if (userInfos != null && !userInfos.isEmpty()) { + List userInfos = USER_INFO_LIST.stream().filter(f -> f.getUserName().contains(userName)).toList(); + if (!userInfos.isEmpty()) { String str = JSON.toJSONString(userInfos); return String.format("Available user list for %s", str); } @@ -50,8 +74,8 @@ public String apply(UserInfo userInfo, ToolContext toolContext) { String department = userInfo.getDepartmentName(); if (StringUtils.hasLength(department)) { - List userInfos = USER_INFO_LIST.stream().filter(f -> f.getDepartmentName().equals(department)).toList(); - if (userInfos != null && !userInfos.isEmpty()) { + List userInfos = USER_INFO_LIST.stream().filter(f -> f.getDepartmentName().contains(department)).toList(); + if (!userInfos.isEmpty()) { String str = JSON.toJSONString(userInfos); return String.format("Available user list for %s", str); } diff --git a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/resources/application.yml b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/resources/application.yml index fb187374..8404a286 100644 --- a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/resources/application.yml +++ b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/resources/application.yml @@ -1,6 +1,6 @@ spring: application: - name: subagent-personal-assistant-examplee + name: subagent-personal-assistant-example ai: dashscope: api-key: ${DASHSCOPE_API_KEY} @@ -8,4 +8,4 @@ options: model: 'qwen3-max-2026-01-23' server: - port: 8083 + port: 8080 From 31901c1be5657d0e32476ecd5ee919dc6559017f Mon Sep 17 00:00:00 2001 From: wangjx Date: Fri, 13 Feb 2026 14:40:08 +0800 Subject: [PATCH 4/6] feat: add subagent personal assistant --- .../subagent-personal-assistant-example/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/README.md b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/README.md index 359ef18f..ebc74060 100644 --- a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/README.md +++ b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/README.md @@ -1,4 +1,4 @@ -# Spring AI Alibaba SQL Agent Example +# Spring AI Alibaba SubAgent Personal Assistant Example 本示例展示如何使用 Spring AI Alibaba 的 ReactAgent 框架构建一个多智能体监督者模式系统,通过主智能体协调多个子智能体,实现日历代理与邮件发送功能。 ## 功能特性 From 8aff290bbe763a7c8f999ff55159ad207de6d053 Mon Sep 17 00:00:00 2001 From: wangjx <631099904@qq.com> Date: Fri, 20 Mar 2026 22:01:33 +0800 Subject: [PATCH 5/6] feat: add subagent personal assistant --- .../README.md | 3 +-- .../PersonalAssistantController.java | 18 ++---------------- 2 files changed, 3 insertions(+), 18 deletions(-) diff --git a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/README.md b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/README.md index ebc74060..fee93e8d 100644 --- a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/README.md +++ b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/README.md @@ -101,8 +101,7 @@ Email sent to wangwu@agent.cn - Subject: Reminder: Design Team Meeting - Mockup ``` ## 注意事项 1. 当检测到人工接入时,需要带着nodeId重新发起请求。 -2. 中断恢复是阻塞调用,等待时间较长。 -3. 当前模型使用的qwen3-max-2026-01-23,模型不同可能导致示例结果有所偏差。 +2. 当前模型使用的qwen3-max-2026-01-23,模型不同可能导致示例结果有所偏差。 ## 相关链接 diff --git a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/controller/PersonalAssistantController.java b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/controller/PersonalAssistantController.java index 50827735..75e4464b 100644 --- a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/controller/PersonalAssistantController.java +++ b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/controller/PersonalAssistantController.java @@ -84,7 +84,8 @@ public Flux supervisorAgentTest(String query, String threadId, String nodeId) th .addHumanFeedback(approvalMetadata) .build(); TOOL_FEEDBACK_MAP.remove(nodeId); - return resumeStreamingWithHack(config); + return supervisorAgent.stream(query, config) + .doOnNext(this::println); } else { config = RunnableConfig.builder() @@ -96,21 +97,6 @@ public Flux supervisorAgentTest(String query, String threadId, String nodeId) th } - public Flux resumeStreamingWithHack(RunnableConfig config) { - - return Mono.fromCallable(() -> { - // 使用 invokeAndGetOutput 恢复(同步阻塞) - Optional result = supervisorAgent.invokeAndGetOutput("", config); - if (result.isPresent()) { - println(result.get()); - return result.get(); - } - return ""; - }) - .subscribeOn(Schedulers.boundedElastic()) - .flux() - .concatMap(Flux::just); - } private void println(NodeOutput nodeOutput) { if (nodeOutput instanceof StreamingOutput streamingOutput) { String node = streamingOutput.node(); From 17c071449e97ce42f2d1e443c93bf592ed933d54 Mon Sep 17 00:00:00 2001 From: wangjx Date: Mon, 23 Mar 2026 17:13:01 +0800 Subject: [PATCH 6/6] Fix the issues raised by Copilot. --- pom.xml | 1 - .../README.md | 6 +++--- .../controller/PersonalAssistantController.java | 5 ----- .../agent/tool/AvailableTimeSlotsTool.java | 7 ++----- .../agent/tool/CreateCalendarEventTool.java | 3 +-- .../ai/example/agent/tool/DateTimeTools.java | 4 ++-- .../ai/example/agent/tool/SendEmailTool.java | 17 +++++++---------- 7 files changed, 15 insertions(+), 28 deletions(-) diff --git a/pom.xml b/pom.xml index 169e8aab..4414ea16 100644 --- a/pom.xml +++ b/pom.xml @@ -80,7 +80,6 @@ spring-ai-alibaba-bailian-example spring-ai-alibaba-evaluation-example spring-ai-alibaba-mem0-example - spring-ai-alibaba-agent-example/subagent-personal-assistant-example diff --git a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/README.md b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/README.md index fee93e8d..c96a50eb 100644 --- a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/README.md +++ b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/README.md @@ -3,14 +3,14 @@ 本示例展示如何使用 Spring AI Alibaba 的 ReactAgent 框架构建一个多智能体监督者模式系统,通过主智能体协调多个子智能体,实现日历代理与邮件发送功能。 ## 功能特性 -- **多智能体**: Subagent使用Tool Calling 模式被 Supervisor Agen调用。 -- **人工介入**: 通过在Supervisor Agent配置hooks加入中断功能。 +- **多智能体**: Subagent 使用 Tool Calling 模式被 Supervisor Agent 调用。 +- **人工介入**: 通过在 Supervisor Agent 配置 hooks 加入中断功能。 ## 快速开始 ### 前置条件 -- Java 17+ +- Java 21+ - Maven 3.6+ - DashScope API Key diff --git a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/controller/PersonalAssistantController.java b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/controller/PersonalAssistantController.java index 75e4464b..5dcd904a 100644 --- a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/controller/PersonalAssistantController.java +++ b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/controller/PersonalAssistantController.java @@ -17,7 +17,6 @@ package com.cloud.alibaba.ai.example.agent.controller; import com.alibaba.cloud.ai.graph.NodeOutput; -import com.alibaba.cloud.ai.graph.OverAllState; import com.alibaba.cloud.ai.graph.RunnableConfig; import com.alibaba.cloud.ai.graph.action.InterruptionMetadata; import com.alibaba.cloud.ai.graph.agent.ReactAgent; @@ -32,12 +31,9 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import reactor.core.scheduler.Schedulers; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; /** @@ -130,7 +126,6 @@ private void println(NodeOutput nodeOutput) { System.out.println("描述: " + feedback.getDescription()); } String node = interruptionMetadata.node(); - OverAllState state = interruptionMetadata.state(); System.out.println("检测到中断,等待人工介入... node: " + node); TOOL_FEEDBACK_MAP.put(node, toolFeedbacks); } diff --git a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/tool/AvailableTimeSlotsTool.java b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/tool/AvailableTimeSlotsTool.java index a1e4a65f..d74223d4 100644 --- a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/tool/AvailableTimeSlotsTool.java +++ b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/tool/AvailableTimeSlotsTool.java @@ -37,11 +37,8 @@ public class AvailableTimeSlotsTool implements BiFunction attendees = (List) args.getAttendees(); - String date = (String) args.getDate(); - int durationMinutes = (Integer) args.getDurationMinutes(); - + String date =args.getDate(); + // Validate date format (simplified version) if (!isValidIsoDate(date)) { return "Error: Invalid ISO date format"; diff --git a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/tool/CreateCalendarEventTool.java b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/tool/CreateCalendarEventTool.java index 23bcffa0..4c925ffe 100644 --- a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/tool/CreateCalendarEventTool.java +++ b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/tool/CreateCalendarEventTool.java @@ -41,7 +41,6 @@ public String apply(CalendarInfo calendarInfo, ToolContext toolContext) { String title = calendarInfo.getTitle(); String startTime = calendarInfo.getStartTime(); String endTime = calendarInfo.getEndTime(); - @SuppressWarnings("unchecked") List attendees = calendarInfo.getAttendees(); // Validate time format (simplified version) @@ -51,7 +50,7 @@ public String apply(CalendarInfo calendarInfo, ToolContext toolContext) { // Simulate event creation return String.format("Event created: %s from %s to %s with %d attendees", - title, startTime, endTime, attendees.size()); + title, startTime, endTime, attendees==null ? 0:attendees.size()); } diff --git a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/tool/DateTimeTools.java b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/tool/DateTimeTools.java index 3cb03652..d7038fd0 100644 --- a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/tool/DateTimeTools.java +++ b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/tool/DateTimeTools.java @@ -36,11 +36,11 @@ public class DateTimeTools implements BiFunction, ToolContex @Override public String apply(Map map, ToolContext toolContext) { - return LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")); + return LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); } public ToolCallback toolCallback() { - return FunctionToolCallback.builder("getCurrentDateTime", this) + return FunctionToolCallback.builder("get_current_date_time", this) .description("get_current_date_time") .inputType(Map.class) .build(); diff --git a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/tool/SendEmailTool.java b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/tool/SendEmailTool.java index 85ac8cdc..da219f1c 100644 --- a/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/tool/SendEmailTool.java +++ b/spring-ai-alibaba-agent-example/subagent-personal-assistant-example/src/main/java/com/cloud/alibaba/ai/example/agent/tool/SendEmailTool.java @@ -37,15 +37,12 @@ public class SendEmailTool implements BiFunction @Override public String apply(EmailInfo args, ToolContext toolContext) { // 参数解析 - @SuppressWarnings("unchecked") - List to = (List) args.getTo(); - String subject = (String) args.getSubject(); - String body = (String) args.getBody(); + List to = args.getTo(); + String subject = args.getSubject(); + String body = args.getBody(); // 验证邮箱格式(简化版) - for (String email : to) { - if (!isValidEmail(email)) { - return "Error: Invalid email address: " + email; - } + if (to == null || to.isEmpty()){ + return "Error: No recipient email addresses provided."; } // 模拟发送邮件 System.out.printf("Email sent to %s - Subject: %s%n body: %s", String.join(", ", to), subject, body); @@ -65,8 +62,8 @@ public ToolCallback toolCallback() { Use this when the user wants to send notifications, reminders, or any email communication. Handles recipient extraction, subject generation, and email composition. - Input: Natural language email request (e.g., 'send them a reminder about - the meeting + Input: Natural language email request (e.g., "send them a reminder about + the meeting" """) .inputType(EmailInfo.class) .build();