Skip to content

Commit

Permalink
Merge branch 'release/1.0-20240719'
Browse files Browse the repository at this point in the history
  • Loading branch information
bsorrentino committed Jul 19, 2024
2 parents 860f0aa + 1fd90b0 commit 082f600
Show file tree
Hide file tree
Showing 115 changed files with 3,596 additions and 93 deletions.
47 changes: 42 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

| Date | Release | info
|--------------| --- | ---
| Jul 19, 2024 | `1.0-SNAPSHOT` | Add support of an embed **Playground Webapp** able to run Langgrap4j flow - [issue #9](https://github.com/bsorrentino/langgraph4j/issues/9)
| Jun 21, 2024 | `1.0-SNAPSHOT` | Add support of [Mermaid] diagram generation - [issue #5](https://github.com/bsorrentino/langgraph4j/issues/5)
| Jun 19, 2024 | `1.0-SNAPSHOT` | Add [adaptive rag](adaptice-rag/README.md) sample
| Jun 10, 2024 | `1.0-SNAPSHOT` | Refactoring how generate graph representation (plantuml)
Expand All @@ -26,7 +27,6 @@
**Maven**

**JDK8 compliant**
```xml
<dependency>
<groupId>org.bsc.langgraph4j</groupId>
Expand All @@ -35,10 +35,6 @@
<dependency>
```

**JDK17 compliant**
> _work in progress_

### Define the agent state

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.
Expand Down Expand Up @@ -190,6 +186,47 @@ return app.stream( inputs );
* [Agent Executor](agent-executor/README.md)
* [Image To PlantUML Diagram](image-to-diagram/README.md)
* [Adaptive RAG](adaptive-rag/README.md)

# Playground Webapp

It is available an **embed playground webapp** able to run a Langgraph4j workflow in visual way.

## Maven

```xml
<dependency>
<groupId>org.bsc.langgraph4j</groupId>
<artifactId>langgraph4j-server-jetty</artifactId>
<version>1.0-SNAPSHOT</version>
<dependency>
```

## Sample

### Code
```java
StateGraph<AgentState> workflow = new StateGraph<>( AgentState::new );

// define your workflow

...

// compile workflow
CompiledGraph<AgentState> app = workflow.compile();

// connect playgroud webapp to workflow
var server = LangGraphStreamingServer.builder()
.port(8080)
.title("LANGGRAPH4j - TEST")
.addInputStringArg("input")
.build(app);
// start playground
server.start().join();

```
### Demo
![result](assets/playground-demo.gif)

# References

* [LangGraph - LangChain Blog][langgraph.blog]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import dev.langchain4j.model.output.FinishReason;
import lombok.var;
import org.bsc.async.AsyncGenerator;
import org.bsc.langgraph4j.CompiledGraph;
import org.bsc.langgraph4j.StateGraph;
import org.bsc.langgraph4j.NodeOutput;
import org.bsc.langgraph4j.state.AgentState;
Expand Down Expand Up @@ -93,29 +94,28 @@ String shouldContinue(State state) {
return "continue";
}

public AsyncGenerator<NodeOutput<State>> execute(ChatLanguageModel chatLanguageModel, Map<String, Object> inputs, List<Object> objectsWithTools) throws Exception {

public CompiledGraph<State> compile(ChatLanguageModel chatLanguageModel, List<Object> objectsWithTools) throws Exception {
var toolInfoList = ToolInfo.fromList( objectsWithTools );

final List<ToolSpecification> toolSpecifications = toolInfoList.stream()
.map(ToolInfo::specification)
.collect(Collectors.toList());

var agentRunnable = Agent.builder()
.chatLanguageModel(chatLanguageModel)
.tools( toolSpecifications )
.build();
.chatLanguageModel(chatLanguageModel)
.tools( toolSpecifications )
.build();

var workflow = new StateGraph<>(State::new);

workflow.setEntryPoint("agent");

workflow.addNode( "agent", node_async( state ->
runAgent(agentRunnable, state))
runAgent(agentRunnable, state))
);

workflow.addNode( "action", node_async( state ->
executeTools(toolInfoList, state))
executeTools(toolInfoList, state))
);

workflow.addConditionalEdges(
Expand All @@ -126,7 +126,13 @@ public AsyncGenerator<NodeOutput<State>> execute(ChatLanguageModel chatLanguageM

workflow.addEdge("action", "agent");

var app = workflow.compile();
return workflow.compile();

}

public AsyncGenerator<NodeOutput<State>> execute(ChatLanguageModel chatLanguageModel, Map<String, Object> inputs, List<Object> objectsWithTools) throws Exception {

var app = compile(chatLanguageModel, objectsWithTools);

return app.stream( inputs );
}
Expand Down
Binary file added assets/playground-demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion core-jdk8/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
<dependency>
<groupId>org.bsc.async</groupId>
<artifactId>async-generator-jdk8</artifactId>
<version>1.0.0</version>
<version>2.0.0</version>
</dependency>

<dependency>
Expand Down
54 changes: 46 additions & 8 deletions core-jdk8/src/main/java/org/bsc/langgraph4j/CompiledGraph.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import org.bsc.async.AsyncGenerator;
import org.bsc.async.AsyncGeneratorQueue;
import org.bsc.langgraph4j.action.AsyncNodeAction;
import org.bsc.langgraph4j.diagram.PlantUMLGenerator;
import org.bsc.langgraph4j.state.AgentState;

import java.util.*;
Expand All @@ -16,6 +15,12 @@
import static java.lang.String.format;
import static java.util.concurrent.CompletableFuture.completedFuture;

/**
* Represents a compiled graph of nodes and edges.
* This class manage the StateGraph execution
*
* @param <State> the type of the state associated with the graph
*/
@Slf4j
public class CompiledGraph<State extends AgentState> {

Expand Down Expand Up @@ -114,11 +119,19 @@ public AsyncGenerator<NodeOutput<State>> stream(Map<String,Object> inputs ) thro
try {
var currentState = stateGraph.getStateFactory().apply(inputs);

queue.add( AsyncGenerator.Data.of( completedFuture( NodeOutput.of("start", currentState)) ));
log.trace( "START");

var currentNodeId = this.getEntryPoint( currentState );

Map<String, Object> partialState;

for(int i = 0; i < maxIterations && !Objects.equals(currentNodeId, StateGraph.END); ++i ) {
int iteration = 0;

while( !Objects.equals(currentNodeId, StateGraph.END) ) {

log.trace( "NEXT NODE: {}", currentNodeId);

var action = nodes.get(currentNodeId);
if (action == null) {
throw StateGraph.RunnableErrors.missingNode.exception(currentNodeId);
Expand All @@ -128,18 +141,28 @@ public AsyncGenerator<NodeOutput<State>> stream(Map<String,Object> inputs ) thro

currentState = currentState.mergeWith(partialState, stateGraph.getStateFactory());

var data = new NodeOutput<>(currentNodeId, currentState);

queue.add( AsyncGenerator.Data.of( completedFuture(data) ));
queue.add( AsyncGenerator.Data.of( completedFuture( NodeOutput.of(currentNodeId, currentState) ) ));

if (Objects.equals(currentNodeId, stateGraph.getFinishPoint())) {
if ( Objects.equals(currentNodeId, stateGraph.getFinishPoint()) ) {
break;
}

currentNodeId = nextNodeId(currentNodeId, currentState);

if ( Objects.equals(currentNodeId, StateGraph.END) ) {
break;
}

if( ++iteration > maxIterations ) {
log.warn( "Maximum number of iterations ({}) reached!", maxIterations);
break;
}

}

queue.add( AsyncGenerator.Data.of( completedFuture( NodeOutput.of("stop", currentState) ) ));
log.trace( "STOP");

} catch (Exception e) {
throw new RuntimeException( e );
}
Expand All @@ -166,6 +189,21 @@ public Optional<State> invoke(Map<String,Object> inputs ) throws Exception {
return result.reduce((a, b) -> b).map( NodeOutput::state);
}

/**
* Generates a drawable graph representation of the state graph.
*
* @param type the type of graph representation to generate
* @param title the title of the graph
* @param printConditionalEdges whether to print conditional edges
* @return a diagram code of the state graph
*/
public GraphRepresentation getGraph( GraphRepresentation.Type type, String title, boolean printConditionalEdges ) {

String content = type.generator.generate( this, title, printConditionalEdges);

return new GraphRepresentation( type, content );
}

/**
* Generates a drawable graph representation of the state graph.
*
Expand All @@ -175,7 +213,7 @@ public Optional<State> invoke(Map<String,Object> inputs ) throws Exception {
*/
public GraphRepresentation getGraph( GraphRepresentation.Type type, String title ) {

String content = type.generator.generate( this,title);
String content = type.generator.generate( this, title, true);

return new GraphRepresentation( type, content );
}
Expand All @@ -187,7 +225,7 @@ public GraphRepresentation getGraph( GraphRepresentation.Type type, String title
* @return a diagram code of the state graph
*/
public GraphRepresentation getGraph( GraphRepresentation.Type type ) {
return getGraph(type, "Graph Diagram");
return getGraph(type, "Graph Diagram", true);
}

}
27 changes: 18 additions & 9 deletions core-jdk8/src/main/java/org/bsc/langgraph4j/DiagramGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ public abstract class DiagramGenerator {
protected abstract void declareConditionalStart( StringBuilder sb, String name ) ;
protected abstract void declareNode( StringBuilder sb, String name ) ;
protected abstract void declareConditionalEdge( StringBuilder sb, int ordinal ) ;
protected abstract StringBuilder commentLine( StringBuilder sb, boolean yesOrNo );

public final <State extends AgentState> String generate( CompiledGraph<State> compiledGraph,String title ) {
public final <State extends AgentState> String generate( CompiledGraph<State> compiledGraph, String title, boolean printConditionalEdge ) {
StringBuilder sb = new StringBuilder();

appendHeader( sb, title );
Expand All @@ -32,7 +33,7 @@ public final <State extends AgentState> String generate( CompiledGraph<State> co
compiledGraph.getEdges().forEach( (k, v) -> {
if( v.value() != null ) {
conditionalEdgeCount[0] += 1;
declareConditionalEdge( sb, conditionalEdgeCount[0] );
declareConditionalEdge( commentLine(sb, !printConditionalEdge), conditionalEdgeCount[0] );
}
});

Expand All @@ -44,20 +45,20 @@ public final <State extends AgentState> String generate( CompiledGraph<State> co
else if( entryPoint.value() != null ) {
String conditionName = "startcondition";
declareConditionalStart( sb, conditionName );
edgeCondition( sb, entryPoint.value(), "start", conditionName) ;
edgeCondition( sb, entryPoint.value(), "start", conditionName, printConditionalEdge) ;
}

conditionalEdgeCount[0] = 0; // reset

compiledGraph.getEdges().forEach( (k,v) -> {
if( v.id() != null ) {
call( sb, k, v.id() );
return;
}
else if( v.value() != null ) {
conditionalEdgeCount[0] += 1;
String conditionName = format("condition%d", conditionalEdgeCount[0]);
edgeCondition( sb, v.value(), k, conditionName );

edgeCondition( sb, v.value(), k, conditionName, printConditionalEdge );

}
});
Expand All @@ -69,15 +70,23 @@ else if( v.value() != null ) {
return sb.toString();

}
private <State extends AgentState> void edgeCondition(StringBuilder sb, EdgeCondition<State> condition, String key, String conditionName ) {
call( sb, key, conditionName);
private <State extends AgentState> void edgeCondition(StringBuilder sb,
EdgeCondition<State> condition,
String k,
String conditionName,
boolean printConditionalEdge) {
call( commentLine(sb, !printConditionalEdge), k, conditionName);

condition.mappings().forEach( (cond, to) -> {
if( to.equals(StateGraph.END) ) {
finish( sb, conditionName, cond );

finish( commentLine(sb, !printConditionalEdge), conditionName, cond );
finish( commentLine(sb, printConditionalEdge), k, cond );

}
else {
call( sb, conditionName, to, cond );
call( commentLine(sb, !printConditionalEdge), conditionName, to, cond );
call( commentLine(sb, printConditionalEdge), k, to, cond );
}
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
*
* @param <State> the type of the state associated with the node output
*/
@Value
@Value(staticConstructor="of")
@Accessors(fluent = true)
public class NodeOutput<State extends AgentState> {

Expand Down
5 changes: 0 additions & 5 deletions core-jdk8/src/main/java/org/bsc/langgraph4j/StateGraph.java
Original file line number Diff line number Diff line change
@@ -1,19 +1,14 @@
package org.bsc.langgraph4j;

import lombok.var;
import org.bsc.async.AsyncGenerator;
import org.bsc.async.AsyncGeneratorQueue;
import org.bsc.langgraph4j.action.AsyncEdgeAction;
import org.bsc.langgraph4j.action.AsyncNodeAction;
import org.bsc.langgraph4j.state.AgentState;
import org.bsc.langgraph4j.state.AgentStateFactory;

import java.util.*;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.stream.StreamSupport;

import static java.lang.String.format;
import static java.util.concurrent.CompletableFuture.completedFuture;

/**
* Represents a state graph with nodes and edges.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ protected void appendHeader(StringBuilder sb, String title) {

@Override
protected void appendFooter(StringBuilder sb) {

// do nothing
}

@Override
Expand All @@ -41,7 +41,12 @@ protected void declareConditionalEdge(StringBuilder sb, int ordinal) {
sb.append( format("\tcondition%d{\"check state\"}\n", ordinal) );
}

@Override
@Override
protected StringBuilder commentLine(StringBuilder sb, boolean yesOrNo) {
return (yesOrNo) ? sb.append( "\t%%" ) : sb;
}

@Override
protected void start(StringBuilder sb, String entryPoint) {
call( sb, "start", entryPoint );
}
Expand All @@ -58,11 +63,11 @@ protected void finish(StringBuilder sb, String finishPoint, String description)

@Override
protected void call(StringBuilder sb, String from, String to) {
sb.append( format("\t%s --> %s\n", from, to) );
sb.append( format("\t%1$s:::%1$s --> %2$s:::%2$s\n", from, to) );
}

@Override
protected void call(StringBuilder sb, String from, String to, String description) {
sb.append(format("\t%s -->|%s| %s\n", from, description, to));
sb.append(format("\t%1$s:::%1$s -->|%2$s| %3$s:::%3$s\n", from, description, to));
}
}
Loading

0 comments on commit 082f600

Please sign in to comment.