WEBVTT

NOTE Sentence-level transcript of https://www.youtube.com/watch?v=3ySF0I5iE_0

NOTE One cue per sentence. Cue ids are the line anchors on /transcripts/3ySF0I5iE_0.html. A cue ends where the next begins, or 2 s after its last word.

s1
00:00:00.920 --> 00:00:13.960
Hi, I'm Tim Angers from The Good Collective and welcome to AI Engineer's presentation, a practitioner's guide to graphs, how to make your AI applications smarter, cheaper, and more reliable.

s2
00:00:14.280 --> 00:00:19.720
Graphs have always been a powerful foundation of computer science and they look beautiful.

s3
00:00:19.720 --> 00:00:23.560
But sometimes they're genuinely not the right tool for the job.

s4
00:00:23.560 --> 00:00:31.320
We've all felt the wonder of a mesmerizing data science graph or ogled the graph view of our Obsidian vault.

s5
00:00:31.320 --> 00:00:38.760
It can be tempting to rush into something like GraphRAG or rebuilding our e-commerce shop with a graph database.

s6
00:00:38.760 --> 00:00:42.400
But often we don't see the instant payoff we might have expected.

s7
00:00:42.400 --> 00:00:49.920
In frustration, many journeys end here in the dust at the bottom of the valley of despair and disillusionment.

s8
00:00:49.920 --> 00:00:53.080
What's on the other side of the valley and how do we get there?

s9
00:00:53.080 --> 00:00:56.680
That's exactly the question that sparked the idea for this talk.

s10
00:00:56.680 --> 00:00:58.480
Have I nailed all of the answers?

s11
00:00:58.480 --> 00:01:00.040
Definitely not.

s12
00:01:00.040 --> 00:01:09.920
But what I'm finding is that the more I learn about the fundamentals of graph data structures and algorithms, the more interesting opportunities seem to present themselves.

s13
00:01:09.920 --> 00:01:23.920
Many of these graph native use cases or good fits for graphs are also a lovely complement to many of the search, pattern recognition, retrieval, or knowledge-based problems that are ripe for solving in the AI age.

s14
00:01:23.920 --> 00:01:25.720
Now, just a quick disclaimer.

s15
00:01:25.720 --> 00:01:29.880
This talk isn't going to go into GraphRAG or agent memory graphs.

s16
00:01:29.880 --> 00:01:37.000
Not because I'm throwing shade on those patterns and products, but partly because there'll be many other talks covering each of those single topics.

s17
00:01:37.000 --> 00:01:45.160
But more importantly, this talk is for AI builders and I'd like to focus on the underlying patterns, which may just help you come up with your next big graph-powered

s18
00:01:45.160 --> 00:01:47.080
AI application.

s19
00:01:47.080 --> 00:01:50.400
Today, we're going to speed run the basics of graphs.

s20
00:01:50.400 --> 00:01:55.360
Then we'll walk through some tips and tricks for building better graphs to get better results.

s21
00:01:55.360 --> 00:02:01.240
And then we'll look at graph native algorithms that leverage a graph and the benefits that they deliver.

s22
00:02:01.240 --> 00:02:13.760
At each step of the way, we'll open with a principle, look at an easy example and some code, and then finally, we'll reference some real-world examples with real-world benefits.

s23
00:02:14.680 --> 00:02:16.560
All right, let's speed run the basics.

s24
00:02:16.560 --> 00:02:18.560
What's a graph?

s25
00:02:18.560 --> 00:02:26.680
A graph is something that has nodes, also called vertices, and edges, which I sometimes call relationships, that connect the nodes together.

s26
00:02:26.680 --> 00:02:27.560
That's it.

s27
00:02:27.560 --> 00:02:30.680
That is the most basic definition of a graph.

s28
00:02:30.680 --> 00:02:35.480
We can have different types of nodes and edges, which convey more meaning.

s29
00:02:35.480 --> 00:02:40.160
Uh we can also put labels on edges and nodes and have properties.

s30
00:02:40.160 --> 00:02:43.720
And of course, edges can have direction.

s31
00:02:44.840 --> 00:02:53.160
Now that we've speed run that, a really, really important part of getting good value out of graphs is how we build good graphs.

s32
00:02:53.160 --> 00:03:00.880
Today, we're going to focus on extracting graphs from unstructured text, cuz that's a pretty common use case and a pretty popular one at the moment.

