Videos 3ySF0I5iE_0
A Practitioner's Guide to Graphs - Tim Ainge, Good Collective
Scene timeline
31 shot(s).
keyframes kept every frame deduplicated
What was stored
- cues
- 111
- whisperx 111
- chunks
- 26
- from 111 cues
- keyframes
- 25
- kept of 31 captured
- frames with text
- 25
- 418 lines read
- chapters
- 0
- from the source metadata
- keyframe bytes
- 1.9 MB
- word timings on 111 cues
Provenance
| stage | state | model | started | took |
|---|---|---|---|---|
fetch |
done | — | 2026-08-09 23:59 | 1m 17s |
stt |
done | — | 2026-08-10 00:01 | 14s |
chunk |
done | — | 2026-08-10 00:01 | 0s |
text_embed |
done | — | 2026-08-10 19:43 | 0s |
keyframe |
done | — | 2026-08-10 00:01 | 39s |
ocr |
done | — | 2026-08-10 00:02 | 12s |
frame_embed |
done | — | 2026-08-10 19:43 | 5s |
Frames, and what the machine read
-
- AlEngineer0.96
- World'sFair1.00
- A Practitioner's Guide to Graphs1.00
- How to make you Al applications smarter, cheaper, and more reliable0.99
- GoodCollective1.00
- CapabilityxStrategy=Delivery1.00
-
- If I have a hammer, is everything a graph?1.00
- Obsidian knowledge graph1.00
- GraphRA60.95
-
- If I have a hammer, is everything a graph?1.00
- graphs from1.00
- 1st principles1.00
- Obsidian knowledge graph1.00
- A0.52
- GraphRA60.94
-
- The structure of things to come1.00
- The talk1.00
- PART1.00
- Personalised PageRank1.00
- COVERS1.00
- Graph algorithms0.99
- COVERS1.00
- Shortest path0.99
- COVERS1.00
- Subgraph matching1.00
-
- The structure of things to come0.99
- Principle1.00
- Code1.00
- Examples1.00
-
- What is a graph1.00
- Nodes, edges, weights, properties.1.00
- Milk0.97
- CONTAINS1.00
- Whisk together1.00
- HAS_STEP1.00
- Pancakes1.00
- CONTAINS1.00
- CONTAINS1.00
- Recipe1.00
- Egg1.00
- Ingredient1.00
- Flour1.00
- Step1.00
-
- Extract a basic graph0.99
- Principle: unstructured data isn't much more helpful than unstructured text.1.00
- from pydantic import BaseModel1.00
- 4 people serves0.99
- from pydantic_ai import Agent1.00
- pancake recipe0.99
- requires1.00
- class Triple(BaseModel):1.00
- needs1.00
- subject: str0.97
- predicate: str1.00
- plain fid@pbined with0.88
- object: str1.00
- Milk1.00
- contains1.00
- # no schema, no vocabulary, no entity resolution0.99
- breakfast1.00
- agent = Agent(0.98
- is a0.92
- "anthropic:claude-sonnet-4-6",1.00
- Pancakes1.00
- has ingredient1.00
- output_type=list[Triple],1.00
- eggs1.00
- system_prompt=(1.00
- needs1.00
- "Pull the key facts out as (subject, predicate,1.00
- "object) triples. Use whatever vocab fits, yolo."1.00
- ),0.94
- Flour1.00
- made withixed with0.99
- triples = agent.run_sync(recipe_text).output0.99
- cooked on0.97
- uses1.00
- the batter0.95
- Egg1.00
- a pan0.99
-
- Defining the schema (the shape)1.00
- Principle: give the extractor a shape - a schema - to fill. A shape gets you structure.0.98
- from pydantic import BaseModel, Field0.98
- from pydantic_ai import Agent1.00
- garlic1.00
- class Ingredient(BaseModel):1.00
- name: str1.00
- quantity: float | None = None0.99
- unit: str |0.97
- None = None0.99
- # 'cup', 'g', 'tbsp'0.97
- parsley1.00
- parmesan1.00
- class Recipe(BaseModel):0.99
- title: str0.97
- ingredients: list[Ingredient]0.99
- Garlic Butter Pasta1.00
- CONTAINS1.00
- # the schema IS the contract - no free-form vocab0.99
- ...0.94
- agent = Agent("anthropic:claude-sonnet-4-6", output_type=Recipe)0.99
- recipe = agent.run_sync(recipe_text).output1.00
- spaghetti1.00
- butter1.00
-
- A recipe is more than its ingredients0.98
- The shape grows with the domain - add steps , and the technique each one applies.0.98
- toss1.00
- class Ingredient(BaseModel):1.00
- name: str0.98
- TECHNIQUE1.00
- quantity: float | None = Field(None, description="...")0.97
- unit: str |0.95
- None = Field(None, description="... ")0.96
- Toss the pasta in tese's0.87
- parsley1.00
- class Step(BaseModel):1.00
- HAS_STEP1.00
- text: str1.00
- parmesan1.00
- CONTAINS1.00
- technique: str | None = Field(None, description="... ")0.97
- spaghetti1.00
- CONTAINS1.00
- CONTAINS1.00
- uses: list[str] = Field(default_factory=list, description=".0.99
- CONTAINS1.00
- garlic1.00
- USES1.00
- HAS_STEP1.00
- Garlic Butter Pasta1.00
- HAS_STEP0.99
- CONTAINS0.99
- class Recipe(BaseModel):1.00
- title: str0.99
- TECHNIQUE0.99
- Boil the spaghetti un...0.99
- Melt the butter, then...0.95
- steps: list[Step]0.99
- ingredients: list[Ingredient]0.99
- boil0.95
- butter0.98
- TECHNIQUE1.00
- saute1.00
- + Techniques - the verb each step applies (saute, boil, toss)0.98
-
- Adding ontology in the prompt0.99
- Principle: a schema provides shape, the ontology describes what should go into it.0.99
- Oyster Sauce0.99
- Salt1.00
- # the ontology, expressed as rules in the system prompt0.99
- Oil1.00
- Minced Garlic1.00
- SYSTEM_PROMPT = (0.98
- Bean Sprouts0.98
- 1 tbs pinch0.97
- "Extract recipes into STRICT, schema-conformant form:\n"0.99
- 5 tbs0.99
- 1 tsp0.97
- Egg1.00
- "1. CANONICAL UNITS - convert to SI:\n"0.99
- 1 cup0.96
- mass → grams ('g'); volume → millilitres ('ml')\n"0.99
- Sesame Seed Oil1.00
- 2 tsp0.99
- 0.51.00
- (1 cup → 240 ml; 1 tbsp → 15 ml; 1 oz → 28 g)\n"0.97
- Mushrooms1.00
- countable items (eggs, cloves) keep a null unit.\n"1.00
- CANONICAL NAMES - clean, lowercase, singular:\n"0.99
- 0.25 lb0.97
- 'plain flour' / 'all-purpose' → 'flour'\n"0.98
- Soy Sauce1.00
- 0.5 cup1.00
- Beef Lo Mein1.00
- Noodles1.00
- strip brands & loose adjectives ('chopped', 'fresh'),\n"1.00
- 0.5 lb0.93
- keep distinguishing words ('smoked', 'self-raising').\n"1.00
- "Conform to the schema; don't invent ingredients."1.00
- Onion1.00
- 3 tbs1.00
- Sugar1.00
- Beef1.00
- Pepper1.00
- Water1.00
- Ginger1.00
- Starch1.00
-
- Simple entity matching1.00
- Principle: epic power-up = resolve your entities.0.99
- CONTAINS0.98
- soy sauce0.99
- Beef Lo Mein0.99
- # a synonym / index table maps surface forms → one canonical nod0.99
- INGREDIENTS = {0.97
- CONTAINS1.00
- CONTAINSCONTAINS0.99
- "garlic": ["garlic clove", "minced garlic", "2 cloves garlic"1.00
- CONTAINS1.00
- water1.00
- "shallot": ["challots", "eschalot", "golden shallot"],0.99
- Teriyaki Chicken CasS.CONTANS0.95
- concaunSpiced Fish Tac...0.81
- 10.65
- garlic1.00
- def normalise(name: str) → str:0.98
- onion1.00
- cleaned = clean(name)0.99
- # lowercase, str0.97
- CONT1.00
- CONTANS0.94
- EONTAINSCONTAINS0.96
- return SYN_TO_CANON.get(cleaned, cleaned)0.99
- # exact hit → ca0.94
- Egyptian Fatteh0.98
- Chicken Handi0.99
- CONTAINS1.00
- cumin1.00
-
- Better entity matching1.00
- Principle: Hybrid approaches often yield the best results. In this case semantic entity matching.1.00
- # hybrid match = semantic vector score + lexical score0.98
- def hybrid_lookup(query, candidates):0.99
- q, *cands = embed([query, *candidates])0.99
- # all-MiniLM-L6-v20.99
- coriander1.00
- capsicurhilli1.00
- scores = []0.99
- apoing onion0.96
- for cand, vec in zip(candidates, cands):0.99
- flour0.98
- vector = (cosine(q, vec) + 1) / 20.97
- # semantic kinship1.00
- lexical = token_sort_ratio(query, cand) / 100 # surface overlap0.99
- milk1.00
- sugar1.00
- scores.append(0.6 * vector + 0.4 * lexical)0.99
- return best_first(candidates, scores)1.00
- hybrid_lookup("garbanzo", existing_nodes) # → 'chickpea'0.97
-
- Graph queries1.00
- Principle: query by relationship, not just by node – and get a subgraph back.0.99
- // graph - walk the relationship0.95
- MATCH (r:Recipe)-[:CONTAINS]→(i:Ingredient)0.99
- WHERE i.name = 'garlic'0.97
- RETURN r,i0.99
- -- relational - join through the link table0.99
- garlic?1.00
- SELECT r.title1.00
- FROM recipes r0.99
- JOIN recipe_ingredients ri ON ri.recipe_id = r.id1.00
- JOIN ingredients0.98
- i ON i.id = ri.ingredient_id1.00
- WHERE i.name = 'garlic';1.00
- garlic?– the question, as a seed node0.98
-
- Graph queries1.00
- Principle: query by relationship, not just by node – and get a subgraph back.0.99
- cannellini beans0.99
- CONTAINS1.00
- // graph - walk the relationship0.97
- MATCH (r:Recipe)-[:CONTAINS]→(i:Ingredient)0.99
- Croatian Bean Stew0.99
- WHERE i.name = 'garlic'0.98
- CONTAINS0.98
- CONTAINS1.00
- RETURN r, i0.96
- beef0.96
- CONTAINS0.99
- Egyptian Fatteh1.00
- CONTAINS1.00
- -- relational - join through the link table0.99
- CONTAINS0.95
- garlic?1.00
- CONTAINS1.00
- SELECT r.title1.00
- FROM recipes r0.99
- Beef Lo Mein1.00
- CONTAINS1.00
- Chicken Handi1.00
- CONTAINS1.00
- onion1.00
- JOIN recipe_ingredients ri ON ri.recipe_id = r.id0.99
- JOIN ingredients0.99
- i ON i.id = ri.ingredient_id0.98
- WHERE i.name = 'garlic';1.00
- Cajun Spiced Fish Tac...1.00
- CONTAINS0.97
- white fish1.00
- → + their other ingredients - the subgraph you'd hand a model0.97
-
- Personalised PageRank0.99
- Plainly: pick one node, wander the edges, keep resetting to where you started. Where you land most often is0.99
- what's relevant.0.98
- ChickenHandi1.00
-
- Personalised PageRank1.00
- Plainly: pick one node, wander the edges, keep resetting to where you started. Where you land most often is0.99
- what's relevant.0.99
- Egyptian Fatteh0.96
-
- Personalised PageRank1.00
- Principle: The most value comes from being able to highlight related nodes when they aren't obvious.1.00
- ginger0.99
- garlic1.00
- Add the onions and0.98
-
- Personalised PageRank0.99
- Principle: The most value comes from being able to highlight related nodes when they aren't obvious.1.00
-
- PPR in the wild – landmark law0.97
- Real-world: seed a walk at one routine case; out of 27,885, the landmark it stands on surfaces.1.00
- Johnsonv.Zerbst1.00
- Malloy v.Hogan0.97
- cites1.00
- cites1.00
- Buchananv.Kentucky0.98
- Powell v Texas0.89
- Powell v.Alabama0.98
- cites1.00
- Estellev.Smith1.00
- cites1.00
- cites1.00
- cites1.00
- cites1.00
- cites1.00
- Kansas v. Cheever0.96
- cites1.00
- Massiahv.UnitedStates1.00
- Fitzpatrickv.UnitedStates1.00
- UnitedStatesv.Wade1.00
- cites1.00
- cites1.00
- Kansasv.Marsh0.99
- Brownv.UnitedStates1.00
- one hop further – into the precedent cluster0.97
-
- Shortest path1.00
- Principle: "how does A relate to B, and through what?"0.98
- # the path IS the explanation - one NetworkX call1.00
- path = nx.shortest_path(0.99
- code_graph,1.00
- # the eShop "calls" graph0.99
- source="CheckoutModel.OnPost",1.00
- # the symptom1.00
- target="Basket..ctor",1.00
- # the constructor we touched0.99
- BasketViewModelServic1.00
- → CheckoutModel.OnPost1.00
- CheckoutModel.SetBask..1.00
- #1.00
- CheckoutModel.SetBasketModelAsync1.00
- CheckoutModel.OnPost1.00
- BasketViewModelService.GetOrCreateBasketForUser1.00
- #1.00
- BasketViewModelService.CreateBasketForUser1.00
- #1.00
- Basket..ctor1.00
- the path is the explanation - a 4-hop calls chain from checkout down to the constructor0.99
-
- Shortest path1.00
- Principle: "how does A relate to B, and through what?"0.99
- # the path IS the explanation - one NetworkX call0.99
- path = nx.shortest_path(1.00
- code_graph,1.00
- # the eShop "calls" graph0.98
- source="CheckoutModel.OnPost",1.00
- # the symptom1.00
- target="Basket..ctor",1.00
- # the constructor we touched0.99
- BasketViewModelServic1.00
- → CheckoutModel.OnPost0.99
- CheckoutModel.SetBask..1.00
- #1.00
- #1.00
- → BasketViewModelService.GetOrCreateBasketForUser0.99
- CheckoutModel.SetBasketModelAsync1.00
- .ctor1.00
- BasketViewModelServic...0.96
- CheckoutModel.OnPost1.00
- #1.00
- BasketViewModelService.CreateBasketForUser0.99
- #1.00
- Basket..ctor0.98
- the path is the explanation - a 4-hop calls chain from checkout down to the constructor0.99
Transcript
111 cues· 2,277 words· 12,769 chars
- 0:01 Hi, I'm Tim Ainge from Liquid Collective, and welcome to AI Engineer's Presentation, A Practitioner's Guide to Graphs.
- 0:08 How to make your AI applications smarter, cheaper, and more reliable.
- 0:14 Graphs have always been a powerful foundation of computer science, and they look beautiful,
- 0:19 Sometimes they're genuinely not the right tool for the job.
- 0:23 We've all felt the wonder of a mesmerising data science graph, or ogled the graph view of our obsidian vault.
- 0:31 It can be tempting to rush into something like GraphRack or rebuilding our e-commerce shop with a graph database, but often we don't see the instant payoff we might have expected.
- 0:42 In frustration, many journeys end here, in the dust, at the bottom of the valley of despair and disillusionment.
- 0:50 What's on the other side of the valley and how do we get there?
- 0:53 That's exactly the question that sparked the idea for this talk.
- 0:56 Have I nailed all of the answers?
- 0:58 Definitely not.
- 1:00 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.
- 1:09 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.
- 1:23 Now just a quick disclaimer, this talk isn't going to go into graph rag or agent memory graphs.
- 1:29 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.
- 1:37 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 AI application.
- 1:47 Today we're going to speedrun the basics of graphs, then we'll walk through some tips and tricks for building better graphs to get better results, and then we'll look at graph-native algorithms that leverage a graph and the benefits that they deliver.
- 2:01 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
- 2:14 all right let's speed on the basics what's a graph a graph is something that has nodes also called vertices and edges which i sometimes call relationships that connect the nodes together that's it that is the most basic definition of a graph we can have different types of nodes and edges which convey more meaning and we can also put labels on edges and nodes and have properties and of course edges can have direction
- 2:44 Now that we've speed around that, a really, really important part of getting good value out of graphs is how we build good graphs.
- 2:53 Today, we're going to focus on extracting graphs from unstructured text, because that's a pretty common use case and a pretty popular one at the moment.
- 3:01 So in this example, we've defined a very basic data structure for our graph.
- 3:06 a triple that has a subject, a predicate, and an object, or a node that somehow relates to another node.
- 3:16 And we say to our agent, hey, go and pull the key information out of this thing as subject, predicate, and object triples.
- 3:25 You figure it out.
- 3:28 And then we give it a pancake recipe.
- 3:31 It's done an all right job.
- 3:32 We've got a graph.
- 3:36 We wouldn't get very far with this graph.
- 3:37 It's got some problems and we'll talk through that next.
- 3:42 One of the key principles about building better graphs is giving the extractor a schema to fill.
- 3:48 In this case, if we say instead of using triples, use a recipe and a recipe has ingredients and ingredients have a quantity.
- 3:57 If we give this to an agent with structured outputs, what we get back is instantly way more meaningful than the graph we had before and a lot tidier.
- 4:10 So the benefit here is that with consistent node and edge times, relationships become meaningful and something that we can interrogate or query.
- 4:19 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.
- 4:30 Now we've got a graph with structure that's starting to look a bit interesting.
- 4:34 Now that we have a well-defined schema and a nicely structured graph,
- 4:38 We need to add detail to our ontology.
- 4:43 The ontology describes how to extract information into our graph or precisely what to put into that structure.
- 4:50 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.
- 5:04 These extra instructions are just as important to the TADA model as the schema is.
- 5:09 And boom, there we go.
- 5:11 We've got lowercase ingredients and metric units.
- 5:16 We know that the best prompt in the world isn't bulletproof, though, so we'll look next how to make sure we really do standardize our units.
- 5:24 Here's an example where we have a couple of ingredients that probably shouldn't be represented by multiple nodes.
- 5:30 We've got garlic cloves and minced garlic.
- 5:33 cumin and cumin seeds, vegetable oil and oil.
- 5:38 We've also got plain old garlic down there as well.
- 5:43 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, but it also strengthens the relationships between the different recipes that have common ingredient.
- 5:59 We'll explain why this is helpful later.
loading