So after defining our chains, we want to define our nodes.
And those nodes are going to execute those chains.
And we want to connect those nodes and to connect the execution flow with edges.
So this is what we're going to be building this diagram over here which is going to be our workflow.
So it's going to start with the generate node which is going to run the generation chain on the input.
Then we're going to decide whether we want to finish or we want to go and reflect.
Then we're going to take the output of generation chain.
And we're going to reflect.
And we're going to run the reflection chain.
And then we're going to take the output of the reflection node.
And we want then regenerate the output here.
And we want to go through this loop until we meet a certain condition.
And to do that we'll be implementing everything with graph.
So line graph is going to describe our execution flow.
So in this video we're going to implement the nodes.
We're going to define the state that the graph is going to be working on.
So every node is going to have access to the state.
And this is going to be its input.
And when it's finished executing it's going to update the state here.
Now our state in this case is going to be simply a list of messages where we are going to continue to
append to this list here.
All right.
So now let's go to the code and let's go and implement everything.
Now before I move to the IDE, I just want to give you a heads up I'm going to be using now cursor because
this video was actually refilmed to match the latest versions of Link graph 1.0.
So all the code is the same, just the IDE is different now, so don't freak out.
So let's start with the imports and I'll first want to import, type dict and annotate it.
Did so.
Type dict is a type dictionary which creates a structured dictionary with hints for the keys, and we're
going to be using it when we'll define our state schema.
Now we need this because lang graph would require typed state definitions to know which data flows through
and out of the graph.
Annotated is going to help us add metadata to those type hints.
We also want to import from linked chain the base message, which is the abstract base class for all
message types in link chain, and we're going to be using it as a type hint for the messages list.
That is going to be in our state schema, which we're going to see very, very soon.
And this is going to ensure safety for different message types, whether it's a human message, an AI
message, or a system message.
So we also want to import the human message to represent a message from a user.
And we want this because for specific messages, we want to make a distinguish between the user content
from the AI responses.
So now we want to import from graph the end which is a special constant to mark the graph termination.
So this is the ending node of the graph.
So there is also the state graph.
And this is the main class for building stateful graph graphs.
And when we'll create our workflow which is going to describe the execution of the nodes and edges of
our software of our authentic software, then we need to provide it with a state.
And the state is simply going to be a data structure, usually dictionary or a pedantic class, which
is going to be maintained through the entire execution, and it's going to hold the information of the
execution.
We can store their intermediate results, LLM responses, and basically everything we can think of,
we can store there.
It's very flexible, and every node that is going to run is going to have access to this state.
So it's the input for every node and every node that is going to do some work.
It's going to then update our state.
And I'm knowing very abstract right now.
It will be all clear when we implement this graph in this video.
All right I also want to import this add message method here.
Now this is a link graph reducer function.
Let me show you the implementation here.
And the entire goal of this function is to ensure new messages are appended to the existing conversation
history instead of replacing it.
So all this function is doing is simply appending to a list here, and this is how it's going to be
updating the state.
Now, I know this is super, super abstract right now, but I promise you, when we'll see the execution,
everything will be clear here.
All right.
Let's go back to our main code here.
And I want to import from the chains file that we wrote earlier, the generate chain and the reflect
chain.
So those are the chains which are going to be nodes in our graph.
So every node in the graph is going to run a different chain here.
Cool.
So I want now to run as a sanity check this file here just to make sure nothing breaks.
And we can see it's working.
Let's go back now to our main file.
And now I want to implement the state.
So I want to define the schema a of the state of our graph.
And this is the data structure that every node in our graph is going to have access to.
So I'm going to call it message graph.
And I'm going to inherit from type dict.
And this class is going to have only one attribute one key.
And it's going to be messages.
And we want to keep updating these messages key after every iteration after every node execution in
our graph.
So we want to keep appending to this list because every execution is going to generate a message from
the AI.
And we want to go and append and append and append it right.
So this is the goal of this state here.
Simply a data structure to hold all of those list of messages here.
So this is what you need to keep in mind.
And messages is going to be a type of list of base message objects.
And the annotation of add messages.
Here is metadata that will tell Landgraf how to handle state updates.
So once we write it like this, then Landgraf will know that when it's going to change the state and
update it.
So instead of updating a dictionary like replacing the key, it will go and append new items to the
value of that key here.
And this is because of our add messages reducer here.
So the reducer by the way is a general terminology in graph.
How do we want to update the state here.
And we can put here any function that we want as long as it adheres to the reducer interface.
And we have total flexibility of how to do it.
And this is one of Landgraf's key advantages when it comes to flexibility.
Cool.
So let me now define two cons.
One is going to be reflect and the other is going to be generate.
And those are going to be the name of our nodes here.
Alrighty.
And now let's go and implement the first node.
And the first node is going to be called the generation node.
The input for this node is going to be the state which is type of message graph.
And I remind you this is only holding all the messages that we have generated so far.
And the first message is going to be the user input here.
And after it every message is going to be an AI message.
And this node is simply going to run the generation chain, which we remember from the previous video,
and it's going to invoke it with all the messages that we have so far.
So in the first iteration, when we execute the generation node, it will have only the user input.
So it will generate the tweet.
And in following and consecutive iteration it's going to have the critique as well.
So this is how it's going to be working here.
And the fact that we are doing it like this.
This is sort of like a prompt engineering technique because the large language model is going to receive
the history.
So it's going to know every time what was the critique, what has changed.
So it will have the entire context all the time here of all the conversation.
And we will see it in the trace after we run it.
So now you can see I'm returning now from this function.
And when I return, I return a dictionary and I remind you the state is simply a dictionary, and that
dictionary is going to have the key of messages.
So this is why I'm populating in this dictionary the messages key.
And the value is going to be the value we get from running the chain, which is an AI message.
And because we defined the reducer add messages, it's going to take this value and it's going to append
this list to the list we have already in our state here.
So let's now go and do the same for the reflection node.
So it's going to receive the same state.
It's going to invoke the reflection chain.
And here we have a prompt engineering technique.
Now notice that when we're going now to update the state when we're going to append to the messages
key here we are appending now a human message.
So the AI response we're going to get is going to be an AI message.
But we are now casting this message into a human message.
So this is a heuristic we're making.
So we want the critique that the LLM generates when we plug it back to the LLM.
We want the LLM to think that this critique was the output of a user.
So a human wrote it.
And the idea behind this is that large language models are also trained for conversation and for human
feedback.
And once we label this text here, this critique, when we label it as a human message, we're hoping
to get a better result this way.
So we have now two nodes.
We have the generation node, we have the reflection node.
And now it's time to stitch everything together and to build our graph.
So I'm going to create an object of the state graph.
And the state schema is going to be the message graph class.
So this is simply to tell a graph what's going to be the state how to update it.
And this is the goal of this argument.
Now we want to go and to create the nodes.
So we'll start and create the generation node.
And the first argument is going to be the name which is going to be the string generate.
And the node a logic is going to be the generation node function.
Now it's important to note that in line graph, every node should receive as an input the state which
should be of the type of the state that we initialize our graph with.
So this is also important to note.
All right so let's go and create the second node which is the reflection node.
And now we want to tell the graph that the first node that we want to execute is to be the generation
node.
And we do this by using the method set entry point and giving it a node name.
And that node name is going to be generate.
And if you take a look at what I marked in yellow.
So in line graph.
Every graph is going to start with this built in start node.
And once we use set entry point to be generate, we actually create an edge from the start node to generate.
So after we defined our nodes we want now to define our edges.
And this is going to describe now the execution flow of our software.
So we want to begin with generate.
And this is what we've done so far.
After we execute the generation node we want to go and reflect or we want to finish.
When do we finish and when do we go and reflect.
We'll discuss it very very soon.
And after we reflect, we want to always go to the generate node to generate a new tweet, which is
based on the reflection we got from the reflect node.
So we have one deterministic edge from the reflect node to the generate node.
And then we have a conditional edge from the generate node either to the reflect node or the end node.
And you can see it in the diagram as a dashed arrow here.
Okay.
So now let me define a new function which is called should continue.
And this function is going to receive the state.
And the output of this function is going to be a string which is going to be the node name.
So this function is going to be called every time after we run the node.
And the output is going to telegraph where to go next, either to go to the reflection node or to go
to end everything.
So let's implement here a very simple logic.
I want to count the number of messages.
If it's six, I want to go and finish.
And if it's below six I want to go to the reflect.
So this would give us two iterations of the reflection node.
And this is right now really really simple because this is one of our first graph graphs.
And you can imagine that instead of this logic we can actually put here a large language model to decide
where to go.
So it's going to make the decision whether we need to do some more iterations or we are satisfied with
the result.
And that's the beauty of using land graph, because we as developers, we can define the flow, we can
define which nodes are going to execute.
And we can put here an LLM to decide where to go in this flow.
So this flow engineering idea here and in this example I gave the number six.
But I could have given any number here or written any other logic to determine whether, if we want
to finish or to go and continue reflect here.
Now some of you may observe that this function is not a node that is going to execute.
This is very important to clarify.
It's actually going to be the function of a conditional edge here.
Now notice here we're not returning a dictionary, but we're returning a string from this function.
And the string must correlate to a node name.
And if it's not going to correlate to a node name we are going to get an error here.
So this is what's going to be used for our conditional edge, which we are going to be writing right
now.
So the first argument is going to be the source node we're going from.
And the second argument is going to be our function that we wrote earlier, which its output is going
to be the strings of which nodes to go here.
So this is a routing function.
And the second edge we want to implement is going to be from the reflection node to the generation node.
And we are done with the moving parts right now.
And we can go and compile the graph.
So let's do that.
And now let's go and print it.
So I'm going to use the get graph method.
And I'm going to use the draw mermaid which is going to output me a mermaid diagram of this graph.
All right.
So let's go now and run everything.
We're not invoking the graph yet.
We're simply going to see what a graph in the diagram.
So we can see.
Now in stdout we get here a this code here which is going to describe our graph graph.
So let me go and copy it and let me go to a application like Excalidraw.
And if I'll go right in the right over here we have here mermaid two.
Excalidraw option.
So let me go and click on that.
And we can simply now give our mermaid code and it will draw it in Excalidraw.
And by looking at this a graph that we see right here, you can see that something is just not adding
up.
We are missing here the conditional edge from the generate node to the reflect node and to the end node.
So something here is just not right.
So it's not a bug it's a feature.
And if we'll run our graph with the input it would work just fine.
This is a display issue.
And this has to do with the way we built our graph and specifically the conditional edge.
And the reason why this is happening is because the way we wrote the code line graph does not explicitly
know where to go from the generate node we just gave it.
The should continue function.
So it has no idea that this function is going to output us to go to the reflect node or to the end node.
So let's go to the code and let's go and fix that.
So the way to fix it is to simply add a third argument to the add conditional branch method right over
here.
And here we want to add something which is called a path map, which is going to be a dictionary which
is going to output the possible outputs of this function to a specific node here.
So here we're going to do something very simple and is going to go to end.
And reflect is going to go to reflect.
And by doing so we are now going to explicitly tell Landgraaf what are the possible destination nodes
of this conditional edge.
So now let me go and rerun it and let me copy now the new mermaid code here.
And let's go back to Excalidraw.
And let me now go and paste it again.
And boom.
Now we can see we do actually see the conditional edge as we want it to.
And we can see there is a conditional edge from the generate node to end and to the reflection node.
So this is clearer now.
And by the way you have also a way to draw the graph in Ascii.
So let me just go right over here and let me go.
And now use the get graph.
And get graph has a method which is called print Ascii.
So let me now rerun this program here.
And we can see we printed now our graph as Ascii.