Skip to content

Commit 227c572

Browse files
committed
fix: add docs
1 parent 0b342f7 commit 227c572

File tree

8 files changed

+155
-21
lines changed

8 files changed

+155
-21
lines changed

spring-ai-alibaba-agent-framework/src/main/java/com/alibaba/cloud/ai/graph/agent/flow/agent/LoopAgent.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,31 @@
2727
import java.util.List;
2828

2929
/**
30+
* Loop Agent that supports multiple loop modes:
31+
* <ul>
32+
* <li><b>COUNT</b>: Execute a fixed number of loops</li>
33+
* <li><b>CONDITION</b>: Continue looping based on a condition, similar to a do-while
34+
* structure, but when the condition is true, terminate the loop</li>
35+
* <li><b>JSON_ARRAY</b>: Parse a JSON array and iterate over its elements</li>
36+
* <li><b>Other Loop Strategy</b>: Users can implement the LoopStrategy interface according to their needs.</li>
37+
* </ul>
38+
*
39+
* <p>
40+
* <b>Note:</b> The LoopAgent must have a subAgent, which is the agent that will be executed in each loop.
41+
* </p>
42+
*
43+
* <p>
44+
* Usage example:
45+
* </p>
46+
* <pre>{@code
47+
* LoopAgent loopAgent = LoopAgent.builder()
48+
* .name("example-loop-agent")
49+
* .description("Example loop agent")
50+
* .loopStrategy(LoopMode.condition(messagePredicate))
51+
* .subAgent(subAgent)
52+
* .build();
53+
* }</pre>
54+
*
3055
* @author vlsmb
3156
* @since 2025/8/25
3257
*/
Lines changed: 57 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,61 @@
1+
/*
2+
* Copyright 2024-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
117
package com.alibaba.cloud.ai.graph.agent.flow.agent.loop;
218

319
import com.alibaba.cloud.ai.graph.OverAllState;
420
import org.springframework.ai.chat.messages.Message;
521
import org.springframework.ai.chat.messages.SystemMessage;
622
import org.springframework.ai.chat.messages.UserMessage;
723
import org.springframework.ai.util.json.JsonParser;
24+
import org.springframework.core.convert.converter.Converter;
825

926
import java.util.List;
1027
import java.util.Map;
1128

29+
/**
30+
* JSON array loop strategy that retrieves a JSON array from the current message state,
31+
* sends each array element as a message to the model, and returns the result.
32+
* By default, the text of the last message is treated as a JSON array, but users can customize the converter.
33+
*
34+
* @author vlsmb
35+
* @since 2025/11/1
36+
*/
1237
public class ArrayLoopStrategy implements LoopStrategy {
1338

39+
private final Converter<List<Message>, List<?>> converter;
40+
41+
public ArrayLoopStrategy(Converter<List<Message>, List<?>> converter) {
42+
this.converter = converter;
43+
}
44+
45+
public ArrayLoopStrategy() {
46+
this(DEFAULT_MESSAGE_CONVERTER);
47+
}
48+
1449
@Override
1550
public Map<String, Object> loopInit(OverAllState state) {
1651
@SuppressWarnings("unchecked")
1752
List<Message> messages = (List<Message>) state.value(LoopStrategy.MESSAGE_KEY).orElse(List.of());
18-
String lastMessage;
19-
if(!messages.isEmpty()) {
20-
lastMessage = messages.get(messages.size() - 1).getText();
21-
} else {
22-
lastMessage = null;
23-
}
24-
if(lastMessage == null) {
25-
return Map.of(loopCountKey(), 0, loopFlagKey(), false, loopListKey(), List.of());
26-
}
27-
try {
28-
List<?> list = JsonParser.fromJson(lastMessage, List.class);
53+
List<?> list = converter.convert(messages);
54+
if(list != null) {
2955
return Map.of(loopCountKey(), 0, loopFlagKey(), true, loopListKey(), list);
30-
} catch (Exception e) {
31-
return Map.of(loopCountKey(), 0, loopFlagKey(), false, loopListKey(), List.of(),
32-
LoopStrategy.MESSAGE_KEY, new SystemMessage("Invalid json array format"));
3356
}
57+
return Map.of(loopCountKey(), 0, loopFlagKey(), false, loopListKey(), List.of(),
58+
LoopStrategy.MESSAGE_KEY, new SystemMessage("Invalid json array format"));
3459
}
3560

3661
@Override
@@ -45,4 +70,22 @@ public Map<String, Object> loopDispatch(OverAllState state) {
4570
return Map.of(loopFlagKey(), false);
4671
}
4772
}
73+
74+
/**
75+
* 默认的转换器,将最后一个消息的文本作为json数组
76+
*/
77+
private static final Converter<List<Message>, List<?>> DEFAULT_MESSAGE_CONVERTER =
78+
messages -> {
79+
String lastMessage;
80+
if(!messages.isEmpty()) {
81+
lastMessage = messages.get(messages.size() - 1).getText();
82+
} else {
83+
lastMessage = null;
84+
}
85+
if(lastMessage == null) {
86+
return null;
87+
}
88+
return JsonParser.fromJson(lastMessage, List.class);
89+
};
90+
4891
}

