Skip to content

Commit a72f52a

Browse files
committed
[Release] Docs Agent version 0.2.1
What's changed: - Integrate the Semantic Retrieval API to Docs Agent. - Turn `chroma.py` and `palm.py` into modules. - Refactor and enhance the pre-processing scripts. - Introduce `files_to_plain_text.py`, which will replace `markdown_to_plain_text.py` - Add a new doc: `doc/whats-new.md`
1 parent 6e66b83 commit a72f52a

File tree

22 files changed

+3361
-1218
lines changed

22 files changed

+3361
-1218
lines changed

demos/palm/python/docs-agent/README.md

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,11 @@ The following list summarizes the tasks and features of the Docs Agent sample ap
130130
Apps Script to convert Google Docs, PDF, and Gmail into Markdown files, which then
131131
can be used as input datasets for Docs Agent. For more information, see the
132132
[`README`][apps-script-readme] file in the `apps_script` directory.
133+
- **Use Gemini's Semantic Retrieval API and AQA model**: You can set up Docs Agent
134+
to use Gemini's [Semantic Retrieval API][semantic-api] and [AQA model][aqa-model].
135+
This API enables you to upload your source documents online, instead of using
136+
a local vector database, and use Gemini's `aqa` model that is specifically
137+
created for question-answering.
133138

134139
## Flow of events
135140

@@ -314,6 +319,41 @@ use these Markdown files as additional input sources for Docs Agent. For more in
314319

315320
**Figure 7**. Docs Agent's pre-processing flow for various doc types.
316321

322+
### Using the Semantic Retrieval API and AQA model
323+
324+
Docs Agent provides options to use Gemini's [Semantic Retrieval API][semantic-api] for storing text
325+
chunks in Google Cloud's online storage (and using this online storage for context retrieval),
326+
in combination with using the [AQA model][aqa-model] for question-answering.
327+
328+
To use the Semantic Retrieval API, update the `config.yaml` file to include the following settings:
329+
330+
```
331+
db_type: "ONLINE_STORAGE"
332+
is_aqa_used: "YES"
333+
```
334+
335+
The setup above uses both the Semantic Retrieval API to store text chunks online and the AQA model.
336+
337+
**Note**: At the moment, when `db_type` is set to `ONLINE_STORAGE`, running the
338+
`populate_vector_database.py` script will also create and popluate a local vector database using
339+
Chroma as well as creating and populating a corpus online using the Semantic Retrieval API.
340+
341+
However, if you want to use only the AQA model for question-answering, but without creating a
342+
corpus online, update the `config.yaml` file to include the following settings instead:
343+
344+
```
345+
db_type: "LOCAL_DB"
346+
is_aqa_used: "YES"
347+
```
348+
349+
The setup above uses the AQA model with your local Chroma vector database. (For more information,
350+
see the [More Options: AQA Using Inline Passages][inline-passages] section on the
351+
_Semantic Retriever Quickstart_ page.)
352+
353+
**Note**: To use the Semantic Retrieval API, you need to complete the OAuth setup for your Google
354+
Cloud project from your host machine. For detailed instructions, see the
355+
[Authentication with OAuth quickstart][oauth-quickstart] page.
356+
317357
## Issues identified
318358

319359
The following issues have been identified and need to be worked on:
@@ -497,12 +537,15 @@ To convert Markdown files to plain text files:
497537
6. Run the Python script:
498538

499539
```
500-
python3 scripts/markdown_to_plain_text.py
540+
python3 scripts/files_to_plain_text.py
501541
```
502542

503543
For a large number of Markdown files, it may take a few minutes to process
504544
Markdown files.
505545

546+
**Important**: The `markdown_to_plain_text.py` script is being deprecated in
547+
favor of the [`files_to_plain_text.py`][files-to-plain-text] script.
548+
506549
### 2. Populate a new vector database
507550

