An open-source, code-first Java toolkit for building, evaluating, and running sophisticated AI agents with flexibility
and control.
adk-java is a Java implementation of Agent Develop Kit for orchestrating Multi-Agents
-
Rich Framework: Utilize any AI frameworks, eg. LangChain4j,SpringAI.
-
Code-First Development: Define agent logic, and orchestration directly in Java for ultimate flexibility, testability, and versioning.
-
Modular Multi-Agent Systems: Design scalable applications by composing multiple specialized agents into flexible hierarchies.
-
Strong Ecosystem: seamlessly integrate with maven, Spring And Easily deploy application on JVM.
- simple: just one agent
AdkAgentProvider qa = AdkAgentProvider.create("qaAssistant", new CustomAdkAgentInvoker());
AgentRunner runner = AgentRunner.of("Assistant", qa);- chain: some agent
AdkAgentProvider qa = AdkAgentProvider.create("qaAssistant", new CustomAdkAgentInvoker());
AdkAgentProvider qa2 = AdkAgentProvider.create("qaAssistant2", new CustomAdkAgentInvoker2());
AgentRunner runner = AgentRunner.of("AgentChain", qa, qa2);- router: router with some agent
AdkAgentProvider qaRouter = AdkAgentProvider.create("qaRouter", new AdkAgentInvoker() {
@Override
public Mono<ResponseFrame> invoke(ExecutableContext context) {
// mock request llm and response
Map<String, Object> metadata = Map.of("activeAgent", "echoAgent", "answer", "router self answer..balabala...");
context.setMetadata(metadata);
return Mono.empty();
}
@Override
public Flux<ResponseFrame> invokeStream(ExecutableContext context) {
return this.invoke(context).flux();
}
});
BranchSelector branchSelector = (edge, index, size, context) -> {
Object activeAgent = context.getMetadata().get("activeAgent");
return activeAgent != null && activeAgent.toString().equalsIgnoreCase(edge.getName());
};
AdkAgentProvider qa = AdkAgentProvider.create("echoAgent", new CustomAdkAgentInvoker());
AdkAgentProvider qa2 = AdkAgentProvider.create("mathAgent", new CustomAdkAgentInvoker2());
AdkAgentProvider fallback = AdkAgentProvider.create("fallback", new AdkAgentInvoker() {
@Override
public Mono<ResponseFrame> invoke(ExecutableContext context) {
String answer = (String) context.getMetadata().get("answer");
ResponseFrame response = new ResponseFrame();
response.setMessage(answer);
return Mono.just(response);
}
@Override
public Flux<ResponseFrame> invokeStream(ExecutableContext context) {
return this.invoke(context).flux();
}
});
AgentRouterRunner runner = AgentRouterRunner.of("AgentRouter", qaRouter, branchSelector, fallback, qa, qa2);- custom
- Use other existing Runner
- Custom Runner by
extends AbstractRunnerlike CustomComplexRunner - Use low level api about
Executorpublic void customAndRun(){ // define params Payload payload; Executor executor; Graph graph; RootContext rootContext = new RootContext(payload); // execute AdkContext ec = executor.execute(graph, rootContext); ec.getResponse().subscribe(responseFrame -> log.info("custom responseFrame: {}", responseFrame)); }
- run
List<ResponseFrame> responseFrames = runner.run(payload);- runAsync
@Test
public void testAgentChainRunnerAsync() {
AdkAgentProvider qa = AdkAgentProvider.create("qaAssistant", new CustomAdkAgentInvoker());
AdkAgentProvider qa2 = AdkAgentProvider.create("qaAssistant2", new CustomAdkAgentInvoker2());
AgentRunner runner = AgentRunner.of("AgentChain", qa, qa2);
Payload payload = Payload.builder().userId("1").sessionId("2").message("hello").stream(true).build();
// runAsync
runner.runAsync(payload)
.subscribe(responseFrame -> log.info("agent runAsync responseFrame: {}", responseFrame));
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}User payload support some key properties:
- userId
- sessionId
- stream
When stream is true, Runners will use sse by the method
invokeStreamof AdkAgentInvoker to invoke LLM - messages
SupportsAdkTextMessage,AdkImageMessage,AdkVideoMessage,AdkAudioMessage,AdkFileMessage - metadata
AdkPayload payload = AdkPayload.builder()
.userId("1")
.sessionId("2")
.taskId(AdkUtil.uuid4hex())
.messages(List.of(AdkTextMessage.of("hello")))
.build();Implements AdkAgentInvoker interface to interact with LLM by any framework, eg.LangChain4j,a2a4j
@Slf4j
public class CustomAdkAgentInvoker implements AdkAgentInvoker {
@Override
public Mono<ResponseFrame> invoke(ExecutableContext context) {
ResponseFrame response = new ResponseFrame();
response.setMessage("ok");
return Mono.just(response);
}
@Override
public Flux<ResponseFrame> invokeStream(ExecutableContext context) {
return Flux.create(sink -> {
for (int i = 0; i < 10; i++) {
ResponseFrame frame = new ResponseFrame();
frame.setMessage("message" + i);
sink.next(frame);
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
sink.complete();
});
}
}Runner use new Executor(new InMemorySessionService(), new InMemoryEventService()) as default executor.
Your also can use custom executor by initExecutor of Runner.
AgentRunner runner = AgentRunner.of("Assistant", qa).initExecutor(executor);Runner is high level api for Orchestrating your agents and run it synchronously or asynchronously.
- AgentRunner
- AgentRouterRunner
- AgentLoopRunner
- AgentParallelRunner
- Custom yourself Runner
like CustomComplexRunner
More Runners is oncoming!
- generate kinds of images with
PlantUmlGeneratorandGraphbyFileFormat
public void testGenerateUmlPng() {
AgentRouterRunner runner;
// gen uml png
try {
PlantUmlGenerator generator = runner.getPlantUmlGenerator();
Graph graph = runner.getGraph();
FileOutputStream file = new FileOutputStream("target/" + graph.getName() + ".png");
generator.generate(graph, file, FileFormat.PNG);
} catch (IOException e) {
throw new RuntimeException(e);
}
}- generate png image with Runner directly
public void generatePngImage() {
Runner runner;
// gen uml png
try {
FileOutputStream file = new FileOutputStream("target/" + appName + ".png");
runner.generatePng(file);
} catch (IOException e) {
throw new RuntimeException(e);
}
}- generate svg image with Runner directly
public void generateSvgImage() {
Runner runner;
// gen uml svg
try {
FileOutputStream file = new FileOutputStream("target/" + appName + ".svg");
runner.generateSvg(file);
} catch (IOException e) {
throw new RuntimeException(e);
}
}- generate png image with Runner Payload Task directly
public void generatePngImage() {
Runner runner;
// gen task uml png
try {
FileOutputStream file = new FileOutputStream("target/" + appName + "_" + payload.getTaskId() + ".png");
runner.generateTaskPng(payload, file);
} catch (IOException e) {
throw new RuntimeException(e);
}
}RunnerTests.testAgentRouterRunner()
20:09:50.624 [Thread-0] DEBUG io.github.pheonixhkbxoic.adk.event.LogInvokeEventListener -- before event: execute, start
20:09:50.629 [main] DEBUG reactor.util.Loggers -- Using Slf4j logging framework
20:09:50.630 [Thread-0] DEBUG io.github.pheonixhkbxoic.adk.event.LogInvokeEventListener -- after event: execute, start
20:09:50.630 [Thread-0] DEBUG io.github.pheonixhkbxoic.adk.event.LogInvokeEventListener -- before event: execute, qaRouter
20:09:50.630 [Thread-0] INFO io.github.pheonixhkbxoic.adk.event.LogInvokeEventListener -- before event: invoke, qaRouter
20:09:50.666 [Thread-0] INFO io.github.pheonixhkbxoic.adk.event.LogInvokeEventListener -- after event: invoke, qaRouter
20:09:50.666 [Thread-0] DEBUG io.github.pheonixhkbxoic.adk.event.LogInvokeEventListener -- before event: route, qaRouter
20:09:50.666 [Thread-0] DEBUG io.github.pheonixhkbxoic.adk.event.LogInvokeEventListener -- after event: route, qaRouter
20:09:50.666 [Thread-0] DEBUG io.github.pheonixhkbxoic.adk.event.LogInvokeEventListener -- after event: execute, qaRouter
20:09:50.666 [Thread-0] DEBUG io.github.pheonixhkbxoic.adk.event.LogInvokeEventListener -- before event: execute, echoAgent
20:09:50.666 [Thread-0] INFO io.github.pheonixhkbxoic.adk.event.LogInvokeEventListener -- before event: invoke, echoAgent
20:09:50.672 [Thread-0] INFO io.github.pheonixhkbxoic.adk.event.LogInvokeEventListener -- after event: invoke, echoAgent
20:09:50.672 [Thread-0] DEBUG io.github.pheonixhkbxoic.adk.event.LogInvokeEventListener -- after event: execute, echoAgent
20:09:50.672 [Thread-0] DEBUG io.github.pheonixhkbxoic.adk.event.LogInvokeEventListener -- before event: execute, end
20:09:50.672 [Thread-0] DEBUG io.github.pheonixhkbxoic.adk.event.LogInvokeEventListener -- after event: execute, end
20:09:50.689 [main] INFO io.github.pheonixhkbxoic.adk.test.RunnerTests -- before runAsync
20:09:50.703 [boundedElastic-1] INFO io.github.pheonixhkbxoic.adk.test.RunnerTests -- agent router runAsync responseFrame: ResponseFrame(message=message0)
20:09:50.808 [boundedElastic-1] INFO io.github.pheonixhkbxoic.adk.test.RunnerTests -- agent router runAsync responseFrame: ResponseFrame(message=message1)
20:09:50.920 [boundedElastic-1] INFO io.github.pheonixhkbxoic.adk.test.RunnerTests -- agent router runAsync responseFrame: ResponseFrame(message=message2)
20:09:51.031 [boundedElastic-1] INFO io.github.pheonixhkbxoic.adk.test.RunnerTests -- agent router runAsync responseFrame: ResponseFrame(message=message3)
20:09:51.143 [boundedElastic-1] INFO io.github.pheonixhkbxoic.adk.test.RunnerTests -- agent router runAsync responseFrame: ResponseFrame(message=message4)
20:09:51.255 [boundedElastic-1] INFO io.github.pheonixhkbxoic.adk.test.RunnerTests -- agent router runAsync responseFrame: ResponseFrame(message=message5)
20:09:51.366 [boundedElastic-1] INFO io.github.pheonixhkbxoic.adk.test.RunnerTests -- agent router runAsync responseFrame: ResponseFrame(message=message6)
20:09:51.474 [boundedElastic-1] INFO io.github.pheonixhkbxoic.adk.test.RunnerTests -- agent router runAsync responseFrame: ResponseFrame(message=message7)
20:09:51.583 [boundedElastic-1] INFO io.github.pheonixhkbxoic.adk.test.RunnerTests -- agent router runAsync responseFrame: ResponseFrame(message=message8)
20:09:51.691 [boundedElastic-1] INFO io.github.pheonixhkbxoic.adk.test.RunnerTests -- agent router runAsync responseFrame: ResponseFrame(message=message9)
20:09:51.801 [boundedElastic-1] INFO io.github.pheonixhkbxoic.adk.test.RunnerTests -- after runAsync
20:09:53.698 [main] INFO io.github.pheonixhkbxoic.adk.test.RunnerTests -- taskContextChain: [{"id": "606e06fb3b4f444090bd73dfc4ab77a3", "name": "root", "node": null, "metadata": {}, "activeParentId": "", "activeChildId": "9dc083f4d8754dbd9658b7dcb1417c1e"}
, {"id": "9dc083f4d8754dbd9658b7dcb1417c1e", "name": "start", "node": {"type": "start", "status": {"state": "success"}}, "metadata": {}, "activeParentId": "606e06fb3b4f444090bd73dfc4ab77a3", "activeChildId": "19eac3a325764c93b11e68d7a7455426"}
, {"id": "19eac3a325764c93b11e68d7a7455426", "name": "qaRouter", "node": {"type": "agent_router", "status": {"state": "success"}}, "metadata": {}, "activeParentId": "9dc083f4d8754dbd9658b7dcb1417c1e", "activeChildId": "35f1409202f646328005ed32b076d686"}
, {"id": "35f1409202f646328005ed32b076d686", "name": "echoAgent", "node": {"type": "agent", "status": {"state": "success"}}, "metadata": {}, "activeParentId": "19eac3a325764c93b11e68d7a7455426", "activeChildId": "09af97ff99ba4f71a872c93b0311ba3c"}
, {"id": "09af97ff99ba4f71a872c93b0311ba3c", "name": "end", "node": {"type": "end", "status": {"state": "success"}}, "metadata": {}, "activeParentId": "35f1409202f646328005ed32b076d686", "activeChildId": ""}
]
@startuml
start
partition **AgentRouter** {
: start;
switch( qaRouter? )
case ( echoAgent )
: echoAgent;
case ( mathAgent )
: mathAgent;
case ( fallback )
: fallback;
endswitch
: end;
}
stop
@enduml
@startuml
start
partition **AgentRouter** {
-[#red]->
#green: <color:red><b>start;
-[#red]->
switch( <color:red><b>qaRouter? )
case ( <color:red><b>echoAgent )
-[#red]->
#green: <color:red><b>echoAgent;
-[#red]->
case ( mathAgent )
: mathAgent;
case ( fallback )
: fallback;
endswitch
-[#red]->
#green: <color:red><b>end;
-[#red]->
}
stop
@enduml