WEBVTT

00:00.120 --> 00:05.360
So after defining our chains, we want to define our nodes.

00:05.360 --> 00:08.960
And those nodes are going to execute those chains.

00:09.200 --> 00:14.560
And we want to connect those nodes and to connect the execution flow with edges.

00:14.720 --> 00:20.800
So this is what we're going to be building this diagram over here which is going to be our workflow.

00:21.120 --> 00:26.880
So it's going to start with the generate node which is going to run the generation chain on the input.

00:27.040 --> 00:32.680
Then we're going to decide whether we want to finish or we want to go and reflect.

00:32.720 --> 00:35.680
Then we're going to take the output of generation chain.

00:35.680 --> 00:37.360
And we're going to reflect.

00:37.360 --> 00:39.800
And we're going to run the reflection chain.

00:39.800 --> 00:43.880
And then we're going to take the output of the reflection node.

00:43.880 --> 00:48.040
And we want then regenerate the output here.

00:48.240 --> 00:52.120
And we want to go through this loop until we meet a certain condition.

00:52.480 --> 00:57.000
And to do that we'll be implementing everything with graph.

00:57.000 --> 01:01.520
So line graph is going to describe our execution flow.

01:01.870 --> 01:05.630
So in this video we're going to implement the nodes.

01:05.630 --> 01:10.510
We're going to define the state that the graph is going to be working on.

01:10.510 --> 01:13.630
So every node is going to have access to the state.

01:13.630 --> 01:15.270
And this is going to be its input.

01:15.270 --> 01:18.750
And when it's finished executing it's going to update the state here.

01:19.270 --> 01:26.470
Now our state in this case is going to be simply a list of messages where we are going to continue to

01:26.510 --> 01:27.950
append to this list here.

01:28.550 --> 01:29.070
All right.

01:29.070 --> 01:33.750
So now let's go to the code and let's go and implement everything.

01:33.910 --> 01:40.270
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

01:40.310 --> 01:46.590
this video was actually refilmed to match the latest versions of Link graph 1.0.

01:46.910 --> 01:51.750
So all the code is the same, just the IDE is different now, so don't freak out.

01:54.510 --> 02:01.990
So let's start with the imports and I'll first want to import, type dict and annotate it.

02:01.990 --> 02:02.830
Did so.

02:03.150 --> 02:11.230
Type dict is a type dictionary which creates a structured dictionary with hints for the keys, and we're

02:11.230 --> 02:15.030
going to be using it when we'll define our state schema.

02:15.550 --> 02:22.310
Now we need this because lang graph would require typed state definitions to know which data flows through

02:22.470 --> 02:23.910
and out of the graph.

02:25.070 --> 02:29.630
Annotated is going to help us add metadata to those type hints.

02:30.230 --> 02:36.950
We also want to import from linked chain the base message, which is the abstract base class for all

02:36.950 --> 02:44.590
message types in link chain, and we're going to be using it as a type hint for the messages list.

02:44.590 --> 02:49.150
That is going to be in our state schema, which we're going to see very, very soon.

02:49.470 --> 02:54.870
And this is going to ensure safety for different message types, whether it's a human message, an AI

02:54.910 --> 02:56.430
message, or a system message.

02:57.070 --> 03:02.350
So we also want to import the human message to represent a message from a user.

03:02.550 --> 03:09.820
And we want this because for specific messages, we want to make a distinguish between the user content

03:09.820 --> 03:11.980
from the AI responses.

03:12.620 --> 03:21.460
So now we want to import from graph the end which is a special constant to mark the graph termination.

03:21.700 --> 03:25.060
So this is the ending node of the graph.

03:25.660 --> 03:27.940
So there is also the state graph.

03:28.100 --> 03:32.740
And this is the main class for building stateful graph graphs.

03:32.940 --> 03:40.220
And when we'll create our workflow which is going to describe the execution of the nodes and edges of

03:40.220 --> 03:45.220
our software of our authentic software, then we need to provide it with a state.

03:45.460 --> 03:52.220
And the state is simply going to be a data structure, usually dictionary or a pedantic class, which

03:52.220 --> 03:56.580
is going to be maintained through the entire execution, and it's going to hold the information of the

03:56.580 --> 03:57.340
execution.

03:57.620 --> 04:05.770
We can store their intermediate results, LLM responses, and basically everything we can think of,

04:05.810 --> 04:06.650
we can store there.