s33
00:03:01.280 --> 00:03:16.160
So, in this example, we've defined a very basic data structure for our graph, a triple, that has a subject, a predicate, and an object, or a node that somehow relates to another node.

s34
00:03:16.160 --> 00:03:25.680
And we say to our agent, "Hey, go and pull the key information out of this thing as subject, predicate, and object triples.

s35
00:03:25.920 --> 00:03:28.480
You figure it out."

s36
00:03:28.600 --> 00:03:31.080
And then we give it a wrap pancake recipe.

s37
00:03:31.080 --> 00:03:32.959
It's done an all right job.

s38
00:03:32.959 --> 00:03:34.840
We've got a graph.

s39
00:03:34.840 --> 00:03:37.959
But, we wouldn't get very far with this graph.

s40
00:03:37.959 --> 00:03:41.519
It's got some problems, so we'll talk through that next.

s41
00:03:41.880 --> 00:03:48.040
One of the key principles about building better graphs is giving the extractor a schema to fill.

s42
00:03:48.040 --> 00:03:53.040
In this case, if we say, "Instead of using triples, use a recipe.

s43
00:03:53.040 --> 00:03:57.840
And a recipe has ingredients, and ingredients have a quantity.

s44
00:03:57.840 --> 00:04:09.840
If we give this to an agent with its structured outputs, what we get back is instantly way more meaningful than the graph we had before, and a lot tidier.

s45
00:04:10.160 --> 00:04:18.760
So, the benefit here is that with consistent node and edge types, relationships become meaningful and something that we can interrogate or query.

s46
00:04:19.440 --> 00:04:29.960
Let's take this a little bit further to say that a recipe has ingredients, but it also has steps, and each step is the application of a cooking technique.

s47
00:04:29.960 --> 00:04:34.680
Now, we've got a graph with structure that's starting to look a bit interesting.

s48
00:04:34.680 --> 00:04:43.200
Now that we have a well-defined schema and a nicely structured graph, we need to add detail to our ontology.

s49
00:04:43.200 --> 00:04:50.720
The ontology describes how to extract information into our graph, or precisely what to put into that structure.

s50
00:04:50.720 --> 00:05:04.360
In our case, we want to provide instructions to our agent to standardize the formatting of ingredient names and to standardize units on the metric system to make matching and conversion easier.

s51
00:05:04.680 --> 00:05:09.720
These extra instructions are just as important to the title model as the schema is.

s52
00:05:09.720 --> 00:05:11.440
And boom, there we go.

s53
00:05:11.440 --> 00:05:16.880
We've got lowercase ingredients and metric units.

s54
00:05:16.880 --> 00:05:19.560
We know that the best prompt in the world isn't bulletproof, though.

s55
00:05:19.560 --> 00:05:24.320
So, we'll look next at how to make sure we really do standardize our units.

s56
00:05:24.320 --> 00:05:30.360
Here's an example where we have a couple of ingredients that probably shouldn't be represented by multiple nodes.

s57
00:05:30.360 --> 00:05:38.280
We've got garlic cloves and minced garlic, cumin and cumin seeds, vegetable oil and oil.

s58
00:05:38.280 --> 00:05:42.400
We've also got plain old garlic down there as well.

s59
00:05:42.960 --> 00:05:54.840
So, in our first attempt at solving the potato, potato problem, we can see that by taking a naive approach to mapping these, we can eliminate the duplication, which unifies the nodes,

s60
00:05:54.840 --> 00:05:59.800
but it also strengthens the relationships between the different recipes that have common ingredients.

s61
00:05:59.800 --> 00:06:02.800
We'll explain why this is helpful later.

s62
00:06:02.800 --> 00:06:08.240
The problem with this naive approach is that we've applied it retrospectively.

s63
00:06:08.240 --> 00:06:12.880
And for this to work well, we'd have to know all of the ingredients ahead of time.

s64
00:06:12.880 --> 00:06:17.560
Of course, these days, we have embedding models, which take the pain out of this sort of problem.

s65
00:06:17.560 --> 00:06:29.520
And by using an embedding model here, we have not only more flexible matching, but we also have the ability to match on terms that we don't need to know in advance.

s66
00:06:29.520 --> 00:06:37.400
This is a good example of where graph techniques and AI techniques working in hybrid give us the best result.

