我尝试在AWS上创建了一个用于业务生成的AI Slack机器人

概要:简要概括

这是 AWS for Games Advent Calendar 2023 第八天的文章。

当工作遇到困难时,我想向像ChatGPT这样的人工智能询问!

虽然这样说,但我认为将业务数据读取到ChatGPT中在安全方面多数情况下是不被允许的。
所以!我打算使用提供安全措施的AWS来开发工具。

这篇文章是一位平时不接触AI的普通工程师根据各种资料编写而成。

我按照细分的步骤实施,因此如果你可以追踪下去,就可以在理解的基础上创建一个机器人(真好)。代码有点糟糕,请帮忙重构一下mm。

调查

首先,我查了一下有哪些服务可用。

AI 生成系统 = 隐喻道真

听说Bedrock挺推荐的。
好像在隐私、合规和安全方面表现不错呢(可能是我理解错误)。

根据业务处理的信息给出答案,可以吗?

唔,好像在 Kendra 上使用 Bedrock 进行 RAG(召回增强生成)效果很不错!(但我不懂)

※包括RAG在内的表达真是挺难的呢。。因为是一种方法,所以好像可以说”进行RAG”吧..??

顺便说一句,据说还有被发布的Knowledge base以及对RAG的选择也在增加。
希望能把东京地区也加入进来!

构成案 àn)

在UI的设计中可能有各种不同的变体。

    1. 在专用的网站上提问并得到答案。

可能是用Amplify + Cognito + API Gateway + Lambda + Kendra + Bedrock?

通过Slack进行交流。

可能是用API Gateway + Lambda + Kendra + Bedrock?

※Lambda 的部分可能有很多其他选择!

两者看起来都很不错,但是由于之前一直迷迷糊糊的,我没有完成这篇文章的余地!(自作自受)所以我们会选择相对简单的方针,即通过Slack进行沟通交流来进行。

请小心

如果你打算创建和使用这些服务,请注意每个服务的使用费用。尤其是 Kendra,因为它是一项商业服务,所以费用较高且按量计费。请确保在使用之前检查下面的说明,截至2023年12月06日。

你可以免费开始使用Amazon Kendra开发者版,最初的30天内提供最多750小时的免费使用额度。

进行实施

由于打算使用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的代码:terraform/provider.tf
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指数

Amazon_Kendra___ap-northeast-1.png

使用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
Terraform 代码链接如下: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
}

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 的支持可能还不稳定,所以让我们从控制台进行操作吧!

我将指定我们的公司网站。

Amazon_Kendra___ap-northeast-1.png
    • 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的数据源与同步。

Amazon_Kendra___ap-northeast-1.png

因为需要几分钟到十几分钟的时间,所以我会耐心等待。

如果您是手动设置的话,我认为在CloudWatch中会创建一个名为/aws/kendra/(kendra index 的 id)的日志组,如果想要更改保留期限,请进行相应的设置。
(为了方便辨识日志组名称,希望不要使用ID,而是使用唯一的Name来创建…)

在Kendra中进行数据搜索的操作确认

Amazon_Kendra___ap-northeast-1.png
Cursor_と_Amazon_Kendra___ap-northeast-1.png

让我们试着搜索一下吧!
※我忘记上传图片了…

做得很好啊!

使用Bedrock

激活Claude Instant。

我对这个模型不太了解,但这次我将使用官方教程所介绍的Claude Instant。

在Amazon Bedrock上进行搜索
点击侧边栏的模型访问
点击右上角的管理模型访问
勾选并保存更改即可启用。

如果被问到使用案例,请根据需要填写。
英语并不需要那么准确,好像也没关系。

Amazon_Bedrock___ap-northeast-1.png

尝试与Bedrock进行对话

首先我们来试用一下。

在侧边栏中选择“Chat”,然后从“Select model”中选择Anthropic的Claude Instant 1.2。

Amazon_Bedrock___ap-northeast-1.png

现在我想在 Kendra 上运行 RAG,但是我想快速确认一下是否可以使用 Bedrock,你知道吗?

Amazon_Bedrock___ap-northeast-1.png

看来需要代码呢。
即使没有 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

bks-stg-bks-lambda_-_Lambda.png
Cursor_と_bks-stg-bks-lambda_-_Lambda.png

我已经确认了通过Lambda可以调用Bedrock!

用Kendra执行RAG来调用Bedrock。

接下来我们让Bedrock根据Kendra的信息来回答吧!

编辑Lambda代码

请参考以下选项:

    https://dev.classmethod.jp/articles/amazon-bedrock-kendra-lambda-rag/

由于参考网站非常出色,所以我立即开始编写代码。

下面是代码:看起来要使用Kendra索引ID,因此在模块之间进行传递。
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。

Cursor_と_bks-stg-bks-lambda_-_Lambda.png

我刚才问了与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
API_Gateway_-_リソース.png

当查看事件内容并在 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

Cursor_と_Slack_API__Applications___Slack.png
Slack_API__Applications___Slack.png
Cursor_と_Slack_API__Applications___Slack.png
Slack_API__Applications___DSM_Slack.png

在 Scopes 的 Bot Token Scopes 中选择以下权限作为允许的权限…

    • chat:write

 

    • app_mentions:read

 

    channels:history
Slack_API__Applications___DSM_Slack.png
Cursor_と_Slack_API__Applications___DSM_Slack.png

在 OAuth 和权限中,记下 Bot 用户 OAuth 令牌…
在基本信息中,记下应用凭证的签名密钥。

这就完成了Slack的单独设置!

设置接收Slack事件的配置。

接下来,我们将连接Slack和AWS的服务(API Gateway + Lambda)!

首先打开“事件订阅”…

Cursor_と_Slack_API__Applications___DSM_Slack.png

由于返回参数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 并进行操作确认。

times_izumix_-DSM-2_個の新しいアイテム-_Slack.png

不会回来。

times_izumix_-DSM-6_個の新しいアイテム-_Slack.png

查看日志,发现想要获取的字符串似乎存在于一些复杂的地方。
另外,查阅了一下Slack Bot,发现即使回复了事件请求,似乎也无法在Slack上回复。
(因为已经有大约5年没有做Slack bot了,所以创建bot也是新的发现…还需要做更多的工作…!!)

根据Slack的请求修改代码。

接收到来自Slack的事件后,需要进行发布,因此需要添加库。

# terraform/modules/lambda の venv 環境で実行
pip install requests "urllib3<2"
pip freeze > requirements.txt

改變收到的請求解釋並添加聊天發布。
* 雖然這段程式碼有些凌亂,但因為沒有時間重新整理,所以直接貼上。麻煩諒解一下 mm

Cursor_と_Amazon_Bedrock___ap-northeast-1.png
这里是代码terraform/modules/lambda/src/main.py
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”
}

times_izumix_-DSM-2_個の新しいアイテム-_Slack.png

感觉不错!

请验证一下这是来自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代码也很糟糕,所以在忘记前需要整理一下…

距离圣诞节还有17天!!

广告
将在 10 秒后关闭
bannerAds