Shadowing Practice: The Complete Guide to Hybrid Search in RAG (BM25 + Embeddings + Reranker) - Learn English Speaking with Video

Elige un modo de práctica

Cada modo abre una sala de práctica a pantalla completa.

847 oraciones
Si las oraciones son demasiado cortas o largas, haz clic en Editar para ajustarlas.
1
In this tutorial, we're going to build a hybrid retrieval system from scratch that combines BM25, dense embeddings, reciprocal rank fusion,
2
and a re -ranker to then bring it all together.
3
This is really the production stack that works and that wins in 2026 when you want to build serious production -ready systems.
4
By now, almost everyone kind of like knows how to build rack systems
5
and they've dabbled with it and know what effect their databases, but almost no one understands how to build them from scratch
6
and optimize them and evaluate them for your own data set
7
which is exactly what we're going to do in this video
8
and now a quick word of caution before we dive into things
9
because this tutorial is not going to be like most other RAC tutorials
10
that you'll find online
11
and on YouTube in this one we go really deep
12
this is really for engineers developers who already have a basic understanding of what RAC is
13
and need to use this technology really professionally within their jobs
14
or the clients you're working for and you want to build a system that you truly own and understand.
15
All right, so let's kick things off.
16
And the first thing that we're going to look into is the actual data set that we are going to use.
17
Because for any retrieval task, the data is the most important part.
18
And that's really where we should start.
19
So we're going to use this data set, the financial QA data set, which is also part of the BEAR benchmarks.
20
So this is a benchmark for information retrieval.
21
So let's quickly cover that first, because this is very important to understand because whenever you start with a new data set,
22
you ideally want to have some kind of like ground truth to decide what makes a good retrieval system
23
because otherwise we cannot optimize, right?
24
So what's nice about the Bayer data sets is that they created these pairs of queries, relationships, and corpuses.
25
So as we go through the code base and go through the examples, this will become more and more clear.
26
But for now, you should understand that the data set that we are going to use
27
consists of a whole bunch of financial questions and statements that are linked together.
28
So in the corpus, there are answers.
29
And these are similar to your own documents that you would, for example, put in your retrieval system, whether they are company documents,
30
your sales playbook, customer support questions, pretty much anything that you want to put in your corpus.
31
That's the starting point.
32
Now, then also we have questions that can be answered using documents within that corpus.
33
So let's actually check what this looks like in the code base so we can actually get a feel for this.
34
So I am currently in the hybrid retrieval folder.
35
Then under data, there is a download financeqa .py file.
36
So this is a one -time setup that you can run to download the corpus.
37
So you'll need to set up your project.
38
So I have a PyProjectAtomal over here.
39
I recommend to run UfiSync in order to get this all up and running.
40
We are using the datasets library here from HuggingFace.
41
And because of that, we can simply run this and it will load all of these datasets.
42
So we got the corpus, the queries, and the relationships.
43
Remember how I was talking about this over here?
44
So this is a one -time run that you can do.
45
I already have them downloaded over here.
46
So for me, it will probably skip things.
47
So just run that and then it will store that data into parquet files.
48
So this is a very efficient way to store this data on disk that we can then also later load in.
49
So that's the data set.
50
But now let's actually get to the exploration phase.
51
So I'm going to the one explore data file.
52
And what I'm going to do is I'm going to use the Jupyter Interactive session here on the side.
53
So we can walk through it step by step.
54
If you don't know how to do this, I'll link another video below to explain exactly how I set up my cursor to work like this,
55
similar for Visual Studio Code.
56
So we're starting over here with our data folder that we just downloaded that data to, so here you can see the full path to it.
57
Now we're going to load the parquet files that we just downloaded to get a feel for what this actually looks like.
58
So if I now look at the corpus, you can see this is currently a pandas data frame.
59
So if you've never worked with pandas, then this is pretty much an Excel spreadsheet, but then within the Python programming language.
60
So you can see the table over here.
61
This is also why I like using the Jupyter Interactive Session because it renders these tables really nicely.
62
So you can see we have a whole bunch of rows, a whole bunch of documents, and there are three columns, each containing an ID.
63
Title is empty in this case.
64
This is a default for the bare datasets, but this particular finance QA doesn't have titles.
65
So we just have the text.
66
Now let's look at the first element in here to get a feel for what the corpus looks like.
67
What data are we actually working with?
68
So I'm using some pandas notation over here where I'm going to say, look, I'm going to take the text column.
69
So that would be corpus and then the text, which you can see over here.
70
And then I'm using the ilog0 notation over here to say, let's now just look at the first element in there.
71
So let's see, let me actually, if I clear this for you and run this one more time.
72
So this is the first document that shows up in the corpus.
73
And on its own, it doesn't really say much to us right now.
74
So let's actually continue with the queries.
75
So now I'm going to load the other parquet file, which is the queries.
76
So let's run that.
77
And you can see that now this also results in another data frame.
78
Exactly the same structure.
79
We got an ID, title is empty, and now we got some text in here.
80
So let's again look at the first query that is in there.
81
So if I print this over here, you can see right now, instead of a document with information in it, we have a question.
82
So what is considered a business expense on a business trip?
83
And you can also kind of like iterate through this to see, okay, what is actually in here?
84
Starting a new business online.
85
Let's pick some random ones.
86
New business owner.
87
How do Texas work for the business versus individual?
88
So you can see these are the actual queries that show up.
89
Now, then the third data set that we have in here is the QRLs, which is the relationships.
90
Now, this is where it gets interesting because here we create a mapping between a query ID and a corpus ID.
91
So if I just look at the first record over here, you can simply say that we have query ID 8.
92
So question 8 within that query list relates to or has a relationship to the document with the following ID.
93
And that is indicated by the score is 1.
94
So what you'll find is that in this QRLs, all of the scores are one, the ones with a zero are not in there.
95
So these are all of our relationships.
96
Now, you need to spend some time here to really understand this.
97
And that's why I also created this image for you to dive a little bit deeper into that.
98
Because everything that we're going to do later down the line comes back to documents,
99
queries, and the relationships between them so that when we run the retrieval system, we can see when we ask this question,
100
does our retrieval system, does our hybrid system come forward with the right documents that have a relationship with the query?
101
So make sure you really understand this
102
and then also understand how you can map this back to the code base
103
because this is really the mental model that you need in order to go forward.
104
So then step number two of this first file is we're going to make a simple filter on all of the queries.
105
Because if we look at currently all of the queries that we have,
106
you can see this is a big list over here where we have a lot of queries.
107
But not for every query.
108
We have a document that's associated to it within this data set that we are using.
109
So this is a simple filter that we do where we take the QRLs.
110
So let's see.
111
We take the IDs over here.
112
So these are all the questions that we have and we take the set,
113
which is Python's way to deduplicate a list or a series in this case.
114
So here we have the deduplicated set of that.
115
And then we just do a filter on the test queries.
116
And again, this is a little bit tricky to understand if you've never worked with Pandas, but pretty much what we do,
117
look, if I run this, we now have a filtered list.
118
So you can see that we went from how many did we have?
119
We had 1 ,700 to start with, and now we have 648 left.
120
So that's just a filtering mechanism that we do over here.
121
And then to show you one more thing, if we inspect one query and it's relevant documents, here's how you can see how everything comes together.
122
So let's say we have the following query.
123
So now we have the first test query over here.
124
And if I print that here, you can see what that looks like.
125
So this is where should I park my rainy day slash emergency funds.
126
So this is the first query
127
that we now have in the data set where we also have documents backing it up with the relationship.
128
What we then do is again, pandas notation.
129
We go into the QRLs data frame and we perform a search to check for the query.
130
And this is similar to doing a lookup or a SQL where statement.
131
And now you can see that for the query, where should I park my rainy day emergency funds,
132
which has this ID, you can see that if we apply a filter, we get the document IDs.
133
So bringing this all together, we can now see
134
that this particular query with this ID links to these documents meaning this question can be answered through these documents
135
which is exactly what was shown in the image over here okay
136
so now that you understand the data set
137
and also know how to work with this locally we're going to dive into the actual retrieval pipeline
138
and we're going to work through this step by step
139
so first we'll get into bm25 then the embeddings then rff re -ranking
140
and then bringing everything together so that's going to be the flow of the video building each and every individual component first,
141
and then bringing them together later in the code base to form one cohesive pipeline.
142
So we're going to start with BM25, which is a keyword -based way for information retrieval.
143
And with all of the methods that we're going to cover, I'm going to walk through the code base.
144
I'll explain as much as I can during the video itself
145
but also what I want you to know is that there is a docs folder.
146
And for each and every one of the techniques,
147
there is a additional markdown document with all of the formulas and the math behind it as well, if you want to go deeper,
148
as well as the original papers where all of these techniques came from.
149
But I cannot go into all of the details, otherwise this would literally become like a five to 10 hour lecture.
150
But I'm just going to give you what you need in order to follow along.
151
And then again, if you want to go deeper, the information is there for you.
152
So BM25, also known as sparse retrieval, more on that in a bit, will do keyword overlaps.
153
And then there is a weighting mechanism
154
that is applied to it to actually perform the math to see what we need to retrieve.
155
This is really good for catching exact terms, identifiers, rare words, but it will miss paraphrases.
156
So this is a rather naive way of searching and retrieving information, but in some scenarios can work highly effective.
157
So let's dive into this first and see what it looks like.
158
So if I come back to my code base and then jump into bm25 .py,
159
we have the setup over here and we are going to use the following library.
160
Let me actually open that up for you.
161
So this is a Python BM25 implementation.
162
So this is what we will be using.
163
This is also a dependency in the project.
164
So pyproject .toml, you can see BM25s.
165
So now you know where this import is coming from.
166
And the nice thing about this implementation is that we don't have to worry about the mathematics.
167
We can just rely on what was already built and use this
168
so we are going to start with loading our data directory again
169
so this is the same one we were using
170
and now we're also going to create an index directory
171
so let me show you over here
172
there is an indices folder over here where we are going to store the information
173
so we are going to use that later
174
so for now it's only the paths that are in here so now we're going to simply read our corpus
175
again similar to what we already did right
176
so remember this is the data frame
177
and now we're going to convert it to a different data format to make it work with the bm25s
178
so we're going to create lists out of it
179
so what you can see right now is we'll use pandas to list
180
so we can get the doc ids
181
which is just a list of all the ids
182
and then we also get the text and we do the same
183
so now we've converted so you can see this is just a huge list
184
so now we convert simply from a data frame to lists.
185
And we put all of the documents in there because we want to create, keep the corpus the original size, right?
186
So remember how we made a filter on the questions.
187
The corpus should be really big because otherwise we kind of like cheat on the retrieval task, right?
188
Because if we only leave in the actual relevant documents, we just reduce the size because the more data there is,
189
the more errors a retriever can potentially make to retrieve nonsense.
190
Okay, now we're going to tokenize the entire corpus.
191
And this now becomes pretty easy because we can use the BM25S library for this.
192
So simply what we do is we call the .tokenize method.
193
We put the list of documents in there
194
and we can also filter for English stop words so
195
because it's a keyword based mechanism and it will create calculations
196
and a score really on how frequent the words are occurring in certain documents etc removing stop words can help
197
because these are generally noise so if we now convert
198
that to tokens you can see that process takes two seconds
199
or so and now
200
if i look in here you can see we get the following tokenized data over here
201
but let's actually figure out okay what is this i see some ids i see a vocabulary
202
but this is all very messy to me still right now
203
so let's actually have a look in that
204
so what essentially token tokenized does it's it creates a tuple of token ids
205
and vocabulary items and
206
if we look at the first element in the tokens
207
you can see this is now you it creates one inner list per document
208
So this is the first document in our corpus, but now with simply these IDs.
209
And these IDs, they reference to the vocabulary.
210
So if I now look into the vocabulary, which is a dictionary with a string and then an integer,
211
you can see there is a lookup over here right now.
212
And how you can read this is as following.
213
It's simply a mapping.
214
So because this is the actual first document in our corpus, you you can see that it starts very sequentially, right?
215
So we have zero, one, two, three, four, five.
216
And so you might ask yourself, okay, why is this all the same?
217
But then here you can see, for example, that we have five again.
218
So five is over here.
219
So we can see what is five?
220
Five is training.
221
So you can see that this is now just a representation
222
of how to structure a document using IDs and a lookup vocabulary table.
223
That's how this is set up.
224
So again, you might need to play with this a little bit to figure things out.
225
If we now take the second element over here, you can also see that right now the ordering is going to be completely different, right?
226
And you can always link it back to the original item in the corpus.
227
So for example, if we are over here, remember, right?
228
So let's say we have the corpus and we take the text out of that.
229
So this is just the same exercise.
230
And we take the iLog1 you can see
231
that over here this is the first document
232
so it removes the stop board
233
so here you can see for example you see training
234
and then here is training one more time so
235
that is how this is the five over here
236
and the five over here reference training and we can see
237
that in the document over here so you can see this is something
238
that you just have to go through you have to kind
239
of like play with this a little bit for it to start makes sense
240
and that's why in this tutorial I want to just spend some time to just go deep into it
241
because especially nowadays with ai it's
242
so easy to just kind of like one -shot these systems
243
and oh yeah just use bm25 do a hybrid system
244
and then just like let it run without actually knowing what's going on behind the scenes
245
and there's just no way to understand that without tinkering with this
246
and actually just looking at those ideas and then making those matches
247
so we created the tokens
248
and now the next step in here is to create the retriever
249
so now we can again just use the bm25 as library very simple we have a retriever
250
and then we say retriever .index and we plug in the tokens
251
so this is now how we create the retriever which is ready
252
and loaded locked
253
and loaded with our own data now it's also one one thing to worth mentioning here is
254
that bm25 also has different implementations different methods so you can see
255
that the default method is the lucene in here
256
which is also indicated now again
257
if you want to go deeper into this here is how the calculations behind
258
this work as well as additional further readings and resources
259
that you can dive into now the next thing
260
that we're going to do is we're going to simply persist this index to the disk
261
so we're going to create a directory
262
and then we call can call the retriever save
263
and then we just simply put a path in there of where we want to store our index
264
and then we write that so if we run this
265
and i come over here i already had it
266
but you should now see
267
that all of the data all of the information is stored in here
268
so one of the interesting things about bm25 also is
269
that you don't really need a often need a database for this this can just live locally on on disk
270
and if you for example look at right now
271
so let me actually show you the size of this you can see
272
that it's 33 megabytes on disk
273
and this is using a corpus of 57 000 documents although short documents the interesting thing is
274
that for most use cases out in the wild
275
when people are building these rack systems these information retrieval systems your corpus is almost never
276
that big unless you're working on enterprise applications, multi -tenant solutions.
277
If you are automating within your own company, doing this as a freelancer or an agency to help local businesses,
278
your corpus is almost never this big.
279
So those files can easily just live on a file.
280
You can even put them into version control.
281
You could also see the indexing was really quick, right?
282
It took like one or two seconds to do this.
283
So no database really needed in order to store this.
284
You just load it in memory and use it.
285
Okay, and then go to step number four, actually running a query.
286
So, okay, we created up until this point, it was all prep work, right?
287
But this is where we're actually going to do the first step of information retrieval,
288
where we take a query and we search it against the entire corpus and then retrieve the document.
289
So here is a implementation of how we do that.
290
And again, because we are using the BM25S library, it's very simple.
291
We just call the, we use the retriever and we call .retrieve.
292
So let's walk through this together to see what this looks like.
293
And I'm going to actually take the following query over here.
294
So where should I park my rainy day funds?
295
And then let me also actually put k is 10 in memory over here.
296
So we're going to retrieve the top k is 10 documents
297
what we then do is we follow the same tokenization process
298
on the query as what we did with the corpus
299
so this is simply just turning any query into query tokens
300
so now you can see our question of where should i park my rainy day funds is tokenized as follows
301
and this removes the stop and then just gives a token to every word in here.
302
And then here you can see how that is built up because the query starts with where and then should.
303
And then you can see I is removed from it.
304
And then park is number three, which is index two.
305
You get the idea.
306
This is how you walk through that tokenization process.
307
And now one thing that's also worth to mention here is
308
that the tokenize is a deterministic mechanism over here that will always yield the same output.
309
So this is not using our customized or trained data at this point because we don't need it.
310
We simply need to tokenize it.
311
Where it does get custom is on the retriever step, remember, right?
312
Because we created the index on that.
313
So that's where our own custom vocabulary and index comes into play.
314
So we take our query tokens that we have
315
and then we run it against the retriever with k is 10 in this case
316
and then what you'll find is that we get the indices
317
and we get the scores and now what we do is we simply bring
318
that together in a list we loop over that
319
and then let me actually show you what that looks like
320
if i run this all together
321
so we can get a feel for what this looks like let's see over here let me actually clean
322
this up and run this one more time so it's the top now you can see that using our BM25 index,
323
if we tokenize where should I park my rainy day funds, these are the actual queries that we retrieve from the corpus.
324
So you'll find the scoring over here, then the index of the document itself and the actual text, five documents retrieved.
325
And then real quick, the fact that you're still watching this video signals to me that you're pretty serious about AI engineering.
326
But one of the big problems that we have right now is
327
that no one is really trained to be an AI engineer to work with LLMs because everything is so new, right?
328
And there are actually very little courses and resources online that really teach proper AI engineering end -to -end.
329
And I even ran into this issue in my own company
330
when I tried to hire AI engineers and figured out that actually nobody's properly trained for this.
331
So that's why I created the Gen AI Accelerator program.
332
This is a six -week track.
333
We've had engineers from booking
334
Intel, Electronic Arts, even from Meta going through this, and it will teach you in six weeks everything
335
that you really need to know to build production ready ai
336
applications all the way from foundation system design principles architecture rack of course llm monitoring
337
and evaluations and also very important deploying your ai applications to the cloud
338
and to actual production environments
339
and now we also have a 30 -day money back guarantee for this program
340
because we've been optimizing this for over a year already
341
and literally everybody loves it so
342
if you don't like it you can try it out literally just shoot me a message
343
and i will happily refund the entire amount for you.
344
And here's what makes this even better.
345
We have officially partnered with Credly to turn this into an actual certification program.
346
So because of this, you'll also find that a lot of students go through this program via their company.
347
So you can also reach out to your manager and see if this fits within your learning budget.
348
All right, so if that sounds interesting, make sure to check out the first link in the description.
349
And now let's get back to the tutorial.
350
So that is the first step of our hybrid retrieval system, BM25.
351
So sparse retrieval, keyword overlap with TF -IDF style waiting.
352
And it's excellent to catch exact terms, identifiers, and rare words, but it will miss paraphrases.
353
Now, let me scroll a little bit down over here to
354
pretty much explain why each piece earns its place in this retrieval pipeline and why they work so well together.
355
So for terms, like you can see over here, like the exact specific terms, BM25 is super good.
356
But it's often, it runs into problems where you have paraphrasing,
357
users asking questions in other terms than how they show up in the documents.
358
And that's where we can get into embeddings using dense embeddings.
359
And this is really why it's
360
so nice is dense embeddings really win exactly where BM25 loses
361
and loses exactly where bm25 wins they're kind of like two counterparts to it that work really well together.
362
So let's now look at how we're going to set up this.
363
And if you followed any previous
364
Rack tutorial, then you are already familiar with these concepts and it will probably be quite easy to understand.
365
So we're going to walk through the embeddings file over here, the three embeddings .py and we're going to use text embedding 3 small for this.
366
Now this requires an OpenAI API key.
367
You can use open source local text embedding models, but for a actual proper retrieval system, I recommend whenever possible,
368
just use one of the better models out there because it will simply improve the results of your system overall.
369
So we're going to follow a quite similar setup.
370
And we'll identify some directories in here.
371
And we'll create some simple functions that we can use to create the embeddings.
372
So you can see we're going to use the OpenAI client over here.
373
And we can use client .embeddings .create.
374
We'll plug in the model, which is the text embedding 3 small model, which will output an array of 1536 dimensions.
375
So that will be the length of the vector embedding in there.
376
And this is a simple batch function that we can use to loop over this.
377
Now, what we are going to do is we're going to read the corpus again.
378
So same structure, right?
379
We did this also with BM25, but now we're going to take a different approach.
380
So we take the IDs, we take that list
381
and now what I want you to be aware of is that
382
when you're going to run this since we're going to do it over the entire corpus
383
this is going to incur some costs
384
so it will be a couple of cents to kind of
385
like run through this we only have to do this once
386
and then it will be cached because we store it to disk
387
but I just want you to be aware of
388
that again for the sake of like following
389
and demonstration you could also swap this with an open source embedding model how to do that
390
Like you can literally simply open this in cursor cloud code and say, look, we're using OpenAI.
391
Can you implement an open source embedding model for this?
392
And it will set you up.
393
The process will be the same, but the output that you will, and the results that you will get in the end will be different.
394
So let's go step -by -step over here
395
and then zoom out a little bit to understand what's going on behind the scenes.
396
So we're again going to take all of the text in here.
397
We're simply going to split that up.
398
And this is where the magic happens.
399
So there's an if statement in here that says, look, if we already have the embeddings,
400
which we're going to store to dense embeddings .npy over here, if that file is already in place,
401
which you can see is the case on my system because I already run it, we will simply load it into memory by using np .load.
402
And if it's not, we'll go over it and we'll build the index, which you can see is the function we specified over here.
403
And this will loop over everything.
404
So it will take batches of size 256 and it will loop over the embedding model over here to create those vectors.
405
Now, one step to kind of like zoom out.
406
And just for your reference, again, if you've never worked with vector embeddings before,
407
you probably want to spend some time to kind of like go over this
408
and understand how factor embeddings work and how we are essentially capturing the semantic meaning in a numerical representation.
409
This is now again the point of this tutorial where I
410
cannot really go deeper into all of this to explain exactly how this works.
411
That's why I added the document over here.
412
What I want to show you right now is show you how this comes together and how we can use these embeddings.
413
Because if I now simply run everything over here and then clear all of this out,
414
you can see that we now have the embeddings over here.
415
So now we have the factors.
416
And we can even say, just to get a feel for what this is, we can take the first one.
417
So this is the factorized version of the first document in our corpus
418
and you can see the exact shape over here, right?
419
So this is how long the array is.
420
And this is because we're using the text embedding three small model.
421
Now, one little kind of like tip
422
that I want to give you here is that if you've worked with Rack in the past,
423
there is almost a 100 % certainty that you used a vector database to store your vectors, right?
424
So this, I'm assuming that for most people following this tutorial, you are familiar with this process of taking the corpus
425
and then turning them into embeddings and then storing them into a vector database.
426
What we are doing over here, what you can see over here, is instead of using a vector database,
427
we're simply using a NumPy array.
428
NumPy is a Python library that's, here you can see it's used for computations.
429
It's essentially the library that all of like AI
430
and deep learning is pretty much built around
431
because this is what actually stores the matrices and does the matrix multiplications that all like neural networks,
432
machine learning models, any types of models really are based on.
433
And we can leverage that really without needing a vector database.
434
We can simply just store these vectors to disk.
435
And that is what you saw over here.
436
So we have an embeddings .npy file over here.
437
And where we do that is the following.
438
So you can see over here, we simply do, after we create all of the embeddings.
439
So here you have all of the embeddings over here.
440
In all other RAG tutorials, you'll find that, okay, now we have the embeddings.
441
Let's put them into any type of vector database.
442
whether that's Quadrant, LensDB, VV8, BGFactor, let's put it into a vector database.
443
But for any corpus really under, I don't know, a million of chunks really in there, you don't really need it.
444
Similar to what I explained with the BM25, right?
445
We had like the 30 MB file.
446
Similar with the embeddings, we can also just run this in memory
447
and then use that without the extra complexity so that's the np .save
448
that we run over here we get the npy embeddings file
449
and let me show you just for the sake of clarity show you how big
450
that file is so you can see this is bigger than what we had with the bm25
451
so this is 350 megabytes on disk but again nothing too crazy too large that any deployment,
452
any compute, any VPS cannot handle, right?
453
And again, this is a pretty substantial corpus.
454
We got 56 ,000 documents in there.
455
So just a quick little tip to show you that you often don't really need a vector database.
456
So now let's continue to creating the search again, right?
457
So how do we use this?
458
Now that we have these embeddings, How do we use it?
459
So now here we have a function.
460
Let me clean this up for you.
461
A function to perform the search.
462
And what we're going to do in this case, we're again going to take the same query.
463
So we have the query, where should I park my rainy day funds?
464
We'll put that k is 10 into memory as well.
465
So we're going to retrieve the top 10 documents and then later show five over here.
466
And now the next step is to create that embedding.
467
So if I run this file over here, you can see that we now turn that where should I park my rainy day fund?
468
We create the embedding similar to what we did with BM25.
469
Remember, we did the tokenization.
470
So we did the prep work.
471
So it is then in a format that we can search against our entire corpus.
472
So we're doing that in the same way over here.
473
But remember here we're making an API call to open AI.
474
So we need
475
that model in order to get this computation with BM25 we could all do it locally because of the BM25S library.
476
Now here there is one step in there
477
which is for this particular embedding model that we're using it's not needed
478
but I wanted to show this in here anyway and
479
that is we perform a normalization and normalization means
480
that we rescale the factor so that the Euclidean distance becomes one in total.
481
Now here is that explained again in more detail
482
but why we include this in here is even though open ai
483
if we use the embedding models they are the vectors are already normalized
484
so we don't have to do it
485
but you can see we pre -normalize it over here during the embedding process on the corpus
486
and we also do it during the search on the actual query this is just a safety precaution
487
so that if you use any other embedding model
488
that doesn't automatically get you normalized factors we perform that step
489
and this is super important because
490
when we're working with normalized factors we can make the retrieval part easier
491
because again if you've worked with factors with rag before then you've probably heard about cosine similarity
492
which is how we actually calculate the similarity the semantic similarity really between two uh two factors within the space
493
that we created
494
so here's a 2d representation over this although in the in the real world
495
it is much more complex as you can see over here
496
but if we use normalized factors the calculation becomes simpler
497
because we can essentially just take the dot product it's explained over here why that works again
498
This is the mathematics behind it.
499
But if we have normalized vectors, we can simply take now our doc embeddings normalized, which is our entire corpus.
500
Remember, we can take our query vector, which we just created, and we can simply use this at notation over here,
501
which is NumPy's way of calculating the dot product.
502
Now, what we're essentially doing over here is we're performing a matrix multiplication.
503
So this is the entire matrix over here.
504
And we pretty much take every array in here and we multiply it with the query fact.
505
And then we get our scores.
506
So now we get all of the scores in here, which we can then sort to the top K.
507
So now you can see the following array over here.
508
And in the function, we will return those particular IDs.
509
So again, this is quite deep to kind of like get into, especially if you want to understand the math behind it.
510
But this is what's going on behind the scenes in most factor databases
511
when you offload your data to that and call the kind of like retrieve or get similarity function.
512
We have now implemented that in almost pure
513
Python really from scratch.
514
Literally just the only kind of like thing that we're leveraging over here is the NumPy library.
515
So if I now store this into memory and then just run the entire thing, you can see that now,
516
let me scroll up, we got back to the same as what we had with BM25, right?
517
We have a query.
518
We can search that against our corpus.
519
And now we, in this case, you can see K is five.
520
So it prints the five most relevant documents, but now through a different mechanism, through a dense search.
521
So you can see the scores, the IDs, any documents associated with it.
522
So that is step number two of our retrieval pipeline, right?
523
So remember to zoom out a little bit, really kind of like summarize
524
and get a clear understanding of where we are now at we have two different ways of creating these indexes
525
and then two different retrieval mechanisms so that we can get five documents
526
or ten documents over here
527
and over here using these exact methods over here where we now also know the strengths
528
and the weaknesses of each one exact keyword matches and the exact inverse, namely more paraphrased and actual meaning behind it.
529
So how do we now bring that together?
530
That is step number three.
531
And for that, we're going to use a technique called reciprocal rank fusion.
532
This is another algorithm.
533
And this is, I would say, the simplest out of everything that I shared.
534
If you really want to kind of like understand some of the math and the calculations behind it, this one is quite easy to follow.
535
Pretty much what we do is you can see over here where, again, what I just explained, we have two ways of getting rankings.
536
Five documents via this, five documents via that.
537
What the RRF essentially now does is it takes, it has this formula over here.
538
K equals 60 tends to be the kind of like norm that was also introduced.
539
It's the smoothing factor, which is almost also introduced in the original paper, which kind of like still works and everyone defaults to.
540
So you can now see if for this particular document,
541
so the green one, BM25 ranks it at number one and dense ranks it at number two.
542
You can see the formula.
543
We do one, we divide it by 60, which is the smoothing factor K, plus and then the rank.
544
So this is for BM25, it's ranked number one.
545
And here for dense, it's number two.
546
So this now becomes the calculation and this becomes the score.
547
You see, it's a little bit small, but that is how it's calculated.
548
Now, if you look at the purple dog over here, you can see rank number four, rank number one, rank number four,
549
rank number one, do the calculation.
550
And then you pretty much sum everything.
551
So this is a way to simply bring together different retrieval algorithms where the original scores cannot be directly compared
552
because it wouldn't make sense to compare the score, the similarity score, like the cosine similarity of dense ranking,
553
and then rank it against BM25, because they're on different scales that would be comparing apples to oranges.
554
So this is a way to say, look, we just care about the rank.
555
And that's how we bring them together.
556
So that's a short little introduction.
557
Again, more information, original papers can be traced back over here.
558
So let's see what this looks like in the code.
559
If we go to RFF, file number four, you can see that same smoothing factor over here, which we start with.
560
And here is the pure
561
Python implementation of what we do.
562
So we pretty much loop over all of everything in our list over here.
563
And then this is where we do the calculation.
564
So here you can see that exact kind of like one divided by K plus rank, right? similar to
565
what we did over here.
566
That is the pure Python implementation.
567
So let's store that in memory.
568
And now we can actually move quite quickly over here because right now you know how the BM25 works,
569
how Dense works, and we have the corpus.
570
So now it's all about the ranker.
571
And what I did in this step, so you can see in the folder, you can see a utils in here,
572
and you can see that we are performing some imports.
573
So from utils retrievers, you can see that I created a let's see the BM25 retriever
574
and the dense retriever so it's a simple I created two simple classes
575
that we can import
576
so we don't have to kind of like redo the code in all of the files
577
so this is also like if you want to take this into your own project
578
import them from the utils simply take these
579
and put them in in whatever you want to use
580
so okay coming back to where were we the rff over here yeah okay
581
so now we just simply load it we have everything
582
and then we define a new function over here to perform the hybrid search
583
because this is not we're now bringing them together right
584
so you can see we perform a bm25 .search
585
and a dense .search same query same candidates that we're retrieving
586
and now what we want to do is
587
because we're bringing things together
588
we want to bump up k a little bit to say look
589
you can just like just get a lot of documents
590
you can take top 50 like just show me what you got
591
and then we bring together and then we can take a subset from there
592
and simply rank them using our reciprocal rank fusion function remember that we create it over here to perform the calculation.
593
So let's see what that looks like.
594
So if I now take my query, where should I park my rainy day funds?
595
And I'm actually going to walk you through it over here.
596
Let me see.
597
Let's do it like this.
598
I should do that one by one.
599
So let's first do, we get K, K is 10.
600
This is really why I love the interactive window, because this is ultimately the best way to learn,
601
because I can run this all at once and immediately show you the results.
602
But if I run this over here, you can see, let's first focus on the BM25 search.
603
So here's the BM25 search and here are the IDs.
604
Remember, K is 50, so we get 50 documents back.
605
Then let's do the dense search.
606
So here we get the dense IDs.
607
Also, 50 documents that we get back.
608
Now, here we're going to perform the rank fusion by putting the two ID lists in here.
609
So you can see we put that into an actual list.
610
So we just stack them together.
611
So you can see here's the first 50 and then here starts the second list.
612
So that is the input.
613
And then if you go over here, you find that this takes a list with a list of strings,
614
which is exactly the format that we have.
615
And that's how it performs the calculations so
616
that is like walking through it step by step if we then run this and show everything
617
now we can see everything coming together
618
so let me show you what this looks like
619
so here we can see that for our original query that we've been using over and over again
620
here's what bm25 will give you back as top five here is what dense will do
621
and then here is the hybrid with RFF.
622
So here we can already start to see these differences, right?
623
And just so you know, right now we have no way really yet of telling or seeing what is correct and what is incorrect.
624
We can only do that once we get to the evaluation
625
and we map the original query back to the original documents, right?
626
I think we checked this in the beginning, but I don't remember which IDs actually belong to this query.
627
But you can see, for example, that this one really stands out over here you have
628
that duff deer over here which is not in bm25
629
and then you can see with rff this is moved a little bit to the top
630
and you can see uh starting with in addition this one this is now actually ranked number one
631
and i could see why because you could see it is number two for bm25
632
and also number two for um for dense
633
so both have a very high rank in uh in both results
634
and that's why it's kind of like pushed up
635
and then the bonds you can see it ranks over here bm25
636
but it's not in the dense so that's why that moved down
637
so this is how this is now all coming together
638
and we get a fused ranking of the two all right so
639
that was step number three of how we can bring the results together
640
now there's going to be one more final step and
641
that is we're going to add the re -ranker
642
so what we're going to do over here is
643
if you look at the process of like the dense embeddings that is what you would call a by encoder.
644
So both the query and the corpus, they go through the vectorization process in separate paths.
645
When using a cross encoder, we can do that at the same time.
646
So the query can go through the encoder together with all of the documents.
647
And let me actually show you over here again what that looks like without going too deep into everything.
648
What a re -ranker essentially does is it looks at all of the results
649
that are retrieved and now also already combined using RFF and then it's going to reorder them based on actually looking at,
650
okay, but now wait, what is the question and what are the actual documents that I have?
651
And going through this.
652
And there are various models that we can use for this.
653
You can use open source models,
654
but I'm going to use one of the best re -ranked models out there and that is a model from Cohere.
655
You can set up an account on Cohere
656
and you can use the free API key because they have a free plan available to follow along.
657
This is what I would recommend, but you could also swap it out for an open source one.
658
It is explained over here what that would look like.
659
So we're going to use Rerank V4 fast.
660
So let's show what that looks like.
661
And this is actually now also a very simple setup because we've done most of the work already.
662
So let's go into the Rerank file.
663
You can see that right now we set the model and we use the cohere client, we get all of our retrievers.
664
And then the only thing that we do is beyond the hybrid candidates.
665
So this line over here is exactly what we did with the previous step, right?
666
To rank them using RFF.
667
Now, all we do is we take that same list
668
and we add a re -ranker on top of it and then we showcase everything.
669
So let's walk through this step -by -step again.
670
So you can get a feel for what this looks like.
671
We'll load everything.
672
Now, let's see.
673
Let's go into the search here and then the show again.
674
Let's take our beautiful query again.
675
Okay, so let's do the same trick, right?
676
We say case 10, candidates is 50.
677
So now, if we do this, these are our 50 candidates.
678
And remember, here RFF is already applied.
679
So what we did, coming back, we should come back to So we did BM25 search, then search, then reciprocal rank fusion,
680
which is RRF, by the way.
681
I just noticed that for the past couple of examples, I said RFF, I think.
682
Sorry about that.
683
So now with this one little line over here, we perform all of these.
684
And now we add the re -ranking on top of that.
685
So here is how that works.
686
You can now see all of the candidate IDs that we have.
687
So we have all of the IDs and then we have the texts.
688
So we get back to, okay, what are all the actual documents that were retrieved?
689
And now this is where the magic happens.
690
We do a co -air, we do the re -rank, we put in the model and now we put in a query.
691
Query, same, Canada text over here.
692
Top N, we set that to 10 in this case.
693
So let's see what we get out of that.
694
So this is another API call that we make
695
and now you can see we get a re -ranked version of it 10 results
696
and also some scores in here and we let
697
that return to we let the function return all of that
698
so now that we went through step by step let me run everything
699
so we can get again to let's see to a top five
700
so now you can see
701
if we do the rrf only the hybrid these are the documents
702
that get retrieved and now if we do hybrid plus the re -ranking you can see
703
that it's different so this is now on number one you can see
704
that duff there again which through rff was on number two
705
but now it's on number five over here
706
so you can see all of these differences are very subtle
707
and we are constantly looking at the top five
708
but what you should always keep in mind is if you scale this to many questions many documents in production environments, also with, I don't know,
709
top 10, top 20, this is actually going to make a difference
710
because the difference between a rack system like kind of like not working
711
or sometimes working is all about which documents show up when you ask which question.
712
And these subtle nuances, they make all the difference and that's how you're going to optimize your system.
713
So that's, it may kind of like feel very repetitive and it's like, yeah, it all kind of like got,
714
get we all kind of like get the same results right but we don't and that's why now we can actually
715
go to the last file evaluate so
716
if you made it up until this point great job we're almost there
717
because now we're going to perform the evaluation to see
718
if what we're doing actually makes sense
719
so coming back to let's see our documents over here let me come to the ndcg
720
so normalized discounted cumulative gain
721
so this is in information retrieval one of the golden standards to measure retrieval quality again
722
there is some math behind it to better understand this
723
but we again look at the rank of the retrieved documents
724
and then whether they're relevant or not and then we compute a score between zero and one.
725
And this is how we can compare different iterations.
726
So what you should keep in mind there is if we run this, higher means better score.
727
We change something, we try a different embedding model, we try a different chunking strategy, we try a different re -ranker, we try a different step,
728
higher score is better, lower score, worse.
729
That's how we're going to optimize this so how i have implemented
730
that for you again you can see the implementation in pure python over here
731
i'm not going to going to go through everything
732
but this is how we can calculate it and
733
if i now scroll down and pretty much well let's go let's go for this step by step
734
because we have to function and then we get we load all of the queries first
735
so let's get our entire corpus ready.
736
So now we have everything ready.
737
Now we have these, the retrievers.
738
So here's where we're going to wire up the retrieval mechanisms, right?
739
And let's see, we have some other helper functions in here.
740
And this is where, let's see, we're actually going to run everything.
741
And I'm simply just going to do this.
742
And then we're going to reverse engineer, because as you can see what we're now going to do is for each query
743
that we have within our data set
744
and by the way we limit it to let's see we limit it to 50 in in this case
745
so I'm going to take a random sample of 50 queries
746
and then we're going to run into that
747
so I'm not going to do the full full corpus size
748
so you can play with this and pretty much
749
if you look at where we're setting
750
that up you can see over here we're taking a random sample
751
so here this is this is 50
752
and we set a random seed of 42 meaning every time
753
you run this the exact slice of the data is exactly the same okay
754
so now this is completed
755
and why i do 50 in this case just to kind of like speed things up
756
and this will also put you within the uh within the free plan of of cohere
757
that you can still use it I believe without running into API errors
758
so okay but now we have some numbers over here
759
so let's look at them and check what's actually the differences
760
because here you can really see the impact of what we're building
761
so you can see that for our score
762
and remember a score between zero
763
and one is what the ngcg at 10 will give you
764
but we multiplied it by 10 or sorry by 100 to kind of like get the percentage in there.
765
This is also usually how it's reported within all of the research papers.
766
So if you see, if you look at, if we only do BM25, we get a score of 28, which is not that great.
767
And that is because this particular data set
768
that we're using is known to not be very effective with direct keyword -based matching.
769
Then if you look at dense, you can see that clearly performs better.
770
So the vector embeddings, that is already a lot better at retrieving the right documents.
771
Now, you can see if we then combine that using RRF, you can see that we are somewhat in the middle.
772
So between these two.
773
But now here is where it gets interesting.
774
If we now take the hybrid and we rank it, all of a sudden we jump up to 47.
775
That is a major increase over dense alone really while just literally adding one simple re -rank step.
776
Because remember also from your code base over here, this is literally all that we add to it.
777
That is what we add to it.
778
And because of that, we jump to this particular score.
779
So that is how you can evaluate your methods and your approaches.
780
And now one, the most important thing to keep in mind is do you always need this entire stack?
781
And the answer is no. So you should figure out for your corpus what the best combination of this is.
782
And I've now shown you an experimental setup that you can use to do this.
783
Because in this case, it's actually interesting.
784
It could be that maybe if we take BM25 out of it
785
and we just do dense and we maybe increase the retrieval or the top K, we increase that in here and then apply the ranker.
786
We may even get higher.
787
We may even get to 50.
788
So this is now the.. experimental part that you need to get into.
789
And that's, I think, one of the main benefits of like going through this entire video
790
and really trying to understand it rather than just like copying samples
791
that you find online or letting cloud code one shot it, is understanding the mechanisms behind it.
792
And what's, let's see, what I want to show right now is finally, now that we're coming to the end of this project,
793
is how you can apply this to your own project.
794
And the setup is like from a code perspective, it's almost exactly the same.
795
So you decide what kind of documents do you want to use, right?
796
Are they Notion, Zendex, whatever, take your data.
797
Check if you need to chunk.
798
So you like, you ideally want to chunk.
799
First of all, you have a hard limit of what you can actually send into the embedding model.
800
But even like in most like best practices, you want to everything over like 1000 tokens.
801
see if you can already split it up, for example.
802
So this is a step we did not have to do in this project
803
because the corpus was already like there was small isolated documents, right?
804
Okay, then you want to pick an embedding model.
805
Then you're going to build the indexes, wire everything up, and then you're going to build the evaluation data set.
806
And this is the most important gap that you'll probably encounter within your own project.
807
And that's why I created an entire guide for this as well because we use the bear style evaluation set, remember, right?
808
And that's why we have this, Curries, Relationships, and Corpus.
809
And that's why we can run this.
810
That's why we can say anything meaningful about the adjustments that we make.
811
In the real world, you don't have this.
812
So then the question is, okay, but how do I improve my system beyond just kind of like vibes
813
and just like running
814
it? how pretty much like 90 probably even more people like working with ai are doing
815
it just chuck everything into the vector database
816
and like just just let it run
817
and then just like check off of vibes this is how you get like scientific about this
818
so how do you create
819
that well let's see coming back to the guides
820
this is the one i created the guide over here on how to do
821
that but the thing is it's it's it's pretty simple you already have the documents
822
that should be your starting point
823
and then you're going to use an llm with a prompt
824
like this you are a user with a question the following
825
document contains the answer contains the answer generate a single realistic question
826
that a user would ask in their own word
827
so rules question must be answered by the document
828
and you can loop over this
829
so you can even use like a small cheap model for this
830
and And now you can exactly structure your own data set with document IDs
831
and questions and relationships in the format that we used in this tutorial.
832
And in that way, you can start to improve it over time, add more to it.
833
And this is just super simple nowadays with LLMs.
834
So that's how you build your own eFiles really, and how you really put this into practice beyond just the tutorial.
835
All right, and with that, we've come to the end of this video.
836
Now, as always, if you find it valuable, please leave a like and also consider subscribing.
837
And if you want to go really deep on AI engineering and you love tutorials and lectures like this,
838
actually covering what works in production and not just like most of the AI crap that's out there online,
839
then make sure to check out the Gen AI Accelerator.
840
It's a six -week track where I pretty much transition
841
you from being a general developer into an actual AI engineer by showing you exactly how we build
842
and deliver all of the client projects for DataLumina, our AI development company, where we've been working on AI projects for close to a decade already.
843
So if that sounds interesting, make sure to check it out.
844
And then if you want to continue learning here on YouTube, I'm going to link this video up here, which is another approach to information retrieval,
845
but then using an agentic approach.
846
So how to go about a Gentec rack is what I explained in this video.
847
So I also highly recommend to check out that.