s67
00:06:37.400 --> 00:06:49.280
So, now that we have a well-structured graph, we have nicely curated information put into that graph, and we've done extra work to make sure that the nodes are matched or the entities are matched before we create new nodes.

s68
00:06:49.280 --> 00:06:53.080
Let's start talking about what we can do with our graph.

s69
00:06:53.080 --> 00:07:01.160
The very first thing we're going to do is just do a simple query to see which recipes contain the ingredient garlic.

s70
00:07:01.160 --> 00:07:09.160
We've got the Cypher or graph database query on top, and the relational SQL query below just for comparison.

s71
00:07:09.560 --> 00:07:16.440
Here we can see all the recipes that have garlic and their ingredients, which we print to the if they're fit on the slide.

s72
00:07:16.440 --> 00:07:27.080
I think if you look at this example, you can see how out of hand the SQL query might get if we wanted to traverse 5, 10, 20 edges to find the nodes that we're looking for.

s73
00:07:27.080 --> 00:07:37.880
In a graph query, not only is it a little bit easier and more natural to write, but traversing relationships like that is where the graph data structures start to inherently excel.

s74
00:07:40.000 --> 00:07:47.960
Stepping things up a notch, the next graph algorithm we've got is the personalized PageRank algorithm.

s75
00:07:48.080 --> 00:07:55.040
This is a variant on vanilla PageRank, which was made famous by a certain Brin and Page back in 1998.

s76
00:07:55.240 --> 00:08:01.920
It works by having a little dude run around the graph, and he marks each node as he passes.

s77
00:08:01.920 --> 00:08:07.640
After a certain amount of hops around the graph, he'll teleport back to the starting node.

s78
00:08:07.640 --> 00:08:09.400
And that's the bit that makes it personalized.

s79
00:08:09.400 --> 00:08:12.520
It's personalized to our starting node.

s80
00:08:12.520 --> 00:08:19.120
By repeating this process until he's completely worn out, some nodes will emerge with more marks on them than others.

s81
00:08:19.120 --> 00:08:24.400
These are the nodes that have a stronger relationship with the starting node than those around them.

s82
00:08:24.400 --> 00:08:30.080
A really common and popular reference point for personalized PageRank is the Pinterest Pixie paper.

s83
00:08:30.080 --> 00:08:32.240
How's that for some alliteration?

s84
00:08:32.240 --> 00:08:38.800
Um that showed how PPR could be used for Pinterest recommendations.

s85
00:08:38.800 --> 00:08:52.600
But, an even more contemporary one is Hippo Rag, uh which uses some other cool graph techniques as well to link memories to questions and answers.

s86
00:08:52.600 --> 00:09:00.120
In the presentation pack, you'll find links and references to different variants and how they can be used.

s87
00:09:01.360 --> 00:09:04.640
So, it looked a bit obvious in that last example.

s88
00:09:04.640 --> 00:09:14.440
But, algorithms like this really shine when we have dense clusters of nodes and relationships, and it isn't that easy to infer which ones are the most important.

s89
00:09:14.880 --> 00:09:25.720
Another real-world example is taking a real-world US Supreme Court case and being able to find the authoritative landmark cases upon which it relies.

s90
00:09:25.720 --> 00:09:30.760
In this example, Miranda v. Arizona is not cited in the Canvas v. Sheba case.

s91
00:09:30.760 --> 00:09:35.720
It's purely through the relationships in the citation graph that we are able to find it.

s92
00:09:35.720 --> 00:09:46.320
Not only to find it, but to be able to return a string of citations that show how it is related to this existing case through another.

s93
00:09:46.320 --> 00:09:52.080
Shortest path algorithm is another good way to look at the relationships between two nodes in a graph.

s94
00:09:52.080 --> 00:09:59.000
In this case where we know both nodes in advance and we don't understand the relationships, we can look at the most direct route between them.

s95
00:09:59.000 --> 00:10:11.200
In this case, if we say the checkout code broke after we changed the basket constructor, but I have no idea why, we can traverse the edges between those two nodes in the code graph and return

s96
00:10:11.200 --> 00:10:16.360
either the symbols or the text or a summary as context.

s97
00:10:17.520 --> 00:10:29.320
In this case, the shortest path is highly useful, but we might want the K shortest paths or the shortest path that passes through a particular node or the cheapest path if the edges are weighted.