spring-ai-alibaba-agent-framework/src/main/java/com/alibaba/cloud/ai/graph/agent/flow/agent/loop/ConditionLoopStrategy.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,19 @@
1+
/*
2+
* Copyright 2024-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
117
package com.alibaba.cloud.ai.graph.agent.flow.agent.loop;
218

319
import com.alibaba.cloud.ai.graph.OverAllState;
@@ -8,6 +24,12 @@
824
import java.util.Map;
925
import java.util.function.Predicate;
1026

27+
/**
28+
* Conditional loop strategy that retries until the Predicate is satisfied or the maximum count is reached.
29+
*
30+
* @author vlsmb
31+
* @since 2025/11/1
32+
*/
1133
public class ConditionLoopStrategy implements LoopStrategy {
1234

1335
private final Predicate<List<Message>> messagePredicate;

spring-ai-alibaba-agent-framework/src/main/java/com/alibaba/cloud/ai/graph/agent/flow/agent/loop/CountLoopStrategy.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,31 @@
1+
/*
2+
* Copyright 2024-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
117
package com.alibaba.cloud.ai.graph.agent.flow.agent.loop;
218

319
import com.alibaba.cloud.ai.graph.OverAllState;
420

521
import java.util.Map;
622

23+
/**
24+
* Fixed count loop strategy
25+
*
26+
* @author vlsmb
27+
* @since 2025/11/1
28+
*/
729
public class CountLoopStrategy implements LoopStrategy {
830

931
private final int maxCount;

spring-ai-alibaba-agent-framework/src/main/java/com/alibaba/cloud/ai/graph/agent/flow/agent/loop/LoopMode.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,17 @@
1717
package com.alibaba.cloud.ai.graph.agent.flow.agent.loop;
1818

1919
import org.springframework.ai.chat.messages.Message;
20+
import org.springframework.core.convert.converter.Converter;
2021

2122
import java.util.List;
2223
import java.util.function.Predicate;
2324

25+
/**
26+
* Built-in loop strategies for LoopAgent
27+
*
28+
* @author vlsmb
29+
* @since 2025/11/1
30+
*/
2431
public final class LoopMode {
2532
private LoopMode() {
2633
throw new UnsupportedOperationException();
@@ -34,6 +41,10 @@ public static ArrayLoopStrategy array() {
3441
return new ArrayLoopStrategy();
3542
}
3643

44+
public static ArrayLoopStrategy array(Converter<List<Message>, List<?>> converter) {
45+
return new ArrayLoopStrategy(converter);
46+
}
47+
3748
public static ConditionLoopStrategy condition(Predicate<List<Message>> messagePredicate) {
3849
return new ConditionLoopStrategy(messagePredicate);
3950
}

spring-ai-alibaba-agent-framework/src/main/java/com/alibaba/cloud/ai/graph/agent/flow/agent/loop/LoopStrategy.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,10 @@
2222
import java.util.Map;
2323

2424
/**
25-
* LoopAgent的循环策略,用来控制LoopAgent的行为。
26-
* 这部分相当于定义了LoopAgent对应StateGraph的loopInitNode和loopDispatchNode。
27-
* 在使用的时候可以直接使用LoopMode提供的内置策略,如果需要自定义循环逻辑,可以实现本接口。
25+
* <p>Loop strategy for LoopAgent, used to control the behavior of LoopAgent.</p>
26+
* <p>This part is equivalent to defining the loopInitNode and loopDispatchNode for the StateGraph corresponding to LoopAgent.</p>
27+
* <p>Built-in strategies provided by LoopMode can be used directly when in use. If custom loop logic is required, this interface can be implemented.</p>
28+
*
2829
* @author vlsmb
2930
* @since 2025/11/1
3031
*/

spring-ai-alibaba-agent-framework/src/main/java/com/alibaba/cloud/ai/graph/agent/flow/strategy/LoopGraphBuildingStrategy.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,15 @@
3838
import static com.alibaba.cloud.ai.graph.action.AsyncEdgeAction.edge_async;
3939
import static com.alibaba.cloud.ai.graph.action.AsyncNodeAction.node_async;
4040

41+
/**
42+
* Converts a LoopAgent into its corresponding StateGraph.
43+
* <p>
44+
* Structure of the loop graph: START -> LoopInitLoop -> LoopDispatchNode (condition met -> SubAgentNode -> LoopDispatchNode; condition not met -> END)
45+
* </p>
46+
*
47+
* @author vlsmb
48+
* @since 2025/8/25
49+
*/
4150
public class LoopGraphBuildingStrategy implements FlowGraphBuildingStrategy {
4251

4352
@Override
@@ -51,7 +60,8 @@ public StateGraph buildGraph(FlowGraphBuilder.FlowGraphConfig config) throws Gra
5160
// Add starting edge
5261
graph.addEdge(START, rootAgent.name());
5362

54-
// 根据loopStrategy构造循环图
63+
64+
// Build loop graph based on loopStrategy
5565
LoopStrategy loopStrategy = (LoopStrategy) config.getCustomProperty(LoopAgent.LOOP_STRATEGY);
5666
graph.addNode(loopStrategy.loopInitNodeName(), node_async(loopStrategy::loopInit));
5767
graph.addEdge(rootAgent.name(), loopStrategy.loopInitNodeName());

spring-ai-alibaba-agent-framework/src/test/java/com/alibaba/cloud/ai/graph/agent/flow/LoopAgentTest.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,9 @@ void testCountMode() throws Exception {
113113
.name("loop_agent")
114114
.description("循环执行一个任务,直到满足条件。")
115115
.subAgent(this.blogAgent)
116-
.loopStrategy(LoopMode.count(3))
116+
.loopStrategy(LoopMode.count(2))
117117
.build();
118-
OverAllState state = loopAgent.invoke("写一篇关于杭州西湖的散文文章。").orElseThrow();
118+
OverAllState state = loopAgent.invoke("帮我写一个Python Socket编程的demo,并优化代码").orElseThrow();
119119
logger.info("Result: {}", state.data());
120120
Optional<Object> optional = state.value("messages");
121121
assert optional.isPresent();
@@ -159,7 +159,7 @@ void testConditionMode() throws Exception {
159159
void testArrayMode() throws Exception {
160160
LoopAgent loopAgent = LoopAgent.builder()
161161
.name("loop_agent")
162-
.description("循环执行一个任务,直到满足条件。")
162+
.description("循环执行任务。")
163163
.subAgent(this.sqlAgent)
164164
.loopStrategy(LoopMode.array())
165165
.build();

0 commit comments

Comments
 (0)