Showing posts with label langchain4j. Show all posts
Showing posts with label langchain4j. Show all posts

Monday, 22 December 2025

Understanding AgenticScope in LangChain4j: How Agents Share and Collaborate

When we build AI systems with multiple agents, it’s not enough for each agent to just do its job in isolation. Agents need to communicate and share information. For example, if one agent writes a poem, another agent might edit it, and yet another might convert it to uppercase. To make this collaboration possible, LangChain4j provides a mechanism called AgenticScope.

Think of AgenticScope as a shared notebook where agents can write down their results and read what others have written. It also keeps track of who did what and when, giving you a complete history of the conversation between agents.

 

What is AgenticScope?

·      A shared environment where agents in the same system can store and read variables.

·      Helps agents collaborate by passing results between each other.

·      Keeps a record of all agent calls and responses so you can reconstruct the conversation later.

 

Imagine you and your friends are solving a puzzle together. Instead of shouting answers, you all write your findings in a shared notebook. Each friend can look at what’s already written, add new notes, or edit something. That’s what AgenticScope does for AI agents.

 

The AgenticScope Interface:

public interface AgenticScope {
    void writeState(String key, Object value);      // Add a piece of info
    Object readState(String key);                   // Get a piece of info
    boolean hasState(String key);                   // Check if something exists
    Map<String, Object> state();                    // Get all shared info
    String contextAsConversation(String... agents); // See conversation history
}

 

Think of writeState as writing in the notebook and readState as reading from it.

 

Example in Action

Let’s say we have three agents:

 

·      Poet: writes a poem about robots

·      PoemEditor: polishes the poem

·      PoemInUppercase: makes it all uppercase

 

We connect them in sequence using LangChain4j:

UntypedAgent untypedAgent =
    AgenticServices.sequenceBuilder()
        .subAgents(poet, poemEditor, poemInUppercase)
        .outputName("poemInCapitalLetters")
        .build();

Map<String, Object> input = Map.of("theme", "Robots");

// Run the agents with AgenticScope
ResultWithAgenticScope<String> poem = untypedAgent.invokeWithAgenticScope(input);
AgenticScope agenticScope = poem.agenticScope();

// Inspect shared state
Map<String, Object> state = agenticScope.state();
System.out.println(state);

What Happens Behind the Scenes

·      The Poet writes a poem about robots and saves it in the AgenticScope.

·      The PoemEditor reads that poem, edits it, and writes the new version back into the scope.

·      The PoemInUppercase reads the edited poem, makes it uppercase, and writes the result.

·      Finally, you can inspect the scope to see all intermediate steps as well as the final result.

 

Find the below working Application.

 

Step 1: Define Agents.

 

Poet.java

package com.sample.app.agents;

import dev.langchain4j.agentic.Agent;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;

public interface Poet {

  @UserMessage(
      """
            You are a poet.
            Write a short poem of no more than
            4 lines about the given theme.
            Return only the poem and nothing else.
            The theme is {{theme}}.
            """)
  @Agent(description = "Generates a short poem based on the given theme", name = "Poet")
  String generatePoem(@V("theme") String theme);
}

 

PoemEditor.java

package com.sample.app.agents;

import dev.langchain4j.agentic.Agent;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;

public interface PoemEditor {

  @UserMessage(
      """
                You are a professional Poem Editor.
               Analyze and rewrite the following {{poem}} to better align
            with the target theme of {{theme}}.

            Return only the poem and nothing else.
                """)
  @Agent(description = "Generates a short poem based on the given theme", name = "Poet")
  String generatePoem(@V("poem") String poemToEdit, @V("theme") String targetTheme);
}

PoemInUppercase.java

package com.sample.app.agents;

import dev.langchain4j.agentic.Agent;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;

public interface PoemInUppercase {

  @UserMessage(
      """
            Return only the following {{editedPoem}} in uppercase and nothing else.
                """)
  @Agent(description = "Convert the Poem to Uppercase", name = "PoemInUppercase")
  String generatePoem(@V("editedPoem") String poem);
}

 

Step 2: Define AgenticScopeDemo class.

 

AgenticScopeDemo.java

