Skip to content

Commit 3445de7

Browse files
authored
Merge pull request #61 from GPS-Solutions/resolve_merge_conflicts_from_main_to_dev2
Updating dev branch with latest commits into css main
2 parents ee4270f + 9ab52cc commit 3445de7

File tree

20 files changed

+265
-61
lines changed

20 files changed

+265
-61
lines changed

CHANGELOG.md

+22
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,27 @@
11
# Releases
22

3+
## v0.4.0
4+
5+
New features:
6+
7+
### GENIE ("GenAI for Enterprise" platform on GCP)
8+
- Multimodal RAG for images using multi-modal embeddings for search
9+
- Chat file upload in both backend and React app. Upload files or specify URLs to pass to model.
10+
- RBAC for model access. See README for LLM Service for docs on how to manage access to models for users.
11+
- Added user role management in React frontend, used for RBAC for models and query engines
12+
- Added documentation on authentication in the platform in docs/AUTH.md
13+
- Updated to recent releases of FastAPI (0.112.2) and associated libraries
14+
- Use L4 GPUs with Truss models
15+
- Chunk size and chunking class are now query engine build params
16+
- Add chunk size to React Query Admin engine build form
17+
- Switched to using llama_index.core.node_parser.SentenceSplitter for chunking by default
18+
- Updated default query generation model to Gemini Flash 1.5 - was set to Palm2
19+
- Added Microsoft login in React frontend
20+
21+
### Fixes
22+
- Fixed download of PDFs from scraped sites
23+
24+
325
## v0.3.2
426

527
New features:

components/frontend_react/README.md

+40-8
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
# GENIE React Frontend
22
This component is a REACT based frontend UI for GENIE.
33

4-
# Install
4+
## Install
55

66
You must deploy GENIE first before deploying this frontend app. See [the install guide for GENIE.](../../INSTALL.md)
77

8-
## Prerequisites
8+
### Prerequisites
99

1010
The following prerequisites must be installed to deploy the React frontend app:
1111

