我尝试在AWS上创建了一个用于业务生成的AI Slack机器人
概要:简要概括
这是 AWS for Games Advent Calendar 2023 第八天的文章。
当工作遇到困难时,我想向像ChatGPT这样的人工智能询问!
虽然这样说,但我认为将业务数据读取到ChatGPT中在安全方面多数情况下是不被允许的。
所以!我打算使用提供安全措施的AWS来开发工具。
这篇文章是一位平时不接触AI的普通工程师根据各种资料编写而成。
调查
首先,我查了一下有哪些服务可用。
AI 生成系统 = 隐喻道真
听说Bedrock挺推荐的。
好像在隐私、合规和安全方面表现不错呢(可能是我理解错误)。
根据业务处理的信息给出答案,可以吗?
唔,好像在 Kendra 上使用 Bedrock 进行 RAG(召回增强生成)效果很不错!(但我不懂)
※包括RAG在内的表达真是挺难的呢。。因为是一种方法,所以好像可以说”进行RAG”吧..??
希望能把东京地区也加入进来!
构成案 àn)
在UI的设计中可能有各种不同的变体。
-
- 在专用的网站上提问并得到答案。
可能是用Amplify + Cognito + API Gateway + Lambda + Kendra + Bedrock?
通过Slack进行交流。
可能是用API Gateway + Lambda + Kendra + Bedrock?
※Lambda 的部分可能有很多其他选择!
两者看起来都很不错,但是由于之前一直迷迷糊糊的,我没有完成这篇文章的余地!(自作自受)所以我们会选择相对简单的方针,即通过Slack进行沟通交流来进行。
请小心
如果你打算创建和使用这些服务,请注意每个服务的使用费用。尤其是 Kendra,因为它是一项商业服务,所以费用较高且按量计费。请确保在使用之前检查下面的说明,截至2023年12月06日。
进行实施
由于打算使用Lambda和API Gateway,因此我计划在这里创建GitHub存储库,并使用Terraform管理AWS资源。
*Kendra数据源目前Terraform的兼容性有些问题,所以我决定手动配置。
*Terraform使用的IAM前提是本地存在的。
创建项目
由于使用了Bedrock和Kendra,因此我将称之为Bot”.bks=(Bedrock Kendra System)”。
*如果您进行测试,请使用不同的名称,这样可以避免与S3存储桶名称重复,从而更加顺利。
首先,让我们准备好Terraform!
mkdir bks
cd bks
mkdir terraform # 振り返るとこのディレクトリいらんかった...
touch terraform/provider.tf
Terraform 提供器设置
※由于我们将在staging环境进行测试,因此在此处称之为staging,但您可以根据需要进行更改。
terraform {
required_version = “>=1.3.5”
required_providers {
aws = {
source = “hashicorp/aws”
version = “~>5.22.0”
}
}
# 将tfstate保存到S3(https://developer.hashicorp.com/terraform/language/settings/backends/s3)
backend “s3” {
bucket = “bks-tfstate-staging” # 需要根据实际情况进行更改。
key = “bks.tfstate” # 需要根据实际情况进行更改。
region = “ap-northeast-1” # 如果没有要求,不一定非得是东京。
profile = “terraform” # 需要根据实际情况进行更改。
}
}
# —————————–
# Provider
# —————————–
provider “aws” {
profile = “terraform” # 需要根据实际情况进行更改。
region = “ap-northeast-1”
default_tags {
tags = {
Env = “stg” # 需要根据实际情况进行更改。
System = “bks” # 需要根据实际情况进行更改。
}
}
}
将tfstate文件保存在S3上。
请随意创建。
terraform 的初始处理
# `terraform` ディレクトリで実行
# cd terraform
terraform init
将数据放入Kendra
创建Kendra指数
使用Terraform创建Kendra索引。
使用Terraform执行,但实际上所做的几乎等同于在控制台上使用默认设置创建。
# プロジェクトルートで実行
touch terraform/main.tf
touch terraform/variables.tf
mkdir -p terraform/modules/kendra
touch terraform/modules/kendra/main.tf
touch terraform/modules/kendra/variables.tf
data “aws_caller_identity” “current” {}
data “aws_region” “current” {}
module “kendra” {
source = “./modules/kendra”
account_id = data.aws_caller_identity.current.account_id
region_name = data.aws_region.current.name
service_name = var.service_name
environment = var.environment
retention_in_days = var.retention_in_days
}
terraform/variables.tf
variable “service_name” {
type = string
default = “bks”
}
variable “environment” {
type = string
default = “stg”
}
variable “retention_in_days” {
type = number
default = 1
}
terraform/modules/kendra/main.tf
# —————————–
# Kendra 索引
# —————————–
resource “aws_kendra_index” “bks_kendra” {
name = “bks”
edition = “DEVELOPER_EDITION”
role_arn = aws_iam_role.bks_kendra.arn
}
# —————————–
# Kendra 索引 IAM 角色
# —————————–
resource “aws_iam_role” “bks_kendra” {
name = “${var.service_name}-${var.environment}-kendra-index”
assume_role_policy = data.aws_iam_policy_document.kendra_assume_role.json
}
// 角色扮演
data “aws_iam_policy_document” “kendra_assume_role” {
statement {
actions = [“sts:AssumeRole”]
effect = “Allow”
principals {
identifiers = [“kendra.amazonaws.com”]
type = “Service”
}
}
}
# 与策略关联
resource “aws_iam_role_policy_attachment” “bks_kendra” {
role = aws_iam_role.bks_kendra.name
policy_arn = aws_iam_policy.bks_kendra.arn
}
# 策略
resource “aws_iam_policy” “bks_kendra” {
name = “${var.service_name}-${var.environment}-kendra-index-policy”
policy = data.aws_iam_policy_document.bks_kendra.json
}
data “aws_iam_policy_document” “bks_kendra” {
statement {
effect = “Allow”
actions = [“cloudwatch:PutMetricData”]
resources = [“*”]
condition {
test = “StringEquals”
values = [“Kendra”]
variable = “cloudwatch:namespace”
}
}
statement {
effect = “Allow”
actions = [“logs:CreateLogGroup”]
resources = [“arn:aws:logs:${var.region_name}:${var.account_id}:log-group:/aws/kendra/*”]
}
statement {
effect = “Allow”
actions = [
“logs:DescribeLogStreams”,
“logs:CreateLogStream”,
“logs:PutLogEvents”
]
resources = [“arn:aws:logs:${var.region_name}:${var.account_id}:log-group:/aws/kendra/*:log-stream:*”]
}
statement {
effect = “Allow”
actions = [“logs:DescribeLogGroups”]
resources = [“*”]
}
}
# —————————–
# 日志
# —————————–
resource “aws_cloudwatch_log_group” “bks_kendra” {
name = “/aws/kendra/${aws_kendra_index.bks_kendra.id}”
retention_in_days = var.retention_in_days
}
terraform/modules/kendra/variables.tf
variable “account_id” {
type = string
}
variable “region_name” {
type = string
}
variable “service_name” {
type = string
}
variable “environment” {
type = string
}
variable “retention_in_days” {
type = number
}
在执行之前,请确认差异!
terraform init # モジュール追加後の1回目は必要!
terraform plan
# よければ
terraform apply
# ここでほんまにデプロイする?と聞かれるので、 yes と答えたらデプロイされます。
因为听说要花大约30分钟时间才能完成,所以我会在这里等待。
创建Kendra数据源
请在下面的选项中选择创建的索引,然后添加数据源。
由于 Terraform 的支持可能还不稳定,所以让我们从控制台进行操作吧!
我将指定我们的公司网站。
-
- Data source name: homepage
Default language: Japanese(ja)
Source URLs: (それぞれクロールしても良いURLを指定して下さい)
IAM role: Create a new role(Recommended)
Role name: AmazonKendra-(サイト名とか) (任意)
Frequency: Run on demand
在此次中,我们选择手动获取数据的方式来进行试验,尽管通过设置频率可以实现自动数据更新。
将Kendra的数据源与同步。
因为需要几分钟到十几分钟的时间,所以我会耐心等待。
(为了方便辨识日志组名称,希望不要使用ID,而是使用唯一的Name来创建…)
在Kendra中进行数据搜索的操作确认
让我们试着搜索一下吧!
※我忘记上传图片了…
做得很好啊!
使用Bedrock
激活Claude Instant。
我对这个模型不太了解,但这次我将使用官方教程所介绍的Claude Instant。
在Amazon Bedrock上进行搜索
点击侧边栏的模型访问
点击右上角的管理模型访问
勾选并保存更改即可启用。
如果被问到使用案例,请根据需要填写。
英语并不需要那么准确,好像也没关系。
尝试与Bedrock进行对话
首先我们来试用一下。
在侧边栏中选择“Chat”,然后从“Select model”中选择Anthropic的Claude Instant 1.2。
现在我想在 Kendra 上运行 RAG,但是我想快速确认一下是否可以使用 Bedrock,你知道吗?
看来需要代码呢。
即使没有 RAG,它似乎也能给出很好的答案。太棒了!
使用Bedrock替代Lambda
因为当Kendra连接突然无法运行时,我会感到紧张,所以首先我将尝试创建一个来自Bedrock的Lambda回答。
※根据参考网站编写,但我很少写Python,所以一些地方可能有所不当。
引用:
-
- https://dev.classmethod.jp/articles/terraform-lambda-deployment/
-
- https://zenn.dev/not75743/articles/7a7d3a2fc7e788
-
- https://www.insurtechlab.net/use_amazon_bedrock/
- https://dev.classmethod.jp/articles/invoke-bedrock-form-lambda-function/
创建 Lambda
我觉得没有必要分成模块,但由于之前使用了Kendra把它分开了,所以还是分开吧。
※回头看的话,肯定是不需要分开的。
# `terraform` で実行
# cd terraform
touch main.tf
touch variables.tf
mkdir -p modules/lambda
touch modules/lambda/main.tf
touch modules/lambda/variables.tf
touch modules/lambda/build-lambda.sh
chmod 755 modules/lambda/build-lambda.sh
一起编辑之后变成了这样。terraform/main.tf
data “aws_caller_identity” “current” {}
data “aws_region” “current” {}
module “kendra” {
source = “./modules/kendra”
account_id = data.aws_caller_identity.current.account_id
region_name = data.aws_region.current.name
service_name = var.service_name
environment = var.environment
retention_in_days = var.retention_in_days
}
module “lambda” {
source = “./modules/lambda”
service_name = var.service_name
environment = var.environment
retention_in_days = var.retention_in_days
}
terraform/variables.tf
variable “service_name” {
type = string
default = “bks”
}
variable “environment” {
type = string
default = “stg”
}
variable “retention_in_days” {
type = number
default = 1
}
terraform/modules/lambda/main.tf
resource “null_resource” “run_script” {
triggers = {
file_hashes = jsonencode({
for fn in fileset(“${path.module}/src”, “**”) :
fn => filesha256(“${path.module}/src/${fn}”)
})
}
provisioner “local-exec” {
command = “${path.module}/build-lambda.sh”
}
}
# —————————–
# Lambda层
# —————————–
data “archive_file” “layer_zip” {
depends_on = [
null_resource.run_script
]
type = “zip”
source_dir = “${path.module}/build/layer”
output_path = “${path.module}/build/layer.zip”
}
resource “aws_lambda_layer_version” “bks_lambda” {
layer_name = “${var.service_name}_${var.environment}_bks_lambda”
filename = data.archive_file.layer_zip.output_path
source_code_hash = data.archive_file.layer_zip.output_base64sha256
}
# —————————–
# 使用Kendra结果调用Bedrock的Lambda代码
# —————————–
data “archive_file” “bks_lambda” {
depends_on = [
null_resource.run_script
]
type = “zip”
source_dir = “${path.module}/build/function”
output_path = “${path.module}/build/function.zip”
}
resource “aws_lambda_function” “bks_lambda” {
function_name = “${var.service_name}-${var.environment}-bks-lambda”
filename = data.archive_file.bks_lambda.output_path
handler = “main.handler”
runtime = “python3.10”
source_code_hash = data.archive_file.bks_lambda.output_base64sha256
timeout = 10
layers = [aws_lambda_layer_version.bks_lambda.arn]
role = aws_iam_role.bks_lambda.arn
environment {
variables = {
KENDRA_INDEX_ID = var.kendra_index_id
}
}
}
# —————————–
# IAM
# —————————–
resource “aws_iam_role” “bks_lambda” {
name = “${var.service_name}-${var.environment}-bks-lambda”
assume_role_policy = data.aws_iam_policy_document.lambda_assume_role.json
}
# 信任策略
data “aws_iam_policy_document” “lambda_assume_role” {
statement {
actions = [“sts:AssumeRole”]
effect = “Allow”
principals {
identifiers = [“lambda.amazonaws.com”]
type = “Service”
}
}
}
# 与策略关联
resource “aws_iam_role_policy_attachment” “lambda_bedrock” {
role = aws_iam_role.bks_lambda.name
policy_arn = aws_iam_policy.lambda_bedrock.arn
}
# 策略
resource “aws_iam_policy” “lambda_bedrock” {
name = “${var.service_name}-${var.environment}-bks-lambda-policy”
policy = data.aws_iam_policy_document.lambda_bedrock.json
}
data “aws_iam_policy_document” “lambda_bedrock” {
version = “2012-10-17”
statement {
effect = “Allow”
actions = [
“logs:CreateLogGroup”,
“logs:CreateLogStream”,
“logs:PutLogEvents”
]
resources = [
aws_cloudwatch_log_group.bks_lambda.arn,
“${aws_cloudwatch_log_group.bks_lambda.arn}:*”
]
}
statement {
effect = “Allow”
actions = [
“bedrock:*”,
]
resources = [“*”]
}
}
# —————————–
# log
# —————————–
resource “aws_cloudwatch_log_group” “bks_lambda” {
name = “/aws/lambda/${aws_lambda_function.bks_lambda.function_name}”
retention_in_days = var.retention_in_days
}
terraform/modules/lambda/variables.tf
variable “service_name” {
type = string
}
variable “environment” {
type = string
}
variable “retention_in_days” {
type = number
}
terraform/modules/lambda/build-lambda.sh
#!/usr/bin/env bash
cd `dirname $0`
if [ -d build ]; then
rm -rf build
fi
# 重新创建构建目录
mkdir -p build/function/ build/layer/
# 复制源文件
echo “复制源文件”
cp -r src/ build/function
# 打包Python库
echo “打包Python库”
pip3 install -r requirements.txt -t build/layer/python
# 在构建目录中删除pycache
find build -type f | grep -E “(__pycache__|\.pyc|\.pyo$)” | xargs rm
terraform/modules/lambda/src/main.py
import boto3
import json
bedrock_runtime = boto3.client(‘bedrock-runtime’, region_name=’ap-northeast-1′)
def handler(event, _):
# 获取要设置在提示中的内容
prompt = event.get(‘prompt’)
# 指定各种参数
model_id = ‘anthropic.claude-instant-v1’
accept = ‘application/json’
content_type = ‘application/json’
# 指定请求BODY
body = json.dumps({
“prompt”: f”\n\nHuman: ${prompt}.\\n\\nAssistant:”,
“max_tokens_to_sample”: 300,
“temperature”: 0.8,
“top_p”: 0.999,
“top_k”: 250,
“stop_sequences”: [“\\n\\nHuman:”],
“anthropic_version”: “bedrock-2023-05-31”
})
# 调用Bedrock API
response = bedrock_runtime.invoke_model(
modelId=model_id,
accept=accept,
contentType=content_type,
body=body
)
# 从API响应中提取BODY
response_body = json.loads(response.get(‘body’).read())
# 从响应BODY中提取响应文本
outputText = response_body.get(‘completion’)
print(outputText)
为Lambda层做准备工作。
我会使用venv做一些准备工作。
# terraform/modules/lambda で実行
python3 -m venv venv # 一度だけでOK
source venv/bin/activate # 仮想環境を有効化(無効にした後は都度実行)
pip install boto3
pip freeze > requirements.txt
让我们部署一下吧!
terraform init
terraform plan
# この段階では`terraform/lambda/build` にファイルできていません
terraform apply
# ここで yes とすると build にファイルが生成されてデプロイされるはずです。
太棒了,你成功了!
从AWS控制台测试Lambda
我已经确认了通过Lambda可以调用Bedrock!
用Kendra执行RAG来调用Bedrock。
接下来我们让Bedrock根据Kendra的信息来回答吧!
编辑Lambda代码
请参考以下选项:
- https://dev.classmethod.jp/articles/amazon-bedrock-kendra-lambda-rag/
由于参考网站非常出色,所以我立即开始编写代码。
touch terraform/modules/kendra/outputs.tf
terraform/modules/kendra/outputs.tf
output “kendra_index_id” {
value = aws_kendra_index.bks_kendra.id
}
terraform/modules/lambda/variables.tf
— a/terraform/modules/lambda/variables.tf
+++ b/terraform/modules/lambda/variables.tf
variable “retention_in_days” {
type = number
}
+
+variable “kendra_index_id” {
+ type = string
+}
terraform/modules/lambda/main.tf
timeout = 10
layers = [aws_lambda_layer_version.bks_lambda.arn]
role = aws_iam_role.bks_lambda.arn
+
+ environment {
+ variables = {
+ KENDRA_INDEX_ID = var.kendra_index_id
+ }
+ }
}
+# 为了使用Kendra进行附加
+resource “aws_iam_role_policy_attachment” “lambda_kendra” {
+ role = aws_iam_role.bks_lambda.name
+ policy_arn = data.aws_iam_policy.kendra_full_access.arn
+}
+
+data “aws_iam_policy” “kendra_full_access” {
+ arn = “arn:aws:iam::aws:policy/AmazonKendraFullAccess”
+}
terraform/main.tf
data “aws_caller_identity” “current” {}
data “aws_region” “current” {}
module “kendra” {
source = “./modules/kendra”
account_id = data.aws_caller_identity.current.account_id
region_name = data.aws_region.current.name
service_name = var.service_name
environment = var.environment
retention_in_days = var.retention_in_days
}
module “lambda” {
source = “./modules/lambda”
service_name = var.service_name
environment = var.environment
retention_in_days = var.retention_in_days
kendra_index_id = module.kendra.kendra_index_id # 添加
}
terraform/modules/lambda/src/main.py
import os
import boto3
import json
kendra = boto3.client(‘kendra’)
bedrock_runtime = boto3.client(‘bedrock-runtime’, region_name=’ap-northeast-1′)
def get_retrieval_result(query_text, index_id):
response = kendra.retrieve(
QueryText=query_text,
IndexId=index_id,
AttributeFilter={
“EqualsTo”: {
“Key”: “_language_code”,
“Value”: {“StringValue”: “ja”},
},
},
)
# 从Kendra的响应中提取前5个结果
results = response[‘ResultItems’][:5] if response[‘ResultItems’] else []
extracted_results = []
for item in results:
content = item.get(‘Content’)
document_uri = item.get(‘DocumentURI’)
extracted_results.append({
‘Content’: content,
‘DocumentURI’: document_uri,
})
print(“Kendra提取的结果:” + json.dumps(extracted_results, ensure_ascii=False))
return extracted_results
def handler(event, _):
# 获取要设置为提示的内容
user_prompt = event.get(‘user_prompt’)
kendra_index_id = os.environ[‘WEB_HOOK_URL’]
prompt = f”””\n\nHuman:
[参考]根据[问题]适当回答。
[问题]
{user_prompt}
[参考]
{get_retrieval_result(user_prompt, kendra_index_id)}
Assistant:
“””
# 指定各种参数
model_id = ‘anthropic.claude-instant-v1’
accept = ‘application/json’
content_type = ‘application/json’
# 指定请求BODY
body = json.dumps({
“prompt”: f”\n\nHuman: ${prompt}.\\n\\nAssistant:”,
“max_tokens_to_sample”: 600,
“temperature”: 0.8,
“top_p”: 0.999,
“top_k”: 250,
“stop_sequences”: [“\\n\\nHuman:”],
“anthropic_version”: “bedrock-2023-05-31”
})
# 调用Bedrock API
response = bedrock_runtime.invoke_model(
modelId=model_id,
accept=accept,
contentType=content_type,
body=body
)
# 从API响应中提取BODY
response_body = json.loads(response.get(‘body’).read())
# 从响应BODY中提取返回文本
return response_body.get(‘completion’)
聊天一下,写的时候会发现 Terraform 模块之间的依赖太强了,导致配置失败对吧?
是的,我错误地分配了它们。
本来应该修正,但我们还是继续吧!
terraform plan
terraform apply
# OKなら yes
部署完成了吧!
从AWS控制台测试Lambda。
我刚才问了与Kendra收集的信息有关的问题,得到了正确的答案!
这样一来,Kendra和Bedrock的调用就完成了。
太厉害了,整个调用过程都非常出色,让人感动…!!
和Slack进行连接
创建 API Gateway
最后的工作是在AWS上创建Slack机器人。
首先,我们将通过 API Gateway 的 REST API 来接收来自 Slack 的事件并代理给 Lambda,构建架构。先搭建 API Gateway,然后可以稍后对 Lambda 进行修改。
考虑一下这个建议,并决定是否接受。
- https://www.beex-inc.com/blog/slackbot-aws-lambda
(似乎不需要分模块.. 但是还是在这里公开,RTA啊啊)
mkdir terraform/modules/lambda
touch terraform/modules/lambda/api_gateway.tfterraform/modules/lambda/api_gateway.tf
# —————————–
# 从Slack接收请求并传递给Lambda的API Gatway
# —————————–
resource “aws_api_gateway_rest_api” “bks” {
name = “${var.service_name}-${var.environment}-api”
endpoint_configuration {
types = [“REGIONAL”] # 不需要太快的速度,所以选择地区
}
body = jsonencode({
openapi = “3.0.1”
info = {
title = “api”
version = “1.0”
}
paths = {
“/slack/events” = {
post = {
x-amazon-apigateway-integration = {
httpMethod = “POST”
payloadFormatVersion = “1.0”
type = “AWS_PROXY”
uri = aws_lambda_function.bks_lambda.invoke_arn
credentials = aws_iam_role.api_gateway_role.arn
}
}
}
}
})
}
resource “aws_api_gateway_deployment” “deployment” {
depends_on = [
aws_api_gateway_rest_api.bks
]
rest_api_id = aws_api_gateway_rest_api.bks.id
stage_name = var.environment
triggers = {
redeployment = sha1(jsonencode(aws_api_gateway_rest_api.bks))
}
}
data “aws_iam_policy_document” “api_gateway_policy” {
statement {
effect = “Allow”
principals {
type = “*”
identifiers = [“*”]
}
actions = [“execute-api:Invoke”]
resources = [“${aws_api_gateway_rest_api.bks.execution_arn}/*”]
}
}
resource “aws_api_gateway_rest_api_policy” “policy” {
rest_api_id = aws_api_gateway_rest_api.bks.id
policy = data.aws_iam_policy_document.api_gateway_policy.json
}
# —————————–
# IAM
# —————————–
resource “aws_iam_role” “api_gateway_role” {
name = “${var.service_name}-${var.environment}-apigateway-role”
assume_role_policy = data.aws_iam_policy_document.api_gateway_assume_role.json
}
resource “aws_iam_role_policy_attachment” “api_gateway_policy_logs” {
role = aws_iam_role.api_gateway_role.name
policy_arn = “arn:aws:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs”
}
resource “aws_iam_role_policy_attachment” “api_gateway_policy_lambda” {
role = aws_iam_role.api_gateway_role.name
policy_arn = “arn:aws:iam::aws:policy/service-role/AWSLambdaRole”
}
data “aws_iam_policy_document” “api_gateway_assume_role” {
statement {
actions = [“sts:AssumeRole”]
effect = “Allow”
principals {
type = “Service”
identifiers = [“apigateway.amazonaws.com”]
}
}
}
那么让我们进行部署吧!
terraform plan
terraform apply
# OK なら yes
当查看事件内容并在 CloudWatchLogs 上查看日志时,似乎配置中存在一个请求主体的 body 字段。
重新编写Lambda,以便通过API Gateway进行测试。
我会重新写!
为了使Lambda控制台上的测试也能通过,我将尝试让它无论是body还是body字段都能运行。关于反馈评论
在与Slack的集成过程中,可能不需要了,但是一直到一半时还是很容易进行本地调试的。
terraform/modules/lambda/src/main.py
import os
import boto3
import json
kendra = boto3.client(‘kendra’)
bedrock_runtime = boto3.client(‘bedrock-runtime’, region_name=’ap-northeast-1′)
def get_retrieval_result(query_text, index_id):
response = kendra.retrieve(
QueryText=query_text,
IndexId=index_id,
AttributeFilter={
“EqualsTo”: {
“Key”: “_language_code”,
“Value”: {“StringValue”: “ja”},
},
},
)
# 从Kendra的响应中提取前5个结果
results = response[‘ResultItems’][:5] if response[‘ResultItems’] else []
extracted_results = []
for item in results:
content = item.get(‘Content’)
document_uri = item.get(‘DocumentURI’)
extracted_results.append({
‘Content’: content,
‘DocumentURI’: document_uri,
})
print(“Kendra提取的结果:” + json.dumps(extracted_results, ensure_ascii=False))
return extracted_results
def handler(event, context):
# 获取要设置为提示的内容
body = json.loads(event[“body”]) if ‘body’ in event else event
print(body)
user_prompt = body.get(‘user_prompt’)
kendra_index_id = os.environ[‘KENDRA_INDEX_ID’]
prompt = f”””\n\n人类:
[参考]请根据[问题]适当地进行回答。
[问题]
{user_prompt}
[参考]
{get_retrieval_result(user_prompt, kendra_index_id)}
助手:
“””
# 指定各种参数
model_id = ‘anthropic.claude-instant-v1’
accept = ‘application/json’
content_type = ‘application/json’
# 指定请求BODY
body = json.dumps({
“prompt”: f”\n\n人类: ${prompt}.\\n\\n助手:”,
“max_tokens_to_sample”: 600,
“temperature”: 0.8,
“top_p”: 0.999,
“top_k”: 250,
“stop_sequences”: [“\\n\\n人类:”],
“anthropic_version”: “bedrock-2023-05-31”
})
# 调用Bedrock API
response = bedrock_runtime.invoke_model(
modelId=model_id,
accept=accept,
contentType=content_type,
body=body
)
# 从API响应中提取出BODY
response_body = json.loads(response.get(‘body’).read())
answer = response_body.get(‘completion’)
print(answer)
response = {
“statusCode”: 200,
‘body’: json.dumps({“text”: answer}, ensure_ascii=False)
}
return response
# 用于本地调试
# print(handler({“user_prompt”: “What is Mount Fuji?”}, None))
那我们开始部署吧!
terraform plan
terraform apply
# OK なら yes
答案收到了,真好!
在Slack的网站上获取用于应用的密钥。
我将根据参考的网站内容进行设置。
首先打开这个网址…https://api.slack.com/apps
在 Scopes 的 Bot Token Scopes 中选择以下权限作为允许的权限…
-
- chat:write
-
- app_mentions:read
- channels:history
在 OAuth 和权限中,记下 Bot 用户 OAuth 令牌…
在基本信息中,记下应用凭证的签名密钥。
这就完成了Slack的单独设置!
设置接收Slack事件的配置。
接下来,我们将连接Slack和AWS的服务(API Gateway + Lambda)!
首先打开“事件订阅”…
由于返回参数challenge,我将暂时返回它,以避免出现怒罚。
def handler(event, context):
# プロンプトに設定する内容を取得
body = json.loads(event["body"]) if 'body' in event else event
print(body)
+ if "challenge" in body:
+ return {
+ "statusCode": 200,
+ 'body': body.get('challenge')
+ }
我们来部署吧。
terraform apply
点击 Slack 设置界面中 Request URL 旁边的 Retry 按钮,一旦部署完成。
已验证通过了!
由于我们需要选择接收作为事件的内容,所以我们选择接收提及的事件。
在 “事件订阅” 下选择 “订阅机器人事件”,然后点击右下角的 “保存更改”。
完成后,我们应该再次保存设置(作为预防措施)。
选择左侧的“基本信息”,展开“将您的应用安装到工作区”,点击“安装到工作区的应用”,然后点击“允许”。
请将其添加到 Slack 并进行操作确认。
不会回来。
查看日志,发现想要获取的字符串似乎存在于一些复杂的地方。
另外,查阅了一下Slack Bot,发现即使回复了事件请求,似乎也无法在Slack上回复。
(因为已经有大约5年没有做Slack bot了,所以创建bot也是新的发现…还需要做更多的工作…!!)
根据Slack的请求修改代码。
接收到来自Slack的事件后,需要进行发布,因此需要添加库。
# terraform/modules/lambda の venv 環境で実行
pip install requests "urllib3<2"
pip freeze > requirements.txt
改變收到的請求解釋並添加聊天發布。
* 雖然這段程式碼有些凌亂,但因為沒有時間重新整理,所以直接貼上。麻煩諒解一下 mm
import os
import boto3
import json
import requests
kendra = boto3.client(‘kendra’)
bedrock_runtime = boto3.client(‘bedrock-runtime’, region_name=’us-west-2′)
url = “https://slack.com/api/chat.postMessage”
# 请注意,最好通过环境传递 Slack 的 Bot User OAuth Token!非常抱歉。
token = “xoxb-xxxxxxxxxxxxx”
def get_retrieval_result(query_text, index_id):
response = kendra.retrieve(
QueryText=query_text,
IndexId=index_id,
AttributeFilter={
“EqualsTo”: {
“Key”: “_language_code”,
“Value”: {“StringValue”: “ja”},
},
},
)
# 从 Kendra 响应中提取前5个结果
results = response[‘ResultItems’][:5] if response[‘ResultItems’] else []
extracted_results = []
for item in results:
content = item.get(‘Content’)
document_uri = item.get(‘DocumentURI’)
extracted_results.append({
‘Content’: content,
‘DocumentURI’: document_uri,
})
print(“Kendra extracted_results:” + json.dumps(extracted_results, ensure_ascii=False))
return extracted_results
def handler(event, context):
print(“event is ” + json.dumps(event))
# 忽略超时请求,因为如果不在3秒内返回,会重试
# https://dev.classmethod.jp/articles/slack-resend-matome/
if (‘X-Slack-Retry-Num’ in event[‘headers’]
and ‘X-Slack-Retry-Reason’ in event[‘headers’]
and event[‘headers’][‘X-Slack-Retry-Reason’] == “http_timeout”):
return {
‘statusCode’: 200,
‘body’: json.dumps({‘message’: ‘No need to resend’})
}
# 获取要设置的提示内容
body = json.loads(event[“body”]) if ‘body’ in event else event
print(“body is” + json.dumps(body))
# 用于连接的临时代码(稍后删除)
if “challenge” in body:
return {
“statusCode”: 200,
‘body’: body.get(‘challenge’)
}
# 获取所需信息
user_prompt = body.get(‘user_prompt’) \
if ‘user_prompt’ in body else body[‘event’][‘text’].replace(“メンションの文字列”, “”)
user_prompt = user_prompt.replace(‘\n’, ‘ ‘)
print(“user_prompt: ” + user_prompt)
channel = body[‘event’][‘channel’] if ‘event’ in body else ”
event_ts = body[‘event’][‘event_ts’] if ‘event’ in body else 0
kendra_index_id = os.environ[‘KENDRA_INDEX_ID’]
prompt = f”””\n\nHuman:
[参考]情報をもとに[質問]に適切に答えてください。
もし、答えのために利用した[参考]情報にURLがあれば、答えの後にリスト形式で返して下さい。
ただし、重複したURLは絶対に返さないようにして下さい。
[質問]
{user_prompt}
[参考]
{get_retrieval_result(user_prompt, kendra_index_id)}
Assistant:
“””
# 指定各种参数
model_id = ‘anthropic.claude-instant-v1’
accept = ‘application/json’
content_type = ‘application/json’
# 指定请求BODY
body = json.dumps({
“prompt”: f”\n\nHuman: ${prompt}.\\n\\nAssistant:”,
“max_tokens_to_sample”: 600,
“temperature”: 0.8,
“top_p”: 0.999,
“top_k”: 250,
“stop_sequences”: [“\\n\\nHuman:”],
“anthropic_version”: “bedrock-2023-05-31”
})
# 调用 Bedrock API
response = bedrock_runtime.invoke_model(
modelId=model_id,
accept=accept,
contentType=content_type,
body=body
)
# 从 API 响应中提取 BODY
response_body = json.loads(response.get(‘body’).read())
answer = response_body.get(‘completion’)
print(answer)
# 回复
header = {
“Authorization”: “Bearer {}”.format(token)
}
data = {
“channel”: channel,
“text”: answer,
“thread_ts”: event_ts,
}
res = requests.post(url, headers=header, json=data)
print(res.json())
return {
“statusCode”: 200,
“body”: “OK”
}
感觉不错!
请验证一下这是来自Slack的请求。
就動作本身而言是沒問題的,可以說已經完成了。
然而,API 網關卻是沒有任何身份驗證的,處於全裸狀態。(即使結果會被發布到 Slack 上…)
這是一個可能隱藏著自動觸發發布到 Slack 的炸彈系統,所以我們需要驗證是否真的是從 Slack 發出的請求。
使用Signing Secret。
参考资料:
– https://zenn.dev/t_kakei/articles/f61196a47f9b14
在参考网站上,还介绍了将密钥存储在AWS Secrets Manager并调用的方法。
实际制作时,我们将使用这种方法!
在这里,先暂时放在terraform.tfstate中。
(只写环境变量传递的部分)terraform/modules/lambda/main.tf
environment {
variables = {
KENDRA_INDEX_ID = var.kendra_index_id
+ SLACK_API_SIGNING_SECRET = var.slack_api_signing_secret
}
}
※只记录更改的部分
TIMESTAMP_DIFFERENCE_THRESHOLD = 60 * 5
def is_valid_request(headers, body):
slack_api_signing_secret = os.environ[“SLACK_API_SIGNING_SECRET”]
try:
request_ts = int(headers[“X-Slack-Request-Timestamp”])
current_timestamp = int(datetime.datetime.now().timestamp())
if abs(request_ts – current_timestamp) > TIMESTAMP_DIFFERENCE_THRESHOLD:
return False
signature = headers[“X-Slack-Signature”]
message = f”v0:{headers[‘X-Slack-Request-Timestamp’]}:{body}”
expected = create_hmac_hash(slack_api_signing_secret, message)
except Exception:
return False
else:
return hmac.compare_digest(expected, signature)
def create_hmac_hash(secret, message):
message_hmac = hmac.new(
bytes(secret, “UTF-8”), bytes(message, “UTF-8″), hashlib.sha256
)
return f”v0={message_hmac.hexdigest()}”
def handler(event, context):
print(“event is ” + json.dumps(event))
# 3秒内未返回会重新发送,因此忽略它
# https://dev.classmethod.jp/articles/slack-resend-matome/
if (‘X-Slack-Retry-Num’ in event[‘headers’]
and ‘X-Slack-Retry-Reason’ in event[‘headers’]
and event[‘headers’][‘X-Slack-Retry-Reason’] == “http_timeout”):
return {
‘statusCode’: 200,
‘body’: json.dumps({‘message’: ‘No need to resend’})
}
# 获取要设置的提示内容
body = json.loads(event[“body”])
print(“body is” + json.dumps(body))
# 验证是否为来自Slack的通信
if not is_valid_request(event[“headers”], event[“body”]):
print(“验证Slack请求失败”)
return {
‘statusCode’: 200,
}
完了了!那么我来试着从 Slack 上提问!
和之前一样,我们收到了回复!
现在,Kendra 已经与 Bedrock 上运行的 RAG 建立了联系,Slack 机器人的开发工作圆满完成!
总结
主要使用的服务
好像用了很多种方法,但主要的只有四个。
简单…!!
-
- API Gateway
-
- Lambda
-
- Kendra
- Bedrock など
在AWS上开发生成型AI工具怎么样?
就算对AI等一无所知,也能轻松制作出好用的工具,这真是太厉害了。
这个选择的动机是安全性和合规性在业务使用中的适应性,而且功能上易于使用,对于模型的更改和数据源的扩展也很方便,我认为这也是它的魅力之处。
关于今后的使用事宜
由于在验证环境中进行了设置,所以请大家继续尝试并进行交互,可以增加数据源、改变模型、尝试调整参数,如果能够得到适合的反馈,我们将计划在正式运营环境中全面使用。
试着做一下…
虽然容易,但因为使用了平时不常用的工具,所以到处都遇到了困难,非常辛苦——意外地,可能“迷上”了Slack机器人…
创造得有点担心…
我做了一些奇怪的模块,创建了一个唯一的terraform目录… 还需要管理密钥… 还需要与SNS合作以检测错误…
最重要的是,Python代码也很糟糕,所以在忘记前需要整理一下…