Descargar app

Todo lo que necesitas para hablar con fluidez

Pronunciación con IAEvalúa cada oración
Práctica de IPADomina cada sonido
VocabularioConstruye tu banco de palabras
Juego de vocabularioAprende mientras juegas
FOR YOU

Recomendar Video

Vocabulario y notas de pronunciación para esta lección

Esta lección de conversación se basa en el vídeo “The Complete Guide to Hybrid Search in RAG”. Este vídeo tiene 847 frases y 10557 palabras para practicar shadowing. La parte hablada dura 59:17. El hablante habla a un ritmo natural de unas 178 palabras por minuto, cercano a una conversación cotidiana. El 87 % de las palabras está entre las 3.000 más comunes del inglés, por lo que el vocabulario es fácil de seguir.

Vocabulario clave de este vídeo

15 palabras del vídeo que vale la pena aprender, con su pronunciación y significado:

PalabraPronunciaciónSignificado
query sustantivo/ˈkwɪɹ.i/consulta, pregunta
corpus sustantivo/ˈkɔɹpəs/corpus
rank sustantivo/ˈɹæŋk/fila
dense adjetivo/ˈdɛns/denso
vector sustantivo/ˈvɛktɚ/vector
retrieve verbo/ɹɪˈtɹiːv/recobrar
database sustantivo/ˈdeɪtəˌbeɪs/base de datos, banco de datos
tutorial sustantivo/ˌtjuːˈtɔːɹɪəl/tutoría, tutorial
hybrid sustantivo/ˈhaɪ.bɹɪd/híbrido
token sustantivo/ˈtoʊkən/seña
calculation sustantivo/ˌkælkjuˈleɪʃn̩/cálculo, calculación
mechanism sustantivo/ˈmɛk.ə.nɪ.zəm/mecanismo
rainy adjetivo/ˈɹeɪni/lluvioso, pluvioso
essentially adverbio/ɪˈsɛnʃəli/esencialmente, en esencia
mathematics sustantivo/mæθ(.ə)ˈmæt.ɪks/matemáticas, matemática

