├── .gitattributes
├── .gitignore
├── .mvn
└── wrapper
│ └── maven-wrapper.properties
├── README.md
├── mvnw
├── mvnw.cmd
├── pom.xml
└── src
├── main
├── java
│ └── net
│ │ └── breezeware
│ │ └── dynamo
│ │ └── ai
│ │ └── agent
│ │ ├── AgentApplication.java
│ │ ├── agentExecutor
│ │ └── AgentExecutor.java
│ │ └── service
│ │ ├── AgentService.java
│ │ ├── FunctionConfiguration.java
│ │ ├── WeatherConfigProperties.java
│ │ └── WeatherService.java
└── resources
│ └── application.properties
└── test
└── java
└── net
└── breezeware
└── dynamo
└── ai
└── agent
└── LanggraphStarterApplicationTests.java
/.gitattributes:
--------------------------------------------------------------------------------
1 | /mvnw text eol=lf
2 | *.cmd text eol=crlf
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | HELP.md
2 | target/
3 | !.mvn/wrapper/maven-wrapper.jar
4 | !**/src/main/**/target/
5 | !**/src/test/**/target/
6 |
7 | ### STS ###
8 | .apt_generated
9 | .classpath
10 | .factorypath
11 | .project
12 | .settings
13 | .springBeans
14 | .sts4-cache
15 |
16 | ### IntelliJ IDEA ###
17 | .idea
18 | *.iws
19 | *.iml
20 | *.ipr
21 |
22 | ### NetBeans ###
23 | /nbproject/private/
24 | /nbbuild/
25 | /dist/
26 | /nbdist/
27 | /.nb-gradle/
28 | build/
29 | !**/src/main/**/build/
30 | !**/src/test/**/build/
31 |
32 | ### VS Code ###
33 | .vscode/
34 |
--------------------------------------------------------------------------------
/.mvn/wrapper/maven-wrapper.properties:
--------------------------------------------------------------------------------
1 | # Licensed to the Apache Software Foundation (ASF) under one
2 | # or more contributor license agreements. See the NOTICE file
3 | # distributed with this work for additional information
4 | # regarding copyright ownership. The ASF licenses this file
5 | # to you under the Apache License, Version 2.0 (the
6 | # "License"); you may not use this file except in compliance
7 | # with the License. You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing,
12 | # software distributed under the License is distributed on an
13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 | # KIND, either express or implied. See the License for the
15 | # specific language governing permissions and limitations
16 | # under the License.
17 | wrapperVersion=3.3.2
18 | distributionType=only-script
19 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | A powerful platform designed to supercharge business app development and streamline internal tool creation. Manage, organize, and innovate—all in one place
5 |
6 |
7 |

8 |

9 |

10 |

11 |