package com.sample.app.agents.workflows;

import com.sample.app.agents.PoemEditor;
import com.sample.app.agents.PoemInUppercase;
import com.sample.app.agents.Poet;
import dev.langchain4j.agentic.AgenticServices;
import dev.langchain4j.agentic.UntypedAgent;
import dev.langchain4j.agentic.scope.AgenticScope;
import dev.langchain4j.agentic.scope.ResultWithAgenticScope;
import dev.langchain4j.model.ollama.OllamaChatModel;
import java.util.Map;

public class AgenticScopeDemo {
  public static void main(String[] args) {
    // Initialize LLM model
    OllamaChatModel chatModel =
        OllamaChatModel.builder().baseUrl("http://localhost:11434").modelName("llama3.2").build();

    Poet poet =
        AgenticServices.agentBuilder(Poet.class).chatModel(chatModel).outputName("poem").build();
    PoemEditor poemEditor =
        AgenticServices.agentBuilder(PoemEditor.class)
            .chatModel(chatModel)
            .outputName("editedPoem")
            .build();

    PoemInUppercase poemInUppercase =
        AgenticServices.agentBuilder(PoemInUppercase.class)
            .chatModel(chatModel)
            .outputName("poemInCapitalLetters")
            .build();

    UntypedAgent untypedAgent =
        AgenticServices.sequenceBuilder()
            .subAgents(poet, poemEditor, poemInUppercase)
            .outputName("poemInCapitalLetters")
            .build();

    Map<String, Object> input = Map.of("theme", "Robots");

    System.out.println("----------------------------------------------------------");
    ResultWithAgenticScope<String> poem = untypedAgent.invokeWithAgenticScope(input);
    System.out.println(poem.result());
    
    AgenticScope agenticScope = poem.agenticScope();
    
    Map<String, Object> state = agenticScope.state();
    for(String key: state.keySet()) {
        System.out.println("\t" + key + " = " + state.get(key).toString().replace("\n", " "));
    }

    System.out.println("\n----------------------------------------------------------");
    poem = untypedAgent.invokeWithAgenticScope(input);
    System.out.println(poem.result());

    agenticScope = poem.agenticScope();
    state = agenticScope.state();
    for(String key: state.keySet()) {
        System.out.println("\t" + key + " = " + state.get(key).toString().replace("\n", " "));
    }
  }
}

 

Output

METAL HEARTS THAT BEAT WITH STONE,
CIRCUIT HUM, A MECHANICAL TONE,
GEARS SHIFT SMOOTH, WITH CALCULATED MIGHT,
FORGED FROM CODE, IN DIGITAL LIGHT.
    theme = Robots
    poem = Metal hearts that beat with stone, Their electronic eyes shine like moan, Rustic limbs that move with precision cold, Forged from wires, souls grown old.
    editedPoem = Metal hearts that beat with stone, Circuits hum, a mechanical tone, Gears shift smooth, with calculated might, Forged from code, in digital light.
    poemInCapitalLetters = METAL HEARTS THAT BEAT WITH STONE, CIRCUIT HUM, A MECHANICAL TONE, GEARS SHIFT SMOOTH, WITH CALCULATED MIGHT, FORGED FROM CODE, IN DIGITAL LIGHT.

----------------------------------------------------------
WHIRRING MINDS WITH LOGIC'S STEADY PACE,
THEY RISE FROM WIRES, A MECHANICAL THRONE,
ECHOES OF CODE, IN METALLIC BONE.
    theme = Robots
    poem = Metal hearts that beat in time, Whirring souls with calculating mind, They rise from dust, a synthetic reign, Echoes of man, in artificial frame.
    editedPoem = Metal hearts that beat in digital space, Whirring minds with logic's steady pace, They rise from wires, a mechanical throne, Echoes of code, in metallic bone.
    poemInCapitalLetters = WHIRRING MINDS WITH LOGIC'S STEADY PACE, THEY RISE FROM WIRES, A MECHANICAL THRONE, ECHOES OF CODE, IN METALLIC BONE.

 

  

Previous                                                    Next                                                    Home

Sequential Workflow in GenAI: Building Step-by-Step Agent Pipelines

