Skip to content

Commit

Permalink
feat(image-to-diagram): update diagrams and workflow for image proces…
Browse files Browse the repository at this point in the history
…sing
  • Loading branch information
bsorrentino committed Nov 29, 2024
1 parent 26f9993 commit 778c8cc
Show file tree
Hide file tree
Showing 12 changed files with 313 additions and 53 deletions.
89 changes: 83 additions & 6 deletions samples/image-to-diagram/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,99 @@

The "<u>Generate PlantUML diagram from Image</u>" flow involves structured sequence of steps. Initially, an agent receives an image and is responsible for analyzing and describing its content. This description is then passed to a specialized agent equipped with the skills to translate the description into PlantUML code. To ensure precision in diagram generation, the type of diagram identified within the image dictates the selection of the appropriately skilled agent for the translation task. This ensures that each diagram type is handled by an agent with specific expertise relevant to that diagram.

### PlantUML Diagram
![diagram][image_to_diagram]
### Diagram of solution
```mermaid
---
title: Image to diagram with correction
---
flowchart TD
__START__((start))
__END__((stop))
agent_describer("agent_describer")
agent_sequence_plantuml("agent_sequence_plantuml")
agent_generic_plantuml("agent_generic_plantuml")
%% condition1{"check state"}
__START__:::__START__ --> agent_describer:::agent_describer
%% agent_describer:::agent_describer --> condition1:::condition1
%% condition1:::condition1 -->|sequence| agent_sequence_plantuml:::agent_sequence_plantuml
agent_describer:::agent_describer -->|sequence| agent_sequence_plantuml:::agent_sequence_plantuml
%% condition1:::condition1 -->|generic| agent_generic_plantuml:::agent_generic_plantuml
agent_describer:::agent_describer -->|generic| agent_generic_plantuml:::agent_generic_plantuml
agent_sequence_plantuml:::agent_sequence_plantuml --> __END__:::__END__
agent_generic_plantuml:::agent_generic_plantuml --> __END__:::__END__
```

### Handle translation errors

In the case that there are errors in result of PlantUML code we have established a supplementary flow that provided a correction process consisting of iteration between both verification and rewrite steps as shown below

#### PlantUML Diagram
![diagram][correction_process]
#### Diagram of solution

```mermaid
---
title: Correction Process
---
flowchart TD
__START__((start))
__END__((stop))
evaluate_result("evaluate_result")
agent_review("agent_review")
%% condition1{"check state"}
__START__:::__START__ --> evaluate_result:::evaluate_result
agent_review:::agent_review --> evaluate_result:::evaluate_result
%% evaluate_result:::evaluate_result --> condition1:::condition1
%% condition1:::condition1 -->|ERROR| agent_review:::agent_review
evaluate_result:::evaluate_result -->|ERROR| agent_review:::agent_review
%% condition1:::condition1 -->|UNKNOWN| __END__:::__END__
evaluate_result:::evaluate_result -->|UNKNOWN| __END__:::__END__
%% condition1:::condition1 -->|OK| __END__:::__END__
evaluate_result:::evaluate_result -->|OK| __END__:::__END__
```

### Merge All

Finally we can put all together having a complete flow that include also a refinement process over result.

#### PlantUML Diagram
![diagram][image_to_diagram_correction]
#### Diagram of solution

```mermaid
---
title: Image to diagram with correction
---
flowchart TD
__START__((start))
__END__((stop))
agent_describer("agent_describer")
agent_sequence_plantuml("agent_sequence_plantuml")
agent_generic_plantuml("agent_generic_plantuml")
subgraph evaluate_result
#__START__@{ shape: start, label: "enter" }
#__END__@{ shape: stop, label: "exit" }
#evaluate_result("evaluate_result")
#agent_review("agent_review")
%% #condition1{"check state"}
#__START__:::__START__ --> #evaluate_result:::evaluate_result
#agent_review:::agent_review --> #evaluate_result:::evaluate_result
%% #evaluate_result:::evaluate_result --> #condition1:::condition1
%% #condition1:::condition1 -->|ERROR| #agent_review:::agent_review
#evaluate_result:::evaluate_result -->|ERROR| #agent_review:::agent_review
%% #condition1:::condition1 -->|UNKNOWN| #__END__:::__END__
#evaluate_result:::evaluate_result -->|UNKNOWN| #__END__:::__END__
%% #condition1:::condition1 -->|OK| #__END__:::__END__
#evaluate_result:::evaluate_result -->|OK| #__END__:::__END__
end
%% condition1{"check state"}
__START__:::__START__ --> agent_describer:::agent_describer
%% agent_describer:::agent_describer --> condition1:::condition1
%% condition1:::condition1 -->|sequence| agent_sequence_plantuml:::agent_sequence_plantuml
agent_describer:::agent_describer -->|sequence| agent_sequence_plantuml:::agent_sequence_plantuml
%% condition1:::condition1 -->|generic| agent_generic_plantuml:::agent_generic_plantuml
agent_describer:::agent_describer -->|generic| agent_generic_plantuml:::agent_generic_plantuml
agent_sequence_plantuml:::agent_sequence_plantuml --> evaluate_result:::evaluate_result
agent_generic_plantuml:::agent_generic_plantuml --> evaluate_result:::evaluate_result
evaluate_result:::evaluate_result --> __END__:::__END__
```