Phrasal verbs que vas a escuchar

PalabraPronunciaciónSignificado
go through verboatravesar
walk through verboexplicar paso a paso, enseñar
come together verbo/ˈkʌm tʊˈɡɛðɚ/juntarse, concitarse
check out verboinvestigar, chequear
get back to verboresponder / contestar (en un rato), volver / tornar a contactar
go over verbosentar, pasar
zoom out verbo/ˌzuːm ˈaʊt/alejar el zoom, quitar zoom (el)
bring together verboconcitar

Frases que vale la pena repetir

Frases cortas y completas del vídeo que puedes reutilizar en la conversación diaria:

  • All right, so let's kick things off.
  • Remember how I was talking about this over here?
  • What data are we actually working with?
  • Let's pick some random ones.
  • Can you implement an open source embedding model for this?

Gramática en este vídeo

Las estructuras que más usa el hablante, con las palabras exactas del vídeo:

EstructuraEn el vídeo
Present perfect continuous have/has been + -ing — una acción que empezó antes y continúawe've been optimizing · we've been using · we've been working
Oraciones condicionales if + oración, will/would + verbo — una condición y su resultadoif it's not, we'll go
Present perfect have/has + participio pasado — una acción pasada que sigue importando ahorathey've dabbled · you've never worked · we've converted
Voz pasiva be + participio pasado — importa lo que ocurre, no quién lo haceare linked · can be answered · is considered

