Skip to content

Commit e0335e0

Browse files
committed
Merge branch 'release/1.0-20240329'
2 parents f404e50 + c1523fc commit e0335e0

File tree

82 files changed

+3041
-231
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

82 files changed

+3041
-231
lines changed

Diff for: .gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,5 @@ build/
3434
### Mac OS ###
3535
.DS_Store
3636
.java-version
37+
38+
.env

Diff for: README.md

+144-2
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,144 @@
1-
# langgraph4j
2-
🚀 LangGraph for Java. A library for building stateful, multi-actor applications with LLMs, built for work with langchain4j
1+
# LangGraph for Java
2+
🚀 LangGraph for Java. A library for building stateful, multi-actor applications with LLMs, built for work with [langchain4j]
3+
> It is a porting of original [LangGraph] from [LangChain AI project][langchain.ai] in Java fashion
4+
5+
6+
## Quick Start
7+
8+
### Adding LangGraph dependency
9+
10+
Currently are available only the developer SNAPSHOTs
11+
12+
**Maven**
13+
14+
** JDK8 compliant **
15+
```xml
16+
<dependency>
17+
<groupId>org.bsc.langgraph4j</groupId>
18+
<artifactId>langgraph4j</artifactId>
19+
<version>1.0-SNAPSHOT</version>
20+
<classifier>jdk8<classifier>
21+
<dependency>
22+
```
23+
24+
** JDK17 compliant **
25+
```xml
26+
<dependency>
27+
<groupId>org.bsc.langgraph4j</groupId>
28+
<artifactId>langgraph4j</artifactId>
29+
<version>1.0-SNAPSHOT</version>
30+
<classifier>jdk17<classifier>
31+
<dependency>
32+
```
33+
34+
### Define the agent state
35+
36+
The main type of graph in `langgraph` is the `StatefulGraph`. This graph is parameterized by a state object that it passes around to each node. Each node then returns operations to update that state. These operations can either SET specific attributes on the state (e.g. overwrite the existing values) or ADD to the existing attribute. Whether to set or add is denoted by initialize the property with a `AppendableValue`. The State must be compliant with `AgentState` interface that essentially is a `Map` wrapper.
37+
38+
```java
39+
public interface AgentState {
40+
41+
java.util.Map<String,Object> data();
42+
43+
}
44+
```
45+
46+
### Define the nodes
47+
48+
We now need to define a few different nodes in our graph. In `langgraph`, a node is a function that accept an `AgentState` as argument. There are two main nodes we need for this:
49+
50+
1. **The agent**: responsible for deciding what (if any) actions to take.
51+
1. **A function to invoke tools**: if the agent decides to take an action, this node will then execute that action.
52+
53+
### Define Edges
54+
55+
We will also need to define some edges. Some of these edges may be conditional. The reason they are conditional is that based on the output of a node, one of several paths may be taken. The path that is taken is not known until that node is run (the LLM decides).
56+
57+
1. **Conditional Edge**: after the agent is called, we should either:
58+
* If the agent said to take an action, then the function to invoke tools should be called
59+
* If the agent said that it was finished, then it should finish
60+
1. **Normal Edge**: after the tools are invoked, it should always go back to the agent to decide what to do next
61+
62+
### Define the graph
63+
64+
We can now put it all together and define the graph! (see example below)
65+
66+
## Integrate with LangChain4j
67+
68+
Like default use case proposed in [LangGraph blog][langgraph.blog], We have ported [AgentExecutor] implementation from [langchain] using LangGraph4j. In the [agents](agents) project's module, you can the complete working code with tests. Feel free to checkout and use it as a reference.
69+
Below you can find a piece of code of the `AgentExecutor` to give you an idea of how is has built in langgraph style.
70+
71+
72+
```java
73+
74+
public static class State implements AgentState {
75+
76+
private final Map<String,Object> data;
77+
78+
public State( Map<String,Object> initData ) {
79+
this.data = new HashMap<>(initData);
80+
this.data.putIfAbsent("intermediate_steps",
81+
new AppendableValue<IntermediateStep>());
82+
}
83+
84+
public Map<String,Object> data() { return Map.copyOf(data); }
85+
86+
Optional<String> input() { return value("input"); }
87+
Optional<AgentOutcome> agentOutcome() { return value("agent_outcome"); }
88+
Optional<List<IntermediateStep>> intermediateSteps() { return appendableValue("intermediate_steps"); }
89+
}
90+
91+
92+
var toolInfoList = ToolInfo.fromList( objectsWithTools );
93+
94+
final List<ToolSpecification> toolSpecifications = toolInfoList.stream()
95+
.map(ToolInfo::specification)
96+
.toList();
97+
98+
var agentRunnable = Agent.builder()
99+
.chatLanguageModel(chatLanguageModel)
100+
.tools( toolSpecifications )
101+
.build();
102+
103+
var workflow = new GraphState<>(State::new);
104+
105+
workflow.setEntryPoint("agent");
106+
107+
workflow.addNode( "agent", node_async( state ->
108+
runAgent(agentRunnable, state)) // see implementation in the repo code
109+
);
110+
111+
workflow.addNode( "action", node_async( state ->
112+
executeTools(toolInfoList, state)) // see implementation in the repo code
113+
);
114+
115+
workflow.addConditionalEdge(
116+
"agent",
117+
edge_async( state -> {
118+
if (state.agentOutcome().map(AgentOutcome::finish).isPresent()) {
119+
return "end";
120+
}
121+
return "continue";
122+
}),
123+
Map.of("continue", "action", "end", END)
124+
);
125+
126+
workflow.addEdge("action", "agent");
127+
128+
var app = workflow.compile();
129+
130+
return app.stream( inputs );
131+
132+
```
133+
134+
# References
135+
136+
* [LangGraph - LangChain Blog][langgraph.blog]
137+
138+
[langgraph.blog]: https://blog.langchain.dev/langgraph/
139+
[langchain4j]: https://github.com/langchain4j/langchain4j
140+
[langchain.ai]: https://github.com/langchain-ai
141+
[langchain]: https://github.com/langchain-ai/langchain/
142+
[langgraph]: https://github.com/langchain-ai/langgraph
143+
[langchain.agents]: https://python.langchain.com/docs/modules/agents/
144+
[AgentExecutor]: https://github.com/langchain-ai/langchain/blob/master/libs/langchain/langchain/agents/agent.py

