Name: Towards AI Legal Name: Towards AI, Inc. Description: Towards AI is the world's leading artificial intelligence (AI) and technology publication. Read by thought-leaders and decision-makers around the world. Phone Number: +1-650-246-9381 Email: pub@towardsai.net
228 Park Avenue South New York, NY 10003 United States
Website: Publisher: https://towardsai.net/#publisher Diversity Policy: https://towardsai.net/about Ethics Policy: https://towardsai.net/about Masthead: https://towardsai.net/about
Name: Towards AI Legal Name: Towards AI, Inc. Description: Towards AI is the world's leading artificial intelligence (AI) and technology publication. Founders: Roberto Iriondo, , Job Title: Co-founder and Advisor Works for: Towards AI, Inc. Follow Roberto: X, LinkedIn, GitHub, Google Scholar, Towards AI Profile, Medium, ML@CMU, FreeCodeCamp, Crunchbase, Bloomberg, Roberto Iriondo, Generative AI Lab, Generative AI Lab VeloxTrend Ultrarix Capital Partners Denis Piffaretti, Job Title: Co-founder Works for: Towards AI, Inc. Louie Peters, Job Title: Co-founder Works for: Towards AI, Inc. Louis-François Bouchard, Job Title: Co-founder Works for: Towards AI, Inc. Cover:
Towards AI Cover
Logo:
Towards AI Logo
Areas Served: Worldwide Alternate Name: Towards AI, Inc. Alternate Name: Towards AI Co. Alternate Name: towards ai Alternate Name: towardsai Alternate Name: towards.ai Alternate Name: tai Alternate Name: toward ai Alternate Name: toward.ai Alternate Name: Towards AI, Inc. Alternate Name: towardsai.net Alternate Name: pub.towardsai.net
5 stars – based on 497 reviews

Frequently Used, Contextual References

TODO: Remember to copy unique IDs whenever it needs used. i.e., URL: 304b2e42315e

Resources

Take our 85+ lesson From Beginner to Advanced LLM Developer Certification: From choosing a project to deploying a working product this is the most comprehensive and practical LLM course out there!

Publication

MongoRAG: Leveraging MongoDB Atlas as a Vector Database with Databricks-Deployed Embedding Model and LLMs for Retrieval-Augmented Generation
Latest   Machine Learning

MongoRAG: Leveraging MongoDB Atlas as a Vector Database with Databricks-Deployed Embedding Model and LLMs for Retrieval-Augmented Generation

Author(s): Dwaipayan Bandyopadhyay

Originally published on Towards AI.

Today, in this article, I will give a detailed walkthrough about how we can leverage MongoDB’s own Atlas as a Vector Search Index and Embedding model and LLM served as an endpoint in the Databricks portal to do Retrieval Augmented Generation (RAG) on a piece of data.

MongoRAG: Leveraging MongoDB Atlas as a Vector Database with Databricks-Deployed Embedding Model and LLMs for Retrieval-Augmented Generation
Source : Image by Author

In today’s AI World, where large amounts of structured and unstructured data are generated daily, accurately using knowledge has become the cornerstone of modern-day technology. Retrieval Augmented Generation (RAG) is a widely used approach that solves real-world data problems by amalgamating the power of Generative AI and Information Retrieval.

Retrieval Augmented Generation generally consists of Three major steps, I will explain them briefly down below –

  1. Information Retrieval — The very first step involves retrieving relevant information from a knowledge base, database, or vector database, where we store the embeddings of the data from which we will retrieve information. This Retrieval part is typically done via Similarity Search, in which we find the similarities between the embedded query and the embeddings already stored in the Vector Database.
  2. Augmentation Step — After retrieving the similar information from the Vector Database, it gets combined with the query asked by the user so that the retriever gets the context to what has been asked and form a better answer for the query.
  3. Generation Step — This is the final step, where a Large Language Model comes into play, we feed the augmented information to the LLM, and it generates a proper human readable answer based on that information provided. Feeding of the augmented information is crucial because otherwise the AI might generate some random information as it doesn’t have any context of what has been asked.

What is MongoDB Atlas?

Atlas is a multi-cloud database service provided by MongoDB in which the developers can create clusters, databases and indexes directly in the cloud, without installing anything locally. Basically, it’s MongoDB on Cloud, users can create an account by signing up from their official website provided below –

