{"id":400370,"date":"2024-06-29T15:13:32","date_gmt":"2024-06-29T15:13:32","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=400370"},"modified":"-0001-11-30T00:00:00","modified_gmt":"2024-06-29T15:13:32","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=400370","title":{"rendered":"<span>How to write simple intelligent code search with Open AI<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>In this article, we will briefly review a technology that underlies ChatGPT \u2014 embeddings. Also we\u2019ll write a simple intelligent search in a codebase of a project.<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/420\/045\/039\/420045039261888e358d0bc893e6352e.png\" width=\"1024\" height=\"591\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/420\/045\/039\/420045039261888e358d0bc893e6352e.png\"\/><figcaption><\/figcaption><\/figure>\n<p>Embedding is the process of converting words or text into a set of numbers called a numeric vector. Vectors can be compared with each other to determine how similar two texts or words are in meaning.<\/p>\n<p>For example, we take two numerical vectors (embeddings) of the words &#171;donate&#187; and &#171;give&#187;. The words are different but the meaning is similar, i.e. they are interconnected, and the result of both will be giving something to someone.<\/p>\n<p>To get a similar result in the context of a code, we can convert the words into embeddings and compare their <a href=\"https:\/\/en.wikipedia.org\/wiki\/Similarity_measure\" rel=\"noopener noreferrer nofollow\">similarity measures<\/a>. The value of measure will range from 0 to 1, where 1 is the maximum similarity and 0 is no correlation. <a href=\"https:\/\/en.wikipedia.org\/wiki\/Cosine_similarity\" rel=\"noopener noreferrer nofollow\">Cosine similarity<\/a> can be taken as such a comparison function. Imagine that we have done all the necessary operations with embeddings and now analyze the result. We also compared two embeddings of the words &#171;give&#187; and &#171;red&#187;. For the words &#171;give&#187; and &#171;donate&#187; the function returned the number 0.80, and for &#171;give&#187; and &#171;red&#187; \u2014 only 0.20. Thus, we can conclude that &#171;give&#187; and &#171;donate&#187; are closer than &#171;give&#187; and &#171;red&#187;.<\/p>\n<p>In the codebase of a project, embeddings can be used to search in a code or documentation. For example, you can make an embedding (vector) out of the search query and then measure similarity of both to find relevant functions or classes.<\/p>\n<p>So, to make it you need an Open AI account and an API token. If you don&#8217;t have an account yet, you can register on the official <a href=\"https:\/\/openai.com\/api\/\" rel=\"noopener noreferrer nofollow\"><u>Open AI website<\/u><\/a>. After registering and verifying your account, go to the <a href=\"https:\/\/platform.openai.com\/account\/api-keys\" rel=\"noopener noreferrer nofollow\"><u>API Keys<\/u><\/a> profile section and generate an API token.<\/p>\n<p>To start they give $18. That was enough to make an example for this article (below) and conduct further testing of the service.<\/p>\n<p>Take a TypeScript project as a codebase. I recommend taking a small one so as not to wait for generating embeddings long. Also you can use <a href=\"https:\/\/github.com\/Hokid\/simple-code-search-engine-using-open-ai-api\" rel=\"noopener noreferrer nofollow\"><u>an example<\/u><\/a>. You also need Python 3+ versions and the library from Open AI. No worries if you don&#8217;t know any of these languages. The code examples below are simple and don&#8217;t require a deep understanding of both.<\/p>\n<p>Let&#8217;s get started. First, you need to write a code to extract various pieces of code from the project, such as functions. TypeScript provides a convenient <a href=\"https:\/\/github.com\/microsoft\/TypeScript\/wiki\/Using-the-Compiler-API\" rel=\"noopener noreferrer nofollow\">compiler API<\/a> for working with an <a href=\"https:\/\/en.wikipedia.org\/wiki\/Abstract_syntax_tree\" rel=\"noopener noreferrer nofollow\">AST<\/a>, which simplifies the task. Install <code>csv-stringify<\/code> library to generate CSV:<\/p>\n<pre><code class=\"bash\">$ npm install csv-stringify<\/code><\/pre>\n<p>Then create the file <code>code-to-csv.js<\/code> and write the extraction of information from the code:<\/p>\n<pre><code class=\"javascript\">const path = require('path'); const ts = require('typescript'); const csv = require('csv-stringify\/sync');   const cwd = process.cwd(); const configJSON = require(path.join(cwd, 'tsconfig.json')); const config = ts.parseJsonConfigFileContent(configJSON, ts.sys, cwd); const program = ts.createProgram(     config.fileNames,      config.options,      ts.createCompilerHost(config.options) ); const checker = program.getTypeChecker();  const rows = [];  const addRow = (fileName, name, code, docs = '') => rows.push({     file_name: path.relative(cwd, fileName),     name,     code,     docs });  function addFunction(fileName, node) {     const symbol = checker.getSymbolAtLocation(node.name);     if (symbol) {         const name = symbol.getName();         const docs = getDocs(symbol);         const code = node.getText();         addRow(fileName, name, code, docs);     } }  function addClass(fileName, node) {     const symbol = checker.getSymbolAtLocation(node.name);     if (symbol) {         const name = symbol.getName();         const docs = getDocs(symbol);         const code = `class ${name} {}`;         addRow(fileName, name, code, docs);         node.members.forEach(m => addClassMember(fileName, name, m));     } }  function addClassMember(fileName, className, node) {     const symbol = checker.getSymbolAtLocation(node.name);     if (symbol) {         const name = className + ':' + symbol.getName();         const docs = getDocs(symbol);         const code = node.getText();         addRow(fileName, name, code, docs);     } }  function addInterface(fileName, node) {     const symbol = checker.getSymbolAtLocation(node.name);     if (symbol) {         const name = symbol.getName();         const docs = getDocs(symbol);         const code = `interface ${name} {}`;         addRow(fileName, name, code, docs);         node.members.forEach(m => addInterfaceMember(fileName, name, m));     } }  function addInterfaceMember(fileName, interfaceName, node) {     if (!ts.isPropertySignature(node) || !ts.isMethodSignature(node)) {         return;     }     const symbol = checker.getSymbolAtLocation(node.name);     if (symbol) {         const name = interfaceName + ':' + symbol.getName();         const docs = getDocs(symbol);         const code = node.getText();         addRow(fileName, name, code, docs);     } }  function getDocs(symbol) {     return ts.displayPartsToString(symbol.getDocumentationComment(checker)); }  for (const fileName of config.fileNames) {     const sourceFile = program.getSourceFile(fileName);     const visitNode = node => {         if (ts.isFunctionDeclaration(node)) {             addFunction(fileName, node);         } else if (ts.isClassDeclaration(node)) {             addClass(fileName, node);         } else if (ts.isInterfaceDeclaration(node)) {             addInterface(fileName, node);         }         ts.forEachChild(node, visitNode);     };     ts.forEachChild(sourceFile, visitNode); }  for (const row of rows) {     row.combined = '';     if (row.docs) {         row.combined += `Code documentation: ${row.docs}; `;     }     row.combined += `Code: ${row.code}; Name: ${row.name};`; }  const output = csv.stringify(rows, {     header: true });  console.log(output);<\/code><\/pre>\n<p>The script collects all the fragments we need and prints a CSV table to the console. CSV table consists of the columns <strong>file_name, name, code, docs, combined<\/strong>.<\/p>\n<ul>\n<li>\n<p><strong>file_name<\/strong> contains the path to the file in the project,<\/p>\n<\/li>\n<li>\n<p><strong>name <\/strong>is the name of the fragment, for example, &#171;function\u2019s name&#187;,<\/p>\n<\/li>\n<li>\n<p><strong>code<\/strong> is the entity code,<\/p>\n<\/li>\n<li>\n<p><strong>docs<\/strong> is the description from the comments to the fragment,<\/p>\n<\/li>\n<li>\n<p><strong>combined <\/strong>is the addition of the contents of the <strong>code <\/strong>and <strong>docs <\/strong>columns, we are going to use this column to generate embeddings.<\/p>\n<\/li>\n<\/ul>\n<p>You don&#8217;t need to run it.<\/p>\n<p>Now to Python.<\/p>\n<p>Install the library from Open AI and utilities for working with embeddings:<\/p>\n<pre><code class=\"bash\">$ pip install openai[embeddings]<\/code><\/pre>\n<p>Create the file <code>create_search_db.py<\/code> with the following code:<\/p>\n<pre><code class=\"python\">from io import StringIO from subprocess import PIPE, run from pandas import read_csv from openai.embeddings_utils import get_embedding as _get_embedding from tenacity import wait_random_exponential, stop_after_attempt  get_embedding = _get_embedding.retry_with(wait=wait_random_exponential(min=1, max=60), stop=stop_after_attempt(10))  if __name__ == '__main__': # 1 result = run(['node', 'code-to-csv.js'], stdout=PIPE, stderr=PIPE, universal_newlines=True) if result.returncode != 0:     raise RuntimeError(result.stderr) # 2 db = read_csv(StringIO(result.stdout)) # 3 db['embedding'] = db['combined'].apply(lambda x: get_embedding(x, engine='text-embedding-ada-002')) # 4 db.to_csv(\"search_db.csv\", index=False)<\/code><\/pre>\n<p>The script runs <code>code-to-csv.js<\/code>(1), the result is loaded into a dataframe(2) and embeddings are generated for the content in the <strong>combined<\/strong>(3) column. Embeddings are written to the <strong>embedding <\/strong>column. The final table with everything needed for the search is saved to the <code>search_db.csv<\/code>(4) file.<\/p>\n<p>For the script to work, you need an API token. The <code>openai<\/code> library can automatically take the token from the environment variables, so you can write a convenient script to make it:<\/p>\n<pre><code class=\"bash\">export OPENAI_API_KEY=YourToken<\/code><\/pre>\n<p>Save it somewhere, for example, in <code>env.sh<\/code>, and run:<\/p>\n<pre><code class=\"bash\">$ source env.sh<\/code><\/pre>\n<p>Everything is ready to generate the search database.<\/p>\n<p>Run the script <code>create_search_db.py<\/code> and wait until the CSV file with the database appears. This may take a couple of minutes. After that, you can start writing a search engine.<\/p>\n<p>Create a new <code>search.py<\/code> \u200b\u200bfile and write the following:<\/p>\n<pre><code class=\"python\">import sys import numpy as np from pandas import read_csv from openai.embeddings_utils import cosine_similarity, get_embedding as _get_embedding from tenacity import  stop_after_attempt, wait_random_exponential  get_embedding = _get_embedding.retry_with(wait=wait_random_exponential(min=1, max=60), stop=stop_after_attempt(10))  def search(db, query): # 4     query_embedding = get_embedding(query, engine='text-embedding-ada-002') # 5     db['similarities'] = db.embedding.apply(lambda x: cosine_similarity(x, query_embedding))     # 6 db.sort_values('similarities', ascending=False, inplace=True)     result = db.head(3)     text = \"\"     for row in result.itertuples(index=False):         score=round(row.similarities, 3)         if type(row.docs) == str:             text += '\/**\\n * {docs}\\n *\/\\n'.format(docs='\\n * '.join(row.docs.split('\\n')))         text += '{code}\\n\\n'.format(code='\\n'.join(row.code.split('\\n')[:7]))         text += '[score={score}] {file_name}:{name}\\n'.format(score=score, file_name=row.file_name, name=row.name)         text += '-' * 70 + '\\n\\n'     return text  if __name__ == '__main__': # 1     db = read_csv('search_db.csv') # 2     db['embedding'] = db.embedding.apply(eval).apply(np.array)     query = sys.argv[1]     print('') # 3     print(search(db, query))<\/code><\/pre>\n<p>Let&#8217;s analyze how the script works. The data from <code>search_db.csv<\/code> is loaded into a dataframe(1), an object-oriented representation of the table. Then embeddings from the table are converted into arrays with numbers(2) so that they can be worked with. At the end, the search function is launched with a search query string(3).<\/p>\n<p>The search function generates an embedding for the query(4), measures similarity of this embedding with each embedding from the base, and stores the similarity score in the <strong>similarities <\/strong>column(5).<\/p>\n<p>The degree of similarity is determined by a number from 0 to 1, where 1 means the maximum fit. The rows in the table are sorted by <strong>similarities<\/strong>(6).<\/p>\n<p>Finally, the first three rows are retrieved from the database and printed to the console.<\/p>\n<p>The search engine is ready, you can test it.<\/p>\n<p>For the test, run the command with the request:<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/444\/757\/0f0\/4447570f001779a8b31b56ea34ae6926.png\" width=\"1840\" height=\"1748\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/444\/757\/0f0\/4447570f001779a8b31b56ea34ae6926.png\"\/><figcaption><\/figcaption><\/figure>\n<p>Now try to enter a request in another language:<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/ff5\/bb1\/ddb\/ff5bb1ddb3e212d24e7497dc0301554a.png\" width=\"1840\" height=\"1748\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/ff5\/bb1\/ddb\/ff5bb1ddb3e212d24e7497dc0301554a.png\"\/><figcaption><\/figcaption><\/figure>\n<p>As you can see, the search is based on the meaning of the words in the query, and not just on keywords.<\/p>\n<p>The tool is not limited to just this case and one project. You can organize a more extensive search for all projects at once. This is useful if you develop several similar applications every year and would like to quickly find code snippets, or you have a lot of documentation, and a keyword search is not a good call. It all depends on the tasks and scope.<\/p>\n<p>Thanks for your attention!<\/p>\n<p>Links:<\/p>\n<ul>\n<li>\n<p><a href=\"https:\/\/openai.com\/api\/\" rel=\"noopener noreferrer nofollow\">Open AI API<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/platform.openai.com\/docs\/guides\/embeddings\" rel=\"noopener noreferrer nofollow\">Open AI Embeddings API<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/github.com\/Hokid\/simple-code-search-engine-using-open-ai-api\" rel=\"noopener noreferrer nofollow\">Example<\/a><\/p>\n<\/li>\n<\/ul>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/718014\/\"> https:\/\/habr.com\/ru\/articles\/718014\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>In this article, we will briefly review a technology that underlies ChatGPT \u2014 embeddings. Also we\u2019ll write a simple intelligent search in a codebase of a project.<\/p>\n<figure class=\"full-width\"><figcaption><\/figcaption><\/figure>\n<p>Embedding is the process of converting words or text into a set of numbers called a numeric vector. Vectors can be compared with each other to determine how similar two texts or words are in meaning.<\/p>\n<p>For example, we take two numerical vectors (embeddings) of the words &#171;donate&#187; and &#171;give&#187;. The words are different but the meaning is similar, i.e. they are interconnected, and the result of both will be giving something to someone.<\/p>\n<p>To get a similar result in the context of a code, we can convert the words into embeddings and compare their <a href=\"https:\/\/en.wikipedia.org\/wiki\/Similarity_measure\" rel=\"noopener noreferrer nofollow\">similarity measures<\/a>. The value of measure will range from 0 to 1, where 1 is the maximum similarity and 0 is no correlation. <a href=\"https:\/\/en.wikipedia.org\/wiki\/Cosine_similarity\" rel=\"noopener noreferrer nofollow\">Cosine similarity<\/a> can be taken as such a comparison function. Imagine that we have done all the necessary operations with embeddings and now analyze the result. We also compared two embeddings of the words &#171;give&#187; and &#171;red&#187;. For the words &#171;give&#187; and &#171;donate&#187; the function returned the number 0.80, and for &#171;give&#187; and &#171;red&#187; \u2014 only 0.20. Thus, we can conclude that &#171;give&#187; and &#171;donate&#187; are closer than &#171;give&#187; and &#171;red&#187;.<\/p>\n<p>In the codebase of a project, embeddings can be used to search in a code or documentation. For example, you can make an embedding (vector) out of the search query and then measure similarity of both to find relevant functions or classes.<\/p>\n<p>So, to make it you need an Open AI account and an API token. If you don&#8217;t have an account yet, you can register on the official <a href=\"https:\/\/openai.com\/api\/\" rel=\"noopener noreferrer nofollow\"><u>Open AI website<\/u><\/a>. After registering and verifying your account, go to the <a href=\"https:\/\/platform.openai.com\/account\/api-keys\" rel=\"noopener noreferrer nofollow\"><u>API Keys<\/u><\/a> profile section and generate an API token.<\/p>\n<p>To start they give $18. That was enough to make an example for this article (below) and conduct further testing of the service.<\/p>\n<p>Take a TypeScript project as a codebase. I recommend taking a small one so as not to wait for generating embeddings long. Also you can use <a href=\"https:\/\/github.com\/Hokid\/simple-code-search-engine-using-open-ai-api\" rel=\"noopener noreferrer nofollow\"><u>an example<\/u><\/a>. You also need Python 3+ versions and the library from Open AI. No worries if you don&#8217;t know any of these languages. The code examples below are simple and don&#8217;t require a deep understanding of both.<\/p>\n<p>Let&#8217;s get started. First, you need to write a code to extract various pieces of code from the project, such as functions. TypeScript provides a convenient <a href=\"https:\/\/github.com\/microsoft\/TypeScript\/wiki\/Using-the-Compiler-API\" rel=\"noopener noreferrer nofollow\">compiler API<\/a> for working with an <a href=\"https:\/\/en.wikipedia.org\/wiki\/Abstract_syntax_tree\" rel=\"noopener noreferrer nofollow\">AST<\/a>, which simplifies the task. Install <code>csv-stringify<\/code> library to generate CSV:<\/p>\n<pre><code class=\"bash\">$ npm install csv-stringify<\/code><\/pre>\n<p>Then create the file <code>code-to-csv.js<\/code> and write the extraction of information from the code:<\/p>\n<pre><code class=\"javascript\">const path = require('path'); const ts = require('typescript'); const csv = require('csv-stringify\/sync');   const cwd = process.cwd(); const configJSON = require(path.join(cwd, 'tsconfig.json')); const config = ts.parseJsonConfigFileContent(configJSON, ts.sys, cwd); const program = ts.createProgram(     config.fileNames,      config.options,      ts.createCompilerHost(config.options) ); const checker = program.getTypeChecker();  const rows = [];  const addRow = (fileName, name, code, docs = '') => rows.push({     file_name: path.relative(cwd, fileName),     name,     code,     docs });  function addFunction(fileName, node) {     const symbol = checker.getSymbolAtLocation(node.name);     if (symbol) {         const name = symbol.getName();         const docs = getDocs(symbol);         const code = node.getText();         addRow(fileName, name, code, docs);     } }  function addClass(fileName, node) {     const symbol = checker.getSymbolAtLocation(node.name);     if (symbol) {         const name = symbol.getName();         const docs = getDocs(symbol);         const code = `class ${name} {}`;         addRow(fileName, name, code, docs);         node.members.forEach(m => addClassMember(fileName, name, m));     } }  function addClassMember(fileName, className, node) {     const symbol = checker.getSymbolAtLocation(node.name);     if (symbol) {         const name = className + ':' + symbol.getName();         const docs = getDocs(symbol);         const code = node.getText();         addRow(fileName, name, code, docs);     } }  function addInterface(fileName, node) {     const symbol = checker.getSymbolAtLocation(node.name);     if (symbol) {         const name = symbol.getName();         const docs = getDocs(symbol);         const code = `interface ${name} {}`;         addRow(fileName, name, code, docs);         node.members.forEach(m => addInterfaceMember(fileName, name, m));     } }  function addInterfaceMember(fileName, interfaceName, node) {     if (!ts.isPropertySignature(node) || !ts.isMethodSignature(node)) {         return;     }     const symbol = checker.getSymbolAtLocation(node.name);     if (symbol) {         const name = interfaceName + ':' + symbol.getName();         const docs = getDocs(symbol);         const code = node.getText();         addRow(fileName, name, code, docs);     } }  function getDocs(symbol) {     return ts.displayPartsToString(symbol.getDocumentationComment(checker)); }  for (const fileName of config.fileNames) {     const sourceFile = program.getSourceFile(fileName);     const visitNode = node => {         if (ts.isFunctionDeclaration(node)) {             addFunction(fileName, node);         } else if (ts.isClassDeclaration(node)) {             addClass(fileName, node);         } else if (ts.isInterfaceDeclaration(node)) {             addInterface(fileName, node);         }         ts.forEachChild(node, visitNode);     };     ts.forEachChild(sourceFile, visitNode); }  for (const row of rows) {     row.combined = '';     if (row.docs) {         row.combined += `Code documentation: ${row.docs}; `;     }     row.combined += `Code: ${row.code}; Name: ${row.name};`; }  const output = csv.stringify(rows, {     header: true });  console.log(output);<\/code><\/pre>\n<p>The script collects all the fragments we need and prints a CSV table to the console. CSV table consists of the columns <strong>file_name, name, code, docs, combined<\/strong>.<\/p>\n<ul>\n<li>\n<p><strong>file_name<\/strong> contains the path to the file in the project,<\/p>\n<\/li>\n<li>\n<p><strong>name <\/strong>is the name of the fragment, for example, &#171;function\u2019s name&#187;,<\/p>\n<\/li>\n<li>\n<p><strong>code<\/strong> is the entity code,<\/p>\n<\/li>\n<li>\n<p><strong>docs<\/strong> is the description from the comments to the fragment,<\/p>\n<\/li>\n<li>\n<p><strong>combined <\/strong>is the addition of the contents of the <strong>code <\/strong>and <strong>docs <\/strong>columns, we are going to use this column to generate embeddings.<\/p>\n<\/li>\n<\/ul>\n<p>You don&#8217;t need to run it.<\/p>\n<p>Now to Python.<\/p>\n<p>Install the library from Open AI and utilities for working with embeddings:<\/p>\n<pre><code class=\"bash\">$ pip install openai[embeddings]<\/code><\/pre>\n<p>Create the file <code>create_search_db.py<\/code> with the following code:<\/p>\n<pre><code class=\"python\">from io import StringIO from subprocess import PIPE, run from pandas import read_csv from openai.embeddings_utils import get_embedding as _get_embedding from tenacity import wait_random_exponential, stop_after_attempt  get_embedding = _get_embedding.retry_with(wait=wait_random_exponential(min=1, max=60), stop=stop_after_attempt(10))  if __name__ == '__main__': # 1 result = run(['node', 'code-to-csv.js'], stdout=PIPE, stderr=PIPE, universal_newlines=True) if result.returncode != 0:     raise RuntimeError(result.stderr) # 2 db = read_csv(StringIO(result.stdout)) # 3 db['embedding'] = db['combined'].apply(lambda x: get_embedding(x, engine='text-embedding-ada-002')) # 4 db.to_csv(\"search_db.csv\", index=False)<\/code><\/pre>\n<p>The script runs <code>code-to-csv.js<\/code>(1), the result is loaded into a dataframe(2) and embeddings are generated for the content in the <strong>combined<\/strong>(3) column. Embeddings are written to the <strong>embedding <\/strong>column. The final table with everything needed for the search is saved to the <code>search_db.csv<\/code>(4) file.<\/p>\n<p>For the script to work, you need an API token. The <code>openai<\/code> library can automatically take the token from the environment variables, so you can write a convenient script to make it:<\/p>\n<pre><code class=\"bash\">export OPENAI_API_KEY=YourToken<\/code><\/pre>\n<p>Save it somewhere, for example, in <code>env.sh<\/code>, and run:<\/p>\n<pre><code class=\"bash\">$ source env.sh<\/code><\/pre>\n<p>Everything is ready to generate the search database.<\/p>\n<p>Run the script <code>create_search_db.py<\/code> and wait until the CSV file with the database appears. This may take a couple of minutes. After that, you can start writing a search engine.<\/p>\n<p>Create a new <code>search.py<\/code> \u200b\u200bfile and write the following:<\/p>\n<pre><code class=\"python\">import sys import numpy as np from pandas import read_csv from openai.embeddings_utils import cosine_similarity, get_embedding as _get_embedding from tenacity import  stop_after_attempt, wait_random_exponential  get_embedding = _get_embedding.retry_with(wait=wait_random_exponential(min=1, max=60), stop=stop_after_attempt(10))  def search(db, query): # 4     query_embedding = get_embedding(query, engine='text-embedding-ada-002') # 5     db['similarities'] = db.embedding.apply(lambda x: cosine_similarity(x, query_embedding))     # 6 db.sort_values('similarities', ascending=False, inplace=True)     result = db.head(3)     text = \"\"     for row in result.itertuples(index=False):         score=round(row.similarities, 3)         if type(row.docs) == str:             text += '\/**\\n * {docs}\\n *\/\\n'.format(docs='\\n * '.join(row.docs.split('\\n')))         text += '{code}\\n\\n'.format(code='\\n'.join(row.code.split('\\n')[:7]))         text += '[score={score}] {file_name}:{name}\\n'.format(score=score, file_name=row.file_name, name=row.name)         text += '-' * 70 + '\\n\\n'     return text  if __name__ == '__main__': # 1     db = read_csv('search_db.csv') # 2     db['embedding'] = db.embedding.apply(eval).apply(np.array)     query = sys.argv[1]     print('') # 3     print(search(db, query))<\/code><\/pre>\n<p>Let&#8217;s analyze how the script works. The data from <code>search_db.csv<\/code> is loaded into a dataframe(1), an object-oriented representation of the table. Then embeddings from the table are converted into arrays with numbers(2) so that they can be worked with. At the end, the search function is launched with a search query string(3).<\/p>\n<p>The search function generates an embedding for the query(4), measures similarity of this embedding with each embedding from the base, and stores the similarity score in the <strong>similarities <\/strong>column(5).<\/p>\n<p>The degree of similarity is determined by a number from 0 to 1, where 1 means the maximum fit. The<\/p>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-400370","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/400370","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=400370"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/400370\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=400370"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=400370"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=400370"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}