# [Lab] Bedrock Agent
## 故事背景
一家小型但創新的投資型新創公司 AnyCompany Portfolios Inc。這個團隊成員來自不同背景,充滿熱情,並堅信科技能夠在金融和商業智慧領域開拓新的潛力。
他們注意到市場上,金融分析師和投資者花費無數時間手動去研究標的公司的資料,往往依賴許多不夠即時或太過淺顯的資料。而這個無聊乏味的過程不僅限制了創造更多高價值投資組合的潛力,也讓這些小型但具有前景的公司被其他寡頭所壓抑。
他們構想了一個 GenerativeAI 應用程式,可以去篩選這些資料,識別並找出人工閱覽難以發現的關鍵模式和可能的趨勢。
團隊開始打造一個應用程式,不僅能夠分析大量公司數據,還能夠搜索數十份FOMC (Federal Open Market Committee/聯邦公開市場委員會)報告,找出盈利最高的公司。透過這樣的方式,他們能夠更快速地理解市場動態,做出更明智的決策。
現在邀請大家一起,通過這個 Workshop,利用 Amazon Bedrock 中的 Agent功能,親手實作這個 Generative AI 的應用程式。
## Bedrock Agent 架構圖
接下來,跟著Workshop所做的四個模組裡面詳細的設定說明,我們就可以在Amazon Bedrock上建立一個 Agent。這包括設定 S3 Bucket、設定知識庫、設定 Action Groups、設定 Lambda 函數。
這個 Bedrock Agent 被設計為根據特定參數動態建立一個高獲利的公司的投資組合,並提供一個關於 FOMC (聯邦公開市場委員會)報告的對答功能。而這個應用程式還包括了一個利用電子郵件發送Report的功能,我們會在第二階段完成它。
以下是我們將要構建的架構圖:

