Skip to content

Commit 8633b9f

Browse files
author
omerh
committed
First release
1 parent a13a0a0 commit 8633b9f

20 files changed

+944
-15
lines changed

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.terraform/
2+
*.tfstate*
3+
4+
dependencies/
5+
6+
.venv/
7+
__pycache__/

CODE_OF_CONDUCT.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
## Code of Conduct
1+
# Code of Conduct
2+
23
This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct).
34
For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact
4-
[email protected] with any additional questions or comments.
5+
[[email protected]](mailto:[email protected]) with any additional questions or comments.

CONTRIBUTING.md

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ documentation, we greatly value feedback and contributions from our community.
66
Please read through this document before submitting any issues or pull requests to ensure we have all the necessary
77
information to effectively respond to your bug report or contribution.
88

9-
109
## Reporting Bugs/Feature Requests
1110

1211
We welcome you to use the GitHub issue tracker to report bugs or suggest features.
@@ -19,8 +18,8 @@ reported the issue. Please try to include as much information as you can. Detail
1918
* Any modifications you've made relevant to the bug
2019
* Anything unusual about your environment or deployment
2120

22-
2321
## Contributing via Pull Requests
22+
2423
Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that:
2524

2625
1. You are working against the latest source on the *main* branch.
@@ -39,20 +38,19 @@ To send us a pull request, please:
3938
GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and
4039
[creating a pull request](https://help.github.com/articles/creating-a-pull-request/).
4140

42-
4341
## Finding contributions to work on
44-
Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start.
4542

43+
Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start.
4644

4745
## Code of Conduct
46+
4847
This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct).
4948
For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact
50-
[email protected] with any additional questions or comments.
51-
49+
[[email protected]](mailto:[email protected]) with any additional questions or comments.
5250

5351
## Security issue notifications
54-
If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue.
5552

53+
If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue.
5654

5755
## Licensing
5856

README.md

Lines changed: 55 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,61 @@
1-
## My Project
1+
# RAG using LangChain with Amazon Bedrock Titan text, and embedding, using OpenSearch vector engine
22