When working with Generative AI (GenAI) applications, tasks are often broken into multiple steps. Instead of asking a single agent to handle everything at once, you can chain smaller, focused agents together. This chaining forms a sequential workflow, a simple but powerful pattern where each agent’s output is passed as input to the next agent, like links in a chain.

This approach makes the system easier to design, debug, and extend. For beginners, sequential workflows are the best place to start before moving into more advanced workflow patterns like branching or parallel execution.

 


What is a Sequential Workflow?

A sequential workflow is a pipeline of agents, executed in order.

 

·      Each agent performs one clear task.

·      The output of one agent becomes the input to the next.

·      The flow continues until the final output is ready.

 

Think of it like a factory assembly line, where each worker (agent) does their part, and the product (data) moves to the next worker until it’s finished.

 

Let's understand this with an example.

 

Step 1: Let's define three agents.

 

Poet.java

package com.sample.app.agents;

import dev.langchain4j.agentic.Agent;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;

public interface Poet {

  @UserMessage(
      """
            You are a poet.
            Write a short poem of no more than
            4 lines about the given theme.
            Return only the poem and nothing else.
            The theme is {{theme}}.
            """)
  @Agent(description = "Generates a short poem based on the given theme", name = "Poet")
  String generatePoem(@V("theme") String theme);
}

PoemEditor.java

package com.sample.app.agents;

import dev.langchain4j.agentic.Agent;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;

public interface PoemEditor {

  @UserMessage("""
          You are a professional Poem Editor.
         Analyze and rewrite the following {{poem}} to better align
      with the target theme of {{theme}}.

      Return only the poem and nothing else.
          """)
  @Agent(description = "Generates a short poem based on the given theme", name = "Poet")
  String generatePoem(@V("poem") String poemToEdit, @V("theme") String targetTheme);
}

PoemInUppercase.java

package com.sample.app.agents;

import dev.langchain4j.agentic.Agent;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;

public interface PoemInUppercase {

  @UserMessage("""
      Return the following {{editedPoem}} in uppercase and nothing else.
          """)
  @Agent(description = "Convert the Poem to Uppercase", name = "PoemInUppercase")
  String generatePoem(@V("editedPoem") String poem);
}

 

Step 2: Let's combine these three agents, where the output of the Poet is passed as input to the PoemEditor, and the output of PoemEditor is passed as input to the PoemInUppercase, and the final output is the edited poem in uppercase.

 

SequentialWorkflow.java

package com.sample.app.agents.workflows;

import com.sample.app.agents.PoemEditor;
import com.sample.app.agents.PoemInUppercase;
import com.sample.app.agents.Poet;
import dev.langchain4j.agentic.AgenticServices;
import dev.langchain4j.agentic.UntypedAgent;
import dev.langchain4j.model.ollama.OllamaChatModel;
import java.util.Map;

public class SequentialWorkflow {
  public static void main(String[] args) {
    // Initialize LLM model
    OllamaChatModel chatModel =
        OllamaChatModel.builder().baseUrl("http://localhost:11434").modelName("llama3.2").build();

    Poet poet =
        AgenticServices.agentBuilder(Poet.class).chatModel(chatModel).outputName("poem").build();
    PoemEditor poemEditor =
        AgenticServices.agentBuilder(PoemEditor.class)
            .chatModel(chatModel)
            .outputName("editedPoem")
            .build();

    PoemInUppercase poemInUppercase =
        AgenticServices.agentBuilder(PoemInUppercase.class)
            .chatModel(chatModel)
            .outputName("poemInCapitalLetters")
            .build();

    UntypedAgent untypedAgent=
        AgenticServices.sequenceBuilder()
            .subAgents(poet, poemEditor, poemInUppercase)
            .outputName("poemInCapitalLetters")
            .build();

    Map<String, Object> input = Map.of("theme", "Robots");

    String poem = (String) untypedAgent.invoke(input);
    System.out.println(poem);
  }
}

 

The workflow can be visualized like below.

 


Output

