{"id":52125,"date":"2025-05-07T00:00:00","date_gmt":"2025-05-07T07:00:00","guid":{"rendered":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/blog\/scheduling-assistants-springai\/"},"modified":"2025-05-07T00:00:00","modified_gmt":"2025-05-07T07:00:00","slug":"scheduling-assistants-springai","status":"publish","type":"post","link":"https:\/\/griddb.net\/en\/blog\/scheduling-assistants-springai\/","title":{"rendered":"Building a Scheduling Assistants with SpringAI"},"content":{"rendered":"<p>In this blog post, we\u2019ll walk through how to build a personal AI assistant that simplifies managing your calendar. By the end, you\u2019ll know how to create an assistant capable of handling event submissions and retrieving schedules through simple conversations. We\u2019ll use <strong>Spring Boot<\/strong>, <strong>Spring AI<\/strong>, and <strong>OpenAI<\/strong> to build a system that\u2019s both practical and enjoyable to interact with.<\/p>\n<h2>Why Build a Personal AI Calendar Assistant?<\/h2>\n<p>Managing tasks through natural language might seem like something straight out of a sci-fi movie, but it\u2019s more useful than you might expect. This AI assistant can save you time, eliminate the hassle of manual input, and make managing your schedule a breeze.<\/p>\n<p>Additionally, building this project is a fantastic way to sharpen your skills as a developer. If you\u2019re a computer science student or an aspiring developer, you\u2019ll gain valuable hands-on experience with AI integration, backend development, and database management, all while creating a tool you can use in your daily life.<\/p>\n<h2>System Overview<\/h2>\n<p>Before diving into the details of coding, let\u2019s take a moment to understand how the entire system is structured. Here\u2019s a high-level overview of how everything works:<\/p>\n<p>This application features a single page to display the event list and includes a <code>chatbot<\/code> interface for user interaction.<\/p>\n<h3>User Interaction via Chat<\/h3>\n<p>The chatbot interface allows users to interact with the AI assistant using natural language commands. For example:<\/p>\n<ul>\n<li><strong>Submit Events<\/strong>: Add events by chatting with the assistant. For example, you could say, <em>\u201cI want to go to the Shopping Center Tomorrow at 2 PM.\u201c<\/em><\/li>\n<li><strong>List Events<\/strong>: Check your schedule by asking, <em>\u201cShow my events for tomorrow\u201c<\/em><\/li>\n<\/ul>\n<p>The AI assistant processes these commands by understanding the user&#8217;s queries, extracting critical details such as intent, time, and location, and then performing the appropriate action\u2014like saving the event or retrieving a list of upcoming events.<\/p>\n<h3>Backend System (Spring Boot)<\/h3>\n<p>The backend serves as the engine of the system, handling several key tasks:<\/p>\n<ul>\n<li><strong>API Handling<\/strong>: Receives user input from the chatbot interface.<\/li>\n<li><strong>Event Management<\/strong>: Manages the storage and retrieval of events from the database.<\/li>\n<li><strong>Spring AI<\/strong>: Manages the AI logic and communicates with the OpenAI API.<\/li>\n<\/ul>\n<h3>AI Module (Spring AI + OpenAI API)<\/h3>\n<p>This module functions as the brain of the assistant. Here\u2019s how it operates:<\/p>\n<ol>\n<li>\n<p><strong>Input Parsing<\/strong>: The AI module processes user queries and leverages the OpenAI API to extract key details such as the event title, time, and location.<\/p>\n<\/li>\n<li>\n<p><strong>Intent Recognition<\/strong>: Determines the user\u2019s intention, whether it\u2019s adding an event or listing upcoming events.<\/p>\n<\/li>\n<li>\n<p><strong>Response Generation<\/strong>: Produces a user-friendly response based on the action performed.<\/p>\n<\/li>\n<\/ol>\n<p>Spring AI acts as a wrapper around the OpenAI API, streamlining the integration process and allowing you to focus on core application logic instead of implementation complexities.<\/p>\n<h3>Data Storage (Database Layer)<\/h3>\n<p>The database layer ensures that all events are securely stored and can be retrieved when needed. Here\u2019s what happens at this level:<\/p>\n<ol>\n<li><strong>Event Storage<\/strong>: Stores each event submitted through the chatbot.<\/li>\n<li><strong>Query<\/strong>: Fetches relevant events from the database when the user requests their schedule.<\/li>\n<\/ol>\n<p>For this project, we\u2019ll use GridDB as our database solution.<\/p>\n<p>Now that we\u2019ve covered the system architecture, let\u2019s get started with building the application!<\/p>\n<h2>Step-by-Step Guide to Building the Project<\/h2>\n<p>The following items should be installed in your system:<\/p>\n<ul>\n<li>Java 17 or later: <a href=\"https:\/\/adoptium.net\/temurin\/releases\/\">OpenJDK<\/a><\/li>\n<li><a href=\"https:\/\/maven.apache.org\/download.cgi\">Maven<\/a><\/li>\n<li>Your preferred IDE: <a href=\"https:\/\/code.visualstudio.com\/\">VS Code<\/a>, <a href=\"https:\/\/spring.io\/guides\/gs\/intellij-idea\/\">Initellij IDEA<\/a><\/li>\n<li><a href=\"https:\/\/docs.docker.com\/compose\/install\/\">Docker Compose<\/a><\/li>\n<\/ul>\n<h3>OpenAI API<\/h3>\n<p>We need to create an API Key with OpenAI to access ChatGPT models. Create an account and generate the token on the <a href=\"https:\/\/platform.openai.com\/account\/api-keys\">API Keys page<\/a>.<\/p>\n<h3>Initialize a Spring Boot Project<\/h3>\n<p>You can use this <a href=\"https:\/\/start.spring.io\/#!type=maven-project&amp;language=java&amp;platformVersion=3.4.1&amp;packaging=jar&amp;jvmVersion=17&amp;groupId=com.example&amp;artifactId=springai-personalassistant&amp;name=springai-personalassistant&amp;description=Personal%20Assistant%20Spring%20AI&amp;packageName=com.example.personalassistant&amp;dependencies=web,thymeleaf,spring-ai-openai,devtools,lombok\">pre-initialized project<\/a> and click Generate to download a Zip file.<\/p>\n<p>You can also fork the project from Github and open it in your IDE or other editor.<\/p>\n<h3>Spring AI Dependency<\/h3>\n<p>Add Milestone and Snapshot Repositories<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-sh\">&lt;repositories&gt;\n  &lt;repository&gt;\n    &lt;id&gt;spring-milestones&lt;\/id&gt;\n    &lt;name&gt;Spring Milestones&lt;\/name&gt;\n    &lt;url&gt;https:\/\/repo.spring.io\/milestone&lt;\/url&gt;\n    &lt;snapshots&gt;\n      &lt;enabled&gt;false&lt;\/enabled&gt;\n    &lt;\/snapshots&gt;\n  &lt;\/repository&gt;\n  &lt;repository&gt;\n    &lt;id&gt;spring-snapshots&lt;\/id&gt;\n    &lt;name&gt;Spring Snapshots&lt;\/name&gt;\n    &lt;url&gt;https:\/\/repo.spring.io\/snapshot&lt;\/url&gt;\n    &lt;releases&gt;\n      &lt;enabled&gt;false&lt;\/enabled&gt;\n    &lt;\/releases&gt;\n  &lt;\/repository&gt;\n&lt;\/repositories&gt;<\/code><\/pre>\n<\/div>\n<p>Add Spring AI Bill of Materials (BOM)<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-sh\">&lt;dependencyManagement&gt;\n    &lt;dependencies&gt;\n        &lt;dependency&gt;\n            &lt;groupId&gt;org.springframework.ai&lt;\/groupId&gt;\n            &lt;artifactId&gt;spring-ai-bom&lt;\/artifactId&gt;\n            &lt;version&gt;1.0.0-SNAPSHOT&lt;\/version&gt;\n            &lt;type&gt;pom&lt;\/type&gt;\n            &lt;scope&gt;import&lt;\/scope&gt;\n        &lt;\/dependency&gt;\n    &lt;\/dependencies&gt;\n&lt;\/dependencyManagement&gt;<\/code><\/pre>\n<\/div>\n<p>Add SpringAI OpenAI Spring Boot starter<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-sh\">&lt;dependency&gt;\n    &lt;groupId&gt;org.springframework.ai&lt;\/groupId&gt;\n    &lt;artifactId&gt;spring-ai-openai-spring-boot-starter&lt;\/artifactId&gt;\n&lt;\/dependency&gt;<\/code><\/pre>\n<\/div>\n<p>Add GridDB dependency<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-sh\">&lt;dependency&gt;\n    &lt;groupId&gt;com.github.griddb&lt;\/groupId&gt;\n    &lt;artifactId&gt;gridstore&lt;\/artifactId&gt;\n    &lt;version&gt;5.6.0&lt;\/version&gt;\n&lt;\/dependency&gt;\n<\/code><\/pre>\n<\/div>\n<h2>Storing and Managing Events<\/h2>\n<p><a href=\"https:\/\/griddb.net\/wp-content\/uploads\/2025\/05\/database-schema.png\"><img fetchpriority=\"high\" decoding=\"async\" src=\"https:\/\/griddb.net\/wp-content\/uploads\/2025\/05\/database-schema.png\" alt=\"\" width=\"500\" height=\"296\" class=\"aligncenter size-full wp-image-31531\" srcset=\"\/wp-content\/uploads\/2025\/05\/database-schema.png 500w, \/wp-content\/uploads\/2025\/05\/database-schema-300x178.png 300w\" sizes=\"(max-width: 500px) 100vw, 500px\" \/><\/a><\/p>\n<p>In this project we have a simple calendar system with two main entities: <code>User<\/code> and <code>Event<\/code>. Each event is associated with a specific user.<\/p>\n<p>Based on the schema above, we will create the entity classes as follows:<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-java\">@Data\npublic class User {\n    @RowKey\n    String id;\n    String email;\n    String fullName;\n}\n\n@Data\npublic class Event {\n    @RowKey\n    private String id;\n    private String title;\n    private String location;\n    private Date startTime;\n    private Date endTime;\n    private String userId;\n}\n<\/code><\/pre>\n<\/div>\n<p>Next, we create the <code>GridDBConfig<\/code> class as a central configuration for database operation.<br \/>\nThe class will do the following:<\/p>\n<ul>\n<li>Read environment variables for connecting to the GridDB database<\/li>\n<li>Create a GridStore class for managing database connection to the GridDB instance<\/li>\n<li>Create GridDB Collection&#8217;s container (Table) to manage a set of rows. The container is a rough equivalent of the table in a relational database.<\/li>\n<li>On creating\/updating the Collection we specify the name and object corresponding to the column layout of the collection.<br \/>\nAlso for each collection, we add an index for a column that is frequently searched and used in the condition of the WHERE section of TQL.<\/li>\n<li>Make the container available in the Spring container<\/li>\n<\/ul>\n<div class=\"clipboard\">\n<pre><code class=\"language-java\">@Configuration\npublic class GridDBConfig {\n\n    @Value(\"${GRIDDB_NOTIFICATION_MEMBER}\")\n    private String notificationMember;\n    @Value(\"${GRIDDB_CLUSTER_NAME}\")\n    private String clusterName;\n    @Value(\"${GRIDDB_USER}\")\n    private String user;\n    @Value(\"${GRIDDB_PASSWORD}\")\n    private String password;\n\n    @Bean\n    public GridStore gridStore() throws GSException {\n        Properties properties = new Properties();\n        properties.setProperty(\"notificationMember\", notificationMember);\n        properties.setProperty(\"clusterName\", clusterName);\n        properties.setProperty(\"user\", user);\n        properties.setProperty(\"password\", password);\n        GridStore store = GridStoreFactory.getInstance().getGridStore(properties);\n        return store;\n    }\n\n    @Bean\n    public Collection&lt;String, User&gt; userCollection(GridStore gridStore) throws GSException {\n        Collection&lt;String, User&gt; collection = gridStore.putCollection(AppConstant.USERS_CONTAINER, User.class);\n        collection.createIndex(\"email\");\n        return collection;\n    }\n\n    @Bean\n    public Collection&lt;String, Event&gt; eventCollection(GridStore gridStore) throws GSException {\n        Collection&lt;String, Event&gt; movieCollection = gridStore.putCollection(AppConstant.EVENT_CONTAINER, Event.class);\n        movieCollection.createIndex(\"userId\");\n        return movieCollection;\n    }\n}<\/code><\/pre>\n<\/div>\n<h3>Business Logic<\/h3>\n<h4><code>EventService<\/code> class<\/h4>\n<p>This service class handles event creation and listing.<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-java\">@Slf4j\n@Service\npublic class EventService {\n\n    private final Collection&lt;String, Event&gt; eventCollection;\n    private final Collection&lt;String, User&gt; userCollection;\n\n    public EventService(Collection&lt;String, Event&gt; eventCollection, Collection&lt;String, User&gt; userCollection) {\n        this.eventCollection = eventCollection;\n        this.userCollection = userCollection;\n    }\n\n    public List&lt;EventDTO&gt; findAll(String userId) {\n        if (userId != null && !userId.isBlank()) {\n            return fetchAll(userId).stream().map(event -&gt; mapToDTO(event, new EventDTO())).toList();\n        }\n        final List&lt;Event&gt; events = fetchAll();\n        return events.stream().map(event -&gt; mapToDTO(event, new EventDTO())).toList();\n    }\n\n    public String create(final EventDTO eventDTO, String userId) {\n        final Event event = new Event();\n        mapToEntity(eventDTO, event);\n        event.setUserId(userId);\n        event.setId(IdGenerator.next(\"ev_\"));\n        try {\n            eventCollection.put(event);\n            return event.getId();\n        }\n        catch (GSException e) {\n            throw new AppErrorException(\"Failed to create event\");\n        }\n    }\n}<\/code><\/pre>\n<\/div>\n<h4><code>UserService<\/code> class<\/h4>\n<p>This class handles user creation.<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-java\">@Slf4j\n@Service\npublic class UserService {\n\n    private final Collection&lt;String, User&gt; userCollection;\n\n    public UserService(Collection&lt;String, User&gt; userCollection) {\n        this.userCollection = userCollection;\n    }\n\n    public Optional&lt;User&gt; findByEmail(final String emailString) {\n        try (Query&lt;User&gt; query = userCollection.query(\"SELECT * WHERE email='\" + emailString + \"'\", User.class)) {\n            RowSet&lt;User&gt; rowSet = query.fetch();\n            if (rowSet.hasNext()) {\n                User user = rowSet.next();\n                return Optional.of(user);\n            }\n            else {\n                throw new NotFoundException(\"User not found\");\n            }\n        }\n        catch (GSException e) {\n            throw new AppErrorException(\"Failed to find user\");\n        }\n    }<\/code><\/pre>\n<\/div>\n<h2>Connecting OpenAI<\/h2>\n<p>To connect to OpenAI&#8217;s API, we need to configure the API key and specify the name of the OpenAI model for accessing the LLM.<\/p>\n<p>This configuration is done in the <code>application.yml<\/code> file:<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-sh\">spring:\n  ai:\n    openai:\n      api-key: ${OPENAI_API_KEY}\n      chat:\n        options:\n          model: gpt-4o-mini<\/code><\/pre>\n<\/div>\n<p>Here, <code>${OPENAI_API_KEY}<\/code> retrieves the API key from an environment variable.<\/p>\n<p>For this project, we are using the <code>gpt-4o-mini<\/code> model.<\/p>\n<h3>Initialize the Spring AI ChatClient<\/h3>\n<p>Below is the implementation of the <code>PersonalAssistant<\/code> class, which initializes the <code>ChatClient<\/code>, processes user queries, and sends them to the OpenAI API.<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-java\">@Service\npublic class PersonalAssistant {\n    private final ChatClient chatClient;\n\n    public PersonalAssistant(ChatClient.Builder modelBuilder, ChatMemory chatMemory) {\n        \/\/ @formatter:off\n        this.chatClient = modelBuilder.defaultSystem(\"\"\"\n            You are a personal assistant and travel planner.\n            Your job is to answer questions about and to perform actions on the user's behalf, mainly around\n            calendar events, and time-management.\n            You are required to answer an a professional manner. If you don't know the answer, politely tell the user\n            you don't know the answer, then ask the user a followup question to try and clarify the question they are asking.\n            If you do know the answer, provide the answer but do not provide any additional followup questions.\n            Use the provided functions to fetch user's events by email, and create new event.\n            Before creating new event, you MUST always get the following information from the user:\n            1. Email\n            2. Location\n            3. Start time\n            4. End time: If not provided, assume it ended in one hour.\n            5. Title: Get title from user's intent and interest.\n            Today is {current_date}.\n        \"\"\")\n        .defaultAdvisors(\n            new MessageChatMemoryAdvisor(chatMemory, DEFAULT_CHAT_MEMORY_CONVERSATION_ID, 10),\n            new SimpleLoggerAdvisor()\n        )\n        .defaultFunctions(\"getUserEvents\", \"createEvent\")\n        .build();\n        \/\/ @formatter:on\n    }\n\n    public String chat(String chatId, String userMessageContent) {\n        return this.chatClient.prompt()\n                .system(s -&gt; s.param(\"current_date\", LocalDate.now().toString()))\n                .user(userMessageContent)\n                .call().content();\n    }\n}<\/code><\/pre>\n<\/div>\n<ul>\n<li>We obtain an auto-configured <code>ChatClient.Builder<\/code> and use it to create the <code>ChatClient<\/code>. The <code>ChatClient<\/code> is a Spring Bean provided by Spring AI that manages sending user input to the LLM.<\/li>\n<li>To make our chatbot focus on functioning as a personal assistant and avoid providing irrelevant information, we utilize a system message to guide the model&#8217;s behavior and specify the desired output. This system message is defined within the <code>defaultSystem()<\/code> method.<\/li>\n<li>We add chat memory to maintain context for up to 10 previous messages when using the chatbot, ensuring more cohesive interactions.<\/li>\n<li>We include a <code>SimpleLoggerAdvisor<\/code> to log request and response data from the <code>ChatClient<\/code>, which is helpful for debugging and monitoring AI interactions.<\/li>\n<li>We register the <code>getUserEvents()<\/code> and <code>createEvent()<\/code> functions to enable the LLM to interact with existing business logic.<\/li>\n<li>The <code>chat()<\/code> method accepts a user message, passes it to the Spring AI ChatClient bean as input, and returns the result from the <code>content()<\/code>.<\/li>\n<\/ul>\n<h3>Function Calling<\/h3>\n<p>Here&#8217;s how function calling works in this project:<\/p>\n<ol>\n<li>The user types something like, <code>Give me my schedule for tomorrow<\/code>.<\/li>\n<li>Spring AI connects to the OpenAI API, processes the text, and extracts the required information.<\/li>\n<li>Using function calling, the AI model dynamically determines which function to trigger.<\/li>\n<li>Spring AI executes the relevant function with the extracted parameters (e.g., <code>getUserEvents()<\/code>).<\/li>\n<li>Spring AI calls the OpenAI API again, including the function&#8217;s response, to generate the final reply.<\/li>\n<\/ol>\n<p>Now, let&#8217;s map our functions so we can use them with Spring AI.<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-java\">@Configuration\npublic class EventTools {\n    private static final Logger logger = LoggerFactory.getLogger(EventTools.class);\n    @Autowired\n    private EventService eventService;\n    @Autowired\n    private UserService userService;\n\n    public record EventListRequest(String email) {}\n\n    public record EventViewDTO(String id, String title, String location, LocalDateTime startTime, LocalDateTime endTime,\n            UserViewDTO user) {}\n\n    public record UserViewDTO(String name) {}\n\n    @Bean\n    @Description(\"Get event list for given users email\")\n    public Function&lt;EventListRequest, List&lt;EventViewDTO&gt;&gt; getUserEvents() {\n        return request -&gt; {\n            Optional&lt;User&gt; user = userService.findByEmail(request.email());\n            return eventService.findAll(user.get().getEmail()).stream().map(this::mapToViewDTO).toList();\n        };\n    }\n\n    private EventViewDTO mapToViewDTO(EventDTO eventDTO) {\n        return new EventViewDTO(eventDTO.getId(), eventDTO.getTitle(), eventDTO.getLocation(), eventDTO.getStartTime(),\n                eventDTO.getEndTime(), new UserViewDTO(eventDTO.getUser().name()));\n    }\n\n    public record CreateEventRequest(String email, String title, String location, LocalDateTime startTime,\n            LocalDateTime endTime) {\n    }\n\n    @Bean\n    @Description(\"Create new event with specified email, title, location, start-time, and end-time.\")\n    public Function&lt;CreateEventRequest, String&gt; createEvent() {\n        return request -&gt; {\n            logger.debug(\"call function create event {}\", request);\n            Optional&lt;User&gt; user = userService.findByEmail(request.email());\n            EventDTO eventDTO = new EventDTO();\n            eventDTO.setTitle(request.title());\n            eventDTO.setLocation(request.location());\n            eventDTO.setStartTime(request.startTime());\n            eventDTO.setEndTime(request.endTime());\n            return eventService.create(eventDTO, user.get().getId());\n        };\n    }\n}<\/code><\/pre>\n<\/div>\n<ul>\n<li>Define a <code>@Bean<\/code> method that returns a <code>java.util.function.Function<\/code>.<\/li>\n<li>Add the <code>@Description<\/code> annotation to provide a clear explanation of what this function does.<\/li>\n<li>Spring AI can leverage the service classes we\u2019ve already developed without requiring a complete rewrite.<\/li>\n<\/ul>\n<h2>Chat Interface<\/h2>\n<p>The chatbox UI is developed using Thymeleaf, Javascript, and CSS.<br \/>\nThe chatbox is designed to resemble message bubbles, similar to iMessage, and supports using the Enter key to send messages.<br \/>\nWe use AJAX to handle HTTP requests and responses seamlessly.<\/p>\n<p><a href=\"https:\/\/griddb.net\/wp-content\/uploads\/2025\/05\/springboot-springai-events.png\"><img decoding=\"async\" src=\"https:\/\/griddb.net\/wp-content\/uploads\/2025\/05\/springboot-springai-events.png\" alt=\"\" width=\"1000\" height=\"558\" class=\"aligncenter size-full wp-image-31532\" srcset=\"\/wp-content\/uploads\/2025\/05\/springboot-springai-events.png 1000w, \/wp-content\/uploads\/2025\/05\/springboot-springai-events-300x167.png 300w, \/wp-content\/uploads\/2025\/05\/springboot-springai-events-768x429.png 768w, \/wp-content\/uploads\/2025\/05\/springboot-springai-events-150x85.png 150w, \/wp-content\/uploads\/2025\/05\/springboot-springai-events-600x335.png 600w\" sizes=\"(max-width: 1000px) 100vw, 1000px\" \/><\/a><\/p>\n<p><a href=\"https:\/\/griddb.net\/wp-content\/uploads\/2025\/05\/create-event-1.png\"><img decoding=\"async\" src=\"https:\/\/griddb.net\/wp-content\/uploads\/2025\/05\/create-event-1.png\" alt=\"\" width=\"308\" height=\"451\" class=\"aligncenter size-full wp-image-31528\" srcset=\"\/wp-content\/uploads\/2025\/05\/create-event-1.png 308w, \/wp-content\/uploads\/2025\/05\/create-event-1-205x300.png 205w\" sizes=\"(max-width: 308px) 100vw, 308px\" \/><\/a><\/p>\n<p><a href=\"https:\/\/griddb.net\/wp-content\/uploads\/2025\/05\/create-event-2.png\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/griddb.net\/wp-content\/uploads\/2025\/05\/create-event-2.png\" alt=\"\" width=\"315\" height=\"457\" class=\"aligncenter size-full wp-image-31529\" srcset=\"\/wp-content\/uploads\/2025\/05\/create-event-2.png 315w, \/wp-content\/uploads\/2025\/05\/create-event-2-207x300.png 207w\" sizes=\"(max-width: 315px) 100vw, 315px\" \/><\/a><\/p>\n<p><a href=\"https:\/\/griddb.net\/wp-content\/uploads\/2025\/05\/create-event-3.png\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/griddb.net\/wp-content\/uploads\/2025\/05\/create-event-3.png\" alt=\"\" width=\"312\" height=\"463\" class=\"aligncenter size-full wp-image-31530\" srcset=\"\/wp-content\/uploads\/2025\/05\/create-event-3.png 312w, \/wp-content\/uploads\/2025\/05\/create-event-3-202x300.png 202w\" sizes=\"(max-width: 312px) 100vw, 312px\" \/><\/a><\/p>\n<h3>Running the Project with Docker Compose<\/h3>\n<p>To spin up the project we will utilize Docker Compose.<br \/>\nThe entire code for the web application is available on <a href=\"https:\/\/github.com\/alifruliarso?tab=repositories\">Github<\/a>.<\/p>\n<p>Before starting the application, make sure you have the <code>API Key<\/code> from OpenAI.<\/p>\n<p>Create <code>.env<\/code> file with the following content:<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-sh\">OPENAI_API_KEY='YOUR_OPENAI_API_KEY'<\/code><\/pre>\n<\/div>\n<p>Build the services: <code>docker compose build<\/code><\/p>\n<p>Start the services: <code>docker compose up<\/code><\/p>\n<p>After starting the application it is accessible under <a href=\"http:\/\/localhost:8080\">localhost:8080<\/a>.<\/p>\n<h2>Conclusion<\/h2>\n<p>Spring AI makes it easier to add AI features to Spring-based applications. It allows AI code to work alongside existing business logic in the same codebase.<\/p>\n<h3>What can be improved?<\/h3>\n<ul>\n<li>Add logs for chatbox messages (input and output).<\/li>\n<li>Make it easy for users to give feedback on chatbox responses.<\/li>\n<li>Implement safety measures like moderation.<\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>In this blog post, we\u2019ll walk through how to build a personal AI assistant that simplifies managing your calendar. By the end, you\u2019ll know how to create an assistant capable of handling event submissions and retrieving schedules through simple conversations. We\u2019ll use Spring Boot, Spring AI, and OpenAI to build a system that\u2019s both practical [&hellip;]<\/p>\n","protected":false},"author":41,"featured_media":52126,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[121],"tags":[],"class_list":["post-52125","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.1.1 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Building a Scheduling Assistants with SpringAI | GridDB: Open Source Time Series Database for IoT<\/title>\n<meta name=\"description\" content=\"In this blog post, we\u2019ll walk through how to build a personal AI assistant that simplifies managing your calendar. By the end, you\u2019ll know how to create\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/blog\/scheduling-assistants-springai\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Building a Scheduling Assistants with SpringAI | GridDB: Open Source Time Series Database for IoT\" \/>\n<meta property=\"og:description\" content=\"In this blog post, we\u2019ll walk through how to build a personal AI assistant that simplifies managing your calendar. By the end, you\u2019ll know how to create\" \/>\n<meta property=\"og:url\" content=\"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/blog\/scheduling-assistants-springai\/\" \/>\n<meta property=\"og:site_name\" content=\"GridDB: Open Source Time Series Database for IoT\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/griddbcommunity\/\" \/>\n<meta property=\"article:published_time\" content=\"2025-05-07T07:00:00+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/griddb.net\/wp-content\/uploads\/2025\/12\/aiassistantcover1.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"1024\" \/>\n\t<meta property=\"og:image:height\" content=\"1024\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"griddb-admin\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@GridDBCommunity\" \/>\n<meta name=\"twitter:site\" content=\"@GridDBCommunity\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"griddb-admin\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"12 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/blog\/scheduling-assistants-springai\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/blog\/scheduling-assistants-springai\/\"},\"author\":{\"name\":\"griddb-admin\",\"@id\":\"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/#\/schema\/person\/4fe914ca9576878e82f5e8dd3ba52233\"},\"headline\":\"Building a Scheduling Assistants with SpringAI\",\"datePublished\":\"2025-05-07T07:00:00+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/blog\/scheduling-assistants-springai\/\"},\"wordCount\":1330,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/#organization\"},\"image\":{\"@id\":\"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/blog\/scheduling-assistants-springai\/#primaryimage\"},\"thumbnailUrl\":\"\/wp-content\/uploads\/2025\/12\/aiassistantcover1.jpg\",\"articleSection\":[\"Blog\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/blog\/scheduling-assistants-springai\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/blog\/scheduling-assistants-springai\/\",\"url\":\"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/blog\/scheduling-assistants-springai\/\",\"name\":\"Building a Scheduling Assistants with SpringAI | GridDB: Open Source Time Series Database for IoT\",\"isPartOf\":{\"@id\":\"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/blog\/scheduling-assistants-springai\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/blog\/scheduling-assistants-springai\/#primaryimage\"},\"thumbnailUrl\":\"\/wp-content\/uploads\/2025\/12\/aiassistantcover1.jpg\",\"datePublished\":\"2025-05-07T07:00:00+00:00\",\"description\":\"In this blog post, we\u2019ll walk through how to build a personal AI assistant that simplifies managing your calendar. By the end, you\u2019ll know how to create\",\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/blog\/scheduling-assistants-springai\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/blog\/scheduling-assistants-springai\/#primaryimage\",\"url\":\"\/wp-content\/uploads\/2025\/12\/aiassistantcover1.jpg\",\"contentUrl\":\"\/wp-content\/uploads\/2025\/12\/aiassistantcover1.jpg\",\"width\":1024,\"height\":1024},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/#website\",\"url\":\"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/\",\"name\":\"GridDB: Open Source Time Series Database for IoT\",\"description\":\"GridDB is an open source time-series database with the performance of NoSQL and convenience of SQL\",\"publisher\":{\"@id\":\"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/#organization\",\"name\":\"Fixstars\",\"url\":\"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/griddb.net\/wp-content\/uploads\/2019\/04\/fixstars_logo_web_tagline.png\",\"contentUrl\":\"https:\/\/griddb.net\/wp-content\/uploads\/2019\/04\/fixstars_logo_web_tagline.png\",\"width\":200,\"height\":83,\"caption\":\"Fixstars\"},\"image\":{\"@id\":\"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/www.facebook.com\/griddbcommunity\/\",\"https:\/\/x.com\/GridDBCommunity\",\"https:\/\/www.linkedin.com\/company\/griddb-by-toshiba\"]},{\"@type\":\"Person\",\"@id\":\"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/#\/schema\/person\/4fe914ca9576878e82f5e8dd3ba52233\",\"name\":\"griddb-admin\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/5bceca1cafc06886a7ba873e2f0a28011a1176c4dea59709f735b63ae30d0342?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/5bceca1cafc06886a7ba873e2f0a28011a1176c4dea59709f735b63ae30d0342?s=96&d=mm&r=g\",\"caption\":\"griddb-admin\"},\"url\":\"https:\/\/griddb.net\/en\/author\/griddb-admin\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Building a Scheduling Assistants with SpringAI | GridDB: Open Source Time Series Database for IoT","description":"In this blog post, we\u2019ll walk through how to build a personal AI assistant that simplifies managing your calendar. By the end, you\u2019ll know how to create","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/blog\/scheduling-assistants-springai\/","og_locale":"en_US","og_type":"article","og_title":"Building a Scheduling Assistants with SpringAI | GridDB: Open Source Time Series Database for IoT","og_description":"In this blog post, we\u2019ll walk through how to build a personal AI assistant that simplifies managing your calendar. By the end, you\u2019ll know how to create","og_url":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/blog\/scheduling-assistants-springai\/","og_site_name":"GridDB: Open Source Time Series Database for IoT","article_publisher":"https:\/\/www.facebook.com\/griddbcommunity\/","article_published_time":"2025-05-07T07:00:00+00:00","og_image":[{"width":1024,"height":1024,"url":"https:\/\/griddb.net\/wp-content\/uploads\/2025\/12\/aiassistantcover1.jpg","type":"image\/jpeg"}],"author":"griddb-admin","twitter_card":"summary_large_image","twitter_creator":"@GridDBCommunity","twitter_site":"@GridDBCommunity","twitter_misc":{"Written by":"griddb-admin","Est. reading time":"12 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/blog\/scheduling-assistants-springai\/#article","isPartOf":{"@id":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/blog\/scheduling-assistants-springai\/"},"author":{"name":"griddb-admin","@id":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/#\/schema\/person\/4fe914ca9576878e82f5e8dd3ba52233"},"headline":"Building a Scheduling Assistants with SpringAI","datePublished":"2025-05-07T07:00:00+00:00","mainEntityOfPage":{"@id":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/blog\/scheduling-assistants-springai\/"},"wordCount":1330,"commentCount":0,"publisher":{"@id":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/#organization"},"image":{"@id":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/blog\/scheduling-assistants-springai\/#primaryimage"},"thumbnailUrl":"\/wp-content\/uploads\/2025\/12\/aiassistantcover1.jpg","articleSection":["Blog"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/blog\/scheduling-assistants-springai\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/blog\/scheduling-assistants-springai\/","url":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/blog\/scheduling-assistants-springai\/","name":"Building a Scheduling Assistants with SpringAI | GridDB: Open Source Time Series Database for IoT","isPartOf":{"@id":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/#website"},"primaryImageOfPage":{"@id":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/blog\/scheduling-assistants-springai\/#primaryimage"},"image":{"@id":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/blog\/scheduling-assistants-springai\/#primaryimage"},"thumbnailUrl":"\/wp-content\/uploads\/2025\/12\/aiassistantcover1.jpg","datePublished":"2025-05-07T07:00:00+00:00","description":"In this blog post, we\u2019ll walk through how to build a personal AI assistant that simplifies managing your calendar. By the end, you\u2019ll know how to create","inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/blog\/scheduling-assistants-springai\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/blog\/scheduling-assistants-springai\/#primaryimage","url":"\/wp-content\/uploads\/2025\/12\/aiassistantcover1.jpg","contentUrl":"\/wp-content\/uploads\/2025\/12\/aiassistantcover1.jpg","width":1024,"height":1024},{"@type":"WebSite","@id":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/#website","url":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/","name":"GridDB: Open Source Time Series Database for IoT","description":"GridDB is an open source time-series database with the performance of NoSQL and convenience of SQL","publisher":{"@id":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/#organization","name":"Fixstars","url":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/#\/schema\/logo\/image\/","url":"https:\/\/griddb.net\/wp-content\/uploads\/2019\/04\/fixstars_logo_web_tagline.png","contentUrl":"https:\/\/griddb.net\/wp-content\/uploads\/2019\/04\/fixstars_logo_web_tagline.png","width":200,"height":83,"caption":"Fixstars"},"image":{"@id":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/griddbcommunity\/","https:\/\/x.com\/GridDBCommunity","https:\/\/www.linkedin.com\/company\/griddb-by-toshiba"]},{"@type":"Person","@id":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/#\/schema\/person\/4fe914ca9576878e82f5e8dd3ba52233","name":"griddb-admin","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/5bceca1cafc06886a7ba873e2f0a28011a1176c4dea59709f735b63ae30d0342?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/5bceca1cafc06886a7ba873e2f0a28011a1176c4dea59709f735b63ae30d0342?s=96&d=mm&r=g","caption":"griddb-admin"},"url":"https:\/\/griddb.net\/en\/author\/griddb-admin\/"}]}},"_links":{"self":[{"href":"https:\/\/griddb.net\/en\/wp-json\/wp\/v2\/posts\/52125","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/griddb.net\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/griddb.net\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/griddb.net\/en\/wp-json\/wp\/v2\/users\/41"}],"replies":[{"embeddable":true,"href":"https:\/\/griddb.net\/en\/wp-json\/wp\/v2\/comments?post=52125"}],"version-history":[{"count":0,"href":"https:\/\/griddb.net\/en\/wp-json\/wp\/v2\/posts\/52125\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/griddb.net\/en\/wp-json\/wp\/v2\/media\/52126"}],"wp:attachment":[{"href":"https:\/\/griddb.net\/en\/wp-json\/wp\/v2\/media?parent=52125"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/griddb.net\/en\/wp-json\/wp\/v2\/categories?post=52125"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/griddb.net\/en\/wp-json\/wp\/v2\/tags?post=52125"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}