Skip to content

Commit 6496bd2

Browse files
committed
main planner classes
1 parent fd97bc7 commit 6496bd2

5 files changed

Lines changed: 746 additions & 0 deletions

File tree

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Copyright 2025 Google LLC
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+
* http://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+
17+
package com.google.adk.agents;
18+
19+
import io.reactivex.rxjava3.core.Single;
20+
21+
/**
22+
* Strategy interface for planning which sub-agent(s) to execute next.
23+
*
24+
* <p>A {@code Planner} is used by {@link PlannerAgent} to dynamically determine execution order at
25+
* runtime. The planning loop works as follows:
26+
*
27+
* <ol>
28+
* <li>{@link #init} is called once before the loop starts
29+
* <li>{@link #firstAction} returns the first action to execute
30+
* <li>The selected agent(s) execute, producing events and updating session state
31+
* <li>{@link #nextAction} is called with updated context to decide what to do next
32+
* <li>Steps 3-4 repeat until {@link PlannerAction.Done} or max iterations
33+
* </ol>
34+
*
35+
* <p>Returns {@link Single}{@code <PlannerAction>} to support both synchronous planners (wrap in
36+
* {@code Single.just()}) and asynchronous planners that call an LLM.
37+
*/
38+
public interface Planner {
39+
40+
/**
41+
* Initialize the planner with context and available agents. Called once before the planning loop
42+
* starts.
43+
*
44+
* <p>Default implementation is a no-op. Override to perform setup like building dependency
45+
* graphs.
46+
*/
47+
default void init(PlanningContext context) {}
48+
49+
/** Select the first action to execute. */
50+
Single<PlannerAction> firstAction(PlanningContext context);
51+
52+
/** Select the next action based on updated state and events. */
53+
Single<PlannerAction> nextAction(PlanningContext context);
54+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Copyright 2025 Google LLC
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+
* http://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+
17+
package com.google.adk.agents;
18+
19+
import com.google.common.collect.ImmutableList;
20+
21+
/**
22+
* Represents the next action a {@link Planner} wants the {@link PlannerAgent} to take.
23+
*
24+
* <p>This is a sealed interface with four variants:
25+
*
26+
* <ul>
27+
* <li>{@link RunAgents} — execute one or more sub-agents (multiple agents run in parallel)
28+
* <li>{@link Done} — planning is complete, no result to emit
29+
* <li>{@link DoneWithResult} — planning is complete with a final text result
30+
* <li>{@link NoOp} — skip this iteration (no-op), then ask the planner for the next action
31+
* </ul>
32+
*/
33+
public sealed interface PlannerAction
34+
permits PlannerAction.RunAgents,
35+
PlannerAction.Done,
36+
PlannerAction.DoneWithResult,
37+
PlannerAction.NoOp {
38+
39+
/** Run the specified sub-agent(s). Multiple agents are run in parallel. */
40+
record RunAgents(ImmutableList<BaseAgent> agents) implements PlannerAction {
41+
public RunAgents(BaseAgent singleAgent) {
42+
this(ImmutableList.of(singleAgent));
43+
}
44+
}
45+
46+
/** Plan is complete, no result to emit. */
47+
record Done() implements PlannerAction {}
48+
49+
/** Plan is complete with a final text result. */
50+
record DoneWithResult(String result) implements PlannerAction {}
51+
52+
/** Skip this iteration (no-op). */
53+
record NoOp() implements PlannerAction {}
54+
}
Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
/*
2+
* Copyright 2025 Google LLC
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+
* http://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+
17+
package com.google.adk.agents;
18+
19+
import static com.google.common.collect.ImmutableList.toImmutableList;
20+
21+
import com.google.adk.events.Event;
22+
import com.google.adk.events.EventActions;
23+
import com.google.common.collect.ImmutableList;
24+
import com.google.errorprone.annotations.CanIgnoreReturnValue;
25+
import com.google.genai.types.Content;
26+
import com.google.genai.types.Part;
27+
import io.reactivex.rxjava3.core.Flowable;
28+
import java.util.List;
29+
import java.util.concurrent.atomic.AtomicInteger;
30+
import org.slf4j.Logger;
31+
import org.slf4j.LoggerFactory;
32+
33+
/**
34+
* An agent that delegates execution planning to a {@link Planner} strategy.
35+
*
36+
* <p>The {@code PlannerAgent} owns a set of sub-agents and a planner. At runtime, the planner
37+
* inspects session state and decides which sub-agent(s) to run next. This enables dynamic,
38+
* goal-oriented agent orchestration — the execution topology is determined at runtime rather than
39+
* being fixed at build time.
40+
*
41+
* <p>The planning loop:
42+
*
43+
* <ol>
44+
* <li>Planner is initialized with context and available agents
45+
* <li>Planner returns what to do next via {@link PlannerAction}
46+
* <li>Selected sub-agent(s) execute, producing events
47+
* <li>Session state (world state) is updated from events
48+
* <li>Planner sees updated state and decides the next action
49+
* <li>Repeat until {@link PlannerAction.Done} or maxIterations
50+
* </ol>
51+
*
52+
* <p>Example usage with a custom planner:
53+
*
54+
* <pre>{@code
55+
* PlannerAgent agent = PlannerAgent.builder()
56+
* .name("myAgent")
57+
* .subAgents(agentA, agentB, agentC)
58+
* .planner(new GoalOrientedPlanner("finalOutput", metadata))
59+
* .maxIterations(20)
60+
* .build();
61+
* }</pre>
62+
*/
63+
public class PlannerAgent extends BaseAgent {
64+
private static final Logger logger = LoggerFactory.getLogger(PlannerAgent.class);
65+
private static final int DEFAULT_MAX_ITERATIONS = 100;
66+
67+
private final Planner planner;
68+
private final int maxIterations;
69+
70+
private PlannerAgent(
71+
String name,
72+
String description,
73+
List<? extends BaseAgent> subAgents,
74+
Planner planner,
75+
int maxIterations,
76+
List<Callbacks.BeforeAgentCallback> beforeAgentCallback,
77+
List<Callbacks.AfterAgentCallback> afterAgentCallback) {
78+
super(name, description, subAgents, beforeAgentCallback, afterAgentCallback);
79+
this.planner = planner;
80+
this.maxIterations = maxIterations;
81+
}
82+
83+
/** Returns the planner strategy used by this agent. */
84+
public Planner planner() {
85+
return planner;
86+
}
87+
88+
/** Returns the maximum number of planning iterations. */
89+
public int maxIterations() {
90+
return maxIterations;
91+
}
92+
93+
@Override
94+
protected Flowable<Event> runAsyncImpl(InvocationContext invocationContext) {
95+
List<? extends BaseAgent> agents = subAgents();
96+
if (agents == null || agents.isEmpty()) {
97+
return Flowable.empty();
98+
}
99+
100+
ImmutableList<BaseAgent> available =
101+
agents.stream().map(a -> (BaseAgent) a).collect(toImmutableList());
102+
PlanningContext planningContext = new PlanningContext(invocationContext, available);
103+
104+
planner.init(planningContext);
105+
106+
AtomicInteger iteration = new AtomicInteger(0);
107+
108+
return planner
109+
.firstAction(planningContext)
110+
.flatMapPublisher(
111+
firstAction ->
112+
executeActionAndContinue(
113+
firstAction, planningContext, invocationContext, iteration));
114+
}
115+
116+
private Flowable<Event> executeActionAndContinue(
117+
PlannerAction action,
118+
PlanningContext planningContext,
119+
InvocationContext invocationContext,
120+
AtomicInteger iteration) {
121+
122+
int current = iteration.getAndIncrement();
123+
if (current >= maxIterations) {
124+
logger.info("PlannerAgent '{}' reached maxIterations={}", name(), maxIterations);
125+
return Flowable.empty();
126+
}
127+
128+
if (action instanceof PlannerAction.Done) {
129+
return Flowable.empty();
130+
}
131+
132+
if (action instanceof PlannerAction.DoneWithResult doneWithResult) {
133+
Event resultEvent =
134+
Event.builder()
135+
.id(Event.generateEventId())
136+
.invocationId(invocationContext.invocationId())
137+
.author(name())
138+
.branch(invocationContext.branch())
139+
.content(Content.fromParts(Part.fromText(doneWithResult.result())))
140+
.actions(EventActions.builder().build())
141+
.build();
142+
return Flowable.just(resultEvent);
143+
}
144+
145+
if (action instanceof PlannerAction.NoOp) {
146+
return Flowable.defer(
147+
() ->
148+
planner
149+
.nextAction(planningContext)
150+
.flatMapPublisher(
151+
nextAction ->
152+
executeActionAndContinue(
153+
nextAction, planningContext, invocationContext, iteration)));
154+
}
155+
156+
if (action instanceof PlannerAction.RunAgents runAgents) {
157+
Flowable<Event> agentEvents;
158+
if (runAgents.agents().size() == 1) {
159+
agentEvents = runAgents.agents().get(0).runAsync(invocationContext);
160+
} else {
161+
agentEvents =
162+
Flowable.merge(
163+
runAgents.agents().stream()
164+
.map(agent -> agent.runAsync(invocationContext))
165+
.collect(toImmutableList()));
166+
}
167+
168+
return agentEvents.concatWith(
169+
Flowable.defer(
170+
() ->
171+
planner
172+
.nextAction(planningContext)
173+
.flatMapPublisher(
174+
nextAction ->
175+
executeActionAndContinue(
176+
nextAction, planningContext, invocationContext, iteration))));
177+
}
178+
179+
// Unreachable for sealed interface, but required by compiler
180+
return Flowable.empty();
181+
}
182+
183+
@Override
184+
protected Flowable<Event> runLiveImpl(InvocationContext invocationContext) {
185+
return Flowable.error(
186+
new UnsupportedOperationException("runLive is not defined for PlannerAgent yet."));
187+
}
188+
189+
/** Returns a new {@link Builder} for creating {@link PlannerAgent} instances. */
190+
public static Builder builder() {
191+
return new Builder();
192+
}
193+
194+
/** Builder for {@link PlannerAgent}. */
195+
public static class Builder extends BaseAgent.Builder<Builder> {
196+
private Planner planner;
197+
private int maxIterations = DEFAULT_MAX_ITERATIONS;
198+
199+
@CanIgnoreReturnValue
200+
public Builder planner(Planner planner) {
201+
this.planner = planner;
202+
return this;
203+
}
204+
205+
@CanIgnoreReturnValue
206+
public Builder maxIterations(int maxIterations) {
207+
this.maxIterations = maxIterations;
208+
return this;
209+
}
210+
211+
@Override
212+
public PlannerAgent build() {
213+
if (planner == null) {
214+
throw new IllegalStateException(
215+
"PlannerAgent requires a Planner. Call .planner(...) on the builder.");
216+
}
217+
return new PlannerAgent(
218+
name,
219+
description,
220+
subAgents,
221+
planner,
222+
maxIterations,
223+
beforeAgentCallback,
224+
afterAgentCallback);
225+
}
226+
}
227+
}

0 commit comments

Comments
 (0)