04:06.690 --> 04:12.410
It's very flexible, and every node that is going to run is going to have access to this state.

04:12.410 --> 04:16.770
So it's the input for every node and every node that is going to do some work.

04:16.770 --> 04:19.570
It's going to then update our state.

04:19.770 --> 04:22.010
And I'm knowing very abstract right now.

04:22.250 --> 04:25.970
It will be all clear when we implement this graph in this video.

04:26.330 --> 04:31.450
All right I also want to import this add message method here.

04:31.810 --> 04:35.250
Now this is a link graph reducer function.

04:35.250 --> 04:37.170
Let me show you the implementation here.

04:37.530 --> 04:45.730
And the entire goal of this function is to ensure new messages are appended to the existing conversation

04:45.730 --> 04:48.250
history instead of replacing it.

04:48.250 --> 04:54.730
So all this function is doing is simply appending to a list here, and this is how it's going to be

04:54.730 --> 04:56.130
updating the state.

04:56.170 --> 05:01.810
Now, I know this is super, super abstract right now, but I promise you, when we'll see the execution,

05:01.810 --> 05:03.410
everything will be clear here.

05:03.890 --> 05:04.490
All right.

05:04.530 --> 05:07.890
Let's go back to our main code here.

05:08.130 --> 05:14.970
And I want to import from the chains file that we wrote earlier, the generate chain and the reflect

05:14.970 --> 05:15.370
chain.

05:15.690 --> 05:19.490
So those are the chains which are going to be nodes in our graph.

05:19.490 --> 05:22.530
So every node in the graph is going to run a different chain here.

05:22.890 --> 05:23.330
Cool.

05:23.330 --> 05:28.930
So I want now to run as a sanity check this file here just to make sure nothing breaks.

05:29.290 --> 05:30.610
And we can see it's working.

05:31.090 --> 05:33.850
Let's go back now to our main file.

05:33.850 --> 05:37.010
And now I want to implement the state.

05:37.010 --> 05:41.290
So I want to define the schema a of the state of our graph.

05:41.730 --> 05:47.250
And this is the data structure that every node in our graph is going to have access to.

05:47.650 --> 05:50.250
So I'm going to call it message graph.

05:50.250 --> 05:53.210
And I'm going to inherit from type dict.

05:53.450 --> 05:58.090
And this class is going to have only one attribute one key.

05:58.090 --> 06:00.090
And it's going to be messages.

06:00.370 --> 06:07.200
And we want to keep updating these messages key after every iteration after every node execution in

06:07.200 --> 06:07.720
our graph.

06:08.040 --> 06:14.240
So we want to keep appending to this list because every execution is going to generate a message from

06:14.240 --> 06:14.760
the AI.

06:15.000 --> 06:18.280
And we want to go and append and append and append it right.

06:18.320 --> 06:20.160
So this is the goal of this state here.

06:20.160 --> 06:23.960
Simply a data structure to hold all of those list of messages here.

06:24.040 --> 06:25.720
So this is what you need to keep in mind.

06:26.520 --> 06:32.920
And messages is going to be a type of list of base message objects.

06:33.280 --> 06:37.400
And the annotation of add messages.

06:37.400 --> 06:43.600
Here is metadata that will tell Landgraf how to handle state updates.

06:43.840 --> 06:52.200
So once we write it like this, then Landgraf will know that when it's going to change the state and

06:52.200 --> 06:52.960
update it.

06:53.040 --> 07:01.760
So instead of updating a dictionary like replacing the key, it will go and append new items to the

07:01.760 --> 07:03.160
value of that key here.

07:03.240 --> 07:07.080
And this is because of our add messages reducer here.

07:07.080 --> 07:11.520
So the reducer by the way is a general terminology in graph.

07:11.560 --> 07:14.120
How do we want to update the state here.

07:14.400 --> 07:20.200
And we can put here any function that we want as long as it adheres to the reducer interface.

07:20.200 --> 07:23.240
And we have total flexibility of how to do it.

07:23.480 --> 07:27.920
And this is one of Landgraf's key advantages when it comes to flexibility.

07:29.000 --> 07:29.640
Cool.

07:29.640 --> 07:32.080
So let me now define two cons.

07:32.080 --> 07:35.800
One is going to be reflect and the other is going to be generate.

07:35.800 --> 07:38.880
And those are going to be the name of our nodes here.

07:40.760 --> 07:41.480
Alrighty.

07:41.520 --> 07:44.160
And now let's go and implement the first node.