s98
00:10:29.320 --> 00:10:38.720
So, there are multiple variants and they're all very useful at telling us what nodes and edges might help explain the relationship between two other nodes on the graph.

s99
00:10:38.720 --> 00:10:48.800
One of the benefits of this, being able to retrieve a subgraph as context, is that we wouldn't have found these intermediate nodes by doing vector search or even by doing individual

s100
00:10:48.800 --> 00:10:54.400
symbol and reference lookups and the process of figuring that out for ourselves would have been slow.

s101
00:10:54.400 --> 00:11:09.839
In one particular evaluation where we used this technique on a .NET code base, we saw a 40% reduction in tool calls for code search where we used techniques like this to identify the context we needed to give the agent.

s102
00:11:09.920 --> 00:11:11.960
This is another eShop example.

s103
00:11:11.960 --> 00:11:23.240
The one thing I like about this particular example is that instead of starting with a node or a set of nodes and navigating our way through the graph, we're querying entirely on relationships.

s104
00:11:23.520 --> 00:11:28.600
We could specify a node type or a node ID in this query and it would still be a subgraph match.

s105
00:11:28.600 --> 00:11:41.040
However, in In case, we're searching for code in a certain shape, not knowing anything specific about the code we're looking for or any symbols up front.

s106
00:11:41.080 --> 00:11:48.800
We'll search for a decorator pattern in the eShop example, which is commonly used to enhance an existing class.

s107
00:11:48.800 --> 00:12:01.360
So, what we're looking for is a class that wraps its target class or consumes methods from its target class, where both the wrapper and the target class implement the same interface.

s108
00:12:01.360 --> 00:12:02.480
Boom.

s109
00:12:02.480 --> 00:12:04.160
There we go.

s110
00:12:04.160 --> 00:12:14.720
In our eShop code base, we found a catalog view model service and a cached version that calls the same class and implements the same API.

s111
00:12:14.800 --> 00:12:18.880
If we knew we were looking for caching classes, we could have searched on that.

s112
00:12:18.880 --> 00:12:32.400
But, if we're looking for a specific pattern or if we were looking for an anti-pattern or a particular type of security issue, a malicious transaction pattern, or legal arguments in a big corpus,

s113
00:12:32.400 --> 00:12:41.280
sometimes it's really important to be able to look for the shape of something without knowing the specific instance or node details themselves.

s114
00:12:41.280 --> 00:12:46.600
The benefits of subgraph matching, where you have the opportunity to use it, I think are are quite unique.

s115
00:12:46.600 --> 00:12:52.080
It's not so much an optimization problem as like a big enabling algorithm.

s116
00:12:52.080 --> 00:12:57.360
It's something that's just not easy to do uh with other tools.

s117
00:12:58.120 --> 00:12:59.640
All right.

s118
00:12:59.640 --> 00:13:01.080
So, we've covered a lot of ground.

s119
00:13:01.080 --> 00:13:02.720
Thanks for bearing with me.

s120
00:13:02.720 --> 00:13:08.920
We've looked at navigating paths, at ranking how important things are, and finding patterns.

s121
00:13:08.920 --> 00:13:23.080
We've skipped over some of the traditional flow and cost and search algorithms that you might find often used in modeling dependencies or networks, and there's heaps of use cases of those, but I think probably a little bit more run-of-the-mill.

s122
00:13:23.080 --> 00:13:32.280
In the presentation back, we'll also include some notes about some of the things that we couldn't get to today like prediction, similarity, and clustering.

s123
00:13:32.280 --> 00:13:43.680
These are now edging into some of the graph rag, building dynamic graphs, or schema-less graph kind of territory that we deliberately didn't want to go into today, but it is also

s124
00:13:43.680 --> 00:13:46.400
where things get super interesting as well.

s125
00:13:46.400 --> 00:13:49.840
We'll have some references and some pointers in the presentation pack.

s126
00:13:49.840 --> 00:13:53.200
Otherwise, you can go and explore some of those things on your own.

s127
00:13:53.200 --> 00:14:01.120
So, I hope that some of these concepts will have given you some insight or inspired you and that you can take them and either use graph native algorithms or hybrid algorithms

s128
00:14:01.120 --> 00:14:06.640
to help make smarter, cheaper, and more reliable AI applications.

s129
00:14:06.640 --> 00:14:08.800
Thank you.