508551
Once you have plain text files processed and stored in the `output_path` directory,
@@ -675,3 +718,7 @@ Meggin Kearney (`@Meggin`), and Kyo Lee (`@kyolee415`).
675718
[scripts-readme]: ./scripts/README.md
676719
[config-yaml]: config.yaml
677720
[gen-ai-docs-repo]: https://github.com/google/generative-ai-docs
721+
[semantic-api]: https://ai.google.dev/docs/semantic_retriever
722+
[aqa-model]: https://ai.google.dev/models/gemini#model_variations
723+
[oauth-quickstart]: https://ai.google.dev/docs/oauth_quickstart
724+
[inline-passages]: https://ai.google.dev/docs/semantic_retriever#more_options_aqa_using_inline_passages
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
#
2+
# Copyright 2023 Google LLC
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
#
16+
17+
"""AQA module for using the Semantic Retrieval API"""
18+
19+
import google.ai.generativelanguage as glm
20+
21+
22+
class AQA:
23+
def __init__(self):
24+
# Initialize variables for the Semantic Retrieval API
25+
self.generative_service_client = glm.GenerativeServiceClient()
26+
self.retriever_service_client = glm.RetrieverServiceClient()
27+
self.permission_service_client = glm.PermissionServiceClient()
28+
29+
def list_existing_corpora(self):
30+
corpora_list = glm.ListCorporaRequest()
31+
response = self.retriever_service_client.list_corpora(corpora_list)
32+
print("List of existing corpora:\n")
33+
print(response)
34+
35+
def delete_a_corpus(self, corpus_name):
36+
try:
37+
delete = glm.DeleteCorpusRequest(name=corpus_name)
38+
delete = glm.DeleteCorpusRequest(name=corpus_name, force=True)
39+
delete_corpus_response = self.retriever_service_client.delete_corpus(delete)
40+
print("Successfully deleted corpus: " + corpus_name)
41+
except:
42+
print("Failed to delete the corpus: " + corpus_name)
43+
44+
def create_a_new_corpus(self, corpus_display, corpus_name):
45+
try:
46+
# Get an existing corpus
47+
get_corpus_request = glm.GetCorpusRequest(name=corpus_name)
48+
get_corpus_response = self.retriever_service_client.get_corpus(
49+
get_corpus_request
50+
)
51+
print()
52+
print(f"{corpus_name} exists.\n{get_corpus_response}")
53+
except:
54+
# Create a new corpus
55+
corpus = glm.Corpus(display_name=corpus_display, name=corpus_name)
56+
create_corpus_request = glm.CreateCorpusRequest(corpus=corpus)
57+
create_corpus_response = self.retriever_service_client.create_corpus(
58+
create_corpus_request
59+
)
60+
print()
61+
print(f"Created a new corpus {corpus_name}.\n{create_corpus_response}")
62+
63+
def create_a_doc(self, corpus_name, page_title, page_url):
64+
document_resource_name = ""
65+
try:
66+
# Create a new document with a custom display name.
67+
example_document = glm.Document(display_name=page_title)
68+
# Add metadata.
69+
document_metadata = [glm.CustomMetadata(key="url", string_value=page_url)]
70+
example_document.custom_metadata.extend(document_metadata)
71+
# Make the request
72+
create_document_request = glm.CreateDocumentRequest(
73+
parent=corpus_name, document=example_document
74+
)
75+
create_document_response = self.retriever_service_client.create_document(
76+
create_document_request
77+
)
78+
# Set the `document_resource_name` for subsequent sections.
79+
document_resource_name = create_document_response.name
80+
except:
81+
get_document_request = glm.GetDocumentRequest(name=document_resource_name)
82+
# Make the request
83+
get_document_response = self.retriever_service_client.get_document(
84+
get_document_request
85+
)
86+
document_resource_name = get_document_response.name
87+
return document_resource_name
88+
89+
def create_a_chunk(self, doc_name, text):
90+
response = ""
91+
try:
92+
chunk = glm.Chunk(data={"string_value": text})
93+
create_chunk_requests = []
94+
create_chunk_requests.append(
95+
glm.CreateChunkRequest(parent=doc_name, chunk=chunk)
96+
)
97+
# Make the request
98+
request = glm.BatchCreateChunksRequest(
99+
parent=doc_name, requests=create_chunk_requests
100+
)
101+
response = self.retriever_service_client.batch_create_chunks(request)
102+
# Print the response
103+
print("Created a new text chunk:\n")
104+
print(response)
105+
except:
106+
print("[ERROR] Failed to create a text chunk for:\n\n" + text)
107+
# Failed possibly because the size of the text is too large.
108+
# Quick fix: Split the text into 2 chunks
109+
lines = text.splitlines()
110+
text_01 = ""
111+
text_02 = ""
112+
text_size = len(lines)
113+
half_size = int(text_size / 2)
114+
i = 0
115+
for line in lines:
116+
if i < half_size:
117+
text_01 += line + "\n"
118+
else:
119+
text_02 += line + "\n"
120+
i += 1
121+
self.create_a_chunk(doc_name, text_01)
122+
self.create_a_chunk(doc_name, text_02)
123+
return response
124+
125+
def create_a_doc_chunk(self, corpus_name, page_title, page_url, text):
126+
doc_name = self.create_a_doc(corpus_name, page_title, page_url)
127+
return self.create_a_chunk(doc_name, text)

demos/palm/python/docs-agent/chatbot/chatui.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
import uuid
3636
from scripts import read_config
3737

38-
from chroma import Format
38+
from modules.chroma import Format
3939
from docs_agent import DocsAgent
4040

4141

@@ -146,7 +146,9 @@ def ask_model(question):
146146
query_result = docs_agent.query_vector_store(question)
147147
context = query_result.fetch_formatted(Format.CONTEXT)
148148
context_with_instruction = docs_agent.add_instruction_to_context(context)
149-
if "gemini" in docs_agent.get_language_model_name():
149+
if docs_agent.check_if_aqa_is_used():
150+
response = docs_agent.ask_aqa_model(question)
151+
elif "gemini" in docs_agent.get_language_model_name():
150152
response = docs_agent.ask_content_model_with_context(
151153
context_with_instruction, question
152154
)
@@ -196,11 +198,20 @@ def ask_model(question):
196198