MongoDB Atlas: Cloud Document Database | MongoDB

After signing in for the very first time, just follow the steps mentioned in the below documentation to spin up a free cluster.

Get Started with Atlas — MongoDB Atlas

After the Cluster has been created, it’s time to create a Database and a collection. Now, as MongoDB is a NoSQL Database, we have to create a Database first (unlike Schema for SQL Databases, although the concept is same), then inside the Database we have to create a collection, in which we can store documents (It is like creating a table inside a Database). If this feels confusing, please refer to the following article of how to create a Collection and Database, but remember, do not add any documents, just create collection and a database.

Connecting MongoDB with Python — The Coding part starts now

Now, we will connect MongoDB with Python, so that we can do the rest of the steps programmatically, without using the UI for a second.

To connect and access MongoDB Atlas via Python, we need to install a package called ‘pymongo’. It can be installed via the following pip command.

pip install pymongo

After it has been installed, we will import the class MongoClient to connect with MongoDB via Python. For that we will require the connection string, which can be found under Drivers settings after clicking on Connect from the Cluster. The process can be found in the following link, Step — 2.

Quick Start: Getting Started With MongoDB Atlas and Python | MongoDB

After the connection string is found, write and execute the below to connect with MongoDB

from pymongo import MongoClient

client = MongoClient("YOUR_CONNECTION_URL")
dbName = "YOUR_DATABASE_NAME"
collectionName = "YOUR_COLLECTION_NAME"
collection = client[dbName][collectionName]

This will establish the connection with MongoDB, if no errors are encountered, then the connection has been successfully made with MongoDB.

After the connection has been established, let’s talk about all the other packages we require to do the entire RAG process, apart from pymongo. Install the following packages via pip

pip install langchain
pip install langchain_databricks
pip install langchain_mongodb

We only require these three packages to do the entire process. After they are installed successfully, let’s import all the necessary classes from these packages.

Importing Necessary classes from the packages

from pymongo import MongoClient
from langchain_mongodb.vectorstores import MongoDBAtlasVectorSearch
from langchain.document_loaders import TextLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.chains import RetrievalQA
from langchain_databricks import ChatDatabricks
from langchain_databricks import DatabricksEmbeddings

As we have already established the connection with Databricks, let’s just load our data and do the chunking using RecursiveCharacterTextSplitter. We will be keeping each chunk size as 1000 with an overlapping of 100 characters and a new paragraph(\n\n) as a separator.

# Importing the data using TextLoader
loader = TextLoader("story.txt")
data = loader.load()

# Configuring the Chunking strategy
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=100,
separators="\n\n"
)

# keeping the chunks in this variable
chunked_docs = text_splitter.split_documents(data)

Configuring LLM and Embedding Models

Next, we will configure our Embedding model and Large Language Model which we are going to use. Now, here we will be using the models which are serving as an endpoint in Databricks Portal. If someone don’t have the access of Databricks, then they can go with the regular approach of using OpenAIEmbeddings and ChatOpenAI classes, and configure them accordingly.

embeddings = DatabricksEmbeddings(
endpoint="databricks-gte-large-en"
)

llm = ChatDatabricks(
target_uri="databricks",
endpoint="databricks-meta-llama-3-1-70b-instruct",
temperature=0.0,
)

We will be using the GTE-Large embedding model and the Meta LLama 3.1 70B Instruct models for this demo.

Creating a Vector Search Index in MongoDB Atlas

Now, after all the configuration is done, we will be creating a Vector Search Index in Atlas, in which we will store our embeddings and use them later to do the RAG. There are two ways to create the Vector Search Index, one is either the UI or the other way is via Code. Now Atlas provides us with a default Search Index name i.e ‘vector_index’, if someone wants to go by this name, then they can just write and execute the following code

vectorStore = MongoDBAtlasVectorSearch.from_documents(
chunked_docs,
embeddings,
collection=collection).create_vector_search_index(
dimensions=1024
)

This will create a vector search index named vector_index with the dimension 1024, inside the collection we created earlier. We just have the pass the chunked documents, alongside the embeddings and the collection configuration via which we connected to Atlas.

Image before the creation of the Search Index (execution of the above code)

Source : Image by Author

Image after executing the above code (creation of the default search index)

Source : Image by Author