----
> Go to [code](src/main/java/dev/langchain4j/image_to_diagram)
Expand Down
Binary file not shown.
Binary file removed samples/image-to-diagram/image_to_diagram.puml.png
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ public class DiagramCorrectionProcess implements ImageToDiagram {
final AsyncNodeAction<State> reviewResult = new ReviewResult( getModel() );
final AsyncEdgeAction<State> routeEvaluationResult = edge_async( new RouteEvaluationResult() );

public StateGraph<State> workflow() throws Exception {
return workflow( new JSONStateSerializer() );
}

public StateGraph<State> workflow(StateSerializer<State> stateSerializer ) throws Exception {

return new StateGraph<>(State.SCHEMA,stateSerializer)
Expand All @@ -43,16 +47,4 @@ public StateGraph<State> workflow(StateSerializer<State> stateSerializer ) throw

}


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

var stateSerializer = new JSONStateSerializer();

var workflow = workflow(stateSerializer);

var app = workflow.compile();

return app.stream( inputs );

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,49 +45,40 @@ public static ImageUrlOrData of( String data) {
final AsyncNodeAction<State> evaluateResult = new EvaluateResult(getModel());
final AsyncEdgeAction<State> routeDiagramTranslation = new RouteDiagramTranslation();

// public ImageToDiagramProcess(URI image) {
// this(ImageUrlOrData.of(image));
// }
//
// public ImageToDiagramProcess(String resourceName ) throws Exception {
// this( ImageUrlOrData.of(ImageLoader.loadImageAsBase64( resourceName ) ) );
// }

public AsyncGenerator<NodeOutput<State>> execute( @NonNull ImageUrlOrData imageData ) throws Exception {

public StateGraph<State> workflow() throws Exception {
var stateSerializer = new JSONStateSerializer();

var app = new StateGraph<>(State.SCHEMA,stateSerializer)
.addNode("agent_describer", describeDiagramImage )
.addNode("agent_sequence_plantuml", translateSequenceDiagramToPlantUML)
.addNode("agent_generic_plantuml", translateGenericDiagramToPlantUML )
.addNode( "evaluate_result", evaluateResult )
.addConditionalEdges("agent_describer",
routeDiagramTranslation,
mapOf( "sequence", "agent_sequence_plantuml",
"generic", "agent_generic_plantuml" )
)
.addEdge("agent_sequence_plantuml", "evaluate_result")
.addEdge("agent_generic_plantuml", "evaluate_result")
.addEdge( START,"agent_describer")
.addEdge("evaluate_result", END)
.compile();

return app.stream( Map.of( "imageData", imageData ) );
return new StateGraph<>(State.SCHEMA,stateSerializer)
.addNode("agent_describer", describeDiagramImage )
.addNode("agent_sequence_plantuml", translateSequenceDiagramToPlantUML)
.addNode("agent_generic_plantuml", translateGenericDiagramToPlantUML )
//.addNode( "evaluate_result", evaluateResult )
.addConditionalEdges("agent_describer",
routeDiagramTranslation,
mapOf( "sequence", "agent_sequence_plantuml",
"generic", "agent_generic_plantuml" )
)
//.addEdge("agent_sequence_plantuml", "evaluate_result")
//.addEdge("agent_generic_plantuml", "evaluate_result")
.addEdge("agent_sequence_plantuml", END)
.addEdge("agent_generic_plantuml", END)
.addEdge( START,"agent_describer")
//.addEdge("evaluate_result", END)
;
}

public AsyncGenerator<NodeOutput<State>> executeWithCorrection( @NonNull ImageUrlOrData imageData ) throws Exception {
public StateGraph<State> workflowWithCorrection() throws Exception {

var stateSerializer = new JSONStateSerializer();

var diagramCorrectionProcess = new DiagramCorrectionProcess().workflow(stateSerializer).compile();

var app = new StateGraph<>(State.SCHEMA,stateSerializer)
return new StateGraph<>(State.SCHEMA,stateSerializer)
.addNode("agent_describer", describeDiagramImage )
.addNode("agent_sequence_plantuml", translateSequenceDiagramToPlantUML )
.addNode("agent_generic_plantuml", translateGenericDiagramToPlantUML )
.addSubgraph( "agent_diagram_correction", diagramCorrectionProcess )
.addNode( "evaluate_result", evaluateResult )
.addSubgraph( "evaluate_result", diagramCorrectionProcess )
.addConditionalEdges("agent_describer",
routeDiagramTranslation,
mapOf( "sequence", "agent_sequence_plantuml",
Expand All @@ -96,10 +87,15 @@ public AsyncGenerator<NodeOutput<State>> executeWithCorrection( @NonNull ImageUr
.addEdge("agent_sequence_plantuml", "evaluate_result")
.addEdge("agent_generic_plantuml", "evaluate_result")
.addEdge( START,"agent_describer")
.addEdge("evaluate_result", END)
.compile();
.addEdge("evaluate_result", END);
}

return app.stream( Map.of( "imageData", imageData ) );
public AsyncGenerator<NodeOutput<State>> execute( @NonNull ImageUrlOrData imageData ) throws Exception {
return workflow().compile().stream( Map.of( "imageData", imageData ) );
}

public AsyncGenerator<NodeOutput<State>> executeWithCorrection( @NonNull ImageUrlOrData imageData ) throws Exception {
return workflowWithCorrection().compile().stream( Map.of( "imageData", imageData ) );
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public CompletableFuture<Map<String, Object>> apply(ImageToDiagram.State state)

ArrayList<NodeOutput<ImageToDiagram.State>> list = new ArrayList<NodeOutput<ImageToDiagram.State>>();
try {
return diagramCorrectionProcess.execute( state.data() )
return diagramCorrectionProcess.workflow().compile().stream( state.data() )
.collectAsync(list, v -> log.info( v.toString() ) )
.thenApply( v -> {
if( list.isEmpty() ) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import dev.langchain4j.model.input.PromptTemplate;
import lombok.extern.slf4j.Slf4j;

import org.bsc.langgraph4j.GraphRepresentation;
import org.bsc.langgraph4j.NodeOutput;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -159,7 +160,7 @@ private void reviewDiagram( String diagramId ) throws Exception {
final var process = new DiagramCorrectionProcess();

ArrayList<NodeOutput<ImageToDiagram.State>> list = new ArrayList<NodeOutput<ImageToDiagram.State>>();
var result = process.execute( Map.of( "diagramCode", diagramCode ) )
var result = process.workflow().compile().stream( Map.of( "diagramCode", diagramCode ) )
.collectAsync( list, v -> log.trace(v.toString()) )
.thenApply( v -> {
if( list.isEmpty() ) {
Expand Down Expand Up @@ -206,4 +207,72 @@ public void reviewDiagram6() throws Exception {
public void reviewDiagram7() throws Exception {
reviewDiagram("07");
}

@Test
public void getGraph() throws Exception {
var agentExecutor = new ImageToDiagramProcess();

var plantUml = agentExecutor.workflow()
.getGraph( GraphRepresentation.Type.PLANTUML,
"Image to diagram with correction",
false );

assertNotNull(plantUml);
var expected_workflow = readTextResource("01_expected_plantuml.txt");
assertEquals( expected_workflow, plantUml.getContent() );

var plantUmlWithCorrection = agentExecutor.workflowWithCorrection()
.getGraph( GraphRepresentation.Type.PLANTUML,
"Image to diagram with correction",
false );

assertNotNull(plantUmlWithCorrection);
var expected_workflow_with_correction = readTextResource("02_expected_plantuml.txt");
assertEquals( expected_workflow_with_correction, plantUmlWithCorrection.getContent() );

var mermaid = agentExecutor.workflow()
.getGraph( GraphRepresentation.Type.MERMAID,
"Image to diagram with correction",
false );

assertNotNull(mermaid);
expected_workflow = readTextResource("01_expected_mermaid.txt");
assertEquals( expected_workflow, mermaid.getContent() );

var mermaidWithCorrection = agentExecutor.workflowWithCorrection()
.getGraph( GraphRepresentation.Type.MERMAID,
"Image to diagram with correction",
false );

assertNotNull(mermaidWithCorrection);
expected_workflow_with_correction = readTextResource("02_expected_mermaid.txt");
assertEquals( expected_workflow_with_correction, mermaidWithCorrection.getContent() );

var correctionProcess = new DiagramCorrectionProcess();

var correctionPlantUml = correctionProcess.workflow().getGraph( GraphRepresentation.Type.MERMAID,
"Correction Process",
false );

assertEquals( "---\n" +
"title: Correction Process\n" +
"---\n" +
"flowchart TD\n" +
"\t__START__((start))\n" +
"\t__END__((stop))\n" +
"\tevaluate_result(\"evaluate_result\")\n" +
"\tagent_review(\"agent_review\")\n" +
"\t%%\tcondition1{\"check state\"}\n" +
"\t__START__:::__START__ --> evaluate_result:::evaluate_result\n" +
"\tagent_review:::agent_review --> evaluate_result:::evaluate_result\n" +
"\t%%\tevaluate_result:::evaluate_result --> condition1:::condition1\n" +
"\t%%\tcondition1:::condition1 -->|ERROR| agent_review:::agent_review\n" +
"\tevaluate_result:::evaluate_result -->|ERROR| agent_review:::agent_review\n" +
"\t%%\tcondition1:::condition1 -->|UNKNOWN| __END__:::__END__\n" +
"\tevaluate_result:::evaluate_result -->|UNKNOWN| __END__:::__END__\n" +
"\t%%\tcondition1:::condition1 -->|OK| __END__:::__END__\n" +
"\tevaluate_result:::evaluate_result -->|OK| __END__:::__END__\n", correctionPlantUml.getContent());

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
title: Image to diagram with correction
---
flowchart TD
__START__((start))
__END__((stop))
agent_describer("agent_describer")
agent_sequence_plantuml("agent_sequence_plantuml")
agent_generic_plantuml("agent_generic_plantuml")
%% condition1{"check state"}
__START__:::__START__ --> agent_describer:::agent_describer
%% agent_describer:::agent_describer --> condition1:::condition1
%% condition1:::condition1 -->|sequence| agent_sequence_plantuml:::agent_sequence_plantuml
agent_describer:::agent_describer -->|sequence| agent_sequence_plantuml:::agent_sequence_plantuml
%% condition1:::condition1 -->|generic| agent_generic_plantuml:::agent_generic_plantuml
agent_describer:::agent_describer -->|generic| agent_generic_plantuml:::agent_generic_plantuml
agent_sequence_plantuml:::agent_sequence_plantuml --> __END__:::__END__
agent_generic_plantuml:::agent_generic_plantuml --> __END__:::__END__

Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
@startuml Image_to_diagram_with_correction
skinparam usecaseFontSize 14
skinparam usecaseStereotypeFontSize 12
skinparam hexagonFontSize 14
skinparam hexagonStereotypeFontSize 12
title "Image to diagram with correction"
footer

powered by langgraph4j
end footer
circle start<<input>> as __START__
circle stop as __END__
usecase "agent_describer"<<Node>>
usecase "agent_sequence_plantuml"<<Node>>
usecase "agent_generic_plantuml"<<Node>>
'hexagon "check state" as condition1<<Condition>>
"__START__" -down-> "agent_describer"
'"agent_describer" -down-> "condition1"
'"condition1" -down-> "agent_sequence_plantuml": "sequence"
"agent_describer" -down-> "agent_sequence_plantuml": "sequence"
'"condition1" -down-> "agent_generic_plantuml": "generic"
"agent_describer" -down-> "agent_generic_plantuml": "generic"
"agent_sequence_plantuml" -down-> "__END__"
"agent_generic_plantuml" -down-> "__END__"
@enduml

Loading

0 comments on commit 778c8cc

Please sign in to comment.