197199
### PREPARE OTHER ELEMENTS NEEDED BY UI.
198200
# - Create a uuid for this request.
201+
# - (Optional) Prepare the AQA model's JSON response into HTML for rendering.
199202
# - Convert the context returned from the database into HTML for rendering.
200203
# - Convert the first response from the model into HTML for rendering.
201204
# - Convert the fact-check response from the model into HTML for rendering.
202205
# - A workaround to get the server's URL to work with the rewrite and like features.
203206
new_uuid = uuid.uuid1()
207+
aqa_response_in_html = ""
208+
if docs_agent.check_if_aqa_is_used():
209+
aqa_response_json = docs_agent.get_saved_aqa_response_json()
210+
if aqa_response_json:
211+
aqa_response_in_html = "Grounding attributions:<br/><br/>"
212+
aqa_response_in_html += str(aqa_response_json.answer.grounding_attributions)
213+
aqa_response_in_html += "<br/><br/>Answerable probability: "
214+
aqa_response_in_html += str(aqa_response_json.answerable_probability)
204215
context_in_html = markdown.markdown(context, extensions=["fenced_code"])
205216
response_in_html = markdown.markdown(response, extensions=["fenced_code"])
206217
fact_checked_response_in_html = markdown.markdown(fact_checked_response)
@@ -222,6 +233,7 @@ def ask_model(question):
222233
product=product,
223234
server_url=server_url,
224235
uuid=new_uuid,
236+
aqa_response_in_html=aqa_response_in_html,
225237
)
226238

227239

demos/palm/python/docs-agent/chatbot/static/css/style.css

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,3 +327,13 @@ code {
327327
margin-bottom: 0;
328328
}
329329

330+
.accordion #aqa-label {
331+
padding: 2px;
332+
background: #add1e8;
333+
border-radius: 0px;
334+
}
335+
336+
.accordion .content #aqa-content{
337+
background: #d4d4d4;
338+
}
339+

demos/palm/python/docs-agent/chatbot/static/javascript/app.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,24 @@ let loadingDiv = document.getElementById('loading-div');
2020

2121
if (askButton != null){
2222
askButton.addEventListener('click',function (){
23-
console.log("here");
2423
if (loadingDiv.classList.contains("hidden")){
2524
loadingDiv.classList.remove("hidden");
26-
console.log("there");
2725
}
2826
});
2927
}
3028

29+
// Display the "aqa-box" div only if the aqa json response is included.
30+
let aqaContent = document.getElementById('aqa-content');
31+
let aqaBox = document.getElementById('aqa-box');
32+
33+
if (aqaContent != null){
34+
if (aqaContent.innerText.trim() != ""){
35+
if (aqaBox.classList.contains("hidden")){
36+
aqaBox.classList.remove("hidden");
37+
}
38+
}
39+
}
40+
3141
// Toggle the hidden class on the `rewrite-box` div.
3242
let rewriteButton = document.getElementById('rewrite-button');
3343

@@ -144,3 +154,4 @@ if (rewriteSubmitButton != null){
144154
rewriteSubmitButton.classList.add("disable");
145155
}, false);
146156
}
157+

demos/palm/python/docs-agent/chatbot/templates/chatui/result.html

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,18 @@ <h2 class="handle">
2727
<h4>Reference:</h4>
2828
{{ clickable_urls | safe }}
2929
</span>
30+
<br/>
31+
<div class="hidden" id="aqa-box">
32+
<section class="accordion">
33+
<input type="checkbox" name="collapse" id="handle2">
34+
<h2 class="handle">
35+
<label for="handle2" id="aqa-label">AQA response JSON</label>
36+
</h2>
37+
<div class="content" id="aqa-content">
38+
{{ aqa_response_in_html | safe }}
39+
</div>
40+
</section>
41+
</div>
3042
</div>
3143
</section>
3244
<div>

demos/palm/python/docs-agent/config.yaml

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,22 @@ embedding_model: "models/embedding-001"
4343
# the Chroma vector database.
4444
#
4545
# log_level: The verbosity level of logs printed on the terminal
46-
# by the chatbot app: NORMAL or VERBOSE
46+
# by the chatbot app: NORMAL, VERBOSE, or DEBUG
47+
#
48+
# db_type: Use a local vector database or an online storage
49+
# using the Semantic Retrieval API:
50+
# LOCAL_DB or ONLINE_STORAGE
51+
#
52+
# is_aqa_used: Use Gemini's AQA model for question-answering:
53+
# NO or YES
4754
#
4855
product_name: "My product"
4956
output_path: "data/plain_docs"
5057
vector_db_dir: "vector_stores/chroma"
5158
collection_name: "docs_collection"
5259
log_level: "NORMAL"
60+
db_type: "LOCAL_DB"
61+
is_aqa_used: "NO"
5362

5463

5564
### Documentation sources
@@ -96,6 +105,6 @@ about which part of the text they should consider fact-checking? (Please keep
96105
your response concise, focus on only one important item, but DO NOT USE BOLD
97106
TEXT IN YOUR RESPONSE.)"
98107

99-
model_error_message: "PaLM is not able to answer this question at the moment.
108+
model_error_message: "Gemini is not able to answer this question at the moment.
100109
Rephrase the question and try asking again."
101110

0 commit comments

Comments
 (0)