07:44.560 --> 07:47.680
And the first node is going to be called the generation node.

07:48.000 --> 07:53.120
The input for this node is going to be the state which is type of message graph.

07:53.520 --> 07:59.800
And I remind you this is only holding all the messages that we have generated so far.

08:00.080 --> 08:02.840
And the first message is going to be the user input here.

08:03.240 --> 08:07.280
And after it every message is going to be an AI message.

08:07.790 --> 08:14.870
And this node is simply going to run the generation chain, which we remember from the previous video,

08:15.070 --> 08:19.870
and it's going to invoke it with all the messages that we have so far.

08:20.270 --> 08:26.590
So in the first iteration, when we execute the generation node, it will have only the user input.

08:26.590 --> 08:28.670
So it will generate the tweet.

08:29.030 --> 08:34.030
And in following and consecutive iteration it's going to have the critique as well.

08:34.270 --> 08:36.670
So this is how it's going to be working here.

08:36.830 --> 08:40.910
And the fact that we are doing it like this.

08:41.110 --> 08:47.790
This is sort of like a prompt engineering technique because the large language model is going to receive

08:47.790 --> 08:48.750
the history.

08:48.750 --> 08:53.070
So it's going to know every time what was the critique, what has changed.

08:53.190 --> 08:58.590
So it will have the entire context all the time here of all the conversation.

08:58.590 --> 09:00.990
And we will see it in the trace after we run it.

09:01.470 --> 09:06.070
So now you can see I'm returning now from this function.

09:06.270 --> 09:13.460
And when I return, I return a dictionary and I remind you the state is simply a dictionary, and that

09:13.460 --> 09:16.140
dictionary is going to have the key of messages.

09:16.140 --> 09:20.580
So this is why I'm populating in this dictionary the messages key.

09:20.860 --> 09:26.180
And the value is going to be the value we get from running the chain, which is an AI message.

09:26.500 --> 09:33.940
And because we defined the reducer add messages, it's going to take this value and it's going to append

09:33.940 --> 09:37.860
this list to the list we have already in our state here.

09:39.140 --> 09:43.500
So let's now go and do the same for the reflection node.

09:43.500 --> 09:45.780
So it's going to receive the same state.

09:45.980 --> 09:48.580
It's going to invoke the reflection chain.

09:48.780 --> 09:51.860
And here we have a prompt engineering technique.

09:52.100 --> 09:58.220
Now notice that when we're going now to update the state when we're going to append to the messages

09:58.260 --> 10:01.780
key here we are appending now a human message.

10:01.980 --> 10:06.740
So the AI response we're going to get is going to be an AI message.

10:06.740 --> 10:10.700
But we are now casting this message into a human message.

10:11.060 --> 10:13.260
So this is a heuristic we're making.

10:13.260 --> 10:19.020
So we want the critique that the LLM generates when we plug it back to the LLM.

10:19.060 --> 10:24.340
We want the LLM to think that this critique was the output of a user.

10:24.340 --> 10:25.940
So a human wrote it.

10:26.060 --> 10:33.060
And the idea behind this is that large language models are also trained for conversation and for human

10:33.060 --> 10:33.660
feedback.

10:33.820 --> 10:42.220
And once we label this text here, this critique, when we label it as a human message, we're hoping

10:42.260 --> 10:44.100
to get a better result this way.

10:45.140 --> 10:46.860
So we have now two nodes.

10:46.860 --> 10:49.740
We have the generation node, we have the reflection node.

10:50.020 --> 10:54.540
And now it's time to stitch everything together and to build our graph.

10:54.860 --> 10:58.220
So I'm going to create an object of the state graph.

10:58.620 --> 11:03.060
And the state schema is going to be the message graph class.

11:03.220 --> 11:08.340
So this is simply to tell a graph what's going to be the state how to update it.

11:08.500 --> 11:11.090
And this is the goal of this argument.

11:11.610 --> 11:14.610
Now we want to go and to create the nodes.

11:14.610 --> 11:17.770
So we'll start and create the generation node.

11:17.770 --> 11:22.410
And the first argument is going to be the name which is going to be the string generate.

11:22.410 --> 11:28.170
And the node a logic is going to be the generation node function.

11:28.410 --> 11:35.570
Now it's important to note that in line graph, every node should receive as an input the state which

11:35.570 --> 11:39.610
should be of the type of the state that we initialize our graph with.

