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