Pronunciación a tener en cuenta

El hablante usa 151 contracciones y formas reducidas, como we're, I'm, don't. Dilas en su forma corta, tal como las oyes.

  • Los sonidos de “th”: mathematics /mæθ(.ə)ˈmæt.ɪks/, smooth /smuːð/
  • Los sonidos de “sh” y “zh”: calculation /ˌkælkjuˈleɪʃn̩/, essentially /ɪˈsɛnʃəli/, implementation /ˌɪmplɪmənˈteɪʃən/, fusion /ˈfjuː.ʒən/, evaluation /ɪˌvæljuˈeɪʃn̩/
  • Palabras largas — cuida el acento: mechanism /ˈmɛk.ə.nɪ.zəm/, essentially /ɪˈsɛnʃəli/, mathematics /mæθ(.ə)ˈmæt.ɪks/, implementation /ˌɪmplɪmənˈteɪʃən/, vocabulary /vəˈkæb.jə.lə.ɹi/

Sonidos difíciles para hispanohablantes:

  • s + consonante al inicio — sin añadir una “e” delante: smooth /smuːð/, scratch /skɹæt͡ʃ/, stack /stæk/, scroll /skɹoʊl/, swap /ˈswɑp/
  • /v/ — no es /b/: los dientes tocan el labio inferior: vector /ˈvɛktɚ/, retrieve /ɹɪˈtɹiːv/, vocabulary /vəˈkæb.jə.lə.ɹi/, dive /daɪv/, evaluation /ɪˌvæljuˈeɪʃn̩/
  • /z/ — sonora, no /s/: mechanism /ˈmɛk.ə.nɪ.zəm/, representation /ˌɹɛp.ɹə.zɛnˈteɪ.ʃən/, zoom /zuːm/, nowadays /ˈnaʊ.ə.deɪz/, transition /tɹænˈzɪʃ.ən/