As we can see now, after the execution of the above piece of code, our search index with the default name ‘vector_index’ has been created and 129 documents have been inserted (which is the number of chunks created earlier)

But, if someone wants to go a step further and create their own Search Index by providing own custom name, then we need to make some changes in the above code. First, we need to create the custom index using the name provided by the user, and then insert the embeddings into it, this cannot be done in one go (if done programmatically).

Creating custom vector search index

MongoDBAtlasVectorSearch(index_name="mongo_rag",
collection=collection,
embedding=embeddings).create_vector_search_index(
dimensions=1024
)

Here, we are creating an index called mongo_rag first with the dimension of 1024, now the dimension is very crucial whether we create the by-default index or custom index, because if this dimension doesn’t match with the one of the Embedding model, then it will be a major issue, the application will not even execute. for the embedding model used here i.e GTE-Large, the dimension is 1024.

Image after creating the custom index (embeddings are not yet added)

Source : Image by Author

As we can see here, the index has been created successfully, but the Documents are still at 129 values as we haven’t populated the embeddings here. We should delete the previously added chunks first, otherwise we will just push the same chunks again, which will be a repetition which might introduce hallucinations.

Populating the Custom Index with Embeddings

Using the following code, we can populate the custom index with the embeddings

vectorStore = MongoDBAtlasVectorSearch.from_documents(index_name="mongo_rag",
embedding=embeddings,
documents=chunked_docs,
collection=collection)

In this approach, while inserting embeddings into the search index, we are providing the index_name here, this will let us store the indexes in that particular search index.

Designing the RAG function

In this step, we will just design a generic RAG function using the LLMs and Endpoint configuration we defined earlier.

def query_data(query):
# Perform Atlas Vector Search using Langchain's vectorStore
# similarity_search returns MongoDB documents most similar to the query

docs = vectorStore.similarity_search(query, k=3)

# Putting the similar chunks into a list to print it later
similar_chunks = [chunk for chunk in docs]

# Setting up the retriever defined using MongDBAtlasVectorSearch
retriever = vectorStore.as_retriever()

# Load "stuff" documents chain. Stuff documents chain takes a list of documents,
# inserts them all into a prompt and passes that prompt to an LLM.

qa = RetrievalQA.from_chain_type(llm, chain_type="stuff", retriever=retriever)

# Execute the chain
retriever_output = qa.invoke(query)


# Return Atlas Vector Search output, and output generated using RAG Architecture
return (f"Similar Chunks\n-{similar_chunks}\n, Answer-{retriever_output}")

Now we will pass a sample query and check how it is working

query = "Explain the Character of Macbeth"
query_data(query)

Answer