@@ -15,14 +15,39 @@ The following prerequisites must be installed to deploy the React frontend app:
1515
| `npm` | `>= 10.2` | [Mac](https://nodejs.org/en/download/)[Windows](https://nodejs.org/en/download/)[Linux](https://nodejs.org/en/download/package-manager/) |
1616
| `firebase CLI` | `>= v13.1.0` | `utils/install_firebase.sh v13.1.0` |
1717

18-
## Jump host installation
18+
### Jump host installation
1919
If you are installing from the jump host, install npm and the firebase CLI using the links above.
2020

21-
# Build and deploy the app
21+
## Build and deploy the app
2222

23-
## Add Google identity provider
23+
### Add Google identity provider
24+
25+
Add Google as an identity provider. You can do this in the [GCP console](https://console.cloud.google.com/customer-identity/providers) or in the [Firebase console](https://console.firebase.google.com/). In firebase, navigate to Build > Authentication > Sign-in Method. Refer to authentication component [README.md](https://github.com/GPS-Solutions/core-solution-services/blob/main/components/authentication/README.md) for more information.
26+
27+
### Authorizing User Domains during Sign-in
28+
The frontend_react component provides an initial check for authorizing user domains during a user's sign-in process with Google. Thus, you'll need to change the `authProviders` and `authorizedDomains` attribute within `AppConfig` with your user's or client's organizational domain.
29+
30+
Under the `frontend_react/src/src/utils/AppConfig.ts` file:
31+
32+
```
33+
export const AppConfig: IAppConfig = {
34+
siteName: "GenAI for Public Sector",
35+
locale: "en",
36+
logoPath: "/assets/images/rit-logo.png",
37+
simpleLogoPath: "/assets/images/rit-brain.png",
38+
imagesPath: "/assets/images",
39+
theme: "light",
40+
authProviders: ["google", "microsoft", "facebook", "password"],
41+
authorizedDomains: [/@google\.com$/i, /@gmail\.com$/i, /@\w+\.altostrat\.com$/i],
42+
}
43+
```
44+
45+
> Add or Change the `authProviders` and `authorizedDomains` to your respective input.
46+
47+
>**NOTE:** The `authorizedDomain` attributes are in reg expressions. (i.e "/@gmail\.com$/i")
48+
49+
> In addition to this frontend configuration, you'll need to ensure the [Google Cloud Identity](https://console.cloud.google.com/customer-identity/providers) has added the providers on Google Cloud's backend. Each provider (e.g Microsoft, Facebook) will have require an authentication client on the provider-side that Google Cloud refers to via `App ID` and `App Secret` to direct authentication. Ensure Authorized Redirect URIs are set on the authentication provider side. See provider's documentation for more info.
2450
25-
Add Google as an identity provider. You can do this in the [GCP console](https://console.cloud.google.com/customer-identity/providers) or in the [Firebase console](https://console.firebase.google.com/). In firebase, navigate to Build > Authentication > Sign-in Method.
2651

2752
## Install dependencies
2853
Execute all commands below from the `components/frontend_react/webapp` directory. You only need to install dependencies once, unless you update the app.
@@ -59,14 +84,22 @@ You should build the app on first deploy, and every time you make updates to the
5984
npm run build
6085
```
6186

62-
6387
## Deploy with firebase
6488
Deploy the app to firebase hosting with the following command:
6589

6690
```bash
6791
firebase deploy --only hosting
6892
```
6993

94+
### Authorizing Redirect URIs (OAuth 2.0 Authentication)
95+
In the Google Cloud Console -> APIs & Services -> [Credentials](https://console.cloud.google.com/apis/credentials):
96+
- Click on your default Web Client(auto-created by Google Service).
97+
- Under Authorized redirect URIs, add the following with your domain name:
98+
- `https://<your-domain-name>.web.app/__/auth/handler`
99+
100+
>This allows your backend to authorize your frontend web app in requesting an OAuth 2.0 authentication.Without this authorized redirect URIs, you will receive an unauthorized error.
101+
102+
70103
# Development
71104

72105
## Run a local dev server
@@ -75,4 +108,3 @@ This command will start a local instance of the app for development.
75108
```bash
76109
npm run dev
77110
```
78-
Loading

components/frontend_react/webapp/src/components/forms/QueryEngineForm.tsx

+9-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { Link } from "react-router-dom"
1919
import { IFormValidationData, IFormVariable } from "@/utils/types"
2020
import { IQueryEngine } from "@/utils/models"
2121
import { formValidationSchema, initialFormikValues } from "@/utils/forms"
22+
import * as yup from "yup"
2223

2324
interface QueryEngineFormProps {
2425
queryEngine: IQueryEngine | null
@@ -78,13 +79,20 @@ const QueryEngineForm: React.FunctionComponent<QueryEngineFormProps> = ({
7879

7980
const initialValues = Object.assign({}, defaultValues, qEngineInitialFormat)
8081

82+
const validationSettings = {
83+
name: yup
84+
.string()
85+
.max(32, "Query Engine names must be less than or equal to 32 chars")
86+
.matches("^[a-zA-Z0-9][\w\s-]*[a-zA-Z0-9]$", "Invalid query engine name. May contain alphanumerics, dashes or spaces.")
87+
}
8188
const formValidationData: IFormValidationData =
82-
formValidationSchema(currentVarsData)
89+
formValidationSchema(currentVarsData, validationSettings)
8390

8491
const formik = useFormik({
8592
initialValues: initialValues,
8693
enableReinitialize: true,
8794
validateOnMount: true,
95+
validateOnChange: true,
8896
validationSchema: formValidationData,
8997
onSubmit: async (values) => {
9098
await handleSubmit(values)

components/frontend_react/webapp/src/components/query/QueryWindow.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ const QueryWindow: React.FC<QueryWindowProps> = ({ onSubmit, messages, activeJob
6262
)
6363
} else if (message.AIReferences) {
6464
return (
65-
<div className="ml-12 mb-2">
65+
<div key={index++} className="ml-12 mb-2">
6666
<Expander title={"References"}>
6767
<References references={message.AIReferences} />
6868
</Expander>

components/frontend_react/webapp/src/routes/QueryEngineDetail.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
// limitations under the License.
1414

1515
import QueryEngineForm from "@/components/forms/QueryEngineForm"
16+
import { QUERY_ENGINE_FORM_DATA } from "@/utils/data"
1617
import Header from "@/components/typography/Header"
1718
import { fetchEngine, fetchAllEngineJobs } from "@/utils/api"
1819
import Loading from "@/navigation/Loading"

components/frontend_react/webapp/src/utils/api.ts

+1
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ export const createQueryEngine =
178178
"agents": queryEngine.agents,
179179
"associated_engines": queryEngine.child_engines,
180180
"manifest_url": queryEngine.manifest_url,
181+
"chunk_size": queryEngine.chunk_size,
181182
"is_multimodal": queryEngine.is_multimodal ? "True" : "False",
182183
}
183184
}

components/frontend_react/webapp/src/utils/data.ts

+12-1
Original file line numberDiff line numberDiff line change
@@ -162,12 +162,23 @@ export const QUERY_ENGINE_FORM_DATA: IFormVariable[] = [
162162
display: "Depth Limit",
163163
type: "select",
164164
description: "The depth to crawl for web data sources.",
165-
options: [0, 1, 2, 3, 4],
165+
options: [0, 1, 2, 3, 4],
166166
default: 0,
167167
required: false,
168168
group: "queryengine",
169169
order: 4,
170170
},
171+
{
172+
name: "chunk_size",
173+
display: "Chunk Size",
174+
type: "select",
175+
description: "Chunking size for RAG. Smaller is better for accuracy but makes builds take significantly longer.",
176+
options: [50, 100, 200, 300, 400, 500],
177+
default: "500",
178+
required: false,
179+
group: "queryengine",
180+
order: 4,
181+
},
171182
{
172183
name: "description",
173184
display: "Description",

components/frontend_react/webapp/src/utils/forms.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,9 @@ export const downloadFile = async (
8787
})
8888
}
8989

90-
export const formValidationSchema = (variableList: IFormVariable[]) => {
91-
let formValidationData: IFormValidationData = {}
92-
90+
export const formValidationSchema = (variableList: IFormVariable[], formValidationSettings: IFormValidationData = {}) => {
91+
let formValidationData: IFormValidationData = formValidationSettings
92+
9393
variableList.forEach((variable) => {
9494
variable.required
9595
? (formValidationData[variable.name] =

components/frontend_react/webapp/src/utils/types.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,8 @@ export type QueryEngine = {
200200
[key: string]: any
201201
} | null
202202
depth_limit: number | null
203-
agents: string[] | null
203+
chunk_size: number | null
204+
agents: string[] | null
204205
child_engines: string[] | null
205206
is_multimodal: boolean | null
206207
}

components/llm_service/README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,8 @@ curl --location "$BASE_URL/llm-service/api/v1/query/engine" \
239239
--header "Authorization: Bearer $ID_TOKEN" \
240240
--data "{
241241
\"doc_url\": \"gs://$PROJECT_ID-llm-docs\",
242-
\"query_engine\": \"$QUERY_ENGINE_NAME\"
242+
\"query_engine\": \"$QUERY_ENGINE_NAME\",
243+
\"description\": "test"
243244
}"
244245
```
245246

components/llm_service/src/config/config.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ def get_model_config() -> ModelConfig:
125125
DEFAULT_LLM_TYPE = VERTEX_LLM_TYPE_CHAT
126126
DEFAULT_CHAT_LLM_TYPE = VERTEX_LLM_TYPE_CHAT
127127
DEFAULT_MULTIMODAL_LLM_TYPE = VERTEX_LLM_TYPE_GEMINI_FLASH
128-
DEFAULT_QUERY_CHAT_MODEL = VERTEX_LLM_TYPE_BISON_CHAT
128+
DEFAULT_QUERY_CHAT_MODEL = VERTEX_LLM_TYPE_GEMINI_FLASH
129129
DEFAULT_QUERY_EMBEDDING_MODEL = VERTEX_LLM_TYPE_GECKO_EMBEDDING
130130
DEFAULT_QUERY_MULTIMODAL_EMBEDDING_MODEL = VERTEX_LLM_TYPE_GECKO_EMBEDDING_VISION
131131

components/llm_service/src/services/embeddings.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
# token limit across all chunks of 20000, and with 250 chunks we often
4444
# exceeded the 20K limit.
4545
if REGION == "us-central1":
46-
ITEMS_PER_REQUEST = 50
46+
ITEMS_PER_REQUEST = 20
4747
else:
4848
ITEMS_PER_REQUEST = 5
4949

components/llm_service/src/services/query/data_source.py

+43-26
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@
3535
from utils.errors import NoDocumentsIndexedException
3636
from utils import text_helper, gcs_helper
3737
from llama_index.core import SimpleDirectoryReader
38-
from llama_index.core.node_parser import SentenceWindowNodeParser
38+
from llama_index.core.node_parser import (SentenceSplitter,
39+
SentenceWindowNodeParser)
3940
from llama_index.core import Document
4041

4142
# pylint: disable=broad-exception-caught
@@ -50,6 +51,13 @@
5051
# itself be parsed
5152
GENIE_FOLDER_MARKER = "_genie_"
5253

54+
# default chunk size for doc chunks
55+
DEFAULT_CHUNK_SIZE = 250
56+
57+
# datasource param keys
58+
CHUNKING_CLASS_PARAM = "chunking_class"
59+
CHUNK_SIZE_PARAM = "chuck_size"
60+
5361
class DataSourceFile():
5462
""" object storing meta data about a data source file """
5563
def __init__(self,
@@ -85,16 +93,29 @@ class DataSource:
8593
Super class for query data sources. Also implements GCS DataSource.
8694
"""
8795

88-
def __init__(self, storage_client):
96+
def __init__(self, storage_client, params=None):
8997
self.storage_client = storage_client
9098
self.docs_not_processed = []
91-
# use llama index sentence window parser
92-
self.doc_parser = SentenceWindowNodeParser.from_defaults(
93-
window_size=CHUNK_SENTENCE_PADDING,
94-
include_metadata=True,
95-
window_metadata_key="window_text",
96-
original_text_metadata_key="text",
97-
)
99+
self.params = params or {}
100+
101+
# set chunk size
102+
if CHUNK_SIZE_PARAM in self.params:
103+
self.chunk_size = int(self.params[CHUNK_SIZE_PARAM])
104+
else:
105+
self.chunk_size = DEFAULT_CHUNK_SIZE
106+
107+
# use llama index sentence splitter for chunking
108+
if CHUNKING_CLASS_PARAM in self.params \
109+
and self.params[CHUNKING_CLASS_PARAM] == "SentenceWindowNodeParser":
110+
self.doc_parser = SentenceWindowNodeParser.from_defaults(
111+
window_size=CHUNK_SENTENCE_PADDING,
112+
include_metadata=True,
113+
window_metadata_key="window_text",
114+
original_text_metadata_key="text",
115+
)
116+
else:
117+
self.doc_parser = SentenceSplitter(chunk_size=self.chunk_size)
118+
98119

99120
@classmethod
100121
def downloads_bucket_name(cls, q_engine_name: str) -> str:
@@ -194,12 +215,9 @@ def chunk_document(self, doc_name: str, doc_url: str,
194215
doc_url: remote url of document
195216
doc_filepath: local file path of document
196217
Returns:
197-
tuple of
198-
list of text chunks or None if the document could not be processed
199-
list of embedding chunks or None
218+
list of text chunks or None if the document could not be processed
200219
"""
201220

202-
embed_chunks = None
203221
text_chunks = None
204222

205223
Logger.info(f"generating index data for {doc_name}")
@@ -219,28 +237,30 @@ def chunk_document(self, doc_name: str, doc_url: str,
219237
if doc_text_list is not None:
220238
# clean text of escape and other unprintable chars
221239
doc_text_list = [self.clean_text(x) for x in doc_text_list]
240+
222241
# combine text from all pages to try to avoid small chunks
223242
# when there is just title text on a page, for example
224243
doc_text = "\n".join(doc_text_list)
244+
225245
# llama-index base class that is used by all parsers
226246
doc = Document(text=doc_text)
247+
227248
# a node = a chunk of a page
228249
chunks = self.doc_parser.get_nodes_from_documents([doc])
250+
229251
# remove any empty chunks
230-
chunks = [c for c in chunks if c.metadata["text"].strip() != ""]
231-
# this is a sentence parser with overlap --
232-
# each text chunk will include the specified
233-
# number of sentences before and after the current sentence
234-
embed_chunks = [c.metadata["text"] for c in chunks]
235-
text_chunks = [c.metadata["window_text"] for c in chunks]
252+
chunks = [c for c in chunks if c.text.strip() != ""]
253+
254+
# get text chunks
255+
text_chunks = [c.text for c in chunks]
236256

237257
if all(element == "" for element in text_chunks):
238258
Logger.warning(f"All extracted pages from {doc_name} are empty.")
239259
self.docs_not_processed.append(doc_url)
240260
else:
241261
Logger.info(f"generated {len(text_chunks)} text chunks for {doc_name}")
242262

243-
return text_chunks, embed_chunks
263+
return text_chunks
244264

245265
def chunk_document_multimodal(self,
246266
doc_name: str,
@@ -385,19 +405,16 @@ def extract_contextual_text(self, doc_name: str, doc_filepath: str, \
385405
Returns:
386406
str containing the contextual_text of a multimodal doc
387407
"""
388-
#chunk_document returns 2 outputs, text_chunks and contextual_text.
389-
#Each element of text_chunks has the same info as its corresponding
390-
#element in contextual_text, but is padded with adjacent sentences
391-
#before and after. Use the 2nd output here (contextual_text).
392-
_, contextual_text = self.chunk_document(doc_name,
408+
# Chunk the text of the document into a list of strings
409+
contextual_text = self.chunk_document(doc_name,
393410
doc_url, doc_filepath)
394411

395412
# Format text if not None
396413
if contextual_text is not None:
397414
contextual_text = [string.strip() for string in contextual_text]
398415
contextual_text = " ".join(contextual_text)
399416

400-
#TODO: Consider all characters in my_contextual_text,
417+
#TODO: Consider all characters in contextual_text,
401418
#not just the first 1024
402419
contextual_text = contextual_text[0:1023]
403420

0 commit comments

Comments
 (0)