Cómo practicar con este vídeo

  1. Escucha el vídeo entero una vez sin hablar y anota las palabras que no conoces.
  2. Empieza a velocidad 0,75×, haz shadowing frase por frase y vuelve a la velocidad normal cuando te resulte fácil.
  3. Grábate y compara con el original, prestando atención a palabras como query, corpus, rank.

¿Qué es la Técnica de Shadowing?

Shadowing es una técnica de aprendizaje de idiomas respaldada por la ciencia, desarrollada originalmente para la formación de intérpretes profesionales y popularizada por el políglota Dr. Alexander Arguelles. El método es simple pero poderoso: escuchas audio en inglés nativo y lo repites en voz alta de inmediato, como una sombra que sigue al hablante con solo 1-2 segundos de retraso. A diferencia de la escucha pasiva o los ejercicios de gramática, el shadowing obliga a tu cerebro y músculos de la boca a procesar y reproducir simultáneamente patrones de habla reales. Las investigaciones muestran que mejora significativamente la precisión de la pronunciación, la entonación, el ritmo, el habla conectada, la comprensión auditiva y la fluidez al hablar, convirtiéndola en una de las metodologías más efectivas para la preparación del IELTS Speaking y la comunicación en inglés en el mundo real.

Técnica de shadowing: lee la guía completa paso a paso →