'Similar Chunks\n-[Document(metadata={\'_id\': \'6798ecf0a3137ba55ff6b544\', \'source\': \'story.txt\'}, page_content="1606\\nTHE TRAGEDY OF MACBETH\\n\\n\\nby William Shakespeare\\n\\n\\n\\nDramatis Personae\\n\\n DUNCAN, King of Scotland\\n MACBETH, Thane of Glamis and Cawdor, a general in the King\'s\\narmy\\n LADY MACBETH, his wife\\n MACDUFF, Thane of Fife, a nobleman of Scotland\\n LADY MACDUFF, his wife\\n MALCOLM, elder son of Duncan\\n DONALBAIN, younger son of Duncan\\n BANQUO, Thane of Lochaber, a general in the King\'s army\\n FLEANCE, his son\\n LENNOX, nobleman of Scotland\\n ROSS, nobleman of Scotland\\n MENTEITH nobleman of Scotland\\n ANGUS, nobleman of Scotland\\n CAITHNESS, nobleman of Scotland\\n SIWARD, Earl of Northumberland, general of the English forces\\n YOUNG SIWARD, his son\\n SEYTON, attendant to Macbeth\\n HECATE, Queen of the Witches\\n The Three Witches\\n Boy, Son of Macduff \\n Gentlewoman attending on Lady Macbeth\\n An English Doctor\\n A Scottish Doctor\\n A Sergeant\\n A Porter\\n An Old Man\\n The Ghost of Banquo and other Apparitions\\n Lords, Gentlemen, Officers, Soldiers, Murtherers, Attendants,"), Document(metadata={\'_id\': \'6798ecf0a3137ba55ff6b5a7\', \'source\': \'story.txt\'}, page_content="Was a most sainted king; the queen that bore thee,\\n Oftener upon her knees than on her feet,\\n Died every day she lived. Fare thee well!\\n These evils thou repeat\'st upon thyself\\n Have banish\'d me from Scotland. O my breast,\\n Thy hope ends here!\\n MALCOLM. Macduff, this noble passion,\\n Child of integrity, hath from my soul\\n Wiped the black scruples, reconciled my thoughts\\n To thy good truth and honor. Devilish Macbeth\\n By many of these trains hath sought to win me\\n Into his power, and modest wisdom plucks me\\n From over-credulous haste. But God above\\n Deal between thee and me! For even now\\n I put myself to thy direction and \\n Unspeak mine own detraction; here abjure\\n The taints and blames I laid upon myself,\\n For strangers to my nature. I am yet\\n Unknown to woman, never was forsworn,\\n Scarcely have coveted what was mine own,\\n At no time broke my faith, would not betray\\n The devil to his fellow, and delight"), Document(metadata={\'_id\': \'6798ecf0a3137ba55ff6b57d\', \'source\': \'story.txt\'}, page_content="Particular addition, from the bill\\n That writes them all alike; and so of men. \\n Now if you have a station in the file,\\n Not i\' the worst rank of manhood, say it,\\n And I will put that business in your bosoms\\n Whose execution takes your enemy off,\\n Grapples you to the heart and love of us,\\n Who wear our health but sickly in his life,\\n Which in his death were perfect.\\n SECOND MURTHERER. I am one, my liege,\\n Whom the vile blows and buffets of the world\\n Have so incensed that I am reckless what\\n I do to spite the world.\\n FIRST MURTHERER. And I another\\n So weary with disasters, tugg\'d with fortune,\\n That I would set my life on any chance,\\n To mend it or be rid on\'t.\\n MACBETH. Both of you\\n Know Banquo was your enemy.\\n BOTH MURTHERERS. True, my lord.\\n MACBETH. So is he mine, and in such bloody distance\\n That every minute of his being thrusts \\n Against my near\'st of life; and though I could")]\n, 