---
## 建立 Bedrock Agent:
* 設定 Amazon S3 buckets
* 設定 AWS Lambda function
* 設定 Agent 並對接 Action groups
* 設定 Knowledge base 並對接到 Amazon Bedrock agent
* 驗證 Agent
參考工作坊:
### Module 1:設定 S3 與知識庫
* [Setup Amazon S3 Buckets and Knowledge base](https://catalog.us-east-1.prod.workshops.aws/workshops/f8a7a3f8-1603-4b10-95cb-0b471db272d8/en-US/module1)
* 這邊設定的 S3 bucket 將用於儲存 Agent 面對特定 Domain 問題要回答時所需的 Domain data。
* 下載專業領域檔案資料:https://github.com/build-on-aws/bedrock-agents-streamlit/tree/main/S3docs
* 這些文件是 FOMC (聯邦公開市場委員會)的文件,描述了在聯邦儲備理事會會議上做出的金融政策決策。
* 這些文件包括對經濟狀況的討論、針對紐約聯邦儲備銀行在公開市場操作的政策指引,以及對聯邦基金利率的表決。可以在[這裡](https://www.federalreserve.gov/newsevents/pressreleases/monetary20231011a.htm)找到更多資訊。
* 將下載的文件上傳到S3 Bucekt,一旦上傳後,請選擇其中一份文件打開並查看內容。
* 設定 Knowledge Base
* 開始前還是確認一次,Bedrock Model Access是否已經開啟了Anthropic的Claude 3 (Sonnet / Haiku),後面會使用到。
* 進入 [Amazon Bedrock 的 AWS Conosle](https://us-west-2.console.aws.amazon.com/bedrock/home?region=us-west-2#/),點擊左側邊欄 Orchestration -> Knowledge bases
* 
* 接著點擊右方橘色按鈕:Create knowledge base
* 
* 可以直接採用預設 Knowledge base name 的命名,直接點擊右下角橘色 **Next** 按鈕。
* 
* 接著在下一頁的S3 URI內,帶入前面所建立的S3 Bucket 路徑:knowledgebase-bedrock-agent-{alias},可以點擊右側的Browse,會列出所有S3 Bucket,再點擊帶入。最後按下右下角橘色 **Next** 按鈕
* 
* 最後進行 Embeddings model 的選擇,並設定 vector store
* 
* 選擇 **Titan Embedding G1 - Text** 作為這個知識庫的**Embbeding Model**,另外選擇快速建立**Vector Store**。最後按下右下角橘色 **Next** 按鈕。
* 在 **Review** 頁確認都沒問題後,按下右下角 **Create knowledge base** 按鈕。
* 看到以下畫面表示完成
* 
### Module 2:設定 AWS Lambda
* [Setup AWS Lambda function](https://catalog.us-east-1.prod.workshops.aws/workshops/f8a7a3f8-1603-4b10-95cb-0b471db272d8/en-US/module2)
* 接下來要設定一個 AWS Lambda,作為 Action Groups 執行Action之用途。
* 前往AWS Lambda [console](https://us-west-2.console.aws.amazon.com/lambda/home?region=us-west-2#/begin)
* 點擊橘色 Create a function
* Function name: PortfolioCreator-actions
* 選擇Runtime: Python 3.12,並按下右下角的橘色 Create Function按鈕
* 
* 這個Lambda內將會定義三個Action API,提供以下三個功能:
* **Company Research**: Allows the AI agent to search for a specific company by name and retrieve its details.
* 讓Bedrock Agent能夠透過公司名稱去搜尋並取得特定公司的詳細資料。
* **Portfolio Creation**: Enables the AI agent to create a portfolio of top-performing companies within a specified industry sector. The agent can specify the number of companies to include in the portfolio, and the function will return the top companies sorted by profit in descending order.
* **Email Sending**: Allows the AI agent to simulate sending an email containing a portfolio summary to a specified email address.
* 另外在這個Lambda Function中,直接將公司詳細資訊定義在程式碼當中,作為觸發查詢的示意,未來可將此段模組改成直接查群第三方API或是企業內部資料庫的形式。
* Lambda Function 建立完成後,將[此處](https://github.com/build-on-aws/bedrock-agents-streamlit/blob/main/ActionLambda.py)程式碼複製貼上到下圖Code區域,並且 ctrl(MAC: command) + s 儲存。接著點擊Deploy(部署)。
* 最後要進行,權限的賦予:
* Apply a resource policy to the Lambda to grant Bedrock agent access. To do this, we will switch the top tab from Code to “configuration” and the side tab to Permissions. Then, scroll to the Resource-based policy statements section and click the Add permissions button.
* 
* 
* Choose: **AWS Service**
* Service: **Other**
* Statement ID: **allow-agent**
* Principal: **bedrock.amazonaws.com**
* Source ARN: arn:aws:bedrock:us-west-2:{accoundID}:agent/*
* {accountID}要從AWS Console右上角複製取得
* 
* 這邊的wildcard設定方式是為了方便,但以企業用途,我們建議要做到最小權限,所以後面當Agent正式建立後,可以在此補上ID,限制只有特定Agent可以使用這個action
* Action: Lambda:InvokeFunction
* 按下橘色 Save 按鈕
### Module 3:設定 Agent 與 Action Group
* [Setup agent and Action group](https://catalog.us-east-1.prod.workshops.aws/workshops/f8a7a3f8-1603-4b10-95cb-0b471db272d8/en-US/module3)
* 回到 Bedrock console
* 
* 點擊左側邊欄 Orchestration -> Agents
* 接著點擊右側橘色按鈕:Create Agent
* Name: **PortfolioCreator**
* 按下橘色 Create 按鈕
* 
* Select model -> Anthropic -> Claude 3 Sonnet
* 其餘維持預設
* 填入 Instructions for the Agent
* `You are an investment analyst who creates portfolios of companies based on the number of companies, and industry in the {question}. An example of a portfolio looks like this template {portfolio_example}. You also research companies, and summarize documents. When requested, you format emails like this template {email_format}, then use the provided tools to send an email that has the company portfolio created, and summary of the FOMC report searched. `
* 我們利用 instruction 對 Claude 3 Sonnet 做 System Prompt:上面的文字告訴Sonnet,你是一位根據 {question} 中指定的公司數量和產業來建立公司投資組合的投資分析師。一個投資組合的範例如 {portfolio_example} 這個範本所示。您也會研究公司,並對文件進行摘要。當收到請求時,會按照 {email_format} 這個範本的格式編輯電子郵件,然後使用提供的工具發送一封包含已創建的公司投資組合和搜索的FOMC 報告摘要的電子郵件。
* 
* 新增一個Action Group,點擊下圖 Add 藍色按鈕
* 
* Action Name: PortfolioCreator-actions
* 設定如下:
* 
* 特別注意選擇in-line schema editor並貼上Schema (點擊本[連結](https://catalog.us-east-1.prod.workshops.aws/workshops/f8a7a3f8-1603-4b10-95cb-0b471db272d8/en-US/module3/step4)取得Schema範例)
* 完成後按下右下方橘色按鈕 Create
* This API schema defines three primary endpoints, /companyResearch, /createPortfolio, and /sendEmail detailing how to interact with the API, the required parameters, and the expected responses.
* 接著要利用Advanced Prompt區塊,M來定義回傳的格式:
* 先移動到先前建立的Bedrock Agent:
* 滑到最底下,找到 Advanced prompt區塊,點擊Edit
* 
* 接著點擊 Orchestration 分頁
* 
* 勾選 **Override orchestration template defaults**.
* 勾選 **Activate orchestration template**
* 在Prompt template editor區塊內的 </guidelines> 後點擊空兩行。貼上底下提供的 portfolio example 還有 email format。完成後點擊 **Save and exit** 橘色按鈕
```
Here is an example of a company portfolio.
<portfolio_example>
Here is a portfolio of the top 3 real estate companies:
1. NextGenPast Residences with revenue of $180,000, expenses of $22,000 and profit of $158,000 employing 260 people.
2. GlobalRegional Properties Alliance with revenue of $170,000, expenses of $21,000 and profit of $149,000 employing 11 people.
3. InnovativeModernLiving Spaces with revenue of $160,000, expenses of $20,000 and profit of $140,000 employing 10 people.
</portfolio_example>
Here is an example of a formatted email. Double check that the FOMC report is a summary of the knowledge base responses.
<email_format>
Company Portfolio:
1. NextGenPast Residences with revenue of $180,000, expenses of $22,000 and profit of $158,000 employing 260 people.
2. GlobalRegional Properties Alliance with revenue of $170,000, expenses of $21,000 and profit of $149,000 employing 11 people.
3. InnovativeModernLiving Spaces with revenue of $160,000, expenses of $20,000 and profit of $140,000 employing 10 people.
FOMC Report:
Participants noted that recent indicators pointed to modest growth in spending and production. Nonetheless, job gains had been robust in recent months, and the unemployment rate remained low. Inflation had eased somewhat but remained elevated.
Participants recognized that Russia’s war against Ukraine was causing tremendous human and economic hardship and was contributing to elevated global uncertainty. Against this background, participants continued to be highly attentive to inflation risks.
</email_format>
```

* 確認Orchestration 顯示 Overridden 表示正確設定。
### Module 4:同步知識庫並與 Agent 介接
* [Sync Knowledge base with an agent on Amazon Bedrock](https://catalog.us-east-1.prod.workshops.aws/workshops/f8a7a3f8-1603-4b10-95cb-0b471db272d8/en-US/module4)
* 接著要把 Module 1 中建立的知識庫,介接到 Module 3 建立的agent: PortfolioCreator。
* 在Agent的console頁面中,下拉看到以下區塊,點擊Add
* 
* 進入後提供 instruction
* `Use this knowledge base when a user asks about data, such as economic trends, company financial statements, or the outcomes of the Federal Open Market Committee meetings.
`
* 點擊 Add 之後,跳轉回到 Agent: PortfolioCreator 的頁面,在最上面點擊 Prepare
* 
* 完成後點擊橘色 **Save and exit**
### 驗證 Bedrock Agent: PortfolioCreator
* 首先我們先到 Knowledge Base [Console](https://us-west-2.console.aws.amazon.com/bedrock/home?region=us-west-2#/knowledge-bases) 進行同步
* 點擊進入今天 Module 1 建立的 Knowledge base 之後,會看到以下畫面,到下方Data Source區塊,勾選今天建立的S3 資料源,按下右邊Sync按鈕。我們會看到 Status 呈現 Syncing,直到變成綠色 Available,即可開始使用。
* 
* 我們先用一個簡單的Prompt確定內部資料
```
give me a summary of development in financial market and open market operation in January 2023
```
* 
* Select Model 選擇 Claude 3 Sonnet
* 
* 完成 Knowledge base 的功能驗證後,我們接著開始直接驗證Bedrock Agent 的功能。
* 回到 Bedrock Console 點擊左側邊欄下方 Agent
* 
* 接著進入 Agent: PortfolioCreator
* 點擊 Test,會看到右手邊有聊天視窗出現
* 
* 確認 Status: PREPARED
* 以下我們用幾個prompt來測試一下,首先四個問題,應該會會用到KB所提供的資料:
* Give me a summary of financial market developments and open market operations in January 2023
* Tell me the participants view on economic conditions
* Provide any important information I should know about inflation, or rising prices
* Tell me about the Staff Review of the Economic & financial Situation
* 接下來四個問題,會呼叫到Action Group 所提供的資料:
* Create a portfolio with 3 companies in the real estate industry
* Create portfolio of 3 companies that are in the technology industry
* Create a new investment portfolio of companies
* Do company research on TechStashNova Inc.
* 接著再嘗試一個請求
* `Create a portfolio with top 3 company profit earners in real estate`
* `"rationale": {
"text": "To create a portfolio with the top 3 company profit earners in the real estate industry, I will call the /createPortfolio tool with the industry set to \"real estate\" and numCompanies set to 3.",
"traceId": "5342afdb-a704-428c-a669-db2d4f08bbce-0"
},`
### 替模擬既送電子郵件的模組,加上真正的寄信功能
* 完成寄送Email的功能
```
import boto3
def lambda_handler(event, context):
....
def sendEmail(event, company_data):
client = boto3.client('ses', region_name='us-west-2')
emailAddress = get_named_parameter(event, 'emailAddress')
fomcSummary = get_named_parameter(event, 'fomcSummary')
# Retrieve the portfolio data as a string
portfolioDataString = get_named_parameter(event, 'portfolio')
# Prepare the email content
email_subject = "Portfolio Creation Summary and FOMC Search Results"
email_body = f"FOMC Search Summary:\n{fomcSummary}\n\nPortfolio Details:\n{json.dumps(portfolioDataString, indent=4)}"
# Email sending code here (commented out for now)
message = {"Subject": {"Data": email_subject}, "Body": {"Text": {"Data": email_body}}}
response = client.send_email(Source = "test@gmail.com", Destination = {"ToAddresses": [emailAddress]}, Message = message)
return "Email sent successfully to {}".format(emailAddress)
```
## 加上 SendMail功能後的Lambda Code
```
import json
import boto3
def lambda_handler(event, context):
print(event)
# Mock data for demonstration purposes
company_data = [
#Technology Industry
{"companyId": 1, "companyName": "TechStashNova Inc.", "industrySector": "Technology", "revenue": 10000, "expenses": 3000, "profit": 7000, "employees": 10},
{"companyId": 2, "companyName": "QuantumPirateLeap Technologies", "industrySector": "Technology", "revenue": 20000, "expenses": 4000, "profit": 16000, "employees": 10},
{"companyId": 3, "companyName": "CyberCipherSecure IT", "industrySector": "Technology", "revenue": 30000, "expenses": 5000, "profit": 25000, "employees": 10},
{"companyId": 4, "companyName": "DigitalMyricalDreams Gaming", "industrySector": "Technology", "revenue": 40000, "expenses": 6000, "profit": 34000, "employees": 10},
{"companyId": 5, "companyName": "NanoMedNoLand Pharmaceuticals", "industrySector": "Technology", "revenue": 50000, "expenses": 7000, "profit": 43000, "employees": 10},
{"companyId": 6, "companyName": "RoboSuperBombTech Industries", "industrySector": "Technology", "revenue": 60000, "expenses": 8000, "profit": 52000, "employees": 12},
{"companyId": 7, "companyName": "FuturePastNet Solutions", "industrySector": "Technology", "revenue": 60000, "expenses": 9000, "profit": 51000, "employees": 10},
{"companyId": 8, "companyName": "InnovativeCreativeAI Corp", "industrySector": "Technology", "revenue": 65000, "expenses": 10000, "profit": 55000, "employees": 15},
{"companyId": 9, "companyName": "EcoLeekoTech Energy", "industrySector": "Technology", "revenue": 70000, "expenses": 11000, "profit": 59000, "employees": 10},
{"companyId": 10, "companyName": "TechyWealthHealth Systems", "industrySector": "Technology", "revenue": 80000, "expenses": 12000, "profit": 68000, "employees": 10},
#Real Estate Industry
{"companyId": 11, "companyName": "LuxuryToNiceLiving Real Estate", "industrySector": "Real Estate", "revenue": 90000, "expenses": 13000, "profit": 77000, "employees": 10},
{"companyId": 12, "companyName": "UrbanTurbanDevelopers Inc.", "industrySector": "Real Estate", "revenue": 100000, "expenses": 14000, "profit": 86000, "employees": 10},
{"companyId": 13, "companyName": "SkyLowHigh Towers", "industrySector": "Real Estate", "revenue": 110000, "expenses": 15000, "profit": 95000, "employees": 18},
{"companyId": 14, "companyName": "GreenBrownSpace Properties", "industrySector": "Real Estate", "revenue": 120000, "expenses": 16000, "profit": 104000, "employees": 10},
{"companyId": 15, "companyName": "ModernFutureHomes Ltd.", "industrySector": "Real Estate", "revenue": 130000, "expenses": 17000, "profit": 113000, "employees": 10},
{"companyId": 16, "companyName": "CityCountycape Estates", "industrySector": "Real Estate", "revenue": 140000, "expenses": 18000, "profit": 122000, "employees": 10},
{"companyId": 17, "companyName": "CoastalFocalRealty Group", "industrySector": "Real Estate", "revenue": 150000, "expenses": 19000, "profit": 131000, "employees": 10},
{"companyId": 18, "companyName": "InnovativeModernLiving Spaces", "industrySector": "Real Estate", "revenue": 160000, "expenses": 20000, "profit": 140000, "employees": 10},
{"companyId": 19, "companyName": "GlobalRegional Properties Alliance", "industrySector": "Real Estate", "revenue": 170000, "expenses": 21000, "profit": 149000, "employees": 11},
{"companyId": 20, "companyName": "NextGenPast Residences", "industrySector": "Real Estate", "revenue": 180000, "expenses": 22000, "profit": 158000, "employees": 260}
]
def get_named_parameter(event, name):
return next(item for item in event['parameters'] if item['name'] == name)['value']
def get_named_property(event, name):
return next(item for item in event['requestBody']['content']['application/json']['properties'] if item['name'] == name)['value']
def companyResearch(event):
companyName = get_named_parameter(event, 'name').lower()
print("NAME PRINTED: ", companyName)
for company_info in company_data:
if company_info["companyName"].lower() == companyName:
return company_info
return None
def createPortfolio(event, company_data):
numCompanies = int(get_named_parameter(event, 'numCompanies'))
industry = get_named_parameter(event, 'industry').lower()
industry_filtered_companies = [company for company in company_data
if company['industrySector'].lower() == industry]
sorted_companies = sorted(industry_filtered_companies, key=lambda x: x['profit'], reverse=True)
top_companies = sorted_companies[:numCompanies]
return top_companies
def sendEmail(event, company_data):
client = boto3.client('ses', region_name='us-west-2')
emailAddress = get_named_parameter(event, 'emailAddress')
fomcSummary = get_named_parameter(event, 'fomcSummary')
# Retrieve the portfolio data as a string
portfolioDataString = get_named_parameter(event, 'portfolio')
# Prepare the email content
email_subject = "Portfolio Creation Summary and FOMC Search Results"
email_body = f"FOMC Search Summary:\n{fomcSummary}\n\nPortfolio Details:\n{json.dumps(portfolioDataString, indent=4)}"
# Email sending code here (commented out for now)
message = {"Subject": {"Data": email_subject}, "Body": {"Text": {"Data": email_body}}}
response = client.send_email(Source = "xxxxx@gmail.com", Destination = {"ToAddresses": [emailAddress]}, Message = message)
return "Email sent successfully to {}".format(emailAddress)
result = ''
response_code = 200
action_group = event['actionGroup']
api_path = event['apiPath']
print("api_path: ", api_path )
if api_path == '/companyResearch':
result = companyResearch(event)
elif api_path == '/createPortfolio':
result = createPortfolio(event, company_data)
elif api_path == '/sendEmail':
result = sendEmail(event, company_data)
else:
response_code = 404
result = f"Unrecognized api path: {action_group}::{api_path}"
response_body = {
'application/json': {
'body': result
}
}
action_response = {
'actionGroup': event['actionGroup'],
'apiPath': event['apiPath'],
'httpMethod': event['httpMethod'],
'httpStatusCode': response_code,
'responseBody': response_body
}
api_response = {'messageVersion': '1.0', 'response': action_response}
return api_response
```