Using the chat}>
11 |
12 | The multi-chat playground allows user to interact with up to 4 LLM
13 | and RAG workspaces combinations.
14 |
15 | Settings
16 |
17 | You can configure additional settings for the LLM via the setting
18 | action at the bottom-right. You can change the Temperature and Top P
19 | values to be used for the answer generation. You can also enable and
20 | disable streaming mode for those models that support it (the setting
21 | is ignored if the model does not support streaming). Turning on
22 | Metadata displays additional information about the answer, such as
23 | the prompts being used to interact with the LLM and the document
24 | passages that might have been retrieved from the RAG storage.
25 |
26 | Session history
27 |
28 | All individual conversations are saved and can be later accessed via
29 | the Session in the navigation
30 | bar. For example, if you have 3 chats, there will be 3 sessions
31 | saved in the history.
32 |
33 |
34 | }
35 | content={ }
36 | />
37 | );
38 | }
39 |
--------------------------------------------------------------------------------
/lib/shared/layers/python-sdk/python/genai_core/websites/sitemap.py:
--------------------------------------------------------------------------------
1 | import requests
2 | import defusedxml.ElementTree as ET
3 | import gzip
4 | import os
5 |
6 | def decompress_gzip_data(response):
7 | filename = f'/tmp/{hash(response.url)}.gzip'
8 | with open(filename, 'wb') as file:
9 | file.write(response.content)
10 | with gzip.open(filename, 'rb') as f:
11 | sitemap_xml = f.read()
12 | os.remove(filename)
13 | return sitemap_xml
14 |
15 | def extract_urls_from_sitemap(sitemap_url: str):
16 | urls = []
17 | try:
18 | response = requests.get(sitemap_url)
19 | if response.status_code != 200:
20 | print(f'Error while fetching sitemap data: {sitemap_url}')
21 | return []
22 |
23 | # Handle sitemap with gzip compression
24 | if sitemap_url.lower().endswith('gz'):
25 | sitemap = decompress_gzip_data(response)
26 | else:
27 | sitemap = response.content
28 | root = ET.fromstring(sitemap)
29 | root_tag = root.tag.lower()
30 |
31 | # if root element is sitemapindex, fetch individual sitemaps recursively
32 | if 'sitemapindex' in root_tag:
33 | for elem in root.findall("{http://www.sitemaps.org/schemas/sitemap/0.9}sitemap/{http://www.sitemaps.org/schemas/sitemap/0.9}loc"):
34 | links = extract_urls_from_sitemap(elem.text)
35 | urls.extend(links)
36 | elif 'urlset' in root_tag:
37 | for elem in root.findall("{http://www.sitemaps.org/schemas/sitemap/0.9}url/{http://www.sitemaps.org/schemas/sitemap/0.9}loc"):
38 | urls.append(elem.text)
39 | else:
40 | print(f'No valid root tag found for sitemap: {sitemap_url}')
41 | except Exception as e:
42 | print(f'Error while processing sitemaps for {sitemap_url}',e)
43 | else:
44 | return urls
45 |
--------------------------------------------------------------------------------
/lib/user-interface/react-app/src/pages/rag/workspaces/workspaces.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | BreadcrumbGroup,
3 | Header,
4 | HelpPanel,
5 | } from "@cloudscape-design/components";
6 | import useOnFollow from "../../../common/hooks/use-on-follow";
7 | import WorkspacesTable from "./workspaces-table";
8 | import BaseAppLayout from "../../../components/base-app-layout";
9 | import { CHATBOT_NAME } from "../../../common/constants";
10 | import { Link } from "react-router-dom";
11 |
12 | export default function Workspaces() {
13 | const onFollow = useOnFollow();
14 |
15 | return (
16 |
36 | }
37 | content={ }
38 | info={
39 | RAG Workspaces}>
40 |
41 | RAG workspaces are built on top of a{" "}
42 | RAG Engine.
43 |
44 |
45 | {" "}
46 | RAG engines can be modified at deployment time by running{" "}
47 |
55 | npm run config
56 |
57 | .
58 |
59 |
60 | }
61 | />
62 | );
63 | }
64 |
--------------------------------------------------------------------------------
/lib/shared/layers/python-sdk/python/genai_core/utils/comprehend.py:
--------------------------------------------------------------------------------
1 | import boto3
2 | from typing import Optional, List
3 |
4 | comprehend = boto3.client("comprehend")
5 |
6 | aws_to_pg = {
7 | # Afrikaans closely related to Dutch. Might not be accurate. Better than nothing.
8 | "af": "dutch",
9 | "ar": "arabic",
10 | "bn": "hindi",
11 | "cs": "czech",
12 | "da": "danish",
13 | "de": "german",
14 | "el": "greek",
15 | "en": "english",
16 | "es": "spanish",
17 | "fa": "persian",
18 | "fi": "finnish",
19 | "fr": "french",
20 | "he": "hebrew",
21 | "hi": "hindi",
22 | "hu": "hungarian",
23 | "id": "indonesian",
24 | "it": "italian",
25 | "nl": "dutch",
26 | "no": "norwegian",
27 | "pl": "polish",
28 | "pt": "portuguese",
29 | "ro": "romanian",
30 | "ru": "russian",
31 | "sv": "swedish",
32 | "tr": "turkish",
33 | "vi": "vietnamese",
34 | "zh": "chinese",
35 | "zh-TW": "chinese",
36 | }
37 |
38 |
39 | def comprehend_language_code_to_postgres(language_code: str) -> Optional[str]:
40 | return aws_to_pg.get(language_code, None)
41 |
42 |
43 | def get_query_language(query: str, languages: List[str]):
44 | language_name = "english"
45 | comprehend_response = comprehend.detect_dominant_language(Text=query)
46 | comprehend_languages = comprehend_response["Languages"]
47 | detected_languages = [
48 | {"code": language["LanguageCode"], "score": language["Score"]}
49 | for language in comprehend_languages
50 | ]
51 |
52 | if len(comprehend_languages) > 0:
53 | postgres_language_name = comprehend_language_code_to_postgres(
54 | comprehend_languages[0]["LanguageCode"]
55 | )
56 |
57 | if postgres_language_name is not None and postgres_language_name in languages:
58 | language_name = postgres_language_name
59 |
60 | return [language_name, detected_languages]
61 |
--------------------------------------------------------------------------------
/lib/model-interfaces/langchain/functions/request-handler/adapters/bedrock/ai21_j2.py:
--------------------------------------------------------------------------------
1 | import genai_core.clients
2 | from langchain.llms import Bedrock
3 | from langchain.prompts.prompt import PromptTemplate
4 |
5 | from ..base import ModelAdapter
6 | from genai_core.registry import registry
7 |
8 |
9 | class AI21J2Adapter(ModelAdapter):
10 | def __init__(self, model_id, *args, **kwargs):
11 | self.model_id = model_id
12 |
13 | super().__init__(*args, **kwargs)
14 |
15 | def get_llm(self, model_kwargs={}):
16 | bedrock = genai_core.clients.get_bedrock_client()
17 |
18 | params = {}
19 | if "temperature" in model_kwargs:
20 | params["temperature"] = model_kwargs["temperature"]
21 | if "topP" in model_kwargs:
22 | params["topP"] = model_kwargs["topP"]
23 | if "maxTokens" in model_kwargs:
24 | params["maxTokens"] = model_kwargs["maxTokens"]
25 |
26 | return Bedrock(
27 | client=bedrock,
28 | model_id=self.model_id,
29 | model_kwargs=params,
30 | callbacks=[self.callback_handler],
31 | )
32 |
33 | def get_prompt(self):
34 | template = """Human: The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.
35 |
36 | Current conversation:
37 | {chat_history}
38 |
39 | Question: {input}
40 |
41 | Assistant:"""
42 |
43 | input_variables = ["input", "chat_history"]
44 | prompt_template_args = {
45 | "chat_history": "{chat_history}",
46 | "input_variables": input_variables,
47 | "template": template,
48 | }
49 | prompt_template = PromptTemplate(**prompt_template_args)
50 |
51 | return prompt_template
52 |
53 |
54 | # Register the adapter
55 | registry.register(r"^bedrock.ai21.j2*", AI21J2Adapter)
56 |
--------------------------------------------------------------------------------
/lib/user-interface/react-app/src/pages/chatbot/playground/chat-playground.tsx:
--------------------------------------------------------------------------------
1 | import BaseAppLayout from "../../../components/base-app-layout";
2 | import Chat from "../../../components/chatdirect/chat";
3 |
4 | import { Link, useParams } from "react-router-dom";
5 | import { Header, HelpPanel } from "@cloudscape-design/components";
6 |
7 | export default function ChatPlayground() {
8 | const { sessionId } = useParams();
9 |
10 | return (
11 | Using the RFP Assistant}
15 | >
16 |
17 | This chat playground allows user to interact with a chosen LLM and
18 | optional RAG retriever. You can create new RAG workspaces via the{" "}
19 | Workspaces console.
20 |
21 | Settings
22 |
23 | You can configure additional settings for the LLM via the setting
24 | action at the bottom-right. You can change the Temperature and Top P
25 | values to be used for the answer generation. You can also enable and
26 | disable streaming mode for those models that support it (the setting
27 | is ignored if the model does not support streaming). Turning on
28 | Metadata displays additional information about the answer, such as
29 | the prompts being used to interact with the LLM and the document
30 | passages that might have been retrieved from the RAG storage.
31 |
32 | Session history
33 |
34 | All conversations are saved and can be later accessed via the{" "}
35 | Session in the navigation
36 | bar.
37 |
38 |
39 | }
40 | toolsWidth={300}
41 | content={ }
42 | />
43 | );
44 | }
45 |
--------------------------------------------------------------------------------
/lib/user-interface/react-app/src/pages/chatbot/playground/playground.tsx:
--------------------------------------------------------------------------------
1 | import BaseAppLayout from "../../../components/base-app-layout";
2 | import Chat from "../../../components/chatbot/chat";
3 |
4 | import { Link, useParams } from "react-router-dom";
5 | import { Header, HelpPanel } from "@cloudscape-design/components";
6 |
7 | export default function Playground() {
8 | const { sessionId } = useParams();
9 |
10 | return (
11 | Using the RFP Assistant}
15 | >
16 |
17 | This chat playground allows user to interact with a chosen LLM and
18 | upload the RFP questions as excel to generate answers. You can
19 | create new RAG workspaces via the{" "}
20 | Workspaces console.
21 |
22 | Settings
23 |
24 | You can configure additional settings for the LLM via the setting
25 | action at the bottom-right. You can change the Temperature and Top P
26 | values to be used for the answer generation. You can also enable and
27 | disable streaming mode for those models that support it (the setting
28 | is ignored if the model does not support streaming). Turning on
29 | Metadata displays additional information about the answer, such as
30 | the prompts being used to interact with the LLM and the document
31 | passages that might have been retrieved from the RAG storage.
32 |
33 | Session history
34 |
35 | All conversations are saved and can be later accessed via the{" "}
36 | Session in the navigation bar.
37 |
38 |
39 | }
40 | toolsWidth={300}
41 | content={ }
42 | />
43 | );
44 | }
45 |
--------------------------------------------------------------------------------
/lib/model-interfaces/langchain/functions/request-handler/adapters/bedrock/cohere.py:
--------------------------------------------------------------------------------
1 | import genai_core.clients
2 |
3 | from langchain.llms import Bedrock
4 | from langchain.prompts.prompt import PromptTemplate
5 |
6 | from ..base import ModelAdapter
7 | from genai_core.registry import registry
8 |
9 |
10 | class BedrockCohereCommandAdapter(ModelAdapter):
11 | def __init__(self, model_id, *args, **kwargs):
12 | self.model_id = model_id
13 |
14 | super().__init__(*args, **kwargs)
15 |
16 | def get_llm(self, model_kwargs={}):
17 | bedrock = genai_core.clients.get_bedrock_client()
18 |
19 | params = {}
20 | if "temperature" in model_kwargs:
21 | params["temperature"] = model_kwargs["temperature"]
22 | if "maxTokens" in model_kwargs:
23 | params["max_tokens"] = model_kwargs["maxTokens"]
24 | params["return_likelihoods"] = "GENERATION"
25 |
26 | return Bedrock(
27 | client=bedrock,
28 | model_id=self.model_id,
29 | model_kwargs=params,
30 | streaming=model_kwargs.get("streaming", False),
31 | callbacks=[self.callback_handler],
32 | )
33 |
34 | def get_prompt(self):
35 | template = """
36 |
37 | Human: The following is a friendly conversation between a human and an AI. If the AI does not know the answer to a question, it truthfully says it does not know.
38 |
39 | Current conversation:
40 | {chat_history}
41 |
42 | Question: {input}
43 |
44 | Assistant:"""
45 |
46 | input_variables = ["input", "chat_history"]
47 | prompt_template_args = {
48 | "chat_history": "{chat_history}",
49 | "input_variables": input_variables,
50 | "template": template,
51 | }
52 | prompt_template = PromptTemplate(**prompt_template_args)
53 |
54 | return prompt_template
55 |
56 |
57 | # Register the adapter
58 | registry.register(
59 | r"^bedrock\.cohere\.command-(text|light-text).*", BedrockCohereCommandAdapter
60 | )
61 |
--------------------------------------------------------------------------------
/lib/user-interface/react-app/src/common/api-client/sessions-client.ts:
--------------------------------------------------------------------------------
1 | import { API } from "aws-amplify";
2 | import { GraphQLQuery, GraphQLResult } from "@aws-amplify/api";
3 | import { listSessions, getSession } from "../../graphql/queries";
4 | import { deleteSession, deleteUserSessions } from "../../graphql/mutations";
5 | import {
6 | ListSessionsQuery,
7 | GetSessionQuery,
8 | DeleteSessionMutation,
9 | DeleteUserSessionsMutation,
10 | } from "../../API";
11 |
12 | export class SessionsClient {
13 | async getSessions(
14 | sessionType: string
15 | ): Promise>> {
16 | const result = await API.graphql>({
17 | query: listSessions,
18 | variables: {
19 | sessionType: sessionType,
20 | },
21 | });
22 | return result;
23 | }
24 |
25 | async getSession(
26 | sessionId: string,
27 | sessionType: string
28 | ): Promise>> {
29 | const result = await API.graphql>({
30 | query: getSession,
31 | variables: {
32 | id: sessionId,
33 | sessionType: sessionType,
34 | },
35 | });
36 | return result;
37 | }
38 |
39 | async deleteSession(
40 | sessionId: string,
41 | sessionType: string
42 | ): Promise>> {
43 | const result = await API.graphql>({
44 | query: deleteSession,
45 | variables: {
46 | id: sessionId,
47 | sessionType: sessionType,
48 | },
49 | });
50 | return result;
51 | }
52 |
53 | async deleteSessions(
54 | sessionType: string
55 | ): Promise>> {
56 | const result = await API.graphql>({
57 | query: deleteUserSessions,
58 | variables: {
59 | sessionType: sessionType,
60 | },
61 | });
62 | return result;
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/lib/chatbot-api/chatbot-dynamodb-tables/index.ts:
--------------------------------------------------------------------------------
1 | import * as cdk from "aws-cdk-lib";
2 | import { Construct } from "constructs";
3 | import * as dynamodb from "aws-cdk-lib/aws-dynamodb";
4 |
5 | export class ChatBotDynamoDBTables extends Construct {
6 | public readonly sessionsTable: dynamodb.Table;
7 | public readonly questionsTable: dynamodb.Table;
8 | public readonly bySessionIdIndex: string = "bySessionId";
9 |
10 | constructor(scope: Construct, id: string) {
11 | super(scope, id);
12 |
13 | const sessionsTable = new dynamodb.Table(this, "SessionsTable", {
14 | partitionKey: {
15 | name: "UserId",
16 | type: dynamodb.AttributeType.STRING,
17 | },
18 | sortKey: {
19 | name: "SessionType",
20 | type: dynamodb.AttributeType.STRING,
21 | },
22 | billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
23 | encryption: dynamodb.TableEncryption.AWS_MANAGED,
24 | removalPolicy: cdk.RemovalPolicy.DESTROY,
25 | pointInTimeRecovery: true,
26 | });
27 |
28 | sessionsTable.addGlobalSecondaryIndex({
29 | indexName: this.bySessionIdIndex,
30 | partitionKey: { name: "SessionId", type: dynamodb.AttributeType.STRING },
31 | });
32 |
33 | this.sessionsTable = sessionsTable;
34 |
35 | const questionsTable = new dynamodb.Table(this, "QuestionsTable", {
36 | partitionKey: {
37 | name: "QuestionId",
38 | type: dynamodb.AttributeType.STRING,
39 | },
40 | sortKey: {
41 | name: "SessionId",
42 | type: dynamodb.AttributeType.STRING,
43 | },
44 | billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
45 | encryption: dynamodb.TableEncryption.AWS_MANAGED,
46 | removalPolicy: cdk.RemovalPolicy.DESTROY,
47 | pointInTimeRecovery: true,
48 | });
49 |
50 | questionsTable.addGlobalSecondaryIndex({
51 | indexName: this.bySessionIdIndex,
52 | partitionKey: { name: "SessionId", type: dynamodb.AttributeType.STRING },
53 | });
54 |
55 | this.questionsTable = questionsTable;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/lib/model-interfaces/langchain/functions/request-handler/adapters/bedrock/titan.py:
--------------------------------------------------------------------------------
1 | import genai_core.clients
2 | from langchain.prompts.prompt import PromptTemplate
3 |
4 | from langchain.llms import Bedrock
5 |
6 | from ..base import ModelAdapter
7 | from genai_core.registry import registry
8 |
9 |
10 | class BedrockTitanAdapter(ModelAdapter):
11 | def __init__(self, model_id, *args, **kwargs):
12 | self.model_id = model_id
13 |
14 | super().__init__(*args, **kwargs)
15 |
16 | def get_llm(self, model_kwargs={}):
17 | bedrock = genai_core.clients.get_bedrock_client()
18 |
19 | params = {}
20 | if "temperature" in model_kwargs:
21 | params["temperature"] = model_kwargs["temperature"]
22 | if "topP" in model_kwargs:
23 | params["topP"] = model_kwargs["topP"]
24 | if "maxTokens" in model_kwargs:
25 | params["maxTokenCount"] = model_kwargs["maxTokens"]
26 |
27 | return Bedrock(
28 | client=bedrock,
29 | model_id=self.model_id,
30 | model_kwargs=params,
31 | streaming=model_kwargs.get("streaming", False),
32 | callbacks=[self.callback_handler],
33 | )
34 |
35 | def get_prompt(self):
36 | template = """Human: The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.
37 |
38 | Current conversation:
39 | {chat_history}
40 |
41 | Question: {input}
42 |
43 | Assistant:"""
44 |
45 | input_variables = ["input", "chat_history"]
46 | prompt_template_args = {
47 | "chat_history": "{chat_history}",
48 | "input_variables": input_variables,
49 | "template": template,
50 | }
51 | prompt_template = PromptTemplate(**prompt_template_args)
52 |
53 | return prompt_template
54 |
55 |
56 | # Register the adapter
57 | registry.register(r"^bedrock.amazon.titan-t*", BedrockTitanAdapter)
58 |
--------------------------------------------------------------------------------
/lib/sagemaker-model/hf-custom-script-model/build-function/index.py:
--------------------------------------------------------------------------------
1 | import json
2 | import logging
3 | import boto3
4 |
5 | logger = logging.getLogger()
6 | logger.setLevel(logging.INFO)
7 |
8 | codebuild = boto3.client("codebuild")
9 |
10 |
11 | def on_event(event, context):
12 | logger.info(f"Event: {json.dumps(event)}")
13 |
14 | if event["RequestType"] == "Create":
15 | logger.info("Starting the build...")
16 | response = codebuild.start_build(
17 | projectName=event["ResourceProperties"]["ProjectName"]
18 | )
19 | build_id = response["build"]["id"]
20 | logger.info(f"Build started with ID: {build_id}")
21 | return {"PhysicalResourceId": build_id, "Data": {"BuildId": build_id}}
22 |
23 | if event["RequestType"] == "Delete":
24 | logger.info("Delete event - nothing to do")
25 | return {"PhysicalResourceId": event["PhysicalResourceId"], "IsComplete": True}
26 |
27 | error_message = "Invalid request type"
28 | logger.error(error_message)
29 | raise Exception(error_message)
30 |
31 |
32 | def is_complete(event, context):
33 | logger.info(f"Event: {json.dumps(event)}")
34 | if event["RequestType"] == "Delete":
35 | logger.info("Delete event - nothing to do")
36 | return {"PhysicalResourceId": event["PhysicalResourceId"], "IsComplete": True}
37 |
38 | build_id = event["Data"]["BuildId"]
39 | logger.info(f"Checking build status for Build ID: {build_id}")
40 |
41 | response = codebuild.batch_get_builds(ids=[build_id])
42 | build = response["builds"][0]
43 | build_status = build["buildStatus"]
44 |
45 | logger.info(f"Build status: {build_status}")
46 |
47 | if build_status == "SUCCEEDED":
48 | return {"PhysicalResourceId": build_id, "IsComplete": True}
49 |
50 | if build_status in ["FAILED", "FAULT", "STOPPED", "TIMED_OUT"]:
51 | error_message = f"Build failed with status: {build_status}"
52 | logger.error(error_message)
53 | raise Exception(error_message)
54 |
55 | return {"PhysicalResourceId": build_id, "IsComplete": False}
56 |
--------------------------------------------------------------------------------
/lib/chatbot-api/functions/outgoing-message-appsync/index.ts:
--------------------------------------------------------------------------------
1 | import {
2 | BatchProcessor,
3 | EventType,
4 | processPartialResponse,
5 | } from "@aws-lambda-powertools/batch";
6 | import { Logger } from "@aws-lambda-powertools/logger";
7 | import type {
8 | SQSEvent,
9 | SQSRecord,
10 | Context,
11 | SQSBatchResponse,
12 | } from "aws-lambda";
13 | import { graphQlQuery } from "./graphql";
14 |
15 | const processor = new BatchProcessor(EventType.SQS);
16 | const logger = new Logger();
17 |
18 | const recordHandler = async (record: SQSRecord): Promise => {
19 | const payload = record.body;
20 | if (payload) {
21 | const item = JSON.parse(payload);
22 |
23 | const req = JSON.parse(item.Message);
24 | logger.debug("Processed message", req);
25 | /***
26 | * Payload format
27 | *
28 | payload: str = record.body
29 | message: dict = json.loads(payload)
30 | detail: dict = json.loads(message["Message"])
31 | logger.info(detail)
32 | user_id = detail["userId"]
33 | */
34 |
35 | const query = /* GraphQL */ `
36 | mutation Mutation {
37 | publishResponse (data: ${JSON.stringify(item.Message)}, sessionId: "${
38 | req.data.sessionId
39 | }", userId: "${req.userId}") {
40 | data
41 | sessionId
42 | userId
43 | }
44 | }
45 | `;
46 | //logger.info(query);
47 | await graphQlQuery(query);
48 | //logger.info(resp);
49 | }
50 | };
51 |
52 | export const handler = async (
53 | event: SQSEvent,
54 | context: Context
55 | ): Promise => {
56 | logger.debug("Event", { event });
57 | event.Records = event.Records.sort((a, b) => {
58 | try {
59 | const x: number = JSON.parse(JSON.parse(a.body).Message).data?.token
60 | ?.sequenceNumber;
61 | const y: number = JSON.parse(JSON.parse(b.body).Message).data?.token
62 | ?.sequenceNumber;
63 | return x - y;
64 | } catch {
65 | return 0;
66 | }
67 | });
68 | return processPartialResponse(event, recordHandler, processor, {
69 | context,
70 | });
71 | };
--------------------------------------------------------------------------------
/lib/shared/layers/python-sdk/python/genai_core/kendra/client.py:
--------------------------------------------------------------------------------
1 | import os
2 | import boto3
3 | import genai_core.types
4 | import genai_core.parameters
5 |
6 | DEFAULT_KENDRA_INDEX_ID = os.environ.get("DEFAULT_KENDRA_INDEX_ID", "")
7 | DEFAULT_KENDRA_INDEX_NAME = os.environ.get("DEFAULT_KENDRA_INDEX_NAME", "")
8 |
9 | sts_client = boto3.client("sts")
10 |
11 |
12 | def get_kendra_client_for_index(kendra_index_id: str):
13 | is_default = kendra_index_id == DEFAULT_KENDRA_INDEX_ID
14 |
15 | if is_default:
16 | kendra = boto3.client("kendra")
17 | return kendra
18 |
19 | config = genai_core.parameters.get_config()
20 | kendra_config = config.get("rag", {}).get("engines", {}).get("kendra", {})
21 | external = kendra_config.get("external", {})
22 |
23 | for kendraIndex in external:
24 | current_id = kendraIndex.get("kendraId", "")
25 | current_name = kendraIndex.get("name", "")
26 | region_name = kendraIndex.get("region")
27 | role_arn = kendraIndex.get("roleArn")
28 |
29 | if not current_id or not current_name:
30 | continue
31 |
32 | if current_id == kendra_index_id:
33 | kendra_config_data = {"service_name": "kendra"}
34 | if region_name:
35 | kendra_config_data["region_name"] = region_name
36 |
37 | if role_arn:
38 | assumed_role_object = sts_client.assume_role(
39 | RoleArn=role_arn,
40 | RoleSessionName="AssumedRoleSession",
41 | )
42 |
43 | credentials = assumed_role_object["Credentials"]
44 | kendra_config_data["aws_access_key_id"] = credentials["AccessKeyId"]
45 | kendra_config_data["aws_secret_access_key"] = credentials[
46 | "SecretAccessKey"
47 | ]
48 | kendra_config_data["aws_session_token"] = credentials["SessionToken"]
49 |
50 | kendra = boto3.client(**kendra_config_data)
51 |
52 | return kendra
53 |
54 | raise genai_core.types.CommonError(f"Could not find kendra index {kendra_index_id}")
55 |
--------------------------------------------------------------------------------
/lib/chatbot-api/chatbot-s3-buckets/index.ts:
--------------------------------------------------------------------------------
1 | import * as cdk from "aws-cdk-lib";
2 | import { Construct } from "constructs";
3 | import * as s3 from "aws-cdk-lib/aws-s3";
4 | import { NagSuppressions } from "cdk-nag";
5 |
6 | export class ChatBotS3Buckets extends Construct {
7 | public readonly filesBucket: s3.Bucket;
8 | public readonly userFeedbackBucket: s3.Bucket;
9 |
10 | constructor(scope: Construct, id: string) {
11 | super(scope, id);
12 |
13 | const logsBucket = new s3.Bucket(this, "LogsBucket", {
14 | blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
15 | removalPolicy: cdk.RemovalPolicy.DESTROY,
16 | autoDeleteObjects: true,
17 | enforceSSL: true,
18 | });
19 |
20 | const filesBucket = new s3.Bucket(this, "FilesBucket", {
21 | blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
22 | removalPolicy: cdk.RemovalPolicy.DESTROY,
23 | autoDeleteObjects: true,
24 | transferAcceleration: true,
25 | enforceSSL: true,
26 | serverAccessLogsBucket: logsBucket,
27 | cors: [
28 | {
29 | allowedHeaders: ["*"],
30 | allowedMethods: [
31 | s3.HttpMethods.PUT,
32 | s3.HttpMethods.POST,
33 | s3.HttpMethods.GET,
34 | s3.HttpMethods.HEAD,
35 | ],
36 | allowedOrigins: ["*"],
37 | exposedHeaders: ["ETag"],
38 | maxAge: 3000,
39 | },
40 | ],
41 | });
42 |
43 | const userFeedbackBucket = new s3.Bucket(this, "UserFeedbackBucket", {
44 | blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
45 | removalPolicy: cdk.RemovalPolicy.DESTROY,
46 | autoDeleteObjects: true,
47 | enforceSSL: true,
48 | serverAccessLogsBucket: logsBucket,
49 | });
50 |
51 | this.filesBucket = filesBucket;
52 | this.userFeedbackBucket = userFeedbackBucket;
53 |
54 | /**
55 | * CDK NAG suppression
56 | */
57 | NagSuppressions.addResourceSuppressions(logsBucket, [
58 | {
59 | id: "AwsSolutions-S1",
60 | reason: "Logging bucket does not require it's own access logs.",
61 | },
62 | ]);
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/cli/aws-cron-validator.ts:
--------------------------------------------------------------------------------
1 | class AWSCronError extends Error {}
2 |
3 | import {
4 | minuteRegex,
5 | hourRegex,
6 | dayOfMonthRegex,
7 | monthRegex,
8 | dayOfWeekRegex,
9 | yearRegex,
10 | } from "./aws-cron-expressions";
11 |
12 | export class AWSCronValidator {
13 | public static validate(expression: string): string {
14 | if (!expression.trim()) {
15 | throw new AWSCronError(
16 | `No parameters entered, this format is required in UTC: 0 20 ? * SUN-FRI *`
17 | );
18 | }
19 | const valueCount = expression.split(" ").length;
20 | if (valueCount !== 6) {
21 | throw new AWSCronError(
22 | `Incorrect amount of parameters in '${expression}'. 6 required, ${valueCount} provided.`
23 | );
24 | }
25 |
26 | const [minute, hour, dayOfMonth, month, dayOfWeek, year] =
27 | expression.split(" ");
28 |
29 | // special handling for Day of Month and Day of Week
30 | if (
31 | !(
32 | (dayOfMonth === "?" && dayOfWeek !== "?") ||
33 | (dayOfMonth !== "?" && dayOfWeek === "?")
34 | )
35 | ) {
36 | throw new AWSCronError(
37 | `Invalid combination of day-of-month '${dayOfMonth}' and day-of-week '${dayOfWeek}'. One must be a question mark (?)`
38 | );
39 | }
40 |
41 | if (!new RegExp(minuteRegex()).test(minute)) {
42 | throw new AWSCronError(`Invalid minute value '${minute}'.`);
43 | }
44 | if (!new RegExp(hourRegex()).test(hour)) {
45 | throw new AWSCronError(`Invalid hour value '${hour}'.`);
46 | }
47 | if (!new RegExp(dayOfMonthRegex()).test(dayOfMonth)) {
48 | throw new AWSCronError(`Invalid day-of-month value '${dayOfMonth}'.`);
49 | }
50 | if (!new RegExp(monthRegex(), "i").test(month)) {
51 | throw new AWSCronError(`Invalid month value '${month}'.`);
52 | }
53 | if (!new RegExp(dayOfWeekRegex(), "i").test(dayOfWeek)) {
54 | throw new AWSCronError(`Invalid day-of-week value '${dayOfWeek}'.`);
55 | }
56 | if (!new RegExp(yearRegex()).test(year)) {
57 | throw new AWSCronError(`Invalid year value '${year}'.`);
58 | }
59 |
60 | return expression;
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/lib/shared/layers/python-sdk/python/genai_core/opensearch/create.py:
--------------------------------------------------------------------------------
1 | from .client import get_open_search_client
2 |
3 |
4 | def create_workspace_index(workspace: dict):
5 | workspace_id = workspace["workspace_id"]
6 | index_name = workspace_id.replace("-", "")
7 | embeddings_model_dimensions = workspace["embeddings_model_dimensions"]
8 |
9 | client = get_open_search_client()
10 |
11 | ef_search = 512
12 | index_body = {
13 | "settings": {
14 | "index": {
15 | "knn": True,
16 | "knn.algo_param.ef_search": ef_search,
17 | }
18 | },
19 | "mappings": {
20 | "properties": {
21 | "content_embeddings": {
22 | "type": "knn_vector",
23 | "dimension": int(embeddings_model_dimensions),
24 | "method": {
25 | "name": "hnsw",
26 | "space_type": "l2",
27 | "engine": "nmslib",
28 | "parameters": {"ef_construction": 512, "m": 16},
29 | },
30 | },
31 | "chunk_id": {"type": "keyword"},
32 | "workspace_id": {"type": "keyword"},
33 | "document_id": {"type": "keyword"},
34 | "document_sub_id": {"type": "keyword"},
35 | "document_type": {"type": "keyword"},
36 | "document_sub_type": {"type": "keyword"},
37 | "path": {"type": "text"},
38 | "language": {"type": "keyword"},
39 | "title": {"type": "text"},
40 | "content": {"type": "text"},
41 | "content_complement": {"type": "text"},
42 | "metadata": {"type": "object"},
43 | "created_at": {
44 | "type": "date",
45 | "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis",
46 | },
47 | }
48 | },
49 | }
50 |
51 | response = client.indices.create(index_name, body=index_body)
52 |
53 | print("Created workspace index")
54 | print(response)
55 |
--------------------------------------------------------------------------------
/lib/shared/layers/python-sdk/python/genai_core/cross_encoder.py:
--------------------------------------------------------------------------------
1 | import os
2 | import json
3 | import genai_core.types
4 | import genai_core.clients
5 | import genai_core.parameters
6 | from typing import List, Optional
7 |
8 |
9 | SAGEMAKER_RAG_MODELS_ENDPOINT = os.environ.get("SAGEMAKER_RAG_MODELS_ENDPOINT")
10 |
11 |
12 | def rank_passages(
13 | model: genai_core.types.CrossEncoderModel, input: str, passages: List[str]
14 | ):
15 | input = input[:10000]
16 | passages = passages[:1000]
17 | passages = list(map(lambda x: x[:10000], passages))
18 |
19 | if model.provider == "sagemaker":
20 | return _rank_passages_sagemaker(model, input, passages)
21 |
22 | raise genai_core.typesCommonError(f"Unknown provider")
23 |
24 |
25 | def get_cross_encoder_models():
26 | config = genai_core.parameters.get_config()
27 | models = config["rag"]["crossEncoderModels"]
28 |
29 | if not SAGEMAKER_RAG_MODELS_ENDPOINT:
30 | models = list(filter(lambda x: x["provider"] != "sagemaker", models))
31 |
32 | return models
33 |
34 |
35 | def get_cross_encoder_model(
36 | provider: str, name: str
37 | ) -> Optional[genai_core.types.CrossEncoderModel]:
38 | config = genai_core.parameters.get_config()
39 | models = config["rag"]["crossEncoderModels"]
40 |
41 | for model in models:
42 | if model["provider"] == provider and model["name"] == name:
43 | return genai_core.types.CrossEncoderModel(**model)
44 |
45 | return None
46 |
47 |
48 | def _rank_passages_sagemaker(
49 | model: genai_core.types.CrossEncoderModel, input: str, passages: List[str]
50 | ):
51 | client = genai_core.clients.get_sagemaker_client()
52 |
53 | response = client.invoke_endpoint(
54 | EndpointName=SAGEMAKER_RAG_MODELS_ENDPOINT,
55 | ContentType="application/json",
56 | Body=json.dumps(
57 | {
58 | "type": "cross-encoder",
59 | "model": model.name,
60 | "input": input,
61 | "passages": passages,
62 | }
63 | ),
64 | )
65 |
66 | ret_value = json.loads(response["Body"].read().decode())
67 |
68 | return ret_value
69 |
--------------------------------------------------------------------------------
/lib/user-interface/react-app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "aws-genai-rfpassistant",
3 | "private": true,
4 | "version": "1.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "tsc && vite build",
9 | "build:dev": "tsc && cross-env NODE_ENV=development vite build",
10 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
11 | "preview": "vite preview",
12 | "format": "npx prettier --ignore-path .gitignore --write \"**/*.+(tsx|js|ts|json)\""
13 | },
14 | "dependencies": {
15 | "@aws-amplify/ui-react": "^5.3.2",
16 | "@cloudscape-design/components": "^3.0.651",
17 | "@cloudscape-design/design-tokens": "^3.0.36",
18 | "@cloudscape-design/global-styles": "^1.0.27",
19 | "@fortawesome/fontawesome-svg-core": "^6.4.2",
20 | "@fortawesome/free-solid-svg-icons": "^6.4.2",
21 | "@fortawesome/react-fontawesome": "^0.2.0",
22 | "aws-amplify": "^5.3.12",
23 | "luxon": "^3.4.3",
24 | "react": "^18.2.0",
25 | "react-dom": "^18.2.0",
26 | "react-json-view-lite": "^0.9.8",
27 | "react-markdown": "^9.0.0",
28 | "react-router-dom": "^6.15.0",
29 | "react-speech-recognition": "^3.10.0",
30 | "react-textarea-autosize": "^8.5.3",
31 | "react-use-websocket": "^4.5.0",
32 | "regenerator-runtime": "^0.14.0",
33 | "remark-gfm": "^4.0.0",
34 | "uuid": "^9.0.0"
35 | },
36 | "devDependencies": {
37 | "@types/luxon": "^3.3.2",
38 | "@types/react": "^18.2.15",
39 | "@types/react-dom": "^18.2.7",
40 | "@types/react-speech-recognition": "^3.9.2",
41 | "@types/uuid": "^9.0.3",
42 | "@types/zen-observable": "^0.8.5",
43 | "@typescript-eslint/eslint-plugin": "^6.0.0",
44 | "@typescript-eslint/parser": "^6.0.0",
45 | "@vitejs/plugin-react": "^4.0.3",
46 | "autoprefixer": "^10.4.14",
47 | "cross-env": "^7.0.3",
48 | "esbuild": "0.21.5",
49 | "eslint": "^8.45.0",
50 | "eslint-plugin-react-hooks": "^4.6.0",
51 | "eslint-plugin-react-refresh": "^0.4.3",
52 | "postcss": "^8.4.27",
53 | "sass": "^1.65.1",
54 | "typescript": "^5.0.2",
55 | "vite": "^4.5.3",
56 | "zen-observable": "^0.10.0"
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/bin/config.ts:
--------------------------------------------------------------------------------
1 | import { SupportedRegion, SystemConfig } from "../lib/shared/types";
2 | import { existsSync, readFileSync } from "fs";
3 |
4 | export function getConfig(): SystemConfig {
5 | if (existsSync("./bin/config.json")) {
6 | return JSON.parse(readFileSync("./bin/config.json").toString("utf8"));
7 | }
8 | // Default config
9 | return {
10 | prefix: "",
11 | /* vpc: {
12 | vpcId: "vpc-00000000000000000",
13 | createVpcEndpoints: true,
14 | },*/
15 | privateWebsite: false,
16 | companyName: "AnyCompany",
17 | certificate: "",
18 | cfGeoRestrictEnable: false,
19 | cfGeoRestrictList: [],
20 | bedrock: {
21 | enabled: true,
22 | region: SupportedRegion.US_EAST_1,
23 | },
24 | llms: {
25 | // sagemaker: [SupportedSageMakerModels.FalconLite]
26 | sagemaker: [],
27 | },
28 | rag: {
29 | enabled: true,
30 | engines: {
31 | aurora: {
32 | enabled: false,
33 | },
34 | opensearch: {
35 | enabled: true,
36 | },
37 | kendra: {
38 | enabled: false,
39 | createIndex: false,
40 | enterprise: false,
41 | },
42 | },
43 | embeddingsModels: [
44 | {
45 | provider: "bedrock",
46 | name: "amazon.titan-embed-text-v1",
47 | dimensions: 1536,
48 | },
49 | //Support for inputImage is not yet implemented for amazon.titan-embed-image-v1
50 | {
51 | provider: "bedrock",
52 | name: "amazon.titan-embed-image-v1",
53 | dimensions: 1024,
54 | },
55 | {
56 | provider: "bedrock",
57 | name: "cohere.embed-english-v3",
58 | dimensions: 1024,
59 | },
60 | {
61 | provider: "bedrock",
62 | name: "cohere.embed-multilingual-v3",
63 | dimensions: 1024,
64 | default: true,
65 | },
66 | ],
67 | crossEncoderModels: [
68 | {
69 | provider: "sagemaker",
70 | name: "cross-encoder/ms-marco-MiniLM-L-12-v2",
71 | default: true,
72 | },
73 | ],
74 | },
75 | };
76 | }
77 |
78 | export const config: SystemConfig = getConfig();
79 |
--------------------------------------------------------------------------------
/lib/shared/layers/python-sdk/python/genai_core/kendra/data_sync.py:
--------------------------------------------------------------------------------
1 | import os
2 | import genai_core.types
3 | import genai_core.workspaces
4 | from .client import get_kendra_client_for_index
5 |
6 | DEFAULT_KENDRA_S3_DATA_SOURCE_ID = os.environ.get("DEFAULT_KENDRA_S3_DATA_SOURCE_ID")
7 |
8 |
9 | def start_kendra_data_sync(workspace_id: str):
10 | workspace = genai_core.workspaces.get_workspace(workspace_id=workspace_id)
11 |
12 | if not workspace:
13 | raise genai_core.types.CommonError(f"Workspace {workspace_id} not found")
14 |
15 | if workspace["engine"] != "kendra":
16 | raise genai_core.types.CommonError(
17 | f"Workspace {workspace_id} is not a kendra workspace"
18 | )
19 |
20 | if workspace["kendra_index_external"]:
21 | raise genai_core.types.CommonError(
22 | f"Workspace {workspace_id} is an external kendra workspace"
23 | )
24 |
25 | kendra_index_id = workspace["kendra_index_id"]
26 | kendra = get_kendra_client_for_index(kendra_index_id)
27 |
28 | response = kendra.start_data_source_sync_job(
29 | Id=DEFAULT_KENDRA_S3_DATA_SOURCE_ID, IndexId=kendra_index_id
30 | )
31 |
32 | print(response)
33 |
34 |
35 | def kendra_is_syncing(workspace_id: str):
36 | workspace = genai_core.workspaces.get_workspace(workspace_id=workspace_id)
37 |
38 | if not workspace:
39 | raise genai_core.types.CommonError(f"Workspace {workspace_id} not found")
40 |
41 | if workspace["engine"] != "kendra":
42 | raise genai_core.types.CommonError(
43 | f"Workspace {workspace_id} is not a kendra workspace"
44 | )
45 |
46 | if workspace["kendra_index_external"]:
47 | return False
48 |
49 | kendra_index_id = workspace["kendra_index_id"]
50 | kendra = get_kendra_client_for_index(kendra_index_id)
51 |
52 | response = kendra.list_data_source_sync_jobs(
53 | IndexId=kendra_index_id, Id=DEFAULT_KENDRA_S3_DATA_SOURCE_ID, MaxResults=5
54 | )
55 |
56 | ret_value = False
57 | for item in response["History"]:
58 | status = item["Status"]
59 |
60 | if status == "SYNCING" or status == "SYNCING_INDEXING":
61 | ret_value = True
62 | break
63 |
64 | return ret_value
65 |
--------------------------------------------------------------------------------
/lib/user-interface/react-app/src/common/helpers/embeddings-model-helper.ts:
--------------------------------------------------------------------------------
1 | import { SelectProps } from "@cloudscape-design/components";
2 | import { EmbeddingModel } from "../../API";
3 |
4 | export abstract class EmbeddingsModelHelper {
5 | static getSelectOption(model?: string): SelectProps.Option | null {
6 | if (!model) return null;
7 | const [, dimensions, name] = model.split("::") ?? [];
8 | if (!name) return null;
9 |
10 | return {
11 | label: `${name} (${dimensions})`,
12 | value: model,
13 | };
14 | }
15 |
16 | static parseValue(value?: string) {
17 | const retValue = {
18 | provider: "",
19 | dimensions: 0,
20 | name: "",
21 | };
22 |
23 | if (!value) return retValue;
24 | const [provider, dimensionsStr, name] = value.split("::") ?? [];
25 | let dimensions = parseInt(dimensionsStr);
26 | if (isNaN(dimensions)) dimensions = 0;
27 |
28 | return {
29 | provider,
30 | dimensions,
31 | name,
32 | };
33 | }
34 |
35 | static getSelectOptions(embeddingsModels: EmbeddingModel[]) {
36 | const modelsMap = new Map();
37 | embeddingsModels.forEach((model) => {
38 | let items = modelsMap.get(model.provider);
39 | if (!items) {
40 | items = [];
41 | modelsMap.set(model.provider, [model]);
42 | } else {
43 | modelsMap.set(model.provider, [...items, model]);
44 | }
45 | });
46 |
47 | const keys = [...modelsMap.keys()];
48 | keys.sort((a, b) => a.localeCompare(b));
49 |
50 | const options: SelectProps.OptionGroup[] = keys.map((key) => {
51 | const items = modelsMap.get(key);
52 | items?.sort((a, b) => a.name.localeCompare(b.name));
53 |
54 | let label = key;
55 | if (label === "sagemaker") label = "SageMaker";
56 | else if (label === "bedrock") label = "Bedrock";
57 | else if (label === "openai") label = "OpenAI";
58 |
59 | return {
60 | label,
61 | options:
62 | items?.map((item) => ({
63 | label: `${item.name} (${item.dimensions})`,
64 | value: `${item.provider}::${item.dimensions}::${item.name}`,
65 | })) ?? [],
66 | };
67 | });
68 |
69 | return options;
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/cli/aws-cron-expressions.ts:
--------------------------------------------------------------------------------
1 | export const minuteExp = `(0?[0-9]|[1-5][0-9])`; // [0]0-59
2 | export const hourExp = `(0?[0-9]|1[0-9]|2[0-3])`; // [0]0-23
3 | export const dayOfMonthExp = `(0?[1-9]|[1-2][0-9]|3[0-1])`; // [0]1-31
4 | export const monthExp = `(0?[1-9]|1[0-2]|JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)`; // [0]1-12 or JAN-DEC
5 | export const dayOfWeekExp = `([1-7]|SUN|MON|TUE|WED|THU|FRI|SAT)`; // 1-7 or SAT-SUN
6 | export const yearExp = `((19[8-9][0-9])|(2[0-1][0-9][0-9]))`; // 1980-2199
7 | export const numbers = `([0-9]*[1-9][0-9]*)`; // whole numbers greater than 0
8 |
9 | export function dayOfWeekHash(): string {
10 | return `(${dayOfWeekExp}#[1-5])`; // add hash expression to enable supported use case
11 | }
12 |
13 | function rangeRegex(values: string): string {
14 | return `(${values}|(\\*\\-${values})|(${values}\\-${values})|(${values}\\-\\*))`;
15 | }
16 |
17 | function listRangeRegex(values: string): string {
18 | const range = rangeRegex(values);
19 | return `(${range}(\\,${range})*)`;
20 | }
21 |
22 | function slashRegex(values: string): string {
23 | const range = rangeRegex(values);
24 | return `((\\*|${range}|${values})\\/${numbers})`;
25 | }
26 |
27 | function listSlashRegex(values: string): string {
28 | const slash = slashRegex(values);
29 | const slashOrRange = `(${slash}|${rangeRegex(values)})`;
30 | return `(${slashOrRange}(\\,${slashOrRange})*)`;
31 | }
32 |
33 | function commonRegex(values: string): string {
34 | return `(${listRangeRegex(values)}|\\*|${listSlashRegex(values)})`;
35 | }
36 |
37 | export function minuteRegex(): string {
38 | return `^(${commonRegex(minuteExp)})$`;
39 | }
40 |
41 | export function hourRegex(): string {
42 | return `^(${commonRegex(hourExp)})$`;
43 | }
44 |
45 | export function dayOfMonthRegex(): string {
46 | return `^(${commonRegex(dayOfMonthExp)}|\\?|L|LW|${dayOfMonthExp}W)$`;
47 | }
48 |
49 | export function monthRegex(): string {
50 | return `^(${commonRegex(monthExp)})$`;
51 | }
52 |
53 | export function dayOfWeekRegex(): string {
54 | const rangeList = listRangeRegex(dayOfWeekExp);
55 | return `^(${rangeList}|\\*|\\?|${dayOfWeekExp}L|L|L-[1-7]|${dayOfWeekHash()})$`;
56 | }
57 |
58 | export function yearRegex(): string {
59 | return `^(${commonRegex(yearExp)})$`;
60 | }
61 |
--------------------------------------------------------------------------------
/lib/model-interfaces/langchain/functions/request-handler/adapters/bedrock/llama2_chat.py:
--------------------------------------------------------------------------------
1 | import genai_core.clients
2 |
3 | # from langchain.llms import Bedrock (pending https://github.com/langchain-ai/langchain/issues/13316)
4 | from .base import Bedrock
5 |
6 | from langchain.prompts.prompt import PromptTemplate
7 |
8 |
9 | from ..shared.meta.llama2_chat import (
10 | Llama2ChatPromptTemplate,
11 | Llama2ChatQAPromptTemplate,
12 | Llama2ChatCondensedQAPromptTemplate,
13 | )
14 | from ..shared.meta.llama2_chat import Llama2ConversationBufferMemory
15 |
16 | from ..base import ModelAdapter
17 | from genai_core.registry import registry
18 |
19 |
20 | class BedrockMetaLLama2ChatAdapter(ModelAdapter):
21 | def __init__(self, model_id, *args, **kwargs):
22 | self.model_id = model_id
23 |
24 | super().__init__(*args, **kwargs)
25 |
26 | def get_memory(self, output_key=None, return_messages=False):
27 | return Llama2ConversationBufferMemory(
28 | memory_key="chat_history",
29 | chat_memory=self.chat_history,
30 | return_messages=return_messages,
31 | output_key=output_key,
32 | )
33 |
34 | def get_llm(self, model_kwargs={}):
35 | bedrock = genai_core.clients.get_bedrock_client()
36 |
37 | params = {}
38 | if "temperature" in model_kwargs:
39 | params["temperature"] = model_kwargs["temperature"]
40 | if "topP" in model_kwargs:
41 | params["top_p"] = model_kwargs["topP"]
42 | if "maxTokens" in model_kwargs:
43 | params["max_gen_len"] = model_kwargs["maxTokens"]
44 |
45 | return Bedrock(
46 | client=bedrock,
47 | model_id=self.model_id,
48 | model_kwargs=params,
49 | streaming=model_kwargs.get("streaming", False),
50 | callbacks=[self.callback_handler],
51 | )
52 |
53 | def get_prompt(self):
54 | return Llama2ChatPromptTemplate
55 |
56 | def get_qa_prompt(self):
57 | return Llama2ChatQAPromptTemplate
58 |
59 | def get_condense_question_prompt(self):
60 | return Llama2ChatCondensedQAPromptTemplate
61 |
62 |
63 | # Register the adapter
64 | registry.register(
65 | r"^bedrock.meta.llama2-.*-chat.*",
66 | BedrockMetaLLama2ChatAdapter,
67 | )
68 |
--------------------------------------------------------------------------------
/lib/sagemaker-model/container-images.ts:
--------------------------------------------------------------------------------
1 | // https://github.com/aws/deep-learning-containers/blob/master/available_images.md
2 | export class ContainerImages {
3 | /*
4 | HF_PYTORCH_INFERENCE
5 | https://github.com/aws/sagemaker-python-sdk/blob/master/src/sagemaker/image_uri_config/huggingface.json
6 | */
7 | static readonly HF_PYTORCH_INFERENCE_4_26_0 =
8 | "huggingface-pytorch-inference:1.13.1-transformers4.26.0-gpu-py39-cu117-ubuntu20.04";
9 | static readonly HF_PYTORCH_INFERENCE_4_28_1 =
10 | "huggingface-pytorch-inference:2.0.0-transformers4.28.1-gpu-py310-cu118-ubuntu20.04";
11 | static readonly HF_PYTORCH_INFERENCE_LATEST =
12 | ContainerImages.HF_PYTORCH_INFERENCE_4_28_1;
13 | /*
14 | HF_PYTORCH_LLM_TGI_INFERENCE
15 | https://github.com/aws/sagemaker-python-sdk/blob/master/src/sagemaker/image_uri_config/huggingface-llm.json
16 | */
17 | static readonly HF_PYTORCH_LLM_TGI_INFERENCE_0_6_0 =
18 | "huggingface-pytorch-tgi-inference:2.0.0-tgi0.6.0-gpu-py39-cu118-ubuntu20.04";
19 | static readonly HF_PYTORCH_LLM_TGI_INFERENCE_0_8_2 =
20 | "huggingface-pytorch-tgi-inference:2.0.0-tgi0.8.2-gpu-py39-cu118-ubuntu20.04";
21 | static readonly HF_PYTORCH_LLM_TGI_INFERENCE_0_9_3 =
22 | "huggingface-pytorch-tgi-inference:2.0.1-tgi0.9.3-gpu-py39-cu118-ubuntu20.04";
23 | static readonly HF_PYTORCH_LLM_TGI_INFERENCE_1_0_3 =
24 | "huggingface-pytorch-tgi-inference:2.0.1-tgi1.0.3-gpu-py39-cu118-ubuntu20.04";
25 | static readonly HF_PYTORCH_LLM_TGI_INFERENCE_1_1_0 =
26 | "huggingface-pytorch-tgi-inference:2.0.1-tgi1.1.0-gpu-py39-cu118-ubuntu20.04";
27 | static readonly HF_PYTORCH_LLM_TGI_INFERENCE_1_3_3 =
28 | "huggingface-pytorch-tgi-inference:2.1.1-tgi1.3.3-gpu-py310-cu121-ubuntu20.04";
29 | static readonly HF_PYTORCH_LLM_TGI_INFERENCE_LATEST =
30 | ContainerImages.HF_PYTORCH_LLM_TGI_INFERENCE_1_1_0;
31 | /*
32 | DJL_INFERENCE_DEEPSPEED
33 | https://github.com/aws/sagemaker-python-sdk/blob/master/src/sagemaker/image_uri_config/djl-deepspeed.json
34 | */
35 | static readonly DJL_INFERENCE_DEEPSPEED_0_8_3 =
36 | "djl-inference:0.22.1-deepspeed0.8.3-cu118";
37 | static readonly DJL_INFERENCE_DEEPSPEED_0_9_2 =
38 | "djl-inference:0.22.1-deepspeed0.9.2-cu118";
39 | static readonly DJL_INFERENCE_DEEPSPEED_LATEST =
40 | ContainerImages.DJL_INFERENCE_DEEPSPEED_0_9_2;
41 | }
42 |
--------------------------------------------------------------------------------
/lib/user-interface/react-app/src/pages/rag/create-workspace/cross-encoder-selector-field.tsx:
--------------------------------------------------------------------------------
1 | import { useContext, useEffect, useState } from "react";
2 | import { ApiClient } from "../../../common/api-client/api-client";
3 | import { LoadingStatus } from "../../../common/types";
4 | import { AppContext } from "../../../common/app-context";
5 | import { OptionsHelper } from "../../../common/helpers/options-helper";
6 | import { Select, SelectProps } from "@cloudscape-design/components";
7 | import { CrossEncoderData } from "../../../API";
8 | import { Utils } from "../../../common/utils";
9 |
10 | interface CrossEncoderSelectorProps {
11 | submitting: boolean;
12 | onChange: (data: Partial<{ crossEncoderModel: SelectProps.Option }>) => void;
13 | selectedModel: SelectProps.Option | null;
14 | errors: Record;
15 | }
16 |
17 | export function CrossEncoderSelectorField(props: CrossEncoderSelectorProps) {
18 | const appContext = useContext(AppContext);
19 | const [crossEncoderModelsStatus, setCrossEncoderModelsStatus] =
20 | useState("loading");
21 | const [crossEncoderModels, setCrossEncoderModels] = useState<
22 | CrossEncoderData[]
23 | >([]);
24 |
25 | useEffect(() => {
26 | if (!appContext) return;
27 |
28 | (async () => {
29 | const apiClient = new ApiClient(appContext);
30 | try {
31 | const result = await apiClient.crossEncoders.getModels();
32 |
33 | setCrossEncoderModels(result.data?.listCrossEncoders!);
34 | setCrossEncoderModelsStatus("finished");
35 | } catch (error) {
36 | console.error(Utils.getErrorMessage(error));
37 | setCrossEncoderModels([]);
38 | setCrossEncoderModelsStatus("error");
39 | }
40 | })();
41 | }, [appContext]);
42 |
43 | const crossEncoderModelOptions =
44 | OptionsHelper.getSelectOptionGroups(crossEncoderModels);
45 |
46 | return (
47 |
56 | props.onChange({ crossEncoderModel: selectedOption })
57 | }
58 | />
59 | );
60 | }
61 |
--------------------------------------------------------------------------------
/cdk.json:
--------------------------------------------------------------------------------
1 | {
2 | "app": "npx ts-node --prefer-ts-exts bin/aws-genai-rfpassistant.ts",
3 | "watch": {
4 | "include": ["**"],
5 | "exclude": [
6 | "README.md",
7 | "cdk*.json",
8 | "**/*.d.ts",
9 | "**/*.js",
10 | "tsconfig.json",
11 | "package*.json",
12 | "yarn.lock",
13 | "node_modules",
14 | "test"
15 | ]
16 | },
17 | "context": {
18 | "@aws-cdk/aws-lambda:recognizeLayerVersion": true,
19 | "@aws-cdk/core:checkSecretUsage": true,
20 | "@aws-cdk/core:target-partitions": ["aws", "aws-cn"],
21 | "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
22 | "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true,
23 | "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true,
24 | "@aws-cdk/aws-iam:minimizePolicies": true,
25 | "@aws-cdk/core:validateSnapshotRemovalPolicy": true,
26 | "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true,
27 | "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true,
28 | "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true,
29 | "@aws-cdk/aws-apigateway:disableCloudWatchRole": true,
30 | "@aws-cdk/core:enablePartitionLiterals": true,
31 | "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true,
32 | "@aws-cdk/aws-iam:standardizedServicePrincipals": true,
33 | "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true,
34 | "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true,
35 | "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true,
36 | "@aws-cdk/aws-route53-patters:useCertificate": true,
37 | "@aws-cdk/customresources:installLatestAwsSdkDefault": false,
38 | "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true,
39 | "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true,
40 | "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true,
41 | "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true,
42 | "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true,
43 | "@aws-cdk/aws-redshift:columnId": true,
44 | "@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true,
45 | "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true,
46 | "@aws-cdk/aws-apigateway:requestValidatorUniqueId": true,
47 | "@aws-cdk/aws-kms:aliasNameRef": true,
48 | "@aws-cdk/core:includePrefixInUniqueNameGeneration": true
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/lib/user-interface/react-app/src/common/helpers/storage-helper.ts:
--------------------------------------------------------------------------------
1 | import { Mode, applyMode } from "@cloudscape-design/global-styles";
2 | import { NavigationPanelState } from "../types";
3 |
4 | const PREFIX = "aws-genai-llm-chatbot";
5 | const THEME_STORAGE_NAME = `${PREFIX}-theme`;
6 | const SELECTED_MODEL_STORAGE_NAME = `${PREFIX}-selected-model`;
7 | const SELECTED_WORKSPACE_STORAGE_NAME = `${PREFIX}-selected-workspace`;
8 | const NAVIGATION_PANEL_STATE_STORAGE_NAME = `${PREFIX}-navigation-panel-state`;
9 |
10 | export abstract class StorageHelper {
11 | static getTheme() {
12 | const value = localStorage.getItem(THEME_STORAGE_NAME) ?? Mode.Light;
13 | const theme = value === Mode.Dark ? Mode.Dark : Mode.Light;
14 |
15 | return theme;
16 | }
17 |
18 | static applyTheme(theme: Mode) {
19 | localStorage.setItem(THEME_STORAGE_NAME, theme);
20 | applyMode(theme);
21 |
22 | document.documentElement.style.setProperty(
23 | "--app-color-scheme",
24 | theme === Mode.Dark ? "dark" : "light"
25 | );
26 |
27 | return theme;
28 | }
29 |
30 | static getNavigationPanelState(): NavigationPanelState {
31 | const value =
32 | localStorage.getItem(NAVIGATION_PANEL_STATE_STORAGE_NAME) ??
33 | JSON.stringify({
34 | collapsed: true,
35 | });
36 |
37 | let state: NavigationPanelState | null = null;
38 | try {
39 | state = JSON.parse(value);
40 | } catch {
41 | state = {};
42 | }
43 |
44 | return state ?? {};
45 | }
46 |
47 | static setNavigationPanelState(state: Partial) {
48 | const currentState = this.getNavigationPanelState();
49 | const newState = { ...currentState, ...state };
50 | const stateStr = JSON.stringify(newState);
51 | localStorage.setItem(NAVIGATION_PANEL_STATE_STORAGE_NAME, stateStr);
52 |
53 | return newState;
54 | }
55 |
56 | static getSelectedLLM() {
57 | const value = localStorage.getItem(SELECTED_MODEL_STORAGE_NAME) ?? null;
58 |
59 | return value;
60 | }
61 |
62 | static setSelectedLLM(model: string) {
63 | localStorage.setItem(SELECTED_MODEL_STORAGE_NAME, model);
64 | }
65 |
66 | static getSelectedWorkspaceId() {
67 | const value = localStorage.getItem(SELECTED_WORKSPACE_STORAGE_NAME) ?? null;
68 |
69 | return value;
70 | }
71 |
72 | static setSelectedWorkspaceId(workspaceId: string) {
73 | localStorage.setItem(SELECTED_WORKSPACE_STORAGE_NAME, workspaceId);
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/lib/user-interface/react-app/src/components/global-header.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | ButtonDropdownProps,
3 | TopNavigation,
4 | } from "@cloudscape-design/components";
5 | import { Mode } from "@cloudscape-design/global-styles";
6 | import { useEffect, useState } from "react";
7 | import { StorageHelper } from "../common/helpers/storage-helper";
8 | import { Auth } from "aws-amplify";
9 | import useOnFollow from "../common/hooks/use-on-follow";
10 | import { CHATBOT_NAME } from "../common/constants";
11 |
12 | export default function GlobalHeader() {
13 | const onFollow = useOnFollow();
14 | const [userName, setUserName] = useState(null);
15 | const [theme, setTheme] = useState(StorageHelper.getTheme());
16 |
17 | useEffect(() => {
18 | (async () => {
19 | const result = await Auth.currentUserInfo();
20 |
21 | if (!result || Object.keys(result).length === 0) {
22 | Auth.signOut();
23 | return;
24 | }
25 |
26 | const userName = result?.attributes?.email;
27 | setUserName(userName);
28 | })();
29 | }, []);
30 |
31 | const onChangeThemeClick = () => {
32 | if (theme === Mode.Dark) {
33 | setTheme(StorageHelper.applyTheme(Mode.Light));
34 | } else {
35 | setTheme(StorageHelper.applyTheme(Mode.Dark));
36 | }
37 | };
38 |
39 | const onUserProfileClick = ({
40 | detail,
41 | }: {
42 | detail: ButtonDropdownProps.ItemClickDetails;
43 | }) => {
44 | if (detail.id === "signout") {
45 | Auth.signOut();
46 | }
47 | };
48 |
49 | return (
50 |
54 |
80 |
81 | );
82 | }
83 |
--------------------------------------------------------------------------------
/lib/shared/layers/python-sdk/python/genai_core/opensearch/chunks.py:
--------------------------------------------------------------------------------
1 | from typing import List, Optional
2 | from .client import get_open_search_client
3 |
4 |
5 | def add_chunks_open_search(
6 | workspace_id: str,
7 | document_id: str,
8 | document_sub_id: Optional[str],
9 | document_type: str,
10 | document_sub_type: Optional[str],
11 | path: Optional[str],
12 | title: Optional[str],
13 | chunk_ids: List[str],
14 | chunk_embeddings: List[int],
15 | chunks: List[str],
16 | chunk_complements: List[str],
17 | replace: bool,
18 | ):
19 | index_name = workspace_id.replace("-", "")
20 | complements_len = len(chunk_complements) if chunk_complements else 0
21 | removed_vectors = 0
22 |
23 | client = get_open_search_client()
24 |
25 | if replace:
26 | removed_vectors = clean_chunks_open_search(workspace_id, document_id)
27 |
28 | for idx in range(len(chunk_ids)):
29 | chunk_id = chunk_ids[idx]
30 | content = chunks[idx]
31 | content_complement = chunk_complements[idx] if idx < complements_len else None
32 |
33 | add_body = {
34 | "chunk_id": chunk_id,
35 | "workspace_id": workspace_id,
36 | "document_id": document_id,
37 | "document_sub_id": document_sub_id,
38 | "document_type": document_type,
39 | "document_sub_type": document_sub_type,
40 | "path": path,
41 | "title": title,
42 | "content": content,
43 | "content_complement": content_complement,
44 | "content_embeddings": chunk_embeddings[idx],
45 | }
46 |
47 | client.index(index=index_name, body=add_body)
48 |
49 | return {"removed_vectors": removed_vectors, "added_vectors": len(chunk_ids)}
50 |
51 |
52 | def clean_chunks_open_search(workspace_id: str, document_id: str):
53 | index_name = workspace_id.replace("-", "")
54 | client = get_open_search_client()
55 |
56 | query = {
57 | "query": {
58 | "bool": {
59 | "must": [
60 | {"term": {"workspace_id": workspace_id}},
61 | {"term": {"document_id": document_id}},
62 | ]
63 | }
64 | }
65 | }
66 |
67 | response = client.search(index=index_name, body=query)
68 | docs = response["hits"]["hits"]
69 | removed_vectors = len(docs)
70 |
71 | for doc in docs:
72 | client.delete(index=index_name, id=doc["_id"], ignore=[400, 404])
73 |
74 | return removed_vectors
75 |
--------------------------------------------------------------------------------
/lib/user-interface/react-app/src/pages/rag/create-workspace/embeddings-selector-field.tsx:
--------------------------------------------------------------------------------
1 | import { FormField, Select, SelectProps } from "@cloudscape-design/components";
2 | import { LoadingStatus } from "../../../common/types";
3 | import { EmbeddingsModelHelper } from "../../../common/helpers/embeddings-model-helper";
4 | import { useContext, useEffect, useState } from "react";
5 | import { ApiClient } from "../../../common/api-client/api-client";
6 | import { AppContext } from "../../../common/app-context";
7 | import { EmbeddingModel } from "../../../API";
8 | import { Utils } from "../../../common/utils";
9 |
10 | interface EmbeddingsSelectionProps {
11 | submitting: boolean;
12 | selectedModel: SelectProps.Option | null;
13 | onChange: (data: Partial<{ embeddingsModel: SelectProps.Option }>) => void;
14 | errors: Record;
15 | }
16 |
17 | export default function EmbeddingSelector(props: EmbeddingsSelectionProps) {
18 | const appContext = useContext(AppContext);
19 | const [embeddingsModelsStatus, setEmbeddingsModelsStatus] =
20 | useState("loading");
21 | const [embeddingsModels, setEmbeddingsModels] = useState(
22 | []
23 | );
24 |
25 | useEffect(() => {
26 | if (!appContext?.config) return;
27 |
28 | (async () => {
29 | const apiClient = new ApiClient(appContext);
30 | try {
31 | const result = await apiClient.embeddings.getModels();
32 |
33 | setEmbeddingsModels(result.data!.listEmbeddingModels);
34 | setEmbeddingsModelsStatus("finished");
35 | } catch (error) {
36 | console.error(Utils.getErrorMessage(error));
37 | setEmbeddingsModelsStatus("error");
38 | }
39 | })();
40 | }, [appContext]);
41 |
42 | const embeddingsModelOptions =
43 | EmbeddingsModelHelper.getSelectOptions(embeddingsModels);
44 |
45 | return (
46 |
50 |
59 | props.onChange({ embeddingsModel: selectedOption })
60 | }
61 | />
62 |
63 | );
64 | }
65 |
--------------------------------------------------------------------------------
/lib/sagemaker-model/deploy-package-model.ts:
--------------------------------------------------------------------------------
1 | import * as iam from "aws-cdk-lib/aws-iam";
2 | import * as sagemaker from "aws-cdk-lib/aws-sagemaker";
3 | import { Construct } from "constructs";
4 |
5 | import { SageMakerModelProps, ModelPackageConfig } from "./types";
6 | import { NagSuppressions } from "cdk-nag";
7 |
8 | export function deployPackageModel(
9 | scope: Construct,
10 | props: SageMakerModelProps,
11 | modelConfig: ModelPackageConfig
12 | ) {
13 | const { region } = props;
14 | const {
15 | modelId,
16 | instanceType,
17 | containerStartupHealthCheckTimeoutInSeconds = 900,
18 | } = modelConfig;
19 |
20 | const executionRole = new iam.Role(scope, "SageMakerExecutionRole", {
21 | assumedBy: new iam.ServicePrincipal("sagemaker.amazonaws.com"),
22 | managedPolicies: [
23 | iam.ManagedPolicy.fromAwsManagedPolicyName("AmazonSageMakerFullAccess"),
24 | ],
25 | });
26 |
27 | const modelPackageMapping = modelConfig.packages(scope);
28 | const modelPackageName = modelPackageMapping.findInMap(region, "arn");
29 |
30 | const model = new sagemaker.CfnModel(scope, "Model", {
31 | executionRoleArn: executionRole.roleArn,
32 | enableNetworkIsolation: true,
33 | primaryContainer: {
34 | modelPackageName,
35 | },
36 | });
37 |
38 | const endpointConfig = new sagemaker.CfnEndpointConfig(
39 | scope,
40 | "EndpointConfig",
41 | {
42 | productionVariants: [
43 | {
44 | instanceType,
45 | initialVariantWeight: 1,
46 | initialInstanceCount: 1,
47 | variantName: "AllTraffic",
48 | modelName: model.getAtt("ModelName").toString(),
49 | containerStartupHealthCheckTimeoutInSeconds,
50 | },
51 | ],
52 | }
53 | );
54 |
55 | endpointConfig.addDependency(model);
56 |
57 | const endpoint = new sagemaker.CfnEndpoint(scope, modelId, {
58 | endpointConfigName: endpointConfig.getAtt("EndpointConfigName").toString(),
59 | endpointName: modelId.split("/").join("-").split(".").join("-"),
60 | });
61 |
62 | endpoint.addDependency(endpointConfig);
63 |
64 | /**
65 | * CDK NAG suppression
66 | */
67 | NagSuppressions.addResourceSuppressions(executionRole, [
68 | {
69 | id: "AwsSolutions-IAM4",
70 | reason: "Gives user ability to deploy and delete endpoints from the UI.",
71 | },
72 | {
73 | id: "AwsSolutions-IAM5",
74 | reason: "Gives user ability to deploy and delete endpoints from the UI.",
75 | },
76 | ]);
77 |
78 | return { model, endpoint };
79 | }
80 |
--------------------------------------------------------------------------------
/lib/user-interface/react-app/src/common/helpers/options-helper.ts:
--------------------------------------------------------------------------------
1 | import { SelectProps } from "@cloudscape-design/components";
2 |
3 | export abstract class OptionsHelper {
4 | static getSelectOption(model?: string): SelectProps.Option | null {
5 | if (!model) return null;
6 | const [, name] = model.split("::") ?? [];
7 | if (!name) return null;
8 |
9 | return {
10 | label: name,
11 | value: model,
12 | };
13 | }
14 |
15 | static parseValue(value?: string) {
16 | const retValue = {
17 | provider: "",
18 | name: "",
19 | };
20 |
21 | try {
22 | if (!value) return retValue;
23 | const [provider, name] = value.split("::") ?? [];
24 |
25 | return {
26 | provider,
27 | name,
28 | };
29 | } catch (error) {
30 | console.error(error);
31 | return retValue;
32 | }
33 | }
34 |
35 | static getSelectOptionGroups(
36 | data: T[]
37 | ) {
38 | const modelsMap = new Map();
39 | data.forEach((item) => {
40 | let items = modelsMap.get(item.provider);
41 | if (!items) {
42 | items = [];
43 | modelsMap.set(item.provider, [item]);
44 | } else {
45 | modelsMap.set(item.provider, [...items, item]);
46 | }
47 | });
48 |
49 | const keys = [...modelsMap.keys()];
50 | keys.sort((a, b) => a.localeCompare(b));
51 |
52 | const options: SelectProps.OptionGroup[] = keys.map((key) => {
53 | const items = modelsMap.get(key);
54 | items?.sort((a, b) => a.name.localeCompare(b.name));
55 |
56 | return {
57 | label: this.getProviderLabel(key),
58 | options:
59 | items?.map((item) => ({
60 | label: item.name,
61 | value: `${item.provider}::${item.name}`,
62 | })) ?? [],
63 | };
64 | });
65 |
66 | return options;
67 | }
68 |
69 | static getSelectOptions(data: T[]) {
70 | data?.sort((a, b) => a.name.localeCompare(b.name));
71 |
72 | const options: SelectProps.Option[] = data.map((item) => {
73 | return {
74 | label: item.name,
75 | value: item.id,
76 | };
77 | });
78 |
79 | return options;
80 | }
81 |
82 | static getProviderLabel(provider: string) {
83 | let label = provider;
84 | if (label === "sagemaker") label = "SageMaker";
85 | else if (label === "bedrock") label = "Bedrock";
86 | else if (label === "openai") label = "OpenAI";
87 |
88 | return label;
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/lib/sagemaker-model/hf-custom-script-model/build-script/script.py:
--------------------------------------------------------------------------------
1 | import os
2 | import shutil
3 | import subprocess
4 | from pathlib import Path
5 |
6 | import boto3
7 | from huggingface_hub import snapshot_download
8 |
9 | s3_client = boto3.client("s3")
10 |
11 | local_model_folder = os.getenv("LOCAL_MODEL_FOLDER", "./model")
12 | bucket = os.getenv("BUILD_BUCKET", "")
13 | model_ids = os.getenv("MODEL_ID", "")
14 | models_list = list(map(lambda val: val.strip(), model_ids.split(",")))
15 | models_num = len(models_list)
16 |
17 | print(f"Model ID: {model_ids}", flush=True)
18 | print(f"Bucket: {bucket}", flush=True)
19 |
20 | out_folder = Path("out")
21 | if out_folder.exists():
22 | shutil.rmtree(str(out_folder))
23 | out_folder.mkdir(exist_ok=True)
24 |
25 | print(f"Creating new code folder: {out_folder}/code", flush=True)
26 | model_code_folder = Path(os.path.join(out_folder, "code"))
27 | model_code_folder.mkdir(exist_ok=True)
28 |
29 | print(f"Copying contents from {local_model_folder} to {model_code_folder}", flush=True)
30 | shutil.copytree(local_model_folder, str(model_code_folder), dirs_exist_ok=True)
31 |
32 | for model_id in models_list:
33 | if models_num == 1:
34 | model_folder = out_folder
35 | else:
36 | model_folder = Path(out_folder, model_id.split("/")[-1])
37 | if model_folder.exists():
38 | shutil.rmtree(str(model_folder))
39 | model_folder.mkdir(exist_ok=True)
40 |
41 | print(f"Model folder: {model_folder}", flush=True)
42 | print(
43 | f"Downloading model snapshot for: {model_id} into {model_folder}",
44 | flush=True,
45 | )
46 |
47 | snapshot_download(
48 | model_id, local_dir=str(model_folder), local_dir_use_symlinks=False
49 | )
50 |
51 | print(f"Model snapshot downloaded to: {model_folder}", flush=True)
52 |
53 |
54 | print(f"Compressing the out folder: {out_folder}", flush=True)
55 |
56 | current_folder = os.getcwd()
57 | print(f"Current folder: {current_folder}")
58 | os.chdir(str(out_folder))
59 |
60 | print(f"Compressing the model folder: {out_folder}")
61 | command = "tar -cf model.tar.gz --use-compress-program=pigz *"
62 | print(f"Running command: {command}")
63 | subprocess.run(command, shell=True, check=True)
64 | print(f"Model folder compressed: {out_folder}")
65 | print(f"Moving back to: {current_folder}")
66 | os.chdir(current_folder)
67 |
68 | print(f"Uploading the model to S3 bucket: {bucket}")
69 | s3_client.upload_file(out_folder.joinpath("model.tar.gz"), bucket, f"out/model.tar.gz")
70 | model_data = f"s3://{bucket}/out/model.tar.gz"
71 |
72 | print(f"Model archive uploaded to: {model_data}")
73 |
--------------------------------------------------------------------------------
/lib/user-interface/react-app/src/common/helpers/metrics-helper.ts:
--------------------------------------------------------------------------------
1 | export abstract class MetricsHelper {
2 | static magnitude(vector: number[]): number {
3 | const magnitude = Math.sqrt(
4 | vector.reduce((sum, val) => sum + val * val, 0)
5 | );
6 |
7 | return magnitude;
8 | }
9 |
10 | static cosineSimilarity(vecA: number[], vecB: number[]) {
11 | if (vecA.length !== vecB.length) {
12 | throw new Error("Both vectors must have the same number of elements.");
13 | }
14 |
15 | let dotProduct = 0;
16 | let magnitudeA = 0;
17 | let magnitudeB = 0;
18 |
19 | for (let i = 0; i < vecA.length; i++) {
20 | dotProduct += vecA[i] * vecB[i];
21 | magnitudeA += vecA[i] * vecA[i];
22 | magnitudeB += vecB[i] * vecB[i];
23 | }
24 |
25 | magnitudeA = Math.sqrt(magnitudeA);
26 | magnitudeB = Math.sqrt(magnitudeB);
27 |
28 | const retValue = dotProduct / (magnitudeA * magnitudeB);
29 |
30 | return retValue;
31 | }
32 |
33 | static euclideanDistance(vecA: number[], vecB: number[]) {
34 | if (vecA.length !== vecB.length) {
35 | throw new Error("Both vectors must have the same number of elements.");
36 | }
37 |
38 | let sum = 0;
39 | for (let i = 0; i < vecA.length; i++) {
40 | const difference = vecA[i] - vecB[i];
41 | sum += difference * difference;
42 | }
43 |
44 | return Math.sqrt(sum);
45 | }
46 |
47 | static innerProduct(vecA: number[], vecB: number[]) {
48 | if (vecA.length !== vecB.length) {
49 | throw new Error("Both vectors must have the same number of elements.");
50 | }
51 |
52 | let sum = 0;
53 | for (let i = 0; i < vecA.length; i++) {
54 | sum += vecA[i] * vecB[i];
55 | }
56 |
57 | return sum;
58 | }
59 |
60 | static matrices(vectors: number[][]) {
61 | const cosineSimilarity = vectors.map((vecA) => {
62 | return vectors.map((vecB) => {
63 | return this.cosineSimilarity(vecA, vecB);
64 | });
65 | });
66 |
67 | const cosineDistance = vectors.map((vecA) => {
68 | return vectors.map((vecB) => {
69 | return 1 - this.cosineSimilarity(vecA, vecB);
70 | });
71 | });
72 |
73 | const innerProduct = vectors.map((vecA) => {
74 | return vectors.map((vecB) => {
75 | return this.innerProduct(vecA, vecB);
76 | });
77 | });
78 |
79 | const l2 = vectors.map((vecA) => {
80 | return vectors.map((vecB) => {
81 | return this.euclideanDistance(vecA, vecB);
82 | });
83 | });
84 |
85 | return {
86 | cosineSimilarity,
87 | cosineDistance,
88 | innerProduct,
89 | l2,
90 | };
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/lib/shared/layers/python-sdk/python/genai_core/opensearch/delete.py:
--------------------------------------------------------------------------------
1 | import os
2 | import boto3
3 | from .client import get_open_search_client
4 | import genai_core.utils.delete_files_with_prefix
5 |
6 |
7 | PROCESSING_BUCKET_NAME = os.environ["PROCESSING_BUCKET_NAME"]
8 | UPLOAD_BUCKET_NAME = os.environ["UPLOAD_BUCKET_NAME"]
9 | WORKSPACES_TABLE_NAME = os.environ["WORKSPACES_TABLE_NAME"]
10 | DOCUMENTS_TABLE_NAME = os.environ.get("DOCUMENTS_TABLE_NAME")
11 |
12 | WORKSPACE_OBJECT_TYPE = "workspace"
13 |
14 | dynamodb = boto3.resource("dynamodb")
15 |
16 |
17 | def delete_open_search_workspace(workspace: dict):
18 | workspace_id = workspace["workspace_id"]
19 | index_name = workspace_id.replace("-", "")
20 |
21 | genai_core.utils.delete_files_with_prefix.delete_files_with_prefix(
22 | UPLOAD_BUCKET_NAME, workspace_id
23 | )
24 | genai_core.utils.delete_files_with_prefix.delete_files_with_prefix(
25 | PROCESSING_BUCKET_NAME, workspace_id
26 | )
27 |
28 | client = get_open_search_client()
29 | if client.indices.exists(index_name):
30 | client.indices.delete(index=index_name)
31 | print(f"Index {index_name} deleted.")
32 |
33 | workspaces_table = dynamodb.Table(WORKSPACES_TABLE_NAME)
34 | documents_table = dynamodb.Table(DOCUMENTS_TABLE_NAME)
35 |
36 | items_to_delete = []
37 | last_evaluated_key = None
38 | while True:
39 | query_args = {
40 | "KeyConditionExpression": boto3.dynamodb.conditions.Key("workspace_id").eq(
41 | workspace_id
42 | )
43 | }
44 |
45 | if last_evaluated_key:
46 | query_args["ExclusiveStartKey"] = last_evaluated_key
47 |
48 | response = documents_table.query(**query_args)
49 | items_to_delete.extend(response["Items"])
50 |
51 | last_evaluated_key = response.get("LastEvaluatedKey")
52 | if not last_evaluated_key:
53 | break
54 |
55 | # Batch delete in groups of 25
56 | for i in range(0, len(items_to_delete), 25):
57 | with documents_table.batch_writer() as batch:
58 | for item in items_to_delete[i : i + 25]:
59 | batch.delete_item(
60 | Key={
61 | "workspace_id": item["workspace_id"],
62 | "document_id": item["document_id"],
63 | }
64 | )
65 |
66 | print(f"Deleted {len(items_to_delete)} items.")
67 |
68 | response = workspaces_table.delete_item(
69 | Key={"workspace_id": workspace_id, "object_type": WORKSPACE_OBJECT_TYPE},
70 | )
71 |
72 | print(f"Delete Item succeeded: {response}")
73 |
--------------------------------------------------------------------------------
/lib/shared/layers/python-sdk/python/genai_core/aurora/delete.py:
--------------------------------------------------------------------------------
1 | import os
2 | import boto3
3 | import genai_core.utils.delete_files_with_prefix
4 | from psycopg2 import sql
5 | from genai_core.aurora.connection import AuroraConnection
6 |
7 | PROCESSING_BUCKET_NAME = os.environ["PROCESSING_BUCKET_NAME"]
8 | UPLOAD_BUCKET_NAME = os.environ["UPLOAD_BUCKET_NAME"]
9 | WORKSPACES_TABLE_NAME = os.environ["WORKSPACES_TABLE_NAME"]
10 | DOCUMENTS_TABLE_NAME = os.environ.get("DOCUMENTS_TABLE_NAME")
11 |
12 | WORKSPACE_OBJECT_TYPE = "workspace"
13 |
14 | dynamodb = boto3.resource("dynamodb")
15 |
16 |
17 | def delete_aurora_workspace(workspace: dict):
18 | workspace_id = workspace["workspace_id"]
19 | genai_core.utils.delete_files_with_prefix.delete_files_with_prefix(
20 | UPLOAD_BUCKET_NAME, workspace_id
21 | )
22 | genai_core.utils.delete_files_with_prefix.delete_files_with_prefix(
23 | PROCESSING_BUCKET_NAME, workspace_id
24 | )
25 |
26 | table_name = sql.Identifier(workspace_id.replace("-", ""))
27 | with AuroraConnection(autocommit=False) as cursor:
28 | cursor.execute(
29 | sql.SQL("DROP TABLE IF EXISTS {table};").format(table=table_name)
30 | )
31 |
32 | workspaces_table = dynamodb.Table(WORKSPACES_TABLE_NAME)
33 | documents_table = dynamodb.Table(DOCUMENTS_TABLE_NAME)
34 |
35 | items_to_delete = []
36 | last_evaluated_key = None
37 | while True:
38 | query_args = {
39 | "KeyConditionExpression": boto3.dynamodb.conditions.Key("workspace_id").eq(
40 | workspace_id
41 | )
42 | }
43 |
44 | if last_evaluated_key:
45 | query_args["ExclusiveStartKey"] = last_evaluated_key
46 |
47 | response = documents_table.query(**query_args)
48 | items_to_delete.extend(response["Items"])
49 |
50 | last_evaluated_key = response.get("LastEvaluatedKey")
51 | if not last_evaluated_key:
52 | break
53 |
54 | # Batch delete in groups of 25
55 | for i in range(0, len(items_to_delete), 25):
56 | with documents_table.batch_writer() as batch:
57 | for item in items_to_delete[i : i + 25]:
58 | batch.delete_item(
59 | Key={
60 | "workspace_id": item["workspace_id"],
61 | "document_id": item["document_id"],
62 | }
63 | )
64 | print(f"Deleted {len(items_to_delete)} items.")
65 |
66 | response = workspaces_table.delete_item(
67 | Key={"workspace_id": workspace_id, "object_type": WORKSPACE_OBJECT_TYPE},
68 | )
69 |
70 | print(f"Delete Item succeeded: {response}")
71 |
--------------------------------------------------------------------------------
/lib/sagemaker-model/image-repository-mapping.ts:
--------------------------------------------------------------------------------
1 | import * as cdk from "aws-cdk-lib";
2 | import { Construct } from "constructs";
3 |
4 | export interface ImageRepositoryMappingProps {
5 | region: string;
6 | }
7 |
8 | export class ImageRepositoryMapping extends Construct {
9 | public readonly mapping: cdk.CfnMapping;
10 | public readonly account: string;
11 |
12 | constructor(
13 | scope: Construct,
14 | id: string,
15 | props: ImageRepositoryMappingProps
16 | ) {
17 | super(scope, id);
18 |
19 | const { region } = props;
20 |
21 | const mapping = new cdk.CfnMapping(scope, "ImageRepositoryCfnMapping", {
22 | lazy: true,
23 | mapping: {
24 | "af-south-1": { account: "626614931356" },
25 | "ap-east-1": { account: "871362719292" },
26 | "ap-northeast-1": { account: "763104351884" },
27 | "ap-northeast-2": { account: "763104351884" },
28 | "ap-northeast-3": { account: "364406365360" },
29 | "ap-south-1": { account: "763104351884" },
30 | "ap-south-2": { account: "772153158452" },
31 | "ap-southeast-1": { account: "763104351884" },
32 | "ap-southeast-2": { account: "763104351884" },
33 | "ap-southeast-3": { account: "907027046896" },
34 | "ap-southeast-4": { account: "457447274322" },
35 | "ca-central-1": { account: "763104351884" },
36 | "cn-north-1": { account: "727897471807" },
37 | "cn-northwest-1": { account: "727897471807" },
38 | "eu-central-1": { account: "763104351884" },
39 | "eu-central-2": { account: "380420809688" },
40 | "eu-north-1": { account: "763104351884" },
41 | "eu-west-1": { account: "763104351884" },
42 | "eu-west-2": { account: "763104351884" },
43 | "eu-west-3": { account: "763104351884" },
44 | "eu-south-1": { account: "692866216735" },
45 | "eu-south-2": { account: "503227376785" },
46 | "me-south-1": { account: "217643126080" },
47 | "me-central-1": { account: "914824155844" },
48 | "sa-east-1": { account: "763104351884" },
49 | "us-east-1": { account: "763104351884" },
50 | "us-east-2": { account: "763104351884" },
51 | "us-gov-east-1": { account: "446045086412" },
52 | "us-gov-west-1": { account: "442386744353" },
53 | "us-iso-east-1": { account: "886529160074" },
54 | "us-isob-east-1": { account: "094389454867" },
55 | "us-west-1": { account: "763104351884" },
56 | "us-west-2": { account: "763104351884" },
57 | },
58 | });
59 |
60 | const account = mapping.findInMap(region, "account");
61 |
62 | this.mapping = mapping;
63 | this.account = account;
64 | }
65 | }
66 |
--------------------------------------------------------------------------------