12 |
13 |
14 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | * **Accelerate development and reduce effort**. Leverage industry-specific templates, pre-built modules, and out-of-the-box features.
38 | * **Ensure flexibility and scalability**. Build applications with dynamic entities and choose from multiple multi-tenancy models.
39 | * **Utilize familiar technologies**: Java, Spring Boot, JavaScript, HTML, ReactJS, SpringAI, BPMN, Hibernate, PostgreSQL.
40 | * **Expand at will**. Dynamo provides unlimited customization and seamless integration options.
41 | * **Enable multi-agent building** Design and deploy intelligent, collaborative multi-agent systems to solve complex problems efficiently.
42 |
43 |
44 |
45 | Languages and Tools:
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 | # Langgraph4j and SpringAI AgentExecutor sample
72 |
73 | This dynamo sample shows how to use Langgraph4j with SpringAI.
74 |
75 | 
76 |
77 |
78 | ## Prerequisites
79 |
80 | 1. **Java 8+** installed.
81 | 2. **Maven** for dependency management.
82 |
83 |
84 | ## 🚀 Getting Started
85 |
86 | ### Clone the Repository
87 | ```bash
88 | git clone https://github.com/Breezeware-OS/dynamo-multi-ai-agent-langgraph4j-starter.git
89 | cd dynamo-multi-ai-agent-langgraph4j-starter
90 | ```
91 |
92 | ### Setup
93 |
94 | Set API KEYs
95 |
96 | ```properties
97 | spring.ai.openai.api-key=${OPENAI_API_KEY}
98 | weather.api-key=${WEATHER_API_KEY}
99 | weather.api-url=https://api.weatherapi.com/v1
100 | ```
101 |
102 | ### Build and Run
103 | Use Maven to compile and execute the application:
104 | ```bash
105 | mvn clean install -Dmaven.test.skip=true
106 | mvn exec:java
107 | ```
108 | ## A Simple Conceptual Demo Using LangGraph4j and Spring AI in Java
109 |
110 | [Building Stateful AI Agent-Spring AI & LangGraph4J](https://medium.com/@ganeshmoorthy5999/building-stateful-multi-ai-agents-langgraph4j-spring-ai-c0046e293d00)
111 |
112 | ## 🔗 Resources
113 |
114 | - [LangGraph4j GitHub](https://github.com/bsorrentino/langgraph4j)
115 | - [LangGraph4j Wiki](https://github.com/bsorrentino/langgraph4j/wiki)
116 | - [Spring AI](https://docs.spring.io/spring-ai/reference/index.html)
117 | - [LangGraph](https://www.langchain.com/langgraph)
118 |
119 | ## 🗃️ Release notes
120 |
121 | Dynamo is constantly evolving. Check out the changelog:
122 |
123 | ### Dynamo 1.0.0. 🚀
124 |
125 | * Support for Spring AI & LangGraph4J
126 |
127 | ## Support
128 |
129 | For any **business inquiries**, **technical support**, or to report issues, please contact support@breezeware.net.
130 |
131 | **Love Dynamo? Give our repo a star :star: :arrow_up:.**
132 |
--------------------------------------------------------------------------------
/mvnw:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # ----------------------------------------------------------------------------
3 | # Licensed to the Apache Software Foundation (ASF) under one
4 | # or more contributor license agreements. See the NOTICE file
5 | # distributed with this work for additional information
6 | # regarding copyright ownership. The ASF licenses this file
7 | # to you under the Apache License, Version 2.0 (the
8 | # "License"); you may not use this file except in compliance
9 | # with the License. You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing,
14 | # software distributed under the License is distributed on an
15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 | # KIND, either express or implied. See the License for the
17 | # specific language governing permissions and limitations
18 | # under the License.
19 | # ----------------------------------------------------------------------------
20 |
21 | # ----------------------------------------------------------------------------
22 | # Apache Maven Wrapper startup batch script, version 3.3.2
23 | #
24 | # Optional ENV vars
25 | # -----------------
26 | # JAVA_HOME - location of a JDK home dir, required when download maven via java source
27 | # MVNW_REPOURL - repo url base for downloading maven distribution
28 | # MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
29 | # MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output
30 | # ----------------------------------------------------------------------------
31 |
32 | set -euf
33 | [ "${MVNW_VERBOSE-}" != debug ] || set -x
34 |
35 | # OS specific support.
36 | native_path() { printf %s\\n "$1"; }
37 | case "$(uname)" in
38 | CYGWIN* | MINGW*)
39 | [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")"
40 | native_path() { cygpath --path --windows "$1"; }
41 | ;;
42 | esac
43 |
44 | # set JAVACMD and JAVACCMD
45 | set_java_home() {
46 | # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched
47 | if [ -n "${JAVA_HOME-}" ]; then
48 | if [ -x "$JAVA_HOME/jre/sh/java" ]; then
49 | # IBM's JDK on AIX uses strange locations for the executables
50 | JAVACMD="$JAVA_HOME/jre/sh/java"
51 | JAVACCMD="$JAVA_HOME/jre/sh/javac"
52 | else
53 | JAVACMD="$JAVA_HOME/bin/java"
54 | JAVACCMD="$JAVA_HOME/bin/javac"
55 |
56 | if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then
57 | echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2
58 | echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2
59 | return 1
60 | fi
61 | fi
62 | else
63 | JAVACMD="$(
64 | 'set' +e
65 | 'unset' -f command 2>/dev/null
66 | 'command' -v java
67 | )" || :
68 | JAVACCMD="$(
69 | 'set' +e
70 | 'unset' -f command 2>/dev/null
71 | 'command' -v javac
72 | )" || :
73 |
74 | if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then
75 | echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2
76 | return 1
77 | fi
78 | fi
79 | }
80 |
81 | # hash string like Java String::hashCode
82 | hash_string() {
83 | str="${1:-}" h=0
84 | while [ -n "$str" ]; do
85 | char="${str%"${str#?}"}"
86 | h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296))
87 | str="${str#?}"
88 | done
89 | printf %x\\n $h
90 | }
91 |
92 | verbose() { :; }
93 | [ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; }
94 |
95 | die() {
96 | printf %s\\n "$1" >&2
97 | exit 1
98 | }
99 |
100 | trim() {
101 | # MWRAPPER-139:
102 | # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds.
103 | # Needed for removing poorly interpreted newline sequences when running in more
104 | # exotic environments such as mingw bash on Windows.
105 | printf "%s" "${1}" | tr -d '[:space:]'
106 | }
107 |
108 | # parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties
109 | while IFS="=" read -r key value; do
110 | case "${key-}" in
111 | distributionUrl) distributionUrl=$(trim "${value-}") ;;
112 | distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;;
113 | esac
114 | done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties"
115 | [ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties"
116 |
117 | case "${distributionUrl##*/}" in
118 | maven-mvnd-*bin.*)
119 | MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/
120 | case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in
121 | *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;;
122 | :Darwin*x86_64) distributionPlatform=darwin-amd64 ;;
123 | :Darwin*arm64) distributionPlatform=darwin-aarch64 ;;
124 | :Linux*x86_64*) distributionPlatform=linux-amd64 ;;
125 | *)
126 | echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2
127 | distributionPlatform=linux-amd64
128 | ;;
129 | esac
130 | distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip"
131 | ;;
132 | maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;;
133 | *) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;
134 | esac
135 |
136 | # apply MVNW_REPOURL and calculate MAVEN_HOME
137 | # maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/
138 | [ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}"
139 | distributionUrlName="${distributionUrl##*/}"
140 | distributionUrlNameMain="${distributionUrlName%.*}"
141 | distributionUrlNameMain="${distributionUrlNameMain%-bin}"
142 | MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}"
143 | MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")"
144 |
145 | exec_maven() {
146 | unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || :
147 | exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD"
148 | }
149 |
150 | if [ -d "$MAVEN_HOME" ]; then
151 | verbose "found existing MAVEN_HOME at $MAVEN_HOME"
152 | exec_maven "$@"
153 | fi
154 |
155 | case "${distributionUrl-}" in
156 | *?-bin.zip | *?maven-mvnd-?*-?*.zip) ;;
157 | *) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;;
158 | esac
159 |
160 | # prepare tmp dir
161 | if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then
162 | clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; }
163 | trap clean HUP INT TERM EXIT
164 | else
165 | die "cannot create temp dir"
166 | fi
167 |
168 | mkdir -p -- "${MAVEN_HOME%/*}"
169 |
170 | # Download and Install Apache Maven
171 | verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
172 | verbose "Downloading from: $distributionUrl"
173 | verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
174 |
175 | # select .zip or .tar.gz
176 | if ! command -v unzip >/dev/null; then
177 | distributionUrl="${distributionUrl%.zip}.tar.gz"
178 | distributionUrlName="${distributionUrl##*/}"
179 | fi
180 |
181 | # verbose opt
182 | __MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR=''
183 | [ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v
184 |
185 | # normalize http auth
186 | case "${MVNW_PASSWORD:+has-password}" in
187 | '') MVNW_USERNAME='' MVNW_PASSWORD='' ;;
188 | has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;;
189 | esac
190 |
191 | if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then
192 | verbose "Found wget ... using wget"
193 | wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl"
194 | elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then
195 | verbose "Found curl ... using curl"
196 | curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl"
197 | elif set_java_home; then
198 | verbose "Falling back to use Java to download"
199 | javaSource="$TMP_DOWNLOAD_DIR/Downloader.java"
200 | targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName"
201 | cat >"$javaSource" <<-END
202 | public class Downloader extends java.net.Authenticator
203 | {
204 | protected java.net.PasswordAuthentication getPasswordAuthentication()
205 | {
206 | return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() );
207 | }
208 | public static void main( String[] args ) throws Exception
209 | {
210 | setDefault( new Downloader() );
211 | java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() );
212 | }
213 | }
214 | END
215 | # For Cygwin/MinGW, switch paths to Windows format before running javac and java
216 | verbose " - Compiling Downloader.java ..."
217 | "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java"
218 | verbose " - Running Downloader.java ..."
219 | "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")"
220 | fi
221 |
222 | # If specified, validate the SHA-256 sum of the Maven distribution zip file
223 | if [ -n "${distributionSha256Sum-}" ]; then
224 | distributionSha256Result=false
225 | if [ "$MVN_CMD" = mvnd.sh ]; then
226 | echo "Checksum validation is not supported for maven-mvnd." >&2
227 | echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
228 | exit 1
229 | elif command -v sha256sum >/dev/null; then
230 | if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then
231 | distributionSha256Result=true
232 | fi
233 | elif command -v shasum >/dev/null; then
234 | if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then
235 | distributionSha256Result=true
236 | fi
237 | else
238 | echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2
239 | echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
240 | exit 1
241 | fi
242 | if [ $distributionSha256Result = false ]; then
243 | echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2
244 | echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2
245 | exit 1
246 | fi
247 | fi
248 |
249 | # unzip and move
250 | if command -v unzip >/dev/null; then
251 | unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip"
252 | else
253 | tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar"
254 | fi
255 | printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url"
256 | mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
257 |
258 | clean || :
259 | exec_maven "$@"
260 |
--------------------------------------------------------------------------------
/mvnw.cmd:
--------------------------------------------------------------------------------
1 | <# : batch portion
2 | @REM ----------------------------------------------------------------------------
3 | @REM Licensed to the Apache Software Foundation (ASF) under one
4 | @REM or more contributor license agreements. See the NOTICE file
5 | @REM distributed with this work for additional information
6 | @REM regarding copyright ownership. The ASF licenses this file
7 | @REM to you under the Apache License, Version 2.0 (the
8 | @REM "License"); you may not use this file except in compliance
9 | @REM with the License. You may obtain a copy of the License at
10 | @REM
11 | @REM http://www.apache.org/licenses/LICENSE-2.0
12 | @REM
13 | @REM Unless required by applicable law or agreed to in writing,
14 | @REM software distributed under the License is distributed on an
15 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 | @REM KIND, either express or implied. See the License for the
17 | @REM specific language governing permissions and limitations
18 | @REM under the License.
19 | @REM ----------------------------------------------------------------------------
20 |
21 | @REM ----------------------------------------------------------------------------
22 | @REM Apache Maven Wrapper startup batch script, version 3.3.2
23 | @REM
24 | @REM Optional ENV vars
25 | @REM MVNW_REPOURL - repo url base for downloading maven distribution
26 | @REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
27 | @REM MVNW_VERBOSE - true: enable verbose log; others: silence the output
28 | @REM ----------------------------------------------------------------------------
29 |
30 | @IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0)
31 | @SET __MVNW_CMD__=
32 | @SET __MVNW_ERROR__=
33 | @SET __MVNW_PSMODULEP_SAVE=%PSModulePath%
34 | @SET PSModulePath=
35 | @FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @(
36 | IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B)
37 | )
38 | @SET PSModulePath=%__MVNW_PSMODULEP_SAVE%
39 | @SET __MVNW_PSMODULEP_SAVE=
40 | @SET __MVNW_ARG0_NAME__=
41 | @SET MVNW_USERNAME=
42 | @SET MVNW_PASSWORD=
43 | @IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*)
44 | @echo Cannot start maven from wrapper >&2 && exit /b 1
45 | @GOTO :EOF
46 | : end batch / begin powershell #>
47 |
48 | $ErrorActionPreference = "Stop"
49 | if ($env:MVNW_VERBOSE -eq "true") {
50 | $VerbosePreference = "Continue"
51 | }
52 |
53 | # calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties
54 | $distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl
55 | if (!$distributionUrl) {
56 | Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
57 | }
58 |
59 | switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {
60 | "maven-mvnd-*" {
61 | $USE_MVND = $true
62 | $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip"
63 | $MVN_CMD = "mvnd.cmd"
64 | break
65 | }
66 | default {
67 | $USE_MVND = $false
68 | $MVN_CMD = $script -replace '^mvnw','mvn'
69 | break
70 | }
71 | }
72 |
73 | # apply MVNW_REPOURL and calculate MAVEN_HOME
74 | # maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/
75 | if ($env:MVNW_REPOURL) {
76 | $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" }
77 | $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')"
78 | }
79 | $distributionUrlName = $distributionUrl -replace '^.*/',''
80 | $distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$',''
81 | $MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain"
82 | if ($env:MAVEN_USER_HOME) {
83 | $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain"
84 | }
85 | $MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join ''
86 | $MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME"
87 |
88 | if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
89 | Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME"
90 | Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
91 | exit $?
92 | }
93 |
94 | if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) {
95 | Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl"
96 | }
97 |
98 | # prepare tmp dir
99 | $TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile
100 | $TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir"
101 | $TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null
102 | trap {
103 | if ($TMP_DOWNLOAD_DIR.Exists) {
104 | try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
105 | catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
106 | }
107 | }
108 |
109 | New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null
110 |
111 | # Download and Install Apache Maven
112 | Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
113 | Write-Verbose "Downloading from: $distributionUrl"
114 | Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
115 |
116 | $webclient = New-Object System.Net.WebClient
117 | if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) {
118 | $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD)
119 | }
120 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
121 | $webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null
122 |
123 | # If specified, validate the SHA-256 sum of the Maven distribution zip file
124 | $distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum
125 | if ($distributionSha256Sum) {
126 | if ($USE_MVND) {
127 | Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties."
128 | }
129 | Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash
130 | if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) {
131 | Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property."
132 | }
133 | }
134 |
135 | # unzip and move
136 | Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null
137 | Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null
138 | try {
139 | Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null
140 | } catch {
141 | if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) {
142 | Write-Error "fail to move MAVEN_HOME"
143 | }
144 | } finally {
145 | try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
146 | catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
147 | }
148 |
149 | Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
150 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | org.springframework.boot
7 | spring-boot-starter-parent
8 | 3.4.0
9 |
10 |
11 | net.breezeware.dyanmo.ai
12 | dynamo-multi-ai-agent
13 | 0.0.1-SNAPSHOT
14 | dynamo-multi-ai-agent
15 | Demo project for Dynamo Multi AI Agent
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | 17
31 | 1.0.0-M4
32 |
33 |
34 |
35 | org.springframework.boot
36 | spring-boot-starter-web
37 |
38 |
39 |
40 | org.projectlombok
41 | lombok
42 | true
43 |
44 |
45 |
46 | org.springframework.ai
47 | spring-ai-openai-spring-boot-starter
48 |
49 |
50 |
51 | org.springframework.boot
52 | spring-boot-starter-test
53 | test
54 |
55 |
56 | org.bsc.langgraph4j
57 | langgraph4j-core-jdk8
58 | 1.0.0
59 |
60 |
61 |
62 |
63 |
64 | org.springframework.ai
65 | spring-ai-bom
66 | ${spring-ai.version}
67 | pom
68 | import
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 | org.springframework.boot
77 | spring-boot-maven-plugin
78 |
79 |
80 |
81 | org.projectlombok
82 | lombok
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 | spring-milestones
92 | Spring Milestones
93 | https://repo.spring.io/milestone
94 |
95 | false
96 |
97 |
98 |
99 |
100 |
--------------------------------------------------------------------------------
/src/main/java/net/breezeware/dynamo/ai/agent/AgentApplication.java:
--------------------------------------------------------------------------------
1 | package net.breezeware.dynamo.ai.agent;
2 |
3 | import net.breezeware.dynamo.ai.agent.agentExecutor.AgentExecutor;
4 | import net.breezeware.dynamo.ai.agent.service.WeatherConfigProperties;
5 | import lombok.extern.slf4j.Slf4j;
6 | import org.bsc.langgraph4j.StateGraph;
7 | import org.springframework.boot.CommandLineRunner;
8 | import org.springframework.boot.SpringApplication;
9 | import org.springframework.boot.autoconfigure.SpringBootApplication;
10 | import org.springframework.boot.context.properties.EnableConfigurationProperties;
11 |
12 | import java.util.Map;
13 |
14 | /**
15 | * Entry point for the Multi-Agent AI Application. This application initializes the agent workflow,
16 | * processes user queries, and generates results based on the configured state graph.
17 | */
18 | @SpringBootApplication
19 | @Slf4j
20 | @EnableConfigurationProperties(WeatherConfigProperties.class)
21 | public class AgentApplication implements CommandLineRunner {
22 |
23 | private final AgentExecutor agentExecutor;
24 |
25 | /**
26 | * Constructor for injecting the required dependencies.
27 | *
28 | * @param agentExecutor The executor responsible for managing agent workflows.
29 | */
30 | public AgentApplication(AgentExecutor agentExecutor) {
31 | this.agentExecutor = agentExecutor;
32 | }
33 |
34 | /**
35 | * Main method to bootstrap the Spring Boot application.
36 | *
37 | * @param args Command-line arguments passed during application startup.
38 | */
39 | public static void main(String[] args) {
40 | SpringApplication.run(AgentApplication.class, args);
41 | }
42 |
43 | /**
44 | * Executes after the application context is loaded. Builds and invokes the agent workflow graph.
45 | *
46 | * @param args Command-line arguments.
47 | * @throws Exception If an error occurs during graph execution.
48 | */
49 | @Override
50 | public void run(String... args) throws Exception {
51 | log.info("Starting Multi-Agent AI Application");
52 |
53 | // Build the state graph using the AgentExecutor
54 | StateGraph graph = agentExecutor.graphBuilder().build();
55 | var app = graph.compile();
56 |
57 | // Input data to initialize the workflow
58 | var inputData = Map.of(
59 | AgentExecutor.State.INPUT, Map.of("query", "Current weather in Atlanta")
60 | );
61 |
62 | // Input data to initialize the workflow for full node execution
63 | // var inputData = Map.of(
64 | // AgentExecutor.State.INPUT, Map.of("query", "Current weather in Atlanta & Travel recommendations")
65 | // );
66 |
67 |
68 | // Execute the workflow and log the final output
69 | var result = app.invoke(inputData);
70 | log.info("Final Output: {}", result);
71 | }
72 | }
--------------------------------------------------------------------------------
/src/main/java/net/breezeware/dynamo/ai/agent/agentExecutor/AgentExecutor.java:
--------------------------------------------------------------------------------
1 | package net.breezeware.dynamo.ai.agent.agentExecutor;
2 |
3 | import net.breezeware.dynamo.ai.agent.service.AgentService;
4 | import lombok.extern.slf4j.Slf4j;
5 | import org.bsc.langgraph4j.GraphStateException;
6 | import org.bsc.langgraph4j.StateGraph;
7 | import org.bsc.langgraph4j.action.EdgeAction;
8 | import org.bsc.langgraph4j.state.AgentState;
9 | import org.bsc.langgraph4j.state.Channel;
10 | import org.springframework.stereotype.Service;
11 |
12 | import java.util.HashMap;
13 | import java.util.List;
14 | import java.util.Map;
15 |
16 | import static org.bsc.langgraph4j.StateGraph.END;
17 | import static org.bsc.langgraph4j.StateGraph.START;
18 | import static org.bsc.langgraph4j.action.AsyncEdgeAction.edge_async;
19 | import static org.bsc.langgraph4j.action.AsyncNodeAction.node_async;
20 |
21 | /**
22 | * Service responsible for executing agent workflows. This includes handling weather, travel,
23 | * and food recommendations based on user inputs.
24 | */
25 | @Slf4j
26 | @Service
27 | public class AgentExecutor {
28 |
29 | private final AgentService agentService;
30 |
31 | /**
32 | * Constructor to inject dependencies.
33 | *
34 | * @param agentService The service used for agent interactions.
35 | */
36 | public AgentExecutor(AgentService agentService) {
37 | this.agentService = agentService;
38 | }
39 |
40 | /**
41 | * Calls the Weather Agent to retrieve weather information based on user input.
42 | *
43 | * @param state The current state of the workflow.
44 | * @return A map containing the weather details as output.
45 | */
46 | Map callWeatherAgent(State state) {
47 | log.info("callWeatherAgent: {}", state);
48 |
49 | log.info("Weather Agent Input: {}", state.getInput());
50 |
51 | var query = (String) state.getInput().get("query");
52 | var response = agentService.execute(query, List.of());
53 |
54 | Map output = new HashMap<>();
55 | output.put("weather", response);
56 | log.info("Weather Agent Output: {}", output);
57 |
58 | return Map.of(State.OUTPUT, output);
59 | }
60 |
61 | /**
62 | * Calls the Travel Agent to provide travel recommendations based on the weather.
63 | *
64 | * @param state The current state of the workflow.
65 | * @return A map containing travel recommendations as output.
66 | */
67 | Map callTravelAgent(State state) {
68 | log.info("callTravelAgent: {}", state);
69 |
70 | var weather = (String) state.getOutput().get("weather");
71 |
72 | String recommendation;
73 | if (weather.contains("rain")) {
74 | recommendation = "Visit indoor attractions like museums, art galleries, or enjoy a day at the mall.";
75 | } else if (weather.contains("cloudy")) {
76 | recommendation = "Consider activities like visiting an aquarium, a science center, or enjoying a cozy café.";
77 | } else if (weather.contains("storm") || weather.contains("thunder")) {
78 | recommendation = "Stay safe indoors. Enjoy a good book, watch a movie, or try indoor yoga.";
79 | } else if (weather.contains("snow")) {
80 | recommendation = "Explore indoor winter activities like skating in an indoor rink or sipping hot chocolate by a fireplace.";
81 | } else if (weather.contains("misty")) {
82 | recommendation = "Visit indoor attractions like museums, art galleries, or enjoy a day at the mall.";
83 | } else {
84 | recommendation = "Enjoy outdoor activities like hiking, biking, or a picnic in the park!";
85 | }
86 |
87 | Map output = new HashMap<>();
88 | output.put("recommendation", recommendation);
89 | log.info("Travel Agent Output: {}", output);
90 |
91 | return Map.of(State.MID, output);
92 | }
93 |
94 | /**
95 | * Calls the Food Agent to provide food suggestions based on travel recommendations.
96 | *
97 | * @param state The current state of the workflow.
98 | * @return A map containing food suggestions as output.
99 | */
100 | Map callFoodAgent(State state) {
101 | log.info("callFoodAgent: {}", state);
102 |
103 | var recommendation = (String) state.getMID().get("recommendation");
104 |
105 | String foodSuggestion;
106 | if (recommendation.contains("outdoor")) {
107 | foodSuggestion = "Pack some easy-to-carry snacks like sandwiches, granola bars, and fresh fruit. Don't forget plenty of water!";
108 | } else if (recommendation.contains("cloudy")) {
109 | foodSuggestion = "Warm up with comfort food like soups, hot beverages, or enjoy a cozy brunch at a nearby bakery.";
110 | } else if (recommendation.contains("snow") || recommendation.contains("rain") || recommendation.contains("misty")) {
111 | foodSuggestion = "Enjoy hearty meals like stews, hot chocolate, or baked goods to keep you warm.";
112 | } else {
113 | foodSuggestion = "Explore the local street food scene or grab a quick bite from food trucks in the area.";
114 | }
115 |
116 | Map output = new HashMap<>();
117 | output.put("food", foodSuggestion);
118 | log.info("Food Agent Output: {}", output);
119 |
120 | return Map.of(State.FOOD, output);
121 | }
122 |
123 | /**
124 | * Provides a builder to construct the workflow graph.
125 | *
126 | * @return An instance of GraphBuilder.
127 | */
128 | public GraphBuilder graphBuilder() {
129 | return new GraphBuilder();
130 | }
131 |
132 | /**
133 | * Represents the state of the workflow, including input, intermediate, and output data.
134 | */
135 | public static class State extends AgentState {
136 | public static final String INPUT = "question";
137 | public static final String MID = "recommendation";
138 | public static final String OUTPUT = "weather";
139 | public static final String FOOD = "food";
140 |
141 | static Map> SCHEMA = Map.of(
142 | INPUT, Channel.of(() -> new HashMap<>()),
143 | OUTPUT, Channel.of(() -> new HashMap<>()),
144 | MID, Channel.of(() -> new HashMap<>())
145 | );
146 |
147 | /**
148 | * Constructor to initialize state with given data.
149 | *
150 | * @param initData Initial data for the state.
151 | */
152 | public State(Map initData) {
153 | super(initData);
154 | }
155 |
156 | public Map getInput() {
157 | return this.