Answer-{\'query\': \'Explain the Character of Macbeth\', \'result\': "Based on the provided context, Macbeth is a complex character who is the Thane of Glamis and Cawdor, and a general in the King\'s army. He is a prominent figure in the play and is driven by a desire for power and prestige. \\n\\nInitially, Macbeth is portrayed as a respected and accomplished military leader, but as the play progresses, his darker qualities are revealed. He is shown to be ruthless, ambitious, and willing to do whatever it takes to achieve his goals, including murder. \\n\\nMacbeth\'s relationship with his wife, Lady Macbeth, also plays a significant role in shaping his character. He is influenced by her goading and encouragement, which pushes him to commit regicide and seize the throne. \\n\\nHowever, Macbeth\'s actions are also motivated by a sense of insecurity and paranoia, as he becomes increasingly obsessed with the idea of being overthrown and killed. This fear drives him to order the murder of his friend Banquo and his family, further highlighting his descent into darkness and tyranny.\\n\\nThroughout the play, Macbeth\'s character undergoes a significant transformation, from a respected nobleman to a tyrannical and isolated ruler. His downfall is ultimately sealed when he is killed by Macduff, and his head is brought to Malcolm, the rightful king. \\n\\nIt\'s worth noting that the provided context only gives a glimpse into Macbeth\'s character, and a more comprehensive understanding would require a broader analysis of the entire play."}'

The output can be further modified based on the requirement.

Join thousands of data leaders on the AI newsletter. Join over 80,000 subscribers and keep up to date with the latest developments in AI. From research to projects and ideas. If you are building an AI startup, an AI-related product, or a service, we invite you to consider becoming a sponsor.

Published via Towards AI

Feedback ↓

Sign Up for the Course
`; } else { console.error('Element with id="subscribe" not found within the page with class "home".'); } } }); // Remove duplicate text from articles /* Backup: 09/11/24 function removeDuplicateText() { const elements = document.querySelectorAll('h1, h2, h3, h4, h5, strong'); // Select the desired elements const seenTexts = new Set(); // A set to keep track of seen texts const tagCounters = {}; // Object to track instances of each tag elements.forEach(el => { const tagName = el.tagName.toLowerCase(); // Get the tag name (e.g., 'h1', 'h2', etc.) // Initialize a counter for each tag if not already done if (!tagCounters[tagName]) { tagCounters[tagName] = 0; } // Only process the first 10 elements of each tag type if (tagCounters[tagName] >= 2) { return; // Skip if the number of elements exceeds 10 } const text = el.textContent.trim(); // Get the text content const words = text.split(/\s+/); // Split the text into words if (words.length >= 4) { // Ensure at least 4 words const significantPart = words.slice(0, 5).join(' '); // Get first 5 words for matching // Check if the text (not the tag) has been seen before if (seenTexts.has(significantPart)) { // console.log('Duplicate found, removing:', el); // Log duplicate el.remove(); // Remove duplicate element } else { seenTexts.add(significantPart); // Add the text to the set } } tagCounters[tagName]++; // Increment the counter for this tag }); } removeDuplicateText(); */ // Remove duplicate text from articles function removeDuplicateText() { const elements = document.querySelectorAll('h1, h2, h3, h4, h5, strong'); // Select the desired elements const seenTexts = new Set(); // A set to keep track of seen texts const tagCounters = {}; // Object to track instances of each tag // List of classes to be excluded const excludedClasses = ['medium-author', 'post-widget-title']; elements.forEach(el => { // Skip elements with any of the excluded classes if (excludedClasses.some(cls => el.classList.contains(cls))) { return; // Skip this element if it has any of the excluded classes } const tagName = el.tagName.toLowerCase(); // Get the tag name (e.g., 'h1', 'h2', etc.) // Initialize a counter for each tag if not already done if (!tagCounters[tagName]) { tagCounters[tagName] = 0; } // Only process the first 10 elements of each tag type if (tagCounters[tagName] >= 10) { return; // Skip if the number of elements exceeds 10 } const text = el.textContent.trim(); // Get the text content const words = text.split(/\s+/); // Split the text into words if (words.length >= 4) { // Ensure at least 4 words const significantPart = words.slice(0, 5).join(' '); // Get first 5 words for matching // Check if the text (not the tag) has been seen before if (seenTexts.has(significantPart)) { // console.log('Duplicate found, removing:', el); // Log duplicate el.remove(); // Remove duplicate element } else { seenTexts.add(significantPart); // Add the text to the set } } tagCounters[tagName]++; // Increment the counter for this tag }); } removeDuplicateText(); //Remove unnecessary text in blog excerpts document.querySelectorAll('.blog p').forEach(function(paragraph) { // Replace the unwanted text pattern for each paragraph paragraph.innerHTML = paragraph.innerHTML .replace(/Author\(s\): [\w\s]+ Originally published on Towards AI\.?/g, '') // Removes 'Author(s): XYZ Originally published on Towards AI' .replace(/This member-only story is on us\. Upgrade to access all of Medium\./g, ''); // Removes 'This member-only story...' }); //Load ionic icons and cache them if ('localStorage' in window && window['localStorage'] !== null) { const cssLink = 'https://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css'; const storedCss = localStorage.getItem('ionicons'); if (storedCss) { loadCSS(storedCss); } else { fetch(cssLink).then(response => response.text()).then(css => { localStorage.setItem('ionicons', css); loadCSS(css); }); } } function loadCSS(css) { const style = document.createElement('style'); style.innerHTML = css; document.head.appendChild(style); } //Remove elements from imported content automatically function removeStrongFromHeadings() { const elements = document.querySelectorAll('h1, h2, h3, h4, h5, h6, span'); elements.forEach(el => { const strongTags = el.querySelectorAll('strong'); strongTags.forEach(strongTag => { while (strongTag.firstChild) { strongTag.parentNode.insertBefore(strongTag.firstChild, strongTag); } strongTag.remove(); }); }); } removeStrongFromHeadings(); "use strict"; window.onload = () => { /* //This is an object for each category of subjects and in that there are kewords and link to the keywods let keywordsAndLinks = { //you can add more categories and define their keywords and add a link ds: { keywords: [ //you can add more keywords here they are detected and replaced with achor tag automatically 'data science', 'Data science', 'Data Science', 'data Science', 'DATA SCIENCE', ], //we will replace the linktext with the keyword later on in the code //you can easily change links for each category here //(include class="ml-link" and linktext) link: 'linktext', }, ml: { keywords: [ //Add more keywords 'machine learning', 'Machine learning', 'Machine Learning', 'machine Learning', 'MACHINE LEARNING', ], //Change your article link (include class="ml-link" and linktext) link: 'linktext', }, ai: { keywords: [ 'artificial intelligence', 'Artificial intelligence', 'Artificial Intelligence', 'artificial Intelligence', 'ARTIFICIAL INTELLIGENCE', ], //Change your article link (include class="ml-link" and linktext) link: 'linktext', }, nl: { keywords: [ 'NLP', 'nlp', 'natural language processing', 'Natural Language Processing', 'NATURAL LANGUAGE PROCESSING', ], //Change your article link (include class="ml-link" and linktext) link: 'linktext', }, des: { keywords: [ 'data engineering services', 'Data Engineering Services', 'DATA ENGINEERING SERVICES', ], //Change your article link (include class="ml-link" and linktext) link: 'linktext', }, td: { keywords: [ 'training data', 'Training Data', 'training Data', 'TRAINING DATA', ], //Change your article link (include class="ml-link" and linktext) link: 'linktext', }, ias: { keywords: [ 'image annotation services', 'Image annotation services', 'image Annotation services', 'image annotation Services', 'Image Annotation Services', 'IMAGE ANNOTATION SERVICES', ], //Change your article link (include class="ml-link" and linktext) link: 'linktext', }, l: { keywords: [ 'labeling', 'labelling', ], //Change your article link (include class="ml-link" and linktext) link: 'linktext', }, pbp: { keywords: [ 'previous blog posts', 'previous blog post', 'latest', ], //Change your article link (include class="ml-link" and linktext) link: 'linktext', }, mlc: { keywords: [ 'machine learning course', 'machine learning class', ], //Change your article link (include class="ml-link" and linktext) link: 'linktext', }, }; //Articles to skip let articleIdsToSkip = ['post-2651', 'post-3414', 'post-3540']; //keyword with its related achortag is recieved here along with article id function searchAndReplace(keyword, anchorTag, articleId) { //selects the h3 h4 and p tags that are inside of the article let content = document.querySelector(`#${articleId} .entry-content`); //replaces the "linktext" in achor tag with the keyword that will be searched and replaced let newLink = anchorTag.replace('linktext', keyword); //regular expression to search keyword var re = new RegExp('(' + keyword + ')', 'g'); //this replaces the keywords in h3 h4 and p tags content with achor tag content.innerHTML = content.innerHTML.replace(re, newLink); } function articleFilter(keyword, anchorTag) { //gets all the articles var articles = document.querySelectorAll('article'); //if its zero or less then there are no articles if (articles.length > 0) { for (let x = 0; x < articles.length; x++) { //articles to skip is an array in which there are ids of articles which should not get effected //if the current article's id is also in that array then do not call search and replace with its data if (!articleIdsToSkip.includes(articles[x].id)) { //search and replace is called on articles which should get effected searchAndReplace(keyword, anchorTag, articles[x].id, key); } else { console.log( `Cannot replace the keywords in article with id ${articles[x].id}` ); } } } else { console.log('No articles found.'); } } let key; //not part of script, added for (key in keywordsAndLinks) { //key is the object in keywords and links object i.e ds, ml, ai for (let i = 0; i < keywordsAndLinks[key].keywords.length; i++) { //keywordsAndLinks[key].keywords is the array of keywords for key (ds, ml, ai) //keywordsAndLinks[key].keywords[i] is the keyword and keywordsAndLinks[key].link is the link //keyword and link is sent to searchreplace where it is then replaced using regular expression and replace function articleFilter( keywordsAndLinks[key].keywords[i], keywordsAndLinks[key].link ); } } function cleanLinks() { // (making smal functions is for DRY) this function gets the links and only keeps the first 2 and from the rest removes the anchor tag and replaces it with its text function removeLinks(links) { if (links.length > 1) { for (let i = 2; i < links.length; i++) { links[i].outerHTML = links[i].textContent; } } } //arrays which will contain all the achor tags found with the class (ds-link, ml-link, ailink) in each article inserted using search and replace let dslinks; let mllinks; let ailinks; let nllinks; let deslinks; let tdlinks; let iaslinks; let llinks; let pbplinks; let mlclinks; const content = document.querySelectorAll('article'); //all articles content.forEach((c) => { //to skip the articles with specific ids if (!articleIdsToSkip.includes(c.id)) { //getting all the anchor tags in each article one by one dslinks = document.querySelectorAll(`#${c.id} .entry-content a.ds-link`); mllinks = document.querySelectorAll(`#${c.id} .entry-content a.ml-link`); ailinks = document.querySelectorAll(`#${c.id} .entry-content a.ai-link`); nllinks = document.querySelectorAll(`#${c.id} .entry-content a.ntrl-link`); deslinks = document.querySelectorAll(`#${c.id} .entry-content a.des-link`); tdlinks = document.querySelectorAll(`#${c.id} .entry-content a.td-link`); iaslinks = document.querySelectorAll(`#${c.id} .entry-content a.ias-link`); mlclinks = document.querySelectorAll(`#${c.id} .entry-content a.mlc-link`); llinks = document.querySelectorAll(`#${c.id} .entry-content a.l-link`); pbplinks = document.querySelectorAll(`#${c.id} .entry-content a.pbp-link`); //sending the anchor tags list of each article one by one to remove extra anchor tags removeLinks(dslinks); removeLinks(mllinks); removeLinks(ailinks); removeLinks(nllinks); removeLinks(deslinks); removeLinks(tdlinks); removeLinks(iaslinks); removeLinks(mlclinks); removeLinks(llinks); removeLinks(pbplinks); } }); } //To remove extra achor tags of each category (ds, ml, ai) and only have 2 of each category per article cleanLinks(); */ //Recommended Articles var ctaLinks = [ /* ' ' + '

Subscribe to our AI newsletter!

' + */ '

Take our 85+ lesson From Beginner to Advanced LLM Developer Certification: From choosing a project to deploying a working product this is the most comprehensive and practical LLM course out there!

'+ '

Towards AI has published Building LLMs for Production—our 470+ page guide to mastering LLMs with practical projects and expert insights!

' + '
' + '' + '' + '

Note: Content contains the views of the contributing authors and not Towards AI.
Disclosure: This website may contain sponsored content and affiliate links.

' + 'Discover Your Dream AI Career at Towards AI Jobs' + '

Towards AI has built a jobs board tailored specifically to Machine Learning and Data Science Jobs and Skills. Our software searches for live AI jobs each hour, labels and categorises them and makes them easily searchable. Explore over 10,000 live jobs today with Towards AI Jobs!

' + '
' + '

🔥 Recommended Articles 🔥

' + 'Why Become an LLM Developer? Launching Towards AI’s New One-Stop Conversion Course'+ 'Testing Launchpad.sh: A Container-based GPU Cloud for Inference and Fine-tuning'+ 'The Top 13 AI-Powered CRM Platforms
' + 'Top 11 AI Call Center Software for 2024
' + 'Learn Prompting 101—Prompt Engineering Course
' + 'Explore Leading Cloud Providers for GPU-Powered LLM Training
' + 'Best AI Communities for Artificial Intelligence Enthusiasts
' + 'Best Workstations for Deep Learning
' + 'Best Laptops for Deep Learning
' + 'Best Machine Learning Books
' + 'Machine Learning Algorithms
' + 'Neural Networks Tutorial
' + 'Best Public Datasets for Machine Learning
' + 'Neural Network Types
' + 'NLP Tutorial
' + 'Best Data Science Books
' + 'Monte Carlo Simulation Tutorial
' + 'Recommender System Tutorial
' + 'Linear Algebra for Deep Learning Tutorial
' + 'Google Colab Introduction
' + 'Decision Trees in Machine Learning
' + 'Principal Component Analysis (PCA) Tutorial
' + 'Linear Regression from Zero to Hero
'+ '

', /* + '

Join thousands of data leaders on the AI newsletter. It’s free, we don’t spam, and we never share your email address. Keep up to date with the latest work in AI. From research to projects and ideas. If you are building an AI startup, an AI-related product, or a service, we invite you to consider becoming a sponsor.

',*/ ]; var replaceText = { '': '', '': '', '
': '
' + ctaLinks + '
', }; Object.keys(replaceText).forEach((txtorig) => { //txtorig is the key in replacetext object const txtnew = replaceText[txtorig]; //txtnew is the value of the key in replacetext object let entryFooter = document.querySelector('article .entry-footer'); if (document.querySelectorAll('.single-post').length > 0) { //console.log('Article found.'); const text = entryFooter.innerHTML; entryFooter.innerHTML = text.replace(txtorig, txtnew); } else { // console.log('Article not found.'); //removing comment 09/04/24 } }); var css = document.createElement('style'); css.type = 'text/css'; css.innerHTML = '.post-tags { display:none !important } .article-cta a { font-size: 18px; }'; document.body.appendChild(css); //Extra //This function adds some accessibility needs to the site. function addAlly() { // In this function JQuery is replaced with vanilla javascript functions const imgCont = document.querySelector('.uw-imgcont'); imgCont.setAttribute('aria-label', 'AI news, latest developments'); imgCont.title = 'AI news, latest developments'; imgCont.rel = 'noopener'; document.querySelector('.page-mobile-menu-logo a').title = 'Towards AI Home'; document.querySelector('a.social-link').rel = 'noopener'; document.querySelector('a.uw-text').rel = 'noopener'; document.querySelector('a.uw-w-branding').rel = 'noopener'; document.querySelector('.blog h2.heading').innerHTML = 'Publication'; const popupSearch = document.querySelector$('a.btn-open-popup-search'); popupSearch.setAttribute('role', 'button'); popupSearch.title = 'Search'; const searchClose = document.querySelector('a.popup-search-close'); searchClose.setAttribute('role', 'button'); searchClose.title = 'Close search page'; // document // .querySelector('a.btn-open-popup-search') // .setAttribute( // 'href', // 'https://medium.com/towards-artificial-intelligence/search' // ); } // Add external attributes to 302 sticky and editorial links function extLink() { // Sticky 302 links, this fuction opens the link we send to Medium on a new tab and adds a "noopener" rel to them var stickyLinks = document.querySelectorAll('.grid-item.sticky a'); for (var i = 0; i < stickyLinks.length; i++) { /* stickyLinks[i].setAttribute('target', '_blank'); stickyLinks[i].setAttribute('rel', 'noopener'); */ } // Editorial 302 links, same here var editLinks = document.querySelectorAll( '.grid-item.category-editorial a' ); for (var i = 0; i < editLinks.length; i++) { editLinks[i].setAttribute('target', '_blank'); editLinks[i].setAttribute('rel', 'noopener'); } } // Add current year to copyright notices document.getElementById( 'js-current-year' ).textContent = new Date().getFullYear(); // Call functions after page load extLink(); //addAlly(); setTimeout(function() { //addAlly(); //ideally we should only need to run it once ↑ }, 5000); }; function closeCookieDialog (){ document.getElementById("cookie-consent").style.display = "none"; return false; } setTimeout ( function () { closeCookieDialog(); }, 15000); console.log(`%c 🚀🚀🚀 ███ █████ ███████ █████████ ███████████ █████████████ ███████████████ ███████ ███████ ███████ ┌───────────────────────────────────────────────────────────────────┐ │ │ │ Towards AI is looking for contributors! │ │ Join us in creating awesome AI content. │ │ Let's build the future of AI together → │ │ https://towardsai.net/contribute │ │ │ └───────────────────────────────────────────────────────────────────┘ `, `background: ; color: #00adff; font-size: large`); //Remove latest category across site document.querySelectorAll('a[rel="category tag"]').forEach(function(el) { if (el.textContent.trim() === 'Latest') { // Remove the two consecutive spaces (  ) if (el.nextSibling && el.nextSibling.nodeValue.includes('\u00A0\u00A0')) { el.nextSibling.nodeValue = ''; // Remove the spaces } el.style.display = 'none'; // Hide the element } }); // Add cross-domain measurement, anonymize IPs 'use strict'; //var ga = gtag; ga('config', 'G-9D3HKKFV1Q', 'auto', { /*'allowLinker': true,*/ 'anonymize_ip': true/*, 'linker': { 'domains': [ 'medium.com/towards-artificial-intelligence', 'datasets.towardsai.net', 'rss.towardsai.net', 'feed.towardsai.net', 'contribute.towardsai.net', 'members.towardsai.net', 'pub.towardsai.net', 'news.towardsai.net' ] } */ }); ga('send', 'pageview'); -->