• Uncategorised

Implementing Google ADK-Style Agent Teams in LangChain4j Using the Agent-as-Tool Pattern

Multi-agent architectures are becoming a core pattern in modern AI systems. Frameworks like Google’s Agent Development Kit (ADK) allow developers to create a team of agents where a root agent delegates tasks to specialized sub-agents.

If you are working with LangChain4j, you might notice that it does not provide a built-in sub_agents concept like ADK. However, the same architecture can be implemented using a powerful design pattern called Agent-as-Tool.

This article explains:

  1. How Google ADK implements agent teams
  2. What the Agent-as-Tool pattern is
  3. How to implement the same architecture in LangChain4j
  4. Why this pattern is useful for building scalable AI systems

1. Multi-Agent Architecture in Google ADK

Google ADK allows developers to define a root agent that coordinates multiple sub-agents.

Conceptually the system looks like this:

User
  |
  v
Root Agent
  |
  |---- Greeting Agent
  |
  |---- Farewell Agent

The root agent receives every user request and decides whether to:

  • handle it itself
  • call a tool
  • delegate to a sub-agent

For example, the ADK tutorial defines a greeting and farewell agent:

greeting_agent = LlmAgent(
    name="greeting_agent",
    instruction="Handle greetings like Hi or Hello"
)

farewell_agent = LlmAgent(
    name="farewell_agent",
    instruction="Handle farewells like Bye"
)

root_agent = LlmAgent(
    name="assistant",
    instruction="Delegate greetings and farewells to the appropriate agent",
    sub_agents=[greeting_agent, farewell_agent]
)

Here, the root agent automatically decides when to delegate to a sub-agent based on the user query.


2. The Challenge in LangChain4j

LangChain4j currently does not have a direct equivalent of sub_agents.

Instead, LangChain4j provides primitives such as:

  • Language models
  • Tools
  • AI services
  • Chains

To implement agent delegation, we must compose these primitives ourselves.

The cleanest approach is the Agent-as-Tool pattern.


3. What is the Agent-as-Tool Pattern?

Normally, tools in an LLM system represent external capabilities such as:

  • a search API
  • a calculator
  • a database query

Example:

@Tool
public String getWeather(String city)

In the Agent-as-Tool pattern, the tool does not call a simple function.
Instead, the tool invokes another AI agent.

Architecture:

User
  |
  v
Root Agent
  |
  |---- greetingTool() -> GreetingAgent
  |
  |---- farewellTool() -> FarewellAgent

From the root agent’s perspective, sub-agents appear just like tools.


4. Step 1 — Creating Specialized Agents

First we create individual agents that handle specific tasks.

Greeting agent:

interface GreetingAgent {

    @SystemMessage("""
    You are a greeting assistant.

    Respond to greetings such as:
    hi
    hello
    good morning
    """)
    String chat(String message);
}

Farewell agent:

interface FarewellAgent {

    @SystemMessage("""
    You handle goodbyes such as:
    bye
    goodbye
    see you later
    """)
    String chat(String message);
}

Create instances of these agents:

GreetingAgent greetingAgent =
    AiServices.create(GreetingAgent.class, model);

FarewellAgent farewellAgent =
    AiServices.create(FarewellAgent.class, model);

Each agent now has its own prompt and specialization.


5. Step 2 — Exposing Agents as Tools

Next, we wrap these agents inside tools.

class AgentTools {

    GreetingAgent greetingAgent;
    FarewellAgent farewellAgent;

    public AgentTools(GreetingAgent greetingAgent, FarewellAgent farewellAgent) {
        this.greetingAgent = greetingAgent;
        this.farewellAgent = farewellAgent;
    }

    @Tool("Handle greetings like hi or hello")
    public String greetingTool(String message) {
        return greetingAgent.chat(message);
    }

    @Tool("Handle farewells like bye or goodbye")
    public String farewellTool(String message) {
        return farewellAgent.chat(message);
    }
}

Now each tool internally calls another agent.


6. Step 3 — Creating the Root Agent

The root agent receives these tools.

interface RootAgent {

    @SystemMessage("""
    You are a helpful assistant.

    If the user greets you, call greetingTool.
    If the user says goodbye, call farewellTool.

    Otherwise respond normally.
    """)
    String chat(String message);
}

Create the root agent:

AgentTools tools = new AgentTools(greetingAgent, farewellAgent);

RootAgent rootAgent =
    AiServices.builder(RootAgent.class)
        .chatLanguageModel(model)
        .tools(tools)
        .build();

Now the root agent has access to two tools that represent other agents.


7. Runtime Execution Flow

Consider the following user input:

User: Hi there

Execution flow:

User
  |
  v
Root Agent
  |
  |-- LLM decides greetingTool should be used
  |
  v
GreetingAgent
  |
  v
Response generated

The root agent’s LLM chooses the tool automatically.
LangChain4j executes the tool, which calls the appropriate agent.


8. Why This Pattern is Powerful

Modular Architecture

Each agent can have its own:

  • prompt
  • model
  • tools
  • memory

Example:

Root Agent
  |
  |---- Finance Agent
  |
  |---- Travel Agent
  |
  |---- Support Agent

You may also like...