Diff for: agents-jdk17/pom.xml

+115
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
2+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
3+
<modelVersion>4.0.0</modelVersion>
4+
<parent>
5+
<groupId>org.bsc.langgraph4j</groupId>
6+
<artifactId>langgraph4j-parent</artifactId>
7+
<version>1.0-SNAPSHOT</version>
8+
</parent>
9+
10+
<artifactId>langgraph4j-agents-jdk17</artifactId>
11+
<packaging>jar</packaging>
12+
13+
<name>langgraph4j::agents::jdk17</name>
14+
15+
<properties>
16+
</properties>
17+
18+
<dependencies>
19+
20+
<dependency>
21+
<groupId>org.bsc.langgraph4j</groupId>
22+
<artifactId>langgraph4j</artifactId>
23+
<version>${project.version}</version>
24+
<classifier>jdk17</classifier>
25+
</dependency>
26+
27+
<dependency>
28+
<groupId>org.projectlombok</groupId>
29+
<artifactId>lombok</artifactId>
30+
<scope>provided</scope>
31+
</dependency>
32+
33+
<dependency>
34+
<groupId>dev.langchain4j</groupId>
35+
<artifactId>langchain4j</artifactId>
36+
<version>${langchai4j.version}</version>
37+
</dependency>
38+
<dependency>
39+
<groupId>dev.langchain4j</groupId>
40+
<artifactId>langchain4j-open-ai</artifactId>
41+
<version>${langchai4j.version}</version>
42+
</dependency>
43+
<dependency>
44+
<groupId>org.slf4j</groupId>
45+
<artifactId>slf4j-api</artifactId>
46+
</dependency>
47+
<dependency>
48+
<groupId>org.slf4j</groupId>
49+
<artifactId>slf4j-jdk14</artifactId>
50+
</dependency>
51+
52+
<dependency>
53+
<groupId>org.junit.jupiter</groupId>
54+
<artifactId>junit-jupiter</artifactId>
55+
<scope>test</scope>
56+
</dependency>
57+
58+
</dependencies>
59+
60+
<build>
61+
<plugins>
62+
<plugin>
63+
<groupId>org.apache.maven.plugins</groupId>
64+
<artifactId>maven-jar-plugin</artifactId>
65+
<executions>
66+
<execution>
67+
<id>default-package-jdk17</id>
68+
<phase>package</phase>
69+
<goals>
70+
<goal>jar</goal>
71+
</goals>
72+
<configuration>
73+
<classifier>jdk17</classifier>
74+
</configuration>
75+
</execution>
76+
</executions>
77+
</plugin>
78+
79+
<plugin>
80+
<groupId>org.apache.maven.plugins</groupId>
81+
<artifactId>maven-deploy-plugin</artifactId>
82+
<configuration>
83+
<skip>true</skip>
84+
</configuration>
85+
</plugin>
86+
87+
<plugin>
88+
<groupId>org.apache.maven.plugins</groupId>
89+
<artifactId>maven-surefire-plugin</artifactId>
90+
<configuration>
91+
<skipTests>true</skipTests>
92+
</configuration>
93+
</plugin>
94+
95+
<plugin>
96+
<groupId>org.projectlombok</groupId>
97+
<artifactId>lombok-maven-plugin</artifactId>
98+
<version>1.18.20.0</version>
99+
<configuration>
100+
<sourceDirectory>src/main/java</sourceDirectory>
101+
</configuration>
102+
<!--
103+
<executions>
104+
<execution>
105+
<phase>generate-sources</phase>
106+
<goals>
107+
<goal>delombok</goal>
108+
</goals>
109+
</execution>
110+
</executions>
111+
-->
112+
</plugin>
113+
</plugins>
114+
</build>
115+
</project>
+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package dev.langchain4j;
2+
3+
import dev.langchain4j.agent.tool.ToolSpecification;
4+
import dev.langchain4j.data.message.*;
5+
import dev.langchain4j.model.chat.ChatLanguageModel;
6+
import dev.langchain4j.model.input.PromptTemplate;
7+
import dev.langchain4j.model.output.Response;
8+
import lombok.Builder;
9+
import lombok.Singular;
10+
11+
import java.util.ArrayList;
12+
import java.util.List;
13+
import java.util.Map;
14+
15+
@Builder
16+
public class Agent {
17+
18+
private final ChatLanguageModel chatLanguageModel;
19+
@Singular private final List<ToolSpecification> tools;
20+
21+
22+
public Response<AiMessage> execute( String input, List<IntermediateStep> intermediateSteps ) {
23+
var userMessageTemplate = PromptTemplate.from( "{{input}}" )
24+
.apply( Map.of( "input", input));
25+
26+
var messages = new ArrayList<ChatMessage>();
27+
28+
messages.add(new SystemMessage("You are a helpful assistant"));
29+
messages.add(new UserMessage(userMessageTemplate.text()));
30+
31+
if (!intermediateSteps.isEmpty()) {
32+
33+
var toolRequests = intermediateSteps.stream()
34+
.map(IntermediateStep::action)
35+
.map(AgentAction::toolExecutionRequest)
36+
.toList();
37+
38+
messages.add(new AiMessage(toolRequests)); // reply with tool requests
39+
40+
for (IntermediateStep step : intermediateSteps) {
41+
var toolRequest = step.action().toolExecutionRequest();
42+
43+
messages.add(new ToolExecutionResultMessage(toolRequest.id(), toolRequest.name(), step.observation()));
44+
}
45+
}
46+
return chatLanguageModel.generate( messages, tools );
47+
}
48+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package dev.langchain4j;
2+
3+
import dev.langchain4j.agent.tool.ToolExecutionRequest;
4+
5+
import java.util.Objects;
6+
7+
record AgentAction (ToolExecutionRequest toolExecutionRequest, String log ) {
8+
public AgentAction {
9+
Objects.requireNonNull(toolExecutionRequest);
10+
}
11+
}

0 commit comments

Comments
 (0)