11:39.810 --> 11:41.690
So this is also important to note.

11:42.050 --> 11:45.810
All right so let's go and create the second node which is the reflection node.

11:46.250 --> 11:53.930
And now we want to tell the graph that the first node that we want to execute is to be the generation

11:53.930 --> 11:54.290
node.

11:54.450 --> 12:00.410
And we do this by using the method set entry point and giving it a node name.

12:00.610 --> 12:03.010
And that node name is going to be generate.

12:03.010 --> 12:05.570
And if you take a look at what I marked in yellow.

12:05.730 --> 12:07.290
So in line graph.

12:07.330 --> 12:11.240
Every graph is going to start with this built in start node.

12:11.400 --> 12:18.000
And once we use set entry point to be generate, we actually create an edge from the start node to generate.

12:18.400 --> 12:23.400
So after we defined our nodes we want now to define our edges.

12:23.400 --> 12:28.400
And this is going to describe now the execution flow of our software.

12:28.680 --> 12:30.920
So we want to begin with generate.

12:30.920 --> 12:32.600
And this is what we've done so far.

12:33.000 --> 12:39.040
After we execute the generation node we want to go and reflect or we want to finish.

12:39.440 --> 12:42.200
When do we finish and when do we go and reflect.

12:42.240 --> 12:43.960
We'll discuss it very very soon.

12:44.400 --> 12:51.920
And after we reflect, we want to always go to the generate node to generate a new tweet, which is

12:51.920 --> 12:55.040
based on the reflection we got from the reflect node.

12:55.160 --> 13:00.640
So we have one deterministic edge from the reflect node to the generate node.

13:00.760 --> 13:07.240
And then we have a conditional edge from the generate node either to the reflect node or the end node.

13:07.240 --> 13:10.840
And you can see it in the diagram as a dashed arrow here.

13:11.200 --> 13:11.560
Okay.

13:11.600 --> 13:14.960
So now let me define a new function which is called should continue.

13:15.320 --> 13:18.440
And this function is going to receive the state.

13:18.840 --> 13:23.800
And the output of this function is going to be a string which is going to be the node name.

13:24.360 --> 13:30.880
So this function is going to be called every time after we run the node.

13:31.200 --> 13:38.560
And the output is going to telegraph where to go next, either to go to the reflection node or to go

13:38.560 --> 13:39.880
to end everything.

13:40.840 --> 13:44.000
So let's implement here a very simple logic.

13:44.160 --> 13:46.560
I want to count the number of messages.

13:46.560 --> 13:49.400
If it's six, I want to go and finish.

13:49.680 --> 13:53.240
And if it's below six I want to go to the reflect.

13:53.360 --> 13:57.280
So this would give us two iterations of the reflection node.

13:57.720 --> 14:03.440
And this is right now really really simple because this is one of our first graph graphs.

14:03.800 --> 14:11.800
And you can imagine that instead of this logic we can actually put here a large language model to decide

14:11.990 --> 14:12.790
where to go.

14:13.070 --> 14:18.710
So it's going to make the decision whether we need to do some more iterations or we are satisfied with

14:18.710 --> 14:19.430
the result.

14:19.590 --> 14:25.830
And that's the beauty of using land graph, because we as developers, we can define the flow, we can

14:25.830 --> 14:28.150
define which nodes are going to execute.

14:28.150 --> 14:31.990
And we can put here an LLM to decide where to go in this flow.

14:31.990 --> 14:37.870
So this flow engineering idea here and in this example I gave the number six.

14:37.870 --> 14:43.310
But I could have given any number here or written any other logic to determine whether, if we want

14:43.350 --> 14:45.990
to finish or to go and continue reflect here.

14:46.150 --> 14:52.670
Now some of you may observe that this function is not a node that is going to execute.

14:52.670 --> 14:54.550
This is very important to clarify.

14:54.550 --> 14:58.270
It's actually going to be the function of a conditional edge here.

14:58.430 --> 15:04.070
Now notice here we're not returning a dictionary, but we're returning a string from this function.

15:04.310 --> 15:07.470
And the string must correlate to a node name.

15:07.470 --> 15:11.750
And if it's not going to correlate to a node name we are going to get an error here.

15:11.990 --> 15:17.510
So this is what's going to be used for our conditional edge, which we are going to be writing right

15:17.510 --> 15:17.870
now.

15:18.350 --> 15:24.110
So the first argument is going to be the source node we're going from.