METAL HEARTS BEAT WITH COLD DESIGN,
GEARS TICK ON, ELECTRONIC MIND,
IN METALLIC FLESH, NO HEARTBEAT'S FOUND,
A SYNTHETIC HEART BEATS ALONE, PROFOUND.

 

We defined three agents in our workflow:

 

·      Poet

·      PoemEditor

·      PoemInUppercase

 

The Poet agent takes a theme as input and generates a poem related to that theme.

 

The PoemEditor agent receives both the theme and the poem generated by the Poet agent, then refines it and returns the result as editedPoem.

 

The PoemInUppercase agent takes the editedPoem as input and transforms it into uppercase text. The final output is stored in the variable poemInCapitalLetters.

 

Previous                                                    Next                                                    Home

Getting Started with Agents in LangChain4j

When working with Large Language Models (LLMs), you often want them to act like “specialists” that can perform a well-defined task, whether it’s writing a poem, summarizing a document, or answering a customer’s question.

In LangChain4j, these specialists are called agents. An agent is simply an interface that defines a task and lets the LLM perform it. The magic comes from how simple it is to define and connect an agent to your system.

 

What is an Agent?

An agent in LangChain4j is a component that:

 

·      Performs a specific task using an LLM.

·      Is defined as a Java interface.

·      Uses special annotations like @Agent and @UserMessage to describe its purpose and instructions.

 

Think of an agent as a helper bot with a clear job description.

 

Example: A Poet Agent

Here’s a simple agent that writes poems:

public interface Poet {

    @UserMessage("""
            You are a poet.
            Write a short poem of no more than
            4 lines about the given theme.
            Return only the poem and nothing else.
            The theme is {{theme}}.
            """)
    @Agent("Generates a short poem based on the given theme")
    String generatePoem(@V("theme") String theme);
}

 

Here:

·      @UserMessage: Defines what instructions the LLM should follow.

·      @Agent: Declares this as an agent and gives it a description.

·      Method Parameter @V("theme"): Passes the theme (like "friendship" or "nature") into the message template.

 

So, when you call generatePoem("friendship"), the agent will ask the LLM to produce a short poem about friendship.

 

Why Provide a Description?

The description tells other agents or systems what this agent does. This is especially useful in multi-agent systems, where several agents collaborate. You can even supply the description about the agent while constructing an Agent object using AgenticServices.

Poet poet = AgenticServices.agentBuilder(Poet.class)
        .description("Generates a short poem based on the given theme")
        .chatModel(chatModel)
        .build();

Naming Your Agent

Every agent should have a unique name in the system. This can be set in two ways:

 

·      In the @Agent annotation.

·      Programmatically, using .name("Poet") when building the agent.

Poet poet = AgenticServices.agentBuilder(Poet.class)
        .description("Generates a short poem based on the given theme")
        .name("Poet")
        .chatModel(chatModel)
        .build();

If you don’t set a name, LangChain4j uses the method name (generatePoem) as the agent’s name.

 

Find the below working Application.

 

Poet.java

 

package com.sample.app.agents;

import dev.langchain4j.agentic.Agent;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;

public interface Poet {

  @UserMessage(
      """
            You are a poet.
            Write a short poem of no more than
            4 lines about the given theme.
            Return only the poem and nothing else.
            The theme is {{theme}}.
            """)
  @Agent(description = "Generates a short poem based on the given theme", name = "Poet")
  String generatePoem(@V("theme") String theme);
}

AgentHelloWorld.java

package com.sample.app;

import com.sample.app.agents.Poet;
import dev.langchain4j.agentic.AgenticServices;
import dev.langchain4j.model.ollama.OllamaChatModel;

public class AgentHelloWorld {

  public static void main(String[] args) {

    // Initialize LLM model
    OllamaChatModel chatModel =
        OllamaChatModel.builder().baseUrl("http://localhost:11434").modelName("llama3.2").build();

    Poet poet = AgenticServices.agentBuilder(Poet.class).chatModel(chatModel).build();

    String poem = poet.generatePoem("nature");

    System.out.println(poem);
  }
}

Output

Golden sunsets fade to blue,
Nature's canvas, painted anew.
Trees stand tall, their leaves rustle free,
A symphony for you and me.

 

Previous                                                    Next                                                    Home