Build and demo Java AI agent systems with langchain4j-agentic: workflow patterns, supervisor, custom Planner strategies (incl. the flagship typed-verdict / CriticResult-style critic pattern), plus MCP tools, A2A remote agents, build setup, and conference-demo storylines. Pinned to 1.15.0 / 1.15.0-beta25.
84
89%
Does it follow best practices?
Impact
100%
4.76xAverage score across 2 eval scenarios
Passed
No known issues
An agent performs a specific task using an LLM. It is defined as an interface with a single method annotated with @Agent — essentially a normal LangChain4j AI service that can be composed with other agents.
public interface CreativeWriter {
@UserMessage("""
You are a creative writer.
Generate a draft of a story no more than
3 sentences long around the given topic.
Return only the story and nothing else.
The topic is {{topic}}.
""")
@Agent("Generates a story based on the given topic")
String generateStory(@V("topic") String topic);
}@Agent description and name.description(...).@Agent(name = ...), via .name(...), or let it default to the annotated method name.@V argument bindingArguments are not passed directly — their values are read from AgenticScope shared variables with the matching name. @V("topic") reads the topic variable. If the class is compiled with -parameters (parameter names retained at runtime), @V can be omitted and names are inferred.
CreativeWriter creativeWriter = AgenticServices
.agentBuilder(CreativeWriter.class)
.chatModel(myChatModel)
.outputKey("story")
.build();The key difference from a plain AI service is outputKey: the name of the shared AgenticScope variable where the result is stored so other agents can read it. The output key can instead be declared on the annotation:
@Agent(outputKey = "story", description = "Generates a story based on the given topic")You can skip the typed interface entirely. AgenticServices.agentBuilder() (no class) returns an UntypedAgent:
public interface UntypedAgent {
@Agent
Object invoke(Map input);
}UntypedAgent creativeWriter = AgenticServices.agentBuilder()
.chatModel(BASE_MODEL)
.description("Generate a story based on the given topic")
.userMessage("""
You are a creative writer.
Generate a draft of a story no more than
3 sentences long around the given topic.
Return only the story and nothing else.
The topic is {{topic}}.
""")
.inputKey(String.class, "topic")
.returnType(String.class) // String is the default return type for untyped agents
.outputKey("story")
.build();Values in the input Map are copied into the AgenticScope shared variables; the result is read back from the named outputKey.
The AgenticScope is the collection of data shared among agents in a system:
outputKey) for others to read (via @V).output(...) functions, exitCondition predicates, error handlers, dynamic model functions).Common read/write API used throughout the patterns:
agenticScope.readState("score", 0.0); // read with default
agenticScope.writeState("topic", "dragons and wizards");
agenticScope.hasState("score");
agenticScope.state(); // full Map view, e.g. state().keySet()
agenticScope.readState(Category.class); // typed-key read (see declarative-api.md)Any builder result (sequence, loop, supervisor, ...) is itself an agent and can be used as a sub-agent of another system. You may give the composed system its own typed interface for strongly typed invocation — see workflow-patterns.md for NovelCreator/StyledWriter examples.