From 791422aa4a1b87b3badf69bdd507ab737116c3e6 Mon Sep 17 00:00:00 2001 From: Cui <530051970@qq.com> Date: Fri, 30 Aug 2024 11:01:16 +0800 Subject: [PATCH 1/4] update portal --- source/infrastructure/lib/api/api-stack.ts | 4 + source/lambda/intention/constant.py | 28 +++- source/lambda/intention/ddb_utils.py | 117 +++++++++++++++ source/lambda/intention/intention.py | 95 +++++++++--- source/portal/src/locale/en.json | 7 +- source/portal/src/locale/zh.json | 7 +- .../src/pages/components/AddIntention.tsx | 141 ++++++++++++------ .../portal/src/pages/intention/Intention.tsx | 95 +++++------- source/portal/src/types/index.ts | 4 + 9 files changed, 371 insertions(+), 127 deletions(-) create mode 100644 source/lambda/intention/ddb_utils.py diff --git a/source/infrastructure/lib/api/api-stack.ts b/source/infrastructure/lib/api/api-stack.ts index 432a7a1b..ffd3628e 100644 --- a/source/infrastructure/lib/api/api-stack.ts +++ b/source/infrastructure/lib/api/api-stack.ts @@ -471,6 +471,8 @@ export class ApiConstruct extends Construct { handler: "intention.lambda_handler", environment: { INTENTION_TABLE_NAME: props.chatStackOutputs.intentionTableName, + INDEX_TABLE_NAME: props.sharedConstructOutputs.indexTable.tableName, + CHATBOT_TABLE_NAME: props.sharedConstructOutputs.chatbotTable.tableName, S3_BUCKET: s3Bucket.bucketName, }, layers: [apiLambdaOnlineSourceLayer], @@ -534,6 +536,8 @@ export class ApiConstruct extends Construct { proxy: true, }); const apiResourceIntentionManagement = api.root.addResource("intention"); + const indexScan = apiResourceIntentionManagement.addResource("index-used-scan") + indexScan.addMethod("GET", lambdaIntentionIntegration, this.genMethodOption(api, auth, null)); // apiResourceIntentionManagement.addMethod("DELETE", lambdaIntentionIntegration, this.genMethodOption(api, auth, null)); const presignedUrl = apiResourceIntentionManagement.addResource("execution-presigned-url"); presignedUrl.addMethod("POST", lambdaIntentionIntegration, {... diff --git a/source/lambda/intention/constant.py b/source/lambda/intention/constant.py index 54edf426..666b8fbd 100644 --- a/source/lambda/intention/constant.py +++ b/source/lambda/intention/constant.py @@ -1,3 +1,6 @@ +from enum import Enum, unique + + DEFAULT_MAX_ITEMS = 50 DEFAULT_SIZE = 50 HTTPS_PORT_NUMBER = "443" @@ -5,7 +8,30 @@ ROOT_RESOURCE = "/intention" PRESIGNED_URL_RESOURCE = f"{ROOT_RESOURCE}/execution-presigned-url" EXECUTION_RESOURCE = f"{ROOT_RESOURCE}/executions" +INDEX_USED_SCAN_RESOURCE = f"{ROOT_RESOURCE}/index-used-scan" DOWNLOAD_RESOURCE = f"{ROOT_RESOURCE}/download-template" SECRET_NAME = "opensearch-master-user" AOS_INDEX = "aics_intention_index" -BULK_SIZE = 100000000 \ No newline at end of file +BULK_SIZE = 100000000 + +@unique +class IndexType(Enum): + QD = "qd" + QQ = "qq" + INTENTION = "intention" + +@unique +class KBType(Enum): + AOS = "aos" + + +@unique +class Status(Enum): + ACTIVE = "active" + INACTIVE = "inactive" + +ModelDimensionMap = { + "amazon.titan-embed-text-v1": 1536, + "cohere.embed-english-v3": 1024, + "amazon.titan-embed-text-v2:0": 1024 +} \ No newline at end of file diff --git a/source/lambda/intention/ddb_utils.py b/source/lambda/intention/ddb_utils.py new file mode 100644 index 00000000..72afa009 --- /dev/null +++ b/source/lambda/intention/ddb_utils.py @@ -0,0 +1,117 @@ +from datetime import datetime, timezone + +from constant import IndexType, KBType, Status + +def create_item_if_not_exist(ddb_table, item_key: dict, body: str): + response = ddb_table.get_item(Key=item_key) + item = response.get("Item") + if not item: + ddb_table.put_item(Item=body) + return False, item + return True, item + + +# def initiate_model( +# model_table, group_name, model_id, embedding_endpoint, create_time=None +# ): +# if not create_time: +# create_time = str(datetime.now(timezone.utc)) +# embedding_info = get_embedding_info(embedding_endpoint) +# embedding_info["ModelEndpoint"] = embedding_endpoint +# create_item_if_not_exist( +# model_table, +# {"groupName": group_name, "modelId": model_id}, +# { +# "groupName": group_name, +# "modelId": model_id, +# "modelType": ModelType.EMBEDDING.value, +# "parameter": embedding_info, +# "createTime": create_time, +# "updateTime": create_time, +# "status": Status.ACTIVE.value, +# }, +# ) +# return embedding_info["ModelType"] + + +def initiate_index( + index_table, + group_name, + index_id, + model_id, + index_type, + tag, + create_time=None, + description="", +): + if not create_time: + create_time = str(datetime.now(timezone.utc)) + + db_body = { + "groupName": group_name, + "indexId": index_id, + "indexType": index_type, + "kbType": KBType.AOS.value, + "modelIds": {"embedding": model_id}, + "tag": tag, + "createTime": create_time, + "status": Status.ACTIVE.value, + } + + create_item_if_not_exist( + index_table, {"groupName": group_name, "indexId": index_id}, db_body + ) + + +def initiate_chatbot( + chatbot_table, group_name, chatbot_id, index_id, index_type, tag, create_time=None +): + if not create_time: + create_time = str(datetime.now(timezone.utc)) + is_existed, item = create_item_if_not_exist( + chatbot_table, + {"groupName": group_name, "chatbotId": chatbot_id}, + { + "groupName": group_name, + "chatbotId": chatbot_id, + "languages": ["zh"], + "indexIds": {index_type: {"count": 1, "value": {tag: index_id}}}, + "createTime": create_time, + "updateTime": create_time, + "status": Status.ACTIVE.value, + }, + ) + + if is_existed: + index_id_dict = item.get("indexIds", {}) + append_index = True + if index_type in index_id_dict: + # Append it with the same index type + for key in index_id_dict[index_type]["value"].keys(): + if key == tag: + append_index = False + break + + if append_index: + item["indexIds"][index_type]["value"][tag] = index_id + item["indexIds"][index_type]["count"] = len( + item["indexIds"][index_type]["value"] + ) + chatbot_table.put_item(Item=item) + else: + # Add a new index type + item["indexIds"][index_type] = {"count": 1, "value": {tag: index_id}} + chatbot_table.put_item(Item=item) + + +def is_chatbot_existed(ddb_table, group_name: str, chatbot_id: str): + response = ddb_table.get_item( + Key={ + "groupName": group_name, + "chatbotId": chatbot_id, + }, + ) + item = response.get("Item") + if not item: + return False + return True diff --git a/source/lambda/intention/intention.py b/source/lambda/intention/intention.py index 380e8225..597768e1 100644 --- a/source/lambda/intention/intention.py +++ b/source/lambda/intention/intention.py @@ -1,5 +1,5 @@ import __main__ -from datetime import datetime +from datetime import datetime, timezone import hashlib import json import os @@ -15,7 +15,8 @@ from langchain.embeddings.bedrock import BedrockEmbeddings from aos.aos_utils import LLMBotOpenSearchClient -from constant import AOS_INDEX, BULK_SIZE, DEFAULT_CONTENT_TYPE, DEFAULT_MAX_ITEMS, DEFAULT_SIZE, DOWNLOAD_RESOURCE, EXECUTION_RESOURCE, PRESIGNED_URL_RESOURCE, SECRET_NAME +from constant import AOS_INDEX, BULK_SIZE, DEFAULT_CONTENT_TYPE, DEFAULT_MAX_ITEMS, DEFAULT_SIZE, DOWNLOAD_RESOURCE, EXECUTION_RESOURCE, INDEX_USED_SCAN_RESOURCE, PRESIGNED_URL_RESOURCE, SECRET_NAME, IndexType, ModelDimensionMap +from ddb_utils import initiate_chatbot, initiate_index logger = logging.getLogger(__name__) encoder = TokenEncoder() @@ -26,8 +27,12 @@ aos_domain_name = os.environ.get("AOS_DOMAIN_NAME", "smartsearch") aos_secret = os.environ.get("AOS_SECRET_NAME", "opensearch-master-user") intention_table_name = os.getenv("INTENTION_TABLE_NAME","intention") -dynamodb_resource = boto3.resource("dynamodb") -intention_table = dynamodb_resource.Table(intention_table_name) +chatbot_table_name = os.getenv("CHATBOT_TABLE_NAME","chatbot") +index_table_name = os.getenv("INDEX_TABLE_NAME","index") +dynamodb_client = boto3.resource("dynamodb") +intention_table = dynamodb_client.Table(intention_table_name) +index_table = dynamodb_client.Table(index_table_name) +chatbot_table = dynamodb_client.Table(chatbot_table_name) sm_client = boto3.client("secretsmanager") master_user = sm_client.get_secret_value(SecretId=aos_secret)["SecretString"] @@ -97,6 +102,8 @@ def lambda_handler(event, context): output = __get_execution(event) elif resource == DOWNLOAD_RESOURCE: output = __download_template() + elif resource == INDEX_USED_SCAN_RESOURCE: + output = __index_used_scan(event) try: return { "statusCode": 200, @@ -179,12 +186,41 @@ def __create_execution(event, context, email): execution_detail = {} execution_detail["tableItemId"] = context.aws_request_id execution_detail["chatbotId"] = input_body.get("chatbotId") - execution_detail["index"] = input_body.get("index") if input_body.get("index") else f'{input_body.get("chatbotId")}-default-index' + execution_detail["groupName"] = input_body.get("groupName") + # execution_detail["index"] = input_body.get("index") if input_body.get("index") else f'{input_body.get("chatbotId")}-default-index' + execution_detail["index"] = input_body.get("index") execution_detail["model"] = input_body.get("model") execution_detail["fileName"] = input_body.get("s3Prefix").split("/").pop() - execution_detail["tag"] = input_body.get("tag") if input_body.get("tag") else f'{input_body.get("chatbotId")}-default-tag' + execution_detail["tag"] = input_body.get("tag") if input_body.get("tag") else execution_detail["index"] bucket=input_body.get("s3Bucket") prefix=input_body.get("s3Prefix") + create_time = str(datetime.now(timezone.utc)) + # if index is used by other model,return error + + + + # update chatbot table + initiate_chatbot( + chatbot_table, + input_body.get("groupName"), + input_body.get("chatbotId"), + input_body.get("index"), + IndexType.INTENTION.value, + execution_detail["tag"], + create_time, + ) + + # update index table + initiate_index( + index_table, + input_body.get("groupName"), + input_body.get("index"), + input_body.get("model"), + IndexType.INTENTION.value, + execution_detail["tag"], + create_time, + "Answer question based on intention", + ) response = __get_s3_object_with_retry(bucket, prefix) file_content = response['Body'].read() excel_file = BytesIO(file_content) @@ -231,16 +267,13 @@ def __create_execution(event, context, email): } def __save_2_aos(modelId: str, index: str, qaList:list): - index_prefix="amazon-titan" - if modelId.startswith('cohere'): - index_prefix="cohere" - index_exists = aos_client.indices.exists(index=f'{index_prefix}-{index}') + index_exists = aos_client.indices.exists(index=index) if not index_exists: - __create_index(f'{index_prefix}-{index}') - __refresh_index(f'{index_prefix}-{index}', modelId, qaList) + __create_index(index, modelId) + __refresh_index(index, modelId, qaList) -def __create_index(index: str): +def __create_index(index: str, modelId: str): body = { "settings" : { "index":{ @@ -262,7 +295,7 @@ def __create_index(index: str): }, "sentence_vector" : { "type" : "knn_vector", - "dimension" : 1024 if index.startswith("cohere") else 1536, + "dimension" : ModelDimensionMap[modelId], "method" : { "engine" : "nmslib", "space_type" : "l2", @@ -295,8 +328,7 @@ def __append_embeddings(index, modelId, qaList:list): question=item["question"] embedding_func = BedrockEmbeddings( client=bedrock_client, - model_id=modelId, - normalize=True + model_id=modelId ) embeddings_vectors = embedding_func.embed_documents( @@ -314,6 +346,7 @@ def __append_embeddings(index, modelId, qaList:list): "sentence_vector" : embeddings_vectors[0] } ) + for document in documents: yield {"_op_type": "index", "_index": index, "_source": document, "_id": hashlib.md5(str(document).encode('utf-8')).hexdigest() } @@ -374,4 +407,32 @@ def __download_template(): Params={'Bucket': s3_bucket_name, 'Key': "templates/intention_corpus.xlsx"}, ExpiresIn=60 ) - return url \ No newline at end of file + return url + +def __index_used_scan(event): + input_body = json.loads(event["body"]) + index_response = index_table.get_item( + Key={ + "groupName": input_body.get("groupName"), + "chatbotId": input_body.get("chatbotId"), + }, + ) + pre_model = index_response.get("Item",{}).get("modelIds",{}).get("embedding") + if not pre_model and pre_model!=input_body.get("model"): + return { + "statusCode": 200, + "headers": resp_header, + "body": json.dumps({ + "result":"invalid" + }) + + # "body": "The index with the same name is already in use by another model. Please use a different index." + } + else: + return { + "statusCode": 200, + "headers": resp_header, + "body": json.dumps({ + "result":"valid" + } + )} \ No newline at end of file diff --git a/source/portal/src/locale/en.json b/source/portal/src/locale/en.json index 76c2325d..db64a8a3 100644 --- a/source/portal/src/locale/en.json +++ b/source/portal/src/locale/en.json @@ -94,6 +94,8 @@ "bot": "Bot", "model": "Model", "empty":"No resources", + "customizeIndex": "Customize Index", + "downloadTemplate":"Download Template", "button": { "login": "Log in to AI Customer Service", "reload": "Reload", @@ -127,7 +129,10 @@ "invalidJson": "Invalid JSON format", "requiredIndexName": "Please input the index name", "requiredTag": "Please input the tag", - "formatInvalidTagIndex": "Only letters, numbers, -, and _ are allowed" + "formatInvalidTagIndex": "Only letters, numbers, -, and _ are allowed", + "indexNameEmpty": "Index name can not be empty.", + "tagEmpty": "Tag can not be empty.", + "indexExisted":"Index name is the same as the default. Please change index name." } } } diff --git a/source/portal/src/locale/zh.json b/source/portal/src/locale/zh.json index c30e0dc0..8e08b53b 100644 --- a/source/portal/src/locale/zh.json +++ b/source/portal/src/locale/zh.json @@ -96,6 +96,8 @@ "bot": "Bot", "model": "模型", "empty":"没找到语料", + "customizeIndex": "自定义索引", + "downloadTemplate":"下载模板", "button": { "login": "登录到 AWS 大语言模型机器人", "reload": "重新加载", @@ -129,7 +131,10 @@ "invalidJson": "无效的 JSON 格式", "requiredIndexName": "请输入索引名称", "requiredTag": "请输入标签", - "formatInvalidTagIndex": "仅允许字母、数字、- 和 _" + "formatInvalidTagIndex": "仅允许字母、数字、- 和 _", + "indexNameEmpty": "索引名称不能为空", + "tagEmpty": "标签不能为空", + "indexExisted": "索引和默认名称一致,请变更索引名称" } } } diff --git a/source/portal/src/pages/components/AddIntention.tsx b/source/portal/src/pages/components/AddIntention.tsx index 68207cf7..b59b82e4 100644 --- a/source/portal/src/pages/components/AddIntention.tsx +++ b/source/portal/src/pages/components/AddIntention.tsx @@ -15,40 +15,44 @@ import { ProgressBar, Select, SpaceBetween, + Toggle, } from '@cloudscape-design/components'; import { alertMsg, validateNameTagString } from 'src/utils/utils'; import { AxiosProgressEvent } from 'axios'; import { useTranslation } from 'react-i18next'; import useAxiosRequest from 'src/hooks/useAxiosRequest'; -import { ExecutionResponse, PresignedUrlResponse, SelectedOption } from 'src/types'; +import { ExecutionResponse, PresignedUrlResponse, SelectedOption, indexScanResponse } from 'src/types'; import { DEFAULT_EMBEDDING_MODEL } from 'src/utils/const'; interface AddIntentionProps { showAddModal: boolean; + indexName: string; + useDefaultIndex: boolean; models: SelectedOption[]; botsOption: SelectedOption[]; selectedBotOption: SelectedOption | undefined; selectedModelOption: SelectedOption | undefined; + changeUseDefaultIndex: (arg: boolean) => void; changeBotOption: (option: SelectedOption) => void; changeSelectedModel: (option: SelectedOption) => void; setShowAddModal: (show: boolean) => void; + setIndexName: (name: string) => void; reloadIntention: () => void; } const AddIntention: React.FC = (props: AddIntentionProps) => { const { t } = useTranslation(); - const {models, botsOption, selectedModelOption, selectedBotOption, showAddModal, changeBotOption, changeSelectedModel, setShowAddModal, reloadIntention } = props; + const {models, botsOption, selectedModelOption, selectedBotOption, showAddModal, indexName, useDefaultIndex, changeUseDefaultIndex, setIndexName, changeBotOption, changeSelectedModel, setShowAddModal, reloadIntention } = props; const fetchData = useAxiosRequest(); const [uploadFiles, setUploadFiles] = useState([]); const [uploadProgress, setUploadProgress] = useState(0); const [showProgress, setShowProgress] = useState(false); const [fileEmptyError, setFileEmptyError] = useState(false); - - const [indexName, setIndexName] = useState(''); const [indexNameError, setIndexNameError] = useState(''); const [tagName, setTagName] = useState(''); const [tagNameError, setTagNameError] = useState(''); const [advanceExpand, setAdvanceExpand] = useState(false); + const executionIntention = async (bucket: string, prefix: string) => { const resExecution: ExecutionResponse = await fetchData({ url: `intention/executions`, @@ -56,12 +60,10 @@ const AddIntention: React.FC = (props: AddIntentionProps) => data: { s3Bucket: bucket, s3Prefix: prefix, - // offline: 'true', - // qaEnhance: 'false', chatbotId: selectedBotOption?.value.toLocaleLowerCase() ?? 'admin', + groupName: selectedBotOption?.value, index: indexName ? indexName.trim() : undefined, model: selectedModelOption?.value ?? DEFAULT_EMBEDDING_MODEL, - // operationType: 'create', tag: tagName ? tagName.trim() : undefined, }, }); @@ -72,7 +74,6 @@ const AddIntention: React.FC = (props: AddIntentionProps) => }; const downloadTemplate = async ()=>{ - // setLoadingDownload(true); let url:any = await fetchData({ url: `intention/download-template`, method: 'get', @@ -88,23 +89,6 @@ const AddIntention: React.FC = (props: AddIntentionProps) => document.body.removeChild(link); }; - // const downloadTemplate = async (type:string) => { - // console.log('download template'); - // setLoadingDownload(true); - // let url:any - // if(type==="identifier"){ - // url = await downloadIdentifierBatchFiles({ - // filename: `identifier-template-${i18n.language}`, - // }); - // } else { - // url = await downloadDataSourceBatchFiles({ - // filename: `template-${i18n.language}`, - // }); - // } - // setLoadingDownload(false); - // startDownload(url); - // }; - const uploadFilesToS3 = async () => { // validate file if (uploadFiles.length <= 0) { @@ -121,6 +105,39 @@ const AddIntention: React.FC = (props: AddIntentionProps) => setTagNameError('validation.formatInvalidTagIndex'); return; } + + if(!useDefaultIndex && (indexName==null||indexName=='')){ + setIndexNameError('validation.indexNameEmpty') + return; + } + + // console.log(`${selectedBotOption?.value.toLowerCase()}-intention-default`) + + if(!useDefaultIndex && (tagName==null||tagName=='')){ + setTagNameError('validation.tagEmpty') + return; + } + + if(!useDefaultIndex && indexName==`${selectedBotOption?.value.toLowerCase()}-intention-default`){ + setIndexNameError('validation.indexExisted') + return; + } + + const resIndexScan: indexScanResponse = await fetchData({ + url: `intention/execution-presigned-url`, + method: 'get', + data: { + chatbotId: selectedBotOption?.value.toLocaleLowerCase() ?? 'admin', + groupName: selectedBotOption?.value, + index: indexName ? indexName.trim() : undefined, + }, + }); + + if(resIndexScan.result === "invalid") { + setIndexNameError("The index with the same name is already in use by another model. Please customize a different index.") + return + } + setShowProgress(true); const totalSize = uploadFiles.reduce((acc, file) => acc + file.size, 0); let progressMap = new Map(); @@ -218,7 +235,7 @@ const AddIntention: React.FC = (props: AddIntentionProps) => {t('selectFileDesc')}下载模版} + description={<>{t('selectFileDesc')}{t('downloadTemplate')}} >
= (props: AddIntentionProps) => headerText={t('additionalSettings')} > + { + changeSelectedModel(detail.selectedOption); + }} + /> + + + + changeUseDefaultIndex(!detail.checked) + } + checked={!useDefaultIndex} + > + {t('customizeIndex')} + + + + {useDefaultIndex?( - {t('indexName')} -{' '} - - {t('optional')} - + {t('indexName')} } stretch={true} @@ -280,28 +315,36 @@ const AddIntention: React.FC = (props: AddIntentionProps) => { setIndexNameError(''); setIndexName(detail.value); }} /> - - - { + setIndexNameError(''); + setIndexName(detail.value); + }} + /> + + - {t('tag')} -{' '} - - {t('optional')} - + {t('tag')} } stretch={true} @@ -316,7 +359,11 @@ const AddIntention: React.FC = (props: AddIntentionProps) => }} /> - + + )} + + +
{showProgress && ( diff --git a/source/portal/src/pages/intention/Intention.tsx b/source/portal/src/pages/intention/Intention.tsx index 2cb352bc..bb2d0b67 100644 --- a/source/portal/src/pages/intention/Intention.tsx +++ b/source/portal/src/pages/intention/Intention.tsx @@ -37,6 +37,7 @@ const Intention: React.FC = () => { ); const [currentPage, setCurrentPage] = useState(1); const [pageSize, setPageSize] = useState(10); + const [indexName, setIndexName] = useState(''); // const [loadingDelete, setLoadingDelete] = useState(false); const [sortingColumn, setSortingColumn] = useState< TableProps.SortingColumn @@ -45,7 +46,6 @@ const Intention: React.FC = () => { }); const [isDescending, setIsDescending] = useState(true); const auth = useAuth(); - // ingest document const [showAddModal, setShowAddModal] = useState(false); const isFirstRender = useRef(true); @@ -53,11 +53,20 @@ const Intention: React.FC = () => { const [selectedBotsOption, setSelectedBotsOption] = useState(); const [models, setModels] = useState([]) const [selectedModelOption, setSelectedModelOption] = useState(); + const [useDefaultIndex, setUseDefaultIndex] = useState(true); const changeBotOption = (selectedBotOption: SelectedOption)=>{ setSelectedBotsOption(selectedBotOption) + if(useDefaultIndex){ + setIndexName(`${selectedBotOption.value.toLocaleLowerCase()}-intention-default`) + } } + // const changeUseDefaultIndex = (useDefault: boolean)=>{ + // console.log(!useDefault) + // setUseDefaultIndex(!useDefault) + // } + const changeModelOption = (selectedBotOption: SelectedOption)=>{ setSelectedModelOption(selectedBotOption) } @@ -75,34 +84,6 @@ const Intention: React.FC = () => { method: 'get', params, }); - // res.Items=[{ - // executionId:"4d5e6f70-8901-23de-f456-4879WABGFBBC", - // fileName:"基于周会讨论的QA对.xlsx", - // chatbotId:"admin", - // index:"admin-intention-default", - // model:"cohere.embed-english-v3", - // tag:"default", - // executionStatus:"COMPLETED", - // createTime:"2022-08-21 17:28:32" - // },{ - // executionId:"5e6f7081-9012-34ef-5678-BDAL2344BBAA", - // fileName:"pricinglist.xlsx", - // chatbotId:"admin", - // index:"admin-intention-default", - // model:"cohere.embed-english-v3", - // tag:"default", - // executionStatus:"COMPLETED", - // createTime:"2022-08-21 17:28:32" - // },{ - // executionId:"2b3c4d5e-6789-01bc-def2-FD34678CDDAB", - // fileName:"初识语料列表.xlsx", - // chatbotId:"admin", - // index:"admin-intention-default", - // model:"cohere.embed-english-v3", - // tag:"default", - // executionStatus:"COMPLETED", - // createTime:"2022-08-21 17:28:32" - // }] const preSortItem = res.Items preSortItem.sort((a, b) => { @@ -118,24 +99,6 @@ const Intention: React.FC = () => { } }; - // const removeIntention = async () => { - // try { - // setLoadingDelete(true); - // const data = await fetchData({ - // url: `intention/executions`, - // method: 'delete', - // data: { executionId: selectedItems.map((item) => item.executionId) }, - // }); - // setVisible(false); - // getIntentionList(); - // alertMsg(data.message, 'success'); - // setLoadingDelete(false); - // setSelectedItems([]); - // } catch (error: unknown) { - // setLoadingDelete(false); - // } - // }; - const getBots = async ()=>{ const groupName: string[] = auth?.user?.profile?.['cognito:groups'] as any; const data: any = await fetchData({ @@ -152,10 +115,25 @@ const Intention: React.FC = () => { value: item }) }) + + // options.push({ + // label: "Test", + // value: "Test" + // }) setBotsOption(options) setSelectedBotsOption(options[0]) + setIndexName(`${options[0].value.toLocaleLowerCase()}-intention-default`) } + useEffect(()=>{ + if(useDefaultIndex == false){ + setIndexName("") + } else { + setIndexName(`${selectedBotsOption?.value.toLocaleLowerCase()}-intention-default`) + } + + },[useDefaultIndex]) + const getModels = async ()=>{ const tempModels = [{ value: "cohere.embed-english-v3", @@ -164,10 +142,12 @@ const Intention: React.FC = () => { // ,{ // value: "cohere.embed-multilingual-v3", // label: "cohere.embed-multilingual-v3" - // },{ - // value: "amazon.titan-embed-text-v1", - // label: "amazon.titan-embed-text-v1" - // },{ + // } + ,{ + value: "amazon.titan-embed-text-v1", + label: "amazon.titan-embed-text-v1" + } + //,{ // value: "amazon.titan-embed-image-v1", // label: "amazon.titan-embed-image-v1" // } @@ -434,15 +414,6 @@ const Intention: React.FC = () => { > {t('button.cancel')} - {/* */} } @@ -459,10 +430,14 @@ const Intention: React.FC = () => { Date: Mon, 2 Sep 2024 10:15:06 +0800 Subject: [PATCH 2/4] bug fix: insert record into model table when injection intention --- source/infrastructure/lib/api/api-stack.ts | 3 +- source/lambda/intention/constant.py | 7 ++- source/lambda/intention/ddb_utils.py | 51 +++++++++------- source/lambda/intention/embeddings.py | 56 ++++++++++++++++++ source/lambda/intention/intention.py | 59 +++++++++++-------- source/portal/src/locale/en.json | 3 +- source/portal/src/locale/zh.json | 3 +- .../src/pages/components/AddIntention.tsx | 50 ++++++++++------ .../portal/src/pages/intention/Intention.tsx | 16 +++++ 9 files changed, 179 insertions(+), 69 deletions(-) create mode 100644 source/lambda/intention/embeddings.py diff --git a/source/infrastructure/lib/api/api-stack.ts b/source/infrastructure/lib/api/api-stack.ts index 14c0446e..af12f0dd 100644 --- a/source/infrastructure/lib/api/api-stack.ts +++ b/source/infrastructure/lib/api/api-stack.ts @@ -453,6 +453,7 @@ export class ApiConstruct extends Construct { INTENTION_TABLE_NAME: props.chatStackOutputs.intentionTableName, INDEX_TABLE_NAME: props.sharedConstructOutputs.indexTable.tableName, CHATBOT_TABLE_NAME: props.sharedConstructOutputs.chatbotTable.tableName, + MODEL_TABLE_NAME: props.sharedConstructOutputs.modelTable.tableName, S3_BUCKET: s3Bucket.bucketName, }, layers: [apiLambdaOnlineSourceLayer], @@ -534,7 +535,7 @@ export class ApiConstruct extends Construct { }); const apiResourceIntentionManagement = api.root.addResource("intention"); const indexScan = apiResourceIntentionManagement.addResource("index-used-scan") - indexScan.addMethod("GET", lambdaIntentionIntegration, this.genMethodOption(api, auth, null)); + indexScan.addMethod("POST", lambdaIntentionIntegration, this.genMethodOption(api, auth, null)); // apiResourceIntentionManagement.addMethod("DELETE", lambdaIntentionIntegration, this.genMethodOption(api, auth, null)); const presignedUrl = apiResourceIntentionManagement.addResource("execution-presigned-url"); presignedUrl.addMethod("POST", lambdaIntentionIntegration, {... diff --git a/source/lambda/intention/constant.py b/source/lambda/intention/constant.py index 666b8fbd..70cc1a9f 100644 --- a/source/lambda/intention/constant.py +++ b/source/lambda/intention/constant.py @@ -34,4 +34,9 @@ class Status(Enum): "amazon.titan-embed-text-v1": 1536, "cohere.embed-english-v3": 1024, "amazon.titan-embed-text-v2:0": 1024 -} \ No newline at end of file +} + +@unique +class ModelType(Enum): + EMBEDDING = "embedding_and_rerank" + LLM = "llm" \ No newline at end of file diff --git a/source/lambda/intention/ddb_utils.py b/source/lambda/intention/ddb_utils.py index 72afa009..59c830a7 100644 --- a/source/lambda/intention/ddb_utils.py +++ b/source/lambda/intention/ddb_utils.py @@ -1,6 +1,8 @@ from datetime import datetime, timezone +from typing import Any -from constant import IndexType, KBType, Status +from constant import IndexType, KBType, ModelType, Status +from embeddings import get_embedding_info def create_item_if_not_exist(ddb_table, item_key: dict, body: str): response = ddb_table.get_item(Key=item_key) @@ -10,28 +12,33 @@ def create_item_if_not_exist(ddb_table, item_key: dict, body: str): return False, item return True, item +def check_item_exist(ddb_table, item_key: dict): + response = ddb_table.get_item(Key=item_key) + item = response.get("Item") + return True, item if item else False, None -# def initiate_model( -# model_table, group_name, model_id, embedding_endpoint, create_time=None -# ): -# if not create_time: -# create_time = str(datetime.now(timezone.utc)) -# embedding_info = get_embedding_info(embedding_endpoint) -# embedding_info["ModelEndpoint"] = embedding_endpoint -# create_item_if_not_exist( -# model_table, -# {"groupName": group_name, "modelId": model_id}, -# { -# "groupName": group_name, -# "modelId": model_id, -# "modelType": ModelType.EMBEDDING.value, -# "parameter": embedding_info, -# "createTime": create_time, -# "updateTime": create_time, -# "status": Status.ACTIVE.value, -# }, -# ) -# return embedding_info["ModelType"] + +def initiate_model( + model_table, group_name, model_id, embedding_endpoint, create_time=None +): + if not create_time: + create_time = str(datetime.now(timezone.utc)) + embedding_info = dict(get_embedding_info(embedding_endpoint)) + embedding_info["ModelEndpoint"] = embedding_endpoint + create_item_if_not_exist( + model_table, + {"groupName": group_name, "modelId": model_id}, + { + "groupName": group_name, + "modelId": model_id, + "modelType": ModelType.EMBEDDING.value, + "parameter": embedding_info, + "createTime": create_time, + "updateTime": create_time, + "status": Status.ACTIVE.value, + }, + ) + return embedding_info["ModelType"] def initiate_index( diff --git a/source/lambda/intention/embeddings.py b/source/lambda/intention/embeddings.py new file mode 100644 index 00000000..ae0ffc0b --- /dev/null +++ b/source/lambda/intention/embeddings.py @@ -0,0 +1,56 @@ +def get_embedding_info(embedding_endpoint_name): + """ + Get the embedding info from the endpoint name + """ + # Get the embedding info from the endpoint name + if "bge-large-zh" in embedding_endpoint_name: + embeddings_model_provider = "BAAI" + embeddings_model_name = "bge-large-zh-v1-5" + embeddings_model_dimensions = 1024 + embeddings_model_type = "bge-large-zh" + + elif "bge-large-en" in embedding_endpoint_name: + embeddings_model_provider = "BAAI" + embeddings_model_name = "bge-large-en-v1-5" + embeddings_model_dimensions = 1024 + embeddings_model_type = "bge-large-en" + + elif "bge-m3" in embedding_endpoint_name: + embeddings_model_provider = "BAAI" + embeddings_model_name = "bge-m3" + embeddings_model_dimensions = 1024 + embeddings_model_type = "m3" + + elif "embedding" in embedding_endpoint_name: + embeddings_model_provider = "Netease" + embeddings_model_name = "bce_embedding_model.tar.gz" + embeddings_model_dimensions = 768 + embeddings_model_type = "bce" + + elif "cohere" in embedding_endpoint_name: + embeddings_model_provider = "Cohere" + embeddings_model_name = "cohere.embed-english-v3" + embeddings_model_dimensions = 1024 + embeddings_model_type = "bedrock" + elif "titan-embed-text-v1" in embedding_endpoint_name: + embeddings_model_provider = "Titan" + embeddings_model_name = "amazon.titan-embed-text-v1" + embeddings_model_dimensions = 1536 + embeddings_model_type = "bedrock" + elif "titan-embed-text-v2" in embedding_endpoint_name: + embeddings_model_provider = "Titan" + embeddings_model_name = "amazon.titan-embed-text-v2:0" + embeddings_model_dimensions = 1024 + embeddings_model_type = "bedrock" + else: + embeddings_model_provider = "Not Found" + embeddings_model_name = "Not Found" + embeddings_model_dimensions = 1024 + embeddings_model_type = "Not Found" + + return { + "ModelProvider": embeddings_model_provider, + "ModelName": embeddings_model_name, + "ModelDimension": embeddings_model_dimensions, + "ModelType": embeddings_model_type, + } diff --git a/source/lambda/intention/intention.py b/source/lambda/intention/intention.py index 597768e1..ffc714f8 100644 --- a/source/lambda/intention/intention.py +++ b/source/lambda/intention/intention.py @@ -16,7 +16,7 @@ from aos.aos_utils import LLMBotOpenSearchClient from constant import AOS_INDEX, BULK_SIZE, DEFAULT_CONTENT_TYPE, DEFAULT_MAX_ITEMS, DEFAULT_SIZE, DOWNLOAD_RESOURCE, EXECUTION_RESOURCE, INDEX_USED_SCAN_RESOURCE, PRESIGNED_URL_RESOURCE, SECRET_NAME, IndexType, ModelDimensionMap -from ddb_utils import initiate_chatbot, initiate_index +from ddb_utils import check_item_exist, initiate_chatbot, initiate_index, initiate_model logger = logging.getLogger(__name__) encoder = TokenEncoder() @@ -29,10 +29,12 @@ intention_table_name = os.getenv("INTENTION_TABLE_NAME","intention") chatbot_table_name = os.getenv("CHATBOT_TABLE_NAME","chatbot") index_table_name = os.getenv("INDEX_TABLE_NAME","index") +model_table_name = os.getenv("MODEL_TABLE_NAME","model") dynamodb_client = boto3.resource("dynamodb") intention_table = dynamodb_client.Table(intention_table_name) index_table = dynamodb_client.Table(index_table_name) chatbot_table = dynamodb_client.Table(chatbot_table_name) +model_table = dynamodb_client.Table(model_table_name) sm_client = boto3.client("secretsmanager") master_user = sm_client.get_secret_value(SecretId=aos_secret)["SecretString"] @@ -73,12 +75,9 @@ def lambda_handler(event, context): cognito_groups_list = ["Admin"] http_method = event["httpMethod"] resource:str = event["resource"] - chatbot_id = ( - "Admin" if "Admin" in cognito_groups_list else cognito_groups_list[0] - ) if resource == PRESIGNED_URL_RESOURCE: input_body = json.loads(event["body"]) - file_name = f"intentions/{chatbot_id}/[{input_body['timestamp']}]{input_body['file_name']}" + file_name = f"intentions/{input_body['chatbotId'].capitalize()}/[{input_body['timestamp']}]{input_body['file_name']}" presigned_url = __gen_presigned_url(file_name, input_body.get("content_type", DEFAULT_CONTENT_TYPE), input_body.get("expiration", 60*60)) @@ -186,7 +185,7 @@ def __create_execution(event, context, email): execution_detail = {} execution_detail["tableItemId"] = context.aws_request_id execution_detail["chatbotId"] = input_body.get("chatbotId") - execution_detail["groupName"] = input_body.get("groupName") + execution_detail["groupName"] = input_body.get("chatbotId").capitalize() # execution_detail["index"] = input_body.get("index") if input_body.get("index") else f'{input_body.get("chatbotId")}-default-index' execution_detail["index"] = input_body.get("index") execution_detail["model"] = input_body.get("model") @@ -195,14 +194,17 @@ def __create_execution(event, context, email): bucket=input_body.get("s3Bucket") prefix=input_body.get("s3Prefix") create_time = str(datetime.now(timezone.utc)) - # if index is used by other model,return error - - + initiate_model( + model_table=model_table, + group_name=execution_detail["groupName"], + model_id=input_body.get("model"), + embedding_endpoint=input_body.get("model") + ) # update chatbot table initiate_chatbot( chatbot_table, - input_body.get("groupName"), + execution_detail["groupName"], input_body.get("chatbotId"), input_body.get("index"), IndexType.INTENTION.value, @@ -213,7 +215,7 @@ def __create_execution(event, context, email): # update index table initiate_index( index_table, - input_body.get("groupName"), + execution_detail["groupName"], input_body.get("index"), input_body.get("model"), IndexType.INTENTION.value, @@ -411,28 +413,37 @@ def __download_template(): def __index_used_scan(event): input_body = json.loads(event["body"]) + group_name = input_body.get("chatbotId").capitalize() index_response = index_table.get_item( Key={ - "groupName": input_body.get("groupName"), - "chatbotId": input_body.get("chatbotId"), + "groupName": group_name, + "indexId": input_body.get("index"), }, ) - pre_model = index_response.get("Item",{}).get("modelIds",{}).get("embedding") - if not pre_model and pre_model!=input_body.get("model"): + pre_model = index_response.get("Item") + model_name = '' + if pre_model: + model_response = model_table.get_item( + Key={ + "groupName": group_name, + "modelId": pre_model.get("modelIds",{}).get("embedding"), + } + ) + model_name = model_response.get("Item",{}).get("parameter",{}).get("ModelName", "") + # model_name = model_response.get("ModelName", {}).get("S","-") + if not pre_model or model_name==input_body.get("model"): return { - "statusCode": 200, - "headers": resp_header, - "body": json.dumps({ - "result":"invalid" - }) - - # "body": "The index with the same name is already in use by another model. Please use a different index." - } + "statusCode": 200, + "headers": resp_header, + "body": json.dumps({ + "result":"valid" + }) + } else: return { "statusCode": 200, "headers": resp_header, "body": json.dumps({ - "result":"valid" + "result":"invalid" } )} \ No newline at end of file diff --git a/source/portal/src/locale/en.json b/source/portal/src/locale/en.json index 0b4e122b..a5475bf6 100644 --- a/source/portal/src/locale/en.json +++ b/source/portal/src/locale/en.json @@ -138,7 +138,8 @@ "formatInvalidTagIndex": "Only letters, numbers, -, and _ are allowed", "indexNameEmpty": "Index name can not be empty.", "tagEmpty": "Tag can not be empty.", - "indexExisted":"Index name is the same as the default. Please change index name." + "indexValid": "Index is used by other model, please change index name", + "defaultIndexValid": "Default index is used by other model, please customize index name" } } } diff --git a/source/portal/src/locale/zh.json b/source/portal/src/locale/zh.json index e25968cc..90da09c5 100644 --- a/source/portal/src/locale/zh.json +++ b/source/portal/src/locale/zh.json @@ -140,7 +140,8 @@ "formatInvalidTagIndex": "仅允许字母、数字、- 和 _", "indexNameEmpty": "索引名称不能为空", "tagEmpty": "标签不能为空", - "indexExisted": "索引和默认名称一致,请变更索引名称" + "indexValid": "索引已经被其他模型使用,请变更索引名称", + "defaultIndexValid": "该默认索引已经被其他模型使用,请自定义索引名称" } } } diff --git a/source/portal/src/pages/components/AddIntention.tsx b/source/portal/src/pages/components/AddIntention.tsx index b59b82e4..a39026fd 100644 --- a/source/portal/src/pages/components/AddIntention.tsx +++ b/source/portal/src/pages/components/AddIntention.tsx @@ -21,7 +21,7 @@ import { alertMsg, validateNameTagString } from 'src/utils/utils'; import { AxiosProgressEvent } from 'axios'; import { useTranslation } from 'react-i18next'; import useAxiosRequest from 'src/hooks/useAxiosRequest'; -import { ExecutionResponse, PresignedUrlResponse, SelectedOption, indexScanResponse } from 'src/types'; +import { ExecutionResponse, PresignedUrlResponse, SelectedOption } from 'src/types'; import { DEFAULT_EMBEDDING_MODEL } from 'src/utils/const'; interface AddIntentionProps { @@ -89,6 +89,21 @@ const AddIntention: React.FC = (props: AddIntentionProps) => document.body.removeChild(link); }; + + const isValidIndex = async () =>{ + const resIndexScan = await fetchData({ + url: `intention/index-used-scan`, + method: 'post', + data: { + chatbotId: selectedBotOption?.value.toLocaleLowerCase() ?? 'admin', + // groupName: selectedBotOption?.value, + index: indexName ? indexName.trim() : undefined, + model: selectedModelOption?.value + }, + }); + return JSON.parse(resIndexScan.body).result === 'valid' + } + const uploadFilesToS3 = async () => { // validate file if (uploadFiles.length <= 0) { @@ -118,24 +133,15 @@ const AddIntention: React.FC = (props: AddIntentionProps) => return; } - if(!useDefaultIndex && indexName==`${selectedBotOption?.value.toLowerCase()}-intention-default`){ - setIndexNameError('validation.indexExisted') - return; - } + const indexIsValid = await isValidIndex() - const resIndexScan: indexScanResponse = await fetchData({ - url: `intention/execution-presigned-url`, - method: 'get', - data: { - chatbotId: selectedBotOption?.value.toLocaleLowerCase() ?? 'admin', - groupName: selectedBotOption?.value, - index: indexName ? indexName.trim() : undefined, - }, - }); - - if(resIndexScan.result === "invalid") { - setIndexNameError("The index with the same name is already in use by another model. Please customize a different index.") - return + if(!indexIsValid){ + if(useDefaultIndex){ + setIndexNameError('validation.defaultIndexValid') + } else { + setIndexNameError('validation.indexValid') + } + return; } setShowProgress(true); @@ -148,6 +154,7 @@ const AddIntention: React.FC = (props: AddIntentionProps) => url: `intention/execution-presigned-url`, method: 'post', data: { + chatbotId: selectedBotOption?.value.toLocaleLowerCase() ?? 'admin', timestamp: format(new Date(), 'yyyy-MM-dd HH:mm:ss'), file_name: file.name, content_type: file.type, @@ -295,7 +302,12 @@ const AddIntention: React.FC = (props: AddIntentionProps) => - changeUseDefaultIndex(!detail.checked) + { + setIndexNameError(''); + setTagName('') + setTagNameError('') + changeUseDefaultIndex(!detail.checked) + } } checked={!useDefaultIndex} > diff --git a/source/portal/src/pages/intention/Intention.tsx b/source/portal/src/pages/intention/Intention.tsx index bb2d0b67..be1c37fb 100644 --- a/source/portal/src/pages/intention/Intention.tsx +++ b/source/portal/src/pages/intention/Intention.tsx @@ -134,6 +134,17 @@ const Intention: React.FC = () => { },[useDefaultIndex]) + // const getExistedIndex = async ()=>{ + // const data: any = await fetchData({ + // url: 'intention/get-all-index', + // method: 'get', + // data: { + // groupName: groupName?.[0] ?? 'Admin', + // }, + // }); + + // } + const getModels = async ()=>{ const tempModels = [{ value: "cohere.embed-english-v3", @@ -143,6 +154,10 @@ const Intention: React.FC = () => { // value: "cohere.embed-multilingual-v3", // label: "cohere.embed-multilingual-v3" // } + // ,{ + // value: "amazon.titan-embed-text-v1", + // label: "amazon.titan-embed-text-v1" + // } ,{ value: "amazon.titan-embed-text-v1", label: "amazon.titan-embed-text-v1" @@ -164,6 +179,7 @@ const Intention: React.FC = () => { getIntentionList(); getBots(); getModels(); + // getExistedIndex(); }, []); useEffect(() => { From 8d7e2f6bc65b01d0270d505431ac239a3c184484 Mon Sep 17 00:00:00 2001 From: Cui <530051970@qq.com> Date: Mon, 2 Sep 2024 10:17:22 +0800 Subject: [PATCH 3/4] update locale json of chatbot --- source/portal/src/locale/zh.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/portal/src/locale/zh.json b/source/portal/src/locale/zh.json index 90da09c5..a5f8ee32 100644 --- a/source/portal/src/locale/zh.json +++ b/source/portal/src/locale/zh.json @@ -80,7 +80,7 @@ "prompts": "提示词", "chatbot": "聊天机器人", "chatbots": "聊天机器人", - "chatbotManagement": "管理聊天机器人", + "chatbotManagement": "机器人管理", "type": "类型", "updateBy": "更新者", "updateTime": "更新时间", From 79bb7683ae51542c46d50fc723593468e0168bcc Mon Sep 17 00:00:00 2001 From: Cui <530051970@qq.com> Date: Tue, 3 Sep 2024 10:23:10 +0800 Subject: [PATCH 4/4] update UI of chatbot --- .../lib/api/asset/intention_corpus.xlsx | Bin 10948 -> 11020 bytes source/lambda/intention/intention.py | 41 ++++++++++++------ .../chatbotManagement/ChatbotManagement.tsx | 30 +++++-------- .../portal/src/pages/intention/Intention.tsx | 34 ++++----------- source/portal/src/utils/const.ts | 6 +++ 5 files changed, 54 insertions(+), 57 deletions(-) diff --git a/source/infrastructure/lib/api/asset/intention_corpus.xlsx b/source/infrastructure/lib/api/asset/intention_corpus.xlsx index 531ad80d69e3ecbbe34688355c8c121dc048f72a..b9dbbc873c684344419b8ef86620a9a3cec810ce 100644 GIT binary patch delta 4527 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 P=_Hr~7of@)`J4JblL&uu delta 4473 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 E2l>8n-v9sr diff --git a/source/lambda/intention/intention.py b/source/lambda/intention/intention.py index ffc714f8..f09cabf0 100644 --- a/source/lambda/intention/intention.py +++ b/source/lambda/intention/intention.py @@ -67,17 +67,25 @@ def lambda_handler(event, context): ) if authorizer_type == "lambda_authorizer": claims = json.loads(event["requestContext"]["authorizer"]["claims"]) - email = claims["email"] - cognito_groups = claims["cognito:groups"] - cognito_groups_list = cognito_groups.split(",") + # email = claims["email"] + # cognito_groups = claims["cognito:groups"] + # cognito_groups_list = cognito_groups.split(",") + if "use_api_key" in claims: + group_name = __get_query_parameter(event, "GroupName", "Admin") + else: + email = claims["email"] + group_name = claims["cognito:groups"] # Agree to only be in one group else: - email = event["multiValueHeaders"]["author"][0] - cognito_groups_list = ["Admin"] + logger.error("Invalid authorizer type") + raise + # else: + # email = event["multiValueHeaders"]["author"][0] + # cognito_groups_list = ["Admin"] http_method = event["httpMethod"] resource:str = event["resource"] if resource == PRESIGNED_URL_RESOURCE: input_body = json.loads(event["body"]) - file_name = f"intentions/{input_body['chatbotId'].capitalize()}/[{input_body['timestamp']}]{input_body['file_name']}" + file_name = f"intentions/{group_name}/[{input_body['timestamp']}]{input_body['file_name']}" presigned_url = __gen_presigned_url(file_name, input_body.get("content_type", DEFAULT_CONTENT_TYPE), input_body.get("expiration", 60*60)) @@ -92,7 +100,7 @@ def lambda_handler(event, context): } elif resource.startswith(EXECUTION_RESOURCE): if http_method == "POST": - output = __create_execution(event, context, email) + output = __create_execution(event, context, email, group_name) else: if resource == EXECUTION_RESOURCE: output = __list_execution(event) @@ -102,7 +110,7 @@ def lambda_handler(event, context): elif resource == DOWNLOAD_RESOURCE: output = __download_template() elif resource == INDEX_USED_SCAN_RESOURCE: - output = __index_used_scan(event) + output = __index_used_scan(event, group_name) try: return { "statusCode": 200, @@ -180,12 +188,12 @@ def __list_execution(event): return output -def __create_execution(event, context, email): +def __create_execution(event, context, email, group_name): input_body = json.loads(event["body"]) execution_detail = {} execution_detail["tableItemId"] = context.aws_request_id execution_detail["chatbotId"] = input_body.get("chatbotId") - execution_detail["groupName"] = input_body.get("chatbotId").capitalize() + execution_detail["groupName"] = group_name # execution_detail["index"] = input_body.get("index") if input_body.get("index") else f'{input_body.get("chatbotId")}-default-index' execution_detail["index"] = input_body.get("index") execution_detail["model"] = input_body.get("model") @@ -411,9 +419,8 @@ def __download_template(): ) return url -def __index_used_scan(event): +def __index_used_scan(event, group_name): input_body = json.loads(event["body"]) - group_name = input_body.get("chatbotId").capitalize() index_response = index_table.get_item( Key={ "groupName": group_name, @@ -446,4 +453,12 @@ def __index_used_scan(event): "body": json.dumps({ "result":"invalid" } - )} \ No newline at end of file + )} + +def __get_query_parameter(event, parameter_name, default_value=None): + if ( + event.get("queryStringParameters") + and parameter_name in event["queryStringParameters"] + ): + return event["queryStringParameters"][parameter_name] + return default_value \ No newline at end of file diff --git a/source/portal/src/pages/chatbotManagement/ChatbotManagement.tsx b/source/portal/src/pages/chatbotManagement/ChatbotManagement.tsx index d5356170..4026a11e 100644 --- a/source/portal/src/pages/chatbotManagement/ChatbotManagement.tsx +++ b/source/portal/src/pages/chatbotManagement/ChatbotManagement.tsx @@ -28,6 +28,7 @@ import { import useAxiosRequest from 'src/hooks/useAxiosRequest'; import { useTranslation } from 'react-i18next'; import { formatTime } from 'src/utils/utils'; +import { EMBEDDING_MODEL_LIST } from 'src/utils/const'; const ChatbotManagement: React.FC = () => { const [selectedItems, setSelectedItems] = useState([]); @@ -61,25 +62,16 @@ const ChatbotManagement: React.FC = () => { const [showDelete, setShowDelete] = useState(false); const getModelList = async (type: 'create' | 'edit') => { - setLoadingGet(true); - try { - const data = await fetchData({ - url: 'chatbot-management/embeddings', - method: 'get', - }); - const items: string[] = data; - const getModels = items.map((item) => { - return { - label: item, - value: item, - }; - }); - setModelList(getModels); - if (type === 'create') { - setModelOption(getModels[0]); - } - } catch (error: unknown) { - setLoadingGet(false); + const tempModels:{label: string; value:string}[] =[] + EMBEDDING_MODEL_LIST.forEach((item: {model_id: string; model_name: string})=>{ + tempModels.push({ + label: item.model_name, + value: item.model_id + }) + }) + setModelList(tempModels) + if (type === 'create') { + setModelOption(tempModels[0]); } }; diff --git a/source/portal/src/pages/intention/Intention.tsx b/source/portal/src/pages/intention/Intention.tsx index be1c37fb..0fc5d74c 100644 --- a/source/portal/src/pages/intention/Intention.tsx +++ b/source/portal/src/pages/intention/Intention.tsx @@ -20,6 +20,7 @@ import useAxiosRequest from 'src/hooks/useAxiosRequest'; import { useTranslation } from 'react-i18next'; import AddIntention from '../components/AddIntention'; import { useAuth } from 'react-oidc-context'; +import { EMBEDDING_MODEL_LIST } from 'src/utils/const'; const parseDate = (item: IntentionsItem) => { return item.createTime ? new Date(item.createTime) : 0; @@ -146,31 +147,14 @@ const Intention: React.FC = () => { // } const getModels = async ()=>{ - const tempModels = [{ - value: "cohere.embed-english-v3", - label: "cohere.embed-english-v3" - } - // ,{ - // value: "cohere.embed-multilingual-v3", - // label: "cohere.embed-multilingual-v3" - // } - // ,{ - // value: "amazon.titan-embed-text-v1", - // label: "amazon.titan-embed-text-v1" - // } - ,{ - value: "amazon.titan-embed-text-v1", - label: "amazon.titan-embed-text-v1" - } - //,{ - // value: "amazon.titan-embed-image-v1", - // label: "amazon.titan-embed-image-v1" - // } - , - { - value: "amazon.titan-embed-text-v2:0", - label: "amazon.titan-embed-text-v2:0" - }] + const tempModels:{label: string; value:string}[] =[] + + EMBEDDING_MODEL_LIST.forEach((item: {model_id: string; model_name: string})=>{ + tempModels.push({ + label: item.model_name, + value: item.model_id + }) + }) setModels(tempModels) setSelectedModelOption(tempModels[0]) } diff --git a/source/portal/src/utils/const.ts b/source/portal/src/utils/const.ts index 2dd77148..cc9c274b 100644 --- a/source/portal/src/utils/const.ts +++ b/source/portal/src/utils/const.ts @@ -76,3 +76,9 @@ export const DOC_INDEX_TYPE_LIST: SelectProps.Option[] = [ ]; export const DEFAULT_EMBEDDING_MODEL = "cohere.embed-english-v3" + +export const EMBEDDING_MODEL_LIST = [ + {"model_id": "amazon.titan-embed-text-v2:0", "model_name": "amazon.titan-embed-text-v2:0"}, + {"model_id": "cohere.embed-english-v3", "model_name": "cohere.embed-english-v3"}, + {"model_id": "amazon.titan-embed-text-v1", "model_name": "amazon.titan-embed-text-v1"} +]