3-
TODO: Fill this README out!
3+
This sample repository provides a sample code for using RAG (Retrieval augmented generation) method relaying on [Amazon Bedrock](https://aws.amazon.com/bedrock/) [Titan text embedding](https://aws.amazon.com/bedrock/titan/) LLM (Large Language Model), for creating text embedding that will be stored in [Amazon OpenSearch](https://aws.amazon.com/opensearch-service/) with [vector engine support](https://aws.amazon.com/about-aws/whats-new/2023/07/vector-engine-amazon-opensearch-serverless-preview/) for assisting with the prompt engineering task for more accurate response from LLMs.
44

5-
Be sure to:
5+
After we successfully loaded embeddings into OpenSearch, we will then start querying our LLM, by using [LangChain](https://www.langchain.com/). We will ask questions, retrieving similar embedding for a more accurate prompt.
66

7-
* Change the title in this README
8-
* Edit your repository description on GitHub
7+
## Prerequisites
8+
9+
1. This was tested on Python 3.11.4
10+
2. It is advise to work on a clean environment, use `virtualenv` or any other virtual environment packages.
11+
12+
```bash
13+
pip install virtualenv
14+
python -m virtualenv venv
15+
source ./venv/bin/activate
16+
```
17+
18+
3. Run `./download-beta-sdk.sh` to download the beta SDK for using Amazon Bedrock
19+
4. Install requirements `pip install -r requirements.txt`
20+
5. Install [terraform](https://developer.hashicorp.com/terraform/downloads?product_intent=terraform) to create the OpenSearch cluster
21+
22+
```bash
23+
brew tap hashicorp/tap
24+
brew install hashicorp/tap/terraform
25+
```
26+
27+
## Steps for using this sample code
28+
29+
1. In the first step we will launch an OpenSearch cluster using Terraform.
30+
31+
```bash
32+
cd ./terraform
33+
terraform init
34+
terraform apply -auto-approve
35+
```
36+
37+
>>This cluster configuration is for testing proposes only, as it's endpoint is public for simplifying the use of this sample code.
38+
39+
2. Now that we have a running OpenSearch cluster with vector engine support we will start uploading our data that will help us with prompt engineering. For this sample, we will use a data source from [Hugging Face](https://huggingface.co) [embedding-training-data](https://huggingface.co/datasets/sentence-transformers/embedding-training-data) [gooaq_pairs](https://huggingface.co/datasets/sentence-transformers/embedding-training-data/resolve/main/gooaq_pairs.jsonl.gz), we will download it, and invoke Titan embedding to get a text embedding, that we will store in OpenSearch for next steps.
40+
41+
```bash
42+
python load-data-to-opensearch.py --recreate 1 --early-stop 1
43+
```
44+
45+
>>Optional arguments: `--recreate` for recreating the index in OpenSearch, and `--early-stop` to load only 100 embedded documents into OpenSearch
46+
47+
3. Now that we have embedded text, into our OpenSearch cluster, we can start querying our LLM model Titan text in Amazon Bedrock with RAG
48+
49+
```bash
50+
python ask-titan-with-rag.py --ask "your question here"
51+
```
52+
53+
### Cleanup
54+
55+
```bash
56+
cd ./terraform
57+
terraform destroy # When prompt for confirmation, type yes, and press enter.
58+
```
959
1060
## Security
1161
@@ -14,4 +64,3 @@ See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more inform
1464
## License
1565
1666
This library is licensed under the MIT-0 License. See the LICENSE file.
17-

ask-titan-with-rag.py

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import coloredlogs
2+
import logging
3+
import argparse
4+
import sys
5+
import os
6+
from utils.bedrock import get_bedrock_client
7+
from utils import bedrock, opensearch, secret, iam
8+
from langchain.embeddings import BedrockEmbeddings
9+
from langchain.vectorstores import OpenSearchVectorSearch
10+
from langchain.chains import RetrievalQA
11+
from langchain.prompts import PromptTemplate
12+
from langchain.llms.bedrock import Bedrock
13+
14+
15+
coloredlogs.install(fmt='%(asctime)s %(levelname)s %(message)s', datefmt='%H:%M:%S', level='INFO')
16+
logging.basicConfig(level=logging.INFO)
17+
logger = logging.getLogger(__name__)
18+
19+
def parse_args():
20+
parser = argparse.ArgumentParser()
21+
parser.add_argument("--ask", type=str, default="What is <3?")
22+
23+
return parser.parse_known_args()
24+
25+
26+
def get_bedrock_client(region, account_id):
27+
module_path = "."
28+
sys.path.append(os.path.abspath(module_path))
29+
os.environ['AWS_DEFAULT_REGION'] = region
30+
31+
boto3_bedrock = bedrock.get_bedrock_client(
32+
assumed_role=f'arn:aws:iam::{account_id}:role/bedrock',
33+
region=region,
34+
)
35+
return boto3_bedrock
36+
37+
def create_langchain_vector_embedding_using_bedrock(bedrock_client):
38+
bedrock_embeddings_client = BedrockEmbeddings(
39+
client=bedrock_client,
40+
model_id="amazon.titan-e1t-medium")
41+
return bedrock_embeddings_client
42+
43+
44+
def create_opensearch_vector_search_client(index_name, opensearch_password, bedrock_embeddings_client, opensearch_endpoint, _is_aoss=False):
45+
docsearch = OpenSearchVectorSearch(
46+
index_name=index_name,
47+
embedding_function=bedrock_embeddings_client,
48+
opensearch_url=f"https://{opensearch_endpoint}",
49+
http_auth=(index_name, opensearch_password),
50+
is_aoss=_is_aoss
51+
)
52+
return docsearch
53+
54+
55+
def create_bedrock_llm(bedrock_client):
56+
bedrock_llm = Bedrock(
57+
model_id="amazon.titan-tg1-large",
58+
client=bedrock_client,
59+
model_kwargs={'temperature': 0}
60+
)
61+
return bedrock_llm
62+
63+
64+
def main():
65+
logging.info("Starting")
66+
# vars
67+
region = "us-west-2"
68+
index_name = 'rag'
69+
args, _ = parse_args()
70+
71+
# Creating all clients for chain
72+
account_id = iam.get_account_id()
73+
bedrock_client = get_bedrock_client(region, account_id)
74+
bedrock_llm = create_bedrock_llm(bedrock_client)
75+
bedrock_embeddings_client = create_langchain_vector_embedding_using_bedrock(bedrock_client)
76+
opensearch_endpoint = opensearch.get_opensearch_endpoint(index_name, region)
77+
opensearch_password = secret.get_secret(index_name, region)
78+
opensearch_vector_search_client = create_opensearch_vector_search_client(index_name, opensearch_password, bedrock_embeddings_client, opensearch_endpoint)
79+
80+
# LangChain prompt template
81+
question = "what is the meaning of <3?"
82+
83+
prompt_template = """Use the following pieces of context to answer the question at the end. If you don't know the answer, just say that you don't know, don't try to make up an answer. don't include harmful content
84+
85+
{context}
86+
87+
Question: {question}
88+
Answer:"""
89+
PROMPT = PromptTemplate(
90+
template=prompt_template, input_variables=["context", "question"]
91+
)
92+
93+
logging.info(f"Starting the chain with KNN similarity using OpenSearch, and than passing to Bedrock Titan FM")
94+
qa = RetrievalQA.from_chain_type(llm=bedrock_llm,
95+
chain_type="stuff",
96+
retriever=opensearch_vector_search_client.as_retriever(),
97+
return_source_documents=True,
98+
chain_type_kwargs={"prompt": PROMPT, "verbose": True},
99+
verbose=True)
100+
101+
response = qa(question, return_only_outputs=False)
102+
103+
logging.info("This are the similar documents from OpenSearch based on the provided query")
104+
source_documents = response.get('source_documents')
105+
for d in source_documents:
106+
logging.info(f"With the following similar content from OpenSearch:\n{d.page_content}\n")
107+
# logging.info(f"vector: {d.metadata}")
108+
109+
logging.info(f"\nThe answer from Titan: {response.get('result')}")
110+
111+
112+
if __name__ == "__main__":
113+
main()

download-beta-sdk.sh

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#!/bin/sh
2+
3+
echo "Creating directory"
4+
mkdir -p ./dependencies && \
5+
cd ./dependencies && \
6+
echo "Downloading dependencies"
7+
curl -sS https://d2eo22ngex1n9g.cloudfront.net/Documentation/SDK/bedrock-python-sdk.zip > sdk.zip && \
8+
echo "Unpacking dependencies"
9+
unzip -o -q sdk.zip && \
10+
rm sdk.zip
11+
12+
pip install --force-reinstall --no-cache-dir awscli-1.29.21-py3-none-any.whl
13+
pip install --force-reinstall --no-cache-dir boto3-1.28.21-py3-none-any.whl
14+
pip install --force-reinstall --no-cache-dir botocore-1.31.21-py3-none-any.whl

load-data-to-opensearch.py

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import logging
2+
import coloredlogs
3+
import json
4+
import argparse
5+
from utils.bedrock import get_bedrock_client
6+
import sys
7+
import os
8+
from utils import bedrock, dataset, secret, opensearch, iam
9+
10+
coloredlogs.install(fmt='%(asctime)s %(levelname)s %(message)s', datefmt='%H:%M:%S', level='INFO')
11+
logging.basicConfig(level=logging.INFO)
12+
logger = logging.getLogger(__name__)
13+
14+
15+
def parse_args():
16+
parser = argparse.ArgumentParser()
17+
parser.add_argument("--recreate", type=bool, default=0)
18+
parser.add_argument("--early-stop", type=bool, default=0)
19+
20+
return parser.parse_known_args()
21+
22+
23+
24+
def get_bedrock_client(region, account_id):
25+
module_path = "."
26+
sys.path.append(os.path.abspath(module_path))
27+
os.environ['AWS_DEFAULT_REGION'] = region
28+
29+
boto3_bedrock = bedrock.get_bedrock_client(
30+
assumed_role=f'arn:aws:iam::{account_id}:role/bedrock',
31+
region=region,
32+
)
33+
return boto3_bedrock
34+
35+
36+
def create_vector_embedding_with_bedrock(text, name, bedrock_client):
37+
payload = {"inputText": f"{text}"}
38+
body = json.dumps(payload)
39+
modelId = "amazon.titan-e1t-medium"
40+
accept = "application/json"
41+
contentType = "application/json"
42+
43+
response = bedrock_client.invoke_model(
44+
body=body, modelId=modelId, accept=accept, contentType=contentType
45+
)
46+
response_body = json.loads(response.get("body").read())
47+
48+
embedding = response_body.get("embedding")
49+
return {"_index": name, "text": text, "vector_field": embedding}
50+
51+
52+
def main():
53+
logging.info("Starting")
54+
# vars
55+
region = "us-west-2"
56+
name = 'rag'
57+
dataset_url = "https://huggingface.co/datasets/sentence-transformers/embedding-training-data/resolve/main/gooaq_pairs.jsonl.gz"
58+
early_stop_record_count = 100
59+
args, _ = parse_args()
60+
61+
# Prepare OpenSearch index with vector embeddings index mapping
62+
logging.info(f"recreating opensearch index: {args.recreate}, using early stop: {args.early_stop} to insert only {early_stop_record_count} records")
63+
logging.info("Preparing OpenSearch Index")
64+
opensearch_password = secret.get_secret(name, region)
65+
opensearch_client = opensearch.get_opensearch_cluster_client(name, opensearch_password, region)
66+
67+
# Check if to delete OpenSearch index with the argument passed to the script --recreate 1
68+
if args.recreate:
69+
response = opensearch.delete_opensearch_index(opensearch_client, name)
70+
if response:
71+
logging.info("OpenSearch index successfully deleted")
72+
73+
logging.info(f"Checking if index {name} exists in OpenSearch cluster")
74+
exists = opensearch.check_opensearch_index(opensearch_client, name)
75+
if not exists:
76+
logging.info("Creating OpenSearch index")
77+
success = opensearch.create_index(opensearch_client, name)
78+
if success:
79+
logging.info("Creating OpenSearch index mapping")
80+
success = opensearch.create_index_mapping(opensearch_client, name)
81+
logging.info(f"OpenSearch Index mapping created")
82+
83+
# Download sample dataset from HuggingFace
84+
logging.info("Downloading dataset from HuggingFace")
85+
compressed_file_path = dataset.download_dataset(dataset_url)
86+
if compressed_file_path is not None:
87+
file_path = dataset.decompress_dataset(compressed_file_path)
88+
if file_path is not None:
89+
all_records = dataset.prep_for_put(file_path)
90+
91+
# Initialize bedrock client
92+
account_id = iam.get_account_id()
93+
bedrock_client = get_bedrock_client(region, account_id)
94+
95+
# Vector embedding using Amazon Bedrock Titan text embedding
96+
all_json_records = []
97+
logging.info(f"Creating embeddings for {len(all_records)} records")
98+
99+
# using the arg --early-stop
100+
i = 0
101+
for record in all_records:
102+
i += 1
103+
if args.early_stop:
104+
if i > early_stop_record_count:
105+
break
106+
records_with_embedding = create_vector_embedding_with_bedrock(record, name, bedrock_client)
107+
logging.info(f"Embedding for record {i} created")
108+
all_json_records.append(records_with_embedding)
109+
110+
logging.info("Finished creating records using Amazon Bedrock Titan text embedding")
111+
112+
# Bulk put all records to OpenSearch
113+
success, failed = opensearch.put_bulk_in_opensearch(all_json_records, opensearch_client)
114+
logging.info(f"Documents saved {success}, documents failed to save {failed}")
115+
116+
logging.info("Cleaning up")
117+
dataset.delete_file(compressed_file_path)
118+
dataset.delete_file(file_path)
119+
120+
logging.info("Finished")
121+
122+
if __name__ == "__main__":
123+
main()

requirements.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
langchain==0.0.266
2+
coloredlogs==15.0.1
3+
jq==1.4.1
4+
opensearch-py==2.3.0

0 commit comments

Comments
 (0)