15:24.510 --> 15:30.630
And the second argument is going to be our function that we wrote earlier, which its output is going

15:30.630 --> 15:34.190
to be the strings of which nodes to go here.

15:34.470 --> 15:37.070
So this is a routing function.

15:37.750 --> 15:44.590
And the second edge we want to implement is going to be from the reflection node to the generation node.

15:44.750 --> 15:47.030
And we are done with the moving parts right now.

15:47.030 --> 15:49.630
And we can go and compile the graph.

15:50.470 --> 15:51.550
So let's do that.

15:51.990 --> 15:54.990
And now let's go and print it.

15:54.990 --> 15:57.510
So I'm going to use the get graph method.

15:57.510 --> 16:04.310
And I'm going to use the draw mermaid which is going to output me a mermaid diagram of this graph.

16:05.070 --> 16:05.710
All right.

16:05.710 --> 16:08.990
So let's go now and run everything.

16:08.990 --> 16:10.710
We're not invoking the graph yet.

16:10.710 --> 16:15.420
We're simply going to see what a graph in the diagram.

16:15.740 --> 16:16.380
So we can see.

16:16.420 --> 16:22.460
Now in stdout we get here a this code here which is going to describe our graph graph.

16:22.620 --> 16:28.340
So let me go and copy it and let me go to a application like Excalidraw.

16:28.740 --> 16:35.060
And if I'll go right in the right over here we have here mermaid two.

16:35.100 --> 16:36.580
Excalidraw option.

16:36.940 --> 16:39.260
So let me go and click on that.

16:39.620 --> 16:46.820
And we can simply now give our mermaid code and it will draw it in Excalidraw.

16:47.260 --> 16:54.500
And by looking at this a graph that we see right here, you can see that something is just not adding

16:54.500 --> 16:54.820
up.

16:54.820 --> 17:01.340
We are missing here the conditional edge from the generate node to the reflect node and to the end node.

17:01.740 --> 17:04.380
So something here is just not right.

17:04.700 --> 17:08.140
So it's not a bug it's a feature.

17:08.580 --> 17:12.940
And if we'll run our graph with the input it would work just fine.

17:12.980 --> 17:14.770
This is a display issue.

17:14.770 --> 17:19.450
And this has to do with the way we built our graph and specifically the conditional edge.

17:19.650 --> 17:25.490
And the reason why this is happening is because the way we wrote the code line graph does not explicitly

17:25.490 --> 17:28.850
know where to go from the generate node we just gave it.

17:28.850 --> 17:30.450
The should continue function.

17:30.450 --> 17:37.050
So it has no idea that this function is going to output us to go to the reflect node or to the end node.

17:37.250 --> 17:39.970
So let's go to the code and let's go and fix that.

17:40.290 --> 17:47.610
So the way to fix it is to simply add a third argument to the add conditional branch method right over

17:47.610 --> 17:48.050
here.

17:48.330 --> 17:55.250
And here we want to add something which is called a path map, which is going to be a dictionary which

17:55.250 --> 18:03.050
is going to output the possible outputs of this function to a specific node here.

18:03.250 --> 18:07.610
So here we're going to do something very simple and is going to go to end.

18:07.890 --> 18:10.970
And reflect is going to go to reflect.

18:11.210 --> 18:19.370
And by doing so we are now going to explicitly tell Landgraaf what are the possible destination nodes

18:19.410 --> 18:20.970
of this conditional edge.

18:21.530 --> 18:27.050
So now let me go and rerun it and let me copy now the new mermaid code here.

18:28.130 --> 18:30.010
And let's go back to Excalidraw.

18:30.650 --> 18:33.730
And let me now go and paste it again.

18:35.330 --> 18:36.010
And boom.

18:36.210 --> 18:42.530
Now we can see we do actually see the conditional edge as we want it to.

18:42.850 --> 18:49.010
And we can see there is a conditional edge from the generate node to end and to the reflection node.

18:49.050 --> 18:50.450
So this is clearer now.

18:50.730 --> 18:56.330
And by the way you have also a way to draw the graph in Ascii.

18:56.530 --> 19:00.370
So let me just go right over here and let me go.

19:00.370 --> 19:03.210
And now use the get graph.

19:03.210 --> 19:07.370
And get graph has a method which is called print Ascii.

19:07.930 --> 19:11.090
So let me now rerun this program here.

19:11.290 --> 19:14.850
And we can see we printed now our graph as Ascii.
