Abstract
The Gemini API can now do semantic searches, going beyond content generation. This means it can understand the meaning of your search and provide better results, even if your words don’t exactly match the data. This report introduces the enhanced search capabilities of the Gemini API.
Introduction
The Gemini API expands its potential beyond content generation to encompass powerful semantic search capabilities. Searching existing data is crucial in various situations. However, before the introduction of generative AI, traditional search methods relied solely on keyword matching. Recent advancements in semantic search have introduced similarity search, allowing for a more nuanced understanding of queries. Combining this with the generative power of the Gemini API can significantly enhance the search results of existing data. This report explores the possibilities of enhanced search using the Gemini API.
Usage
In this report, Google Apps Script is used.
1. Create a GAS project.
Please create a new Google Spreadsheet and open the script editor.
2. Linking Google Cloud Platform Project to Google Apps Script Project for New IDE
In this case, you can see how to do this at my repository.
Also, please enable Generative Language API at the API console.
After the above setting, the following sample script can be used.
3. Install library
In this script, in order to manage Corpora, a Google Apps Script library of CorporaApp is used. Please install the library. You can see how to install it at my repository.
The library’s project key is 1XrAybct1KUwGcFrEZ9BOd5sa0SoHeQwGhOWkDOHki9lDFAX9OHlO03y_
.
When you install this library, the following required scopes are automatically installed.
https://www.googleapis.com/auth/script.external_request
https://www.googleapis.com/auth/generative-language.retriever
4. Prepare sample data
In order to test the enhanced search using Gemini API, first, it is required to prepare sample data. In this report, my blog is used. In my blog, XML data can be obtained by https://tanaikech.github.io/index.xml. This data is stored in a document in a corpus. The search text is searched with Method: models.generateAnswer.
A. Base variables
The base variable is as follows. This function is used with the following scripts. Of course, you can modify the corpus and the document name from the following script.
function init_() {
const accessToken = ScriptApp.getOAuthToken();
const newCorpusName = {
name: "corpora/sample-1",
displayName: "sample corpus 1",
};
const newDocumentName = {
name: `${newCorpusName.name}/documents/sample-1`,
displayName: "sample document 1",
};
return { accessToken, newCorpusName, newDocumentName };
}
B. Create corpus and document
The script for creating a corpus and a document is as follows. When this script is run, a new corpus of corpora/sample-1
is created, and a new document corpora/sample-1/documents/sample-1
is created in the corpus.
function createCorpusDocument() {
const { accessToken, newCorpusName, newDocumentName } = init_();
const ca = CorporaApp.setAccessToken(accessToken);
const r = {};
const res1 = ca.getCorpora();
if (!res1.some(({ name }) => name == newCorpusName.name)) {
const res = ca.createCorpus(newCorpusName);
r.corpus = res;
}
const res2 = ca.getDocuments(newCorpusName.name);
if (!res2.some(({ name }) => name == newDocumentName.name)) {
const res = ca.createDocument(newCorpusName.name, newDocumentName);
r.document = res;
}
const keys = Object.keys(r);
if (keys.length == 0) {
console.log("Corpus and document have already been existing.");
} else {
console.log(`Created ${keys.join(" and ")}.`);
}
}
If you want to delete a document, you can also use the following script.
function deleteDocument() {
const {
accessToken,
newDocumentName: { name },
} = init_();
const ca = CorporaApp.setAccessToken(accessToken);
ca.deleteDocument(name, true);
}
C. Store data in corpus
The script for storing the blog data is as follows. When this script is run, the values retrieved from the XML data are stored in the created document as chunks.
function setChunks() {
const url = "https://tanaikech.github.io/post/index.xml"; // This is RSS of my blog (https://tanaikech.github.io/).
const { accessToken, newDocumentName } = init_();
const resourceNameOfdocument = newDocumentName.name;
const str = UrlFetchApp.fetch(url).getContentText();
const xml = XmlService.parse(str);
const root = xml.getRootElement();
const ns = root.getNamespace();
const items = root.getChild("channel", ns).getChildren("item");
const keys = ["title", "link", "pubDate", "description"];
let requests = items.map((e) => {
const obj = new Map(keys.map((k) => [k, e.getChild(k, ns).getValue()]));
const data = {
stringValue: `Title is ${obj.get("title")}. Description is as follows.${
obj.get("description").split(".")[0]
}. URL is ${obj.get("link")}.`,
};
obj.delete("description");
return {
parent: resourceNameOfdocument,
chunk: {
data,
customMetadata: [...obj].map(([key, stringValue]) => ({
key,
stringValue,
})),
},
};
});
CorporaApp.setAccessToken(accessToken).setChunks(resourceNameOfdocument, {
requests,
});
console.log("Done.");
}
When a long text is used to the chunk data, an error like At least one text exceeds the token limit by #### for the given embedding model.
occurs. Please be careful about this. I believe that this limitation will be expanded in future updates.
By this step, the preparation has been finished.
5. Test 1
In order to test the search, please run the following script.
function search() {
const text =
"Can you tell me several links showing sample scripts to upload a file to Google Drive?";
const { accessToken, newCorpusName } = init_();
const requestBody = {
contents: [{ parts: [{ text }], role: "user" }],
answerStyle: "VERBOSE",
semanticRetriever: {
source: newCorpusName.name,
query: { parts: [{ text }] },
},
};
const res =
CorporaApp.setAccessToken(accessToken).searchQueryWithGenerateAnswer(
requestBody
);
if (res.getResponseCode() == 200) {
const obj = JSON.parse(res.getContentText());
const result = obj.answer.content.parts[0].text;
console.log(result);
}
}
By this script, the following result is obtained.
Here are several links to sample scripts that show you how to upload a file to Google Drive:
* [Uploading File to Google Drive using HTML and Google Apps Script](https://tanaikech.github.io/2020/02/18/uploading-file-to-google-drive-using-html-and-google-apps-script/)
* [Uploading Local Files to Google Drive without Authorization using HTML Form](https://tanaikech.github.io/2017/11/06/uploading-local-files-to-google-drive-without-authorization-using-html-form/)
* [Uploading File to Google Drive from External HTML without Authorization](https://tanaikech.github.io/2020/08/13/uploading-file-to-google-drive-from-external-html-without-authorization/)
* [Safe-Uploading for Google Drive by HTML in External Server using Google Apps Script](https://tanaikech.github.io/2020/12/29/safe-uploading-for-google-drive-by-html-in-external-server-using-google-apps-script/)
Those results are from my blog. In this result, the URLs are included. The response values include the chunk names. So, from the above setChunks
function, you can also directly retrieve the URLs from the chunks using the chunk names.
As additional information. for example, when the corpus without enough data is used, the following result is obtained.
Here are several links showing sample scripts to upload a file to Google Drive:
* [Upload a File to Google Drive](https://developers.google.com/drive/api/v3/quickstart/python)
* [Uploading Files to Google Drive with Python](https://www.twilio.com/blog/2017/04/uploading-files-to-google-drive-with-python.html)
* [How to Upload Files to Google Drive Using Python](https://www.datacamp.com/courses/how-to-upload-files-to-google-drive-using-python)
* [Upload Files to Google Drive Using Python - Google Cloud](https://cloud.google.com/storage/docs/reference/libraries)
In this case, the broken URLs are included.
When the empty corpus is used, the following error occurs.
Error: {
"error": {
"code": 400,
"message": "No passages found from provided grounding config.",
"status": "FAILED_PRECONDITION"
}
}
6. Test 2
As another test case, the enhanced search can be also used for Google Workspace Updates. Google Workspace Updates can obtain the XML data from http://workspaceupdates.googleblog.com/atom.xml. The scripts are as follows.
Before you use this script, please run the functions deleteDocument
and createCorpusDocument
in order. By this, the document is reset. Of course, you can add the new data to the existing document. But, in this case, when the search is run, the data of both Google Workspace Updates and my blog. Please be careful about this.
This script stores the data from Google Workspace Updates to a document in a corpus.
function setChunks() {
const url = "http://workspaceupdates.googleblog.com/atom.xml"; // This is RSS of https://workspaceupdates.googleblog.com/
const { accessToken, newDocumentName } = init_();
const resourceNameOfdocument = newDocumentName.name;
const str = UrlFetchApp.fetch(url).getContentText();
const xml = XmlService.parse(str);
const root = xml.getRootElement();
const ns = root.getNamespace();
const entries = root.getChildren("entry", ns);
const keys_single = ["title", "published", "content"];
const keys_multiple = ["category", "link"];
let requests = entries.map((e) => {
const ar1 = keys_single.map((k) => [k, e.getChild(k, ns).getValue()]);
const ar2 = keys_multiple.map((k) => {
if (k == "category") {
return [
k,
JSON.stringify(
e.getChildren(k, ns).map((f) => f.getAttribute("term").getValue())
),
];
} else if (k == "link") {
return [
k,
e
.getChildren(k, ns)
.find((f) => f.getAttribute("rel").getValue() == "alternate")
.getAttribute("href")
.getValue(),
];
}
return [k, null];
});
const obj = new Map([...ar1, ...ar2]);
const data = {
stringValue: `Title is ${obj.get("title")}. Description is as follows.${
obj.get("content").split(".")[0]
}. URL is ${obj.get("link")}., Categirues are ${obj.get("category")}.`,
};
obj.delete("content");
return {
parent: resourceNameOfdocument,
chunk: {
data,
customMetadata: [...obj].map(([key, stringValue]) => ({
key,
stringValue,
})),
},
};
});
CorporaApp.setAccessToken(accessToken).setChunks(resourceNameOfdocument, {
requests,
});
console.log("Done.");
}
This script searches from a document in a corpus.
function search() {
const text = "Can you tell me several links related to Google Spreadsheet?";
const { accessToken, newCorpusName } = init_();
const requestBody = {
contents: [{ parts: [{ text }], role: "user" }],
answerStyle: "VERBOSE",
semanticRetriever: {
source: newCorpusName.name,
query: { parts: [{ text }] },
},
};
const ca = CorporaApp.setAccessToken(accessToken);
const res = ca.searchQueryWithGenerateAnswer(requestBody);
if (res.getResponseCode() == 200) {
const obj = JSON.parse(res.getContentText());
const result = obj.answer.content.parts[0].text;
console.log(result);
}
}
When this script is run, the following result is obtained.
Here are several links related to Google Spreadsheet:
* [New ways to annotate Google Docs](http://workspaceupdates.googleblog.com/2024/02/new-ways-to-annotate-google-docs.html)
* [Import and convert sensitive Excel files into client-side encrypted Google Sheets](http://workspaceupdates.googleblog.com/2024/02/import-convert-excel-files-into-client-side-encrypted-google-sheets-now-generally-available.html)
* [Sort, filter and manage comments faster in Google Docs, Sheets and Slides](http://workspaceupdates.googleblog.com/2024/02/manage-comments-faster-google-docs-sheets-slides.html)
* [Google Workspace Updates Weekly Recap - March 1, 2024](http://workspaceupdates.googleblog.com/2024/02/release-notes-03-01-2024.html)