Briswell Tech Blog

ブリスウェルのテックブログです

CloudFrontとブラウザのキャッシュについて

暑い日が続きますね。キュウリがとても美味しい季節となりました。
キュウリは、汗と共に失われる水分を補給し体の冷却を助けてくれます。

今回は、キャッシュ(頻繁にアクセスするデータを一時的に保存して次回のアクセスを速くする仕組み)についてです。

なんとなく、効率化という観点でキュウリとキャッシュ似ているような気がします。こじつけかしらん。

tech.briswell.com

前回はCloudFrontの利用メリットについて書きましたが、キャッシュの仕様についても、正しく理解しておかないと、CDNのCloudFrontを最大限に活かすことができません。

  • CloudFront側のキャッシュ(エッジキャッシュ)
  • ブラウザ側のキャッシュ

それぞれ見ていきましょう。

1. CloudFront側のキャッシュ

CloudFrontは、以下が同じ場合に同一リクエストと判断し、キャッシュをクライアント側に返却します。

  • URL
  • HTTPメソッド
  • 特定の HTTPヘッダー(任意設定)
  • クエリ文字列(任意設定)
  • cookie(任意設定)

キャッシュ期間は、TTL設定(最低存続時間、最大存続時間、デフォルトの存続時間)で定義します。

Time to live (TTL) 設定:
キャッシュキーの管理 - Amazon CloudFront

CloudFrontのInvalidation(1か月に送信した無効化パスのうち最初の1,000 件は無料)を実行することで、CloudFrontのキャッシュを手動削除することも可能です。

1-1. キャッシュが存在しない場合
  • 設定された条件(オリジンリクエストポリシー)に従い、オリジンサーバーにリクエストを転送しデータを取得します。
  • 取得したデータをクライアントに返却します。
  • 取得したデータを設定された条件(キャッシュポリシー)に従ってキャッシュします。
1-2. 有効なキャッシュが存在する場合
  • キャッシュデータをクライアントに返却します。
1-3. 無効な(期限切れ)キャッシュが存在する場合
  • 設定された条件(オリジンリクエストポリシー)に従い、オリジンサーバーにリクエストを転送しデータを取得します。
  • オリジンサーバーのデータがキャッシュと同じ場合
    • キャッシュデータをクライアントに返却します。
    • キャッシュデータのTTLを更新します。
  • オリジンサーバーのデータがキャッシュと違う場合
    • 取得したデータをクライアントに返却します。
    • 取得したデータを設定された条件(キャッシュポリシー)に従ってキャッシュします。

2. ブラウザ側のキャッシュ

ブラウザ側のキャッシュは、Cache-Controlの設定で制御することができます。

この設定を理解しておかないと...
「オリジンサーバーの画像ファイルを差し替えてCloudFrontのキャッシュも最新のはずなのに、いつまで経ってもブラウザのキャッシュ画像を表示している。CloudFrontにもリクエストしていない。なぜ?」
となります。

AWS CLIを使用してS3(オリジンサーバー)にファイルをアップロードする場合、以下のようにCache-Controlヘッダーを設定することができます。

aws s3 cp /path/to/your/file s3://yourbucket/yourpath --cache-control max-age=3600

設定値について記載します。

no-cache

返却値をキャッシュできますが、キャッシュを使用する前に、オリジンサーバーでの検証が必要です。これにより、古いコンテンツが使用されることを防ぐことができます。

no-store

全てのキャッシュを無効にします。コンテンツがセキュアな情報(パスワードやクレジットカード情報など)を含む場合等に使用されます。

max-age=[seconds]

キャッシュを有効とみなす時間を指定します。
例えば
「3600」を指定すると、3600秒(1時間)がキャッシュ保持期間となります。
「0」を指定すると、最初から既に期限切れの状態なので、毎回変更がないか問合せします。

Cache-Controlヘッダーがない場合

この場合、どのような挙動になるのでしょうか?

RFC 7234 - Hypertext Transfer Protocol (HTTP/1.1): Caching

If the response has a Last-Modified header field (Section 2.2 of [RFC7232]), caches are encouraged to use a heuristic expiration value that is no more than some fraction of the interval since that time. A typical setting of this fraction might be 10%.

  • Last-Modifiedヘッダーの日時
  • Dateヘッダーの日時

の差の10%の値をキャッシュの有効期間として定めることが多いとのことです。
コンテンツが変更されない期間が長くなるほど、ブラウザキャッシュの有効期限も長くなる(より長くキャッシュされる)ということですね。

そのため、何らかの事情で、Cache-Controlヘッダーを設定できない場合、
ブラウザ側で古いコンテンツを差し替えて新しいコンテンツを表示するためには...

  • 何らかの操作時にブラウザキャッシュをクリアする処理を入れる
  • CloudFrontからの画像取得パスにクエリ文字列(例:test.jpg?20230713)を入れる

等の対策が必要になってきます。

キャッシュって奥が深いですね!

CloudFront + S3 での画像表示

梅雨シーズン到来です。雨の日も、気持ちだけはスカッと晴れやかにいたいですね。

今回は、「Amazon CloudFront」についてです。その名の通りクラウドのフロントエンド(ユーザに最も近い場所)からコンテンツを提供してくれるサービスです。

以前、ある案件で「Amazon S3」に配置されている大量の画像ファイルをWebページで表示する際に、

  1. S3ダイレクトで画像ファイルを取得する
  2. CloudFront経由でS3の画像ファイルを取得する

どちらの方がいいか悩んだことがありました。

実際にパフォーマンス検証もしてみて、2のCloudFront経由の方が速かったのと、静的Webサイト配信等の事例もあったので、2を採用したのですが、具体的にどのようなメリットがあるのか、改めて振り返ってみます。

1. パフォーマンス

CloudFrontは、コンテンツ配信ネットワークCDN)であり、ユーザーに近い場所にあるエッジロケーションにコンテンツをキャッシュします。

特徴 - Amazon CloudFront | AWS

上記の記事によると、エッジロケーションは、東京(ap-northeast-1)リージョンに「20」もあるとのこと。すごいですね。

これによって、ユーザーから物理的に近い場所からコンテンツが提供されるので、画像ファイルの読み取り速度が向上します。つまり、ユーザーはWebページの画像を早く見ることができます。(S3から直接コンテンツを提供する場合は、ユーザーがS3バケットから遠い場所にいると、画像ファイルの読み取り速度が低下します)

CloudFrontは初回アクセス時やキャッシュ失効時にオリジンからコンテンツを取得します。
S3に格納されている画像ファイル(約4MB)について、CloudFrontに「キャッシュなし」の場合、「キャッシュあり」の場合のダウンロード時間を確認してみました。

キャッシュなしの場合:373 ミリ秒

キャッシュありの場合:27 ミリ秒

14倍近く速いですね!

2. セキュリティ

CloudFrontはOAC(Origin Access Control)を利用してS3コンテンツへのアクセス制限が可能で、S3バケットへのアクセスをCloudFrontからのみに限定することができます。これにより、オリジンのS3を保護することができます。

また、AWS WAFをCloudFrontに導入することで、不正なトラフィックやウェブ攻撃から保護するための設定や、リファラーによるアクセス制限等が可能になります。また、CloudFrontはデフォルトでSSL/TLS 証明書を使用することができ、ユーザーとサーバー間の通信が保護され、データが傍受されるリスクが減少します。

3. コスト

料金 - Amazon S3 |AWS
料金 - Amazon CloudFront | AWS

<リクエスト料金(1万件あたり)>
S3:0.0037 USD(GET、SELECT、他のすべてのリクエスト)
CloudFront:0.012 USD(HTTPSリクエスト)

<10TBまでのデータ転送料金(1GBあたり)>
S3:0.114 USD
CloudFront:0.114 USD

また、CloudFrontについて

Amazon Simple Storage Service (S3)、Amazon Elastic Compute Cloud (EC2)、Elastic Load Balancers など、AWS のあらゆるオリジンからのオリジン取得は無料です。

とのことなので、S3の前段にCloudFrontを置いても、コスト的にあまり変わりはないですね。

今後は悩まずにCloudFront導入に振り切れます!

AI業務自動化ソリューション『AI画像解析』について

AI画像解析
AI画像解析

AI業務自動化ソリューション『AI画像解析』について

はじめに

近年、AI技術が飛躍的に進歩してきました。AI技術は、様々な業界での業務自動化に役立っています。 AIが業務自動化に貢献することで、企業はよりスマートかつ効率的なビジネスプロセスを実現できます。

『AI画像解析』とは?

AI画像解析は、AI技術を活用して、画像解析業務を自動化することを目的としたソリューションです。 ブリスウェルでは『AI業務自動化ソリューション』の一環として、特に『画像解析』にフォーカスしています。 ヒト・モノ・コトの特徴をリアルタイムに検出 ・人物の属性情報の取得、製造・物流現場での不良品の検出、危険エリアへの侵入検知 ・人の目に頼っていた作業の自動化や、業務効率化、安全対策の支援を可能にします。 既に多くの業界で、様々な施策がトライされており、一部では既に効果の兆しが見えています。今後さらにAIに注目、投資されることにより、継続的な精度向上、業績貢献が期待されています。

今後、AI画像解析に期待されている利用例

医療業界における期待:

医療業界においては、MRI画像やCT画像などの医療画像の解析にAI画像解析を応用することで、病気の早期発見や診断精度の向上が期待できます。 例えば、病変部位の特定や病気の進行度合いの予測、治療効果の予測などが可能になります。 AI画像解析によって、医師の判断を補完し、より正確な診断と治療プランの最適化を支援する役割を果たしていくでしょう。

製造業界における期待:

製造業界においては、製品の欠陥検知にAI画像解析を応用することで、品質管理の向上が期待できます。 製品の外観や内部の欠陥をより高精度で検知する、製造プロセスの監視や品質検査を自動化することで、生産性の向上と品質管理の改善が期待できます。 例えば食品の製造過程における、食品の異物混入や傷みなどの検知に応用することができ、品質管理や食品の安全性の確保につながると考えられてます。

建築業界における期待:

建築業界においては、AI画像解析を使用することで、建物の維持管理や施工管理などが効率化されることが期待されています。 建物の外壁や屋根の劣化度合いを分析したり、現場での施工作業の状況をリアルタイムに監視するなどの方法で、施工管理業務を支援することができ、結果としてコスト削減や品質向上につながります。

AI画像解析のメリット

AI画像解析を活用することで、以下のようなメリットが期待できます。

  • 人手による画像解析作業の軽減
  • 高精度な画像解析結果の提供
  • 高度な解析技術の活用による品質改善サイクルの定着化
ブリスウェルのAI画像解析の特徴

特徴① 支援業務の効率化

お客さまの業務の現状をしっかりと把握し、最適な改善策をご提案。プロセス全体での改善を図ります。

特徴② 高精度の認識率

AI画像認識ノウハウと効果的な学習を組み合わせることにより、高精度の認識を実現します。

特徴③ 解析+アルファ

解析するだけでなく、データの可視化・分析、デバイスへの通知等、柔軟に対応いたします。

例:AI powered アナログメーター読み取り

例:AI-powered アナログメーター読み取り
例:AI-powered アナログメーター読み取り

まとめ

AI技術を活用した画像解析は、様々な業界での業務自動化に役立っています。 AI画像解析を活用することで、従業員の負荷を軽減し、生産性を向上させることができます。 今後、AI技術の進歩により、AI画像解析の活用範囲がさらに広がっていくことが期待されます。

資料請求・お問い合わせ

ご興味を持って頂けましたら、ぜひ以下より資料請求、お問い合わせください。 www.briswell.com

LambdaからS3に大量ファイルを高速アップロードする方法

5月に入り、朝から気持ちの良い青空が広がっています。

今回は、サーバーレスの代表選手「AWS Lambda」の処理のお話です。

Lambdaの処理のタイムアウトは15分のため、バッチ処理等でLambdaを利用する場合は15分以内の処理時間に収める必要があります。

また、「Amazon API Gateway」のバックエンドとしてLambdaを利用する場合は、API Gatewayタイムアウトは29秒のため、29秒以内の処理時間に収める必要があります。

可能な限り、Lambdaの処理を高速化したいですね。
高速化の方法として

1. Lambdaのスペック(メモリ+コア数)を上げる

が基本としてありますが、それとあわせて

2. Lambdaの中で並行、並列処理(マルチスレッド化、マルチプロセス化)を行う

が効果的です。

Lambdaの言語としてPythonを利用する場合、
PythonはGIL(プログラムが1つのCPUコア上で1つだけ実行されることを保証する仕組み)があるので、CPUバウンド(CPUに負荷がかかる数値計算等)な処理をマルチスレッドで高速化することは困難となります。この場合は、マルチプロセス化が有効です。

一方、I/Oバウンド(入出力に負担がかかるファイル読み書き、DB接続、ネットワーク通信等)な処理は、マルチスレッド化が有効となります。

このようなイメージとなり、入出力待ちのCPUの空き時間に、他のスレッドが割り当てられるため、処理時間が短くなります。

I/Oバウンドの処理の例として、「Amazon EFS」にある大量のファイルを「Amazon S3」にアップロードする場合があります。
もし、シングルスレッドで、この処理を実行すると

このように、直列の処理になるので、時間がかかってしまいますね。

マルチスレッド化した場合のコードを見てみましょう。

1. ライブラリをインポート

import boto3 #AWSのサービスへのアクセス用
import threading #マルチスレッド実装用
import os #ファイル取得用

2. 変数の定義

s3 = boto3.resource('s3')
bucket_name = 's3-png' #S3のバケット名
num_threads = 10 #スレッドの数
directory = '/mnt/png' #EFSのパス

3. S3へのアップロード処理

def upload_files(file_names):
    for file_name in file_names:
        s3.Bucket(bucket_name).upload_file(file_name, file_name)

4. メイン処理

def lambda_handler(event, context):

    file_names = []
    for root, dirs, files in os.walk(directory):
        for file in files:
            if file.endswith('.png'): #.pngの拡張子を持つファイルを取得
                file_names.append(os.path.join(root, file))

    threads = []
    for i in range(num_threads):
        thread_files = file_names[i::num_threads] #ファイルを分割して各スレッドに割り当てる
        thread = threading.Thread(target=upload_files, args=(thread_files,)) #各スレッドはその割り当てられたファイルでupload_files関数を実行する
        threads.append(thread)
        thread.start() #各スレッドを開始
    
    for thread in threads:
        thread.join() #全てのスレッドが終了するのを待つ

    #成功レスポンス
    return {
        'statusCode': 200,
        'body': 'All files uploaded successfully'
    }

5. 結果

上記のコードをLambdaのメモリ2048(MB)で実行し、1000個のファイル(1ファイルサイズ:数100byte)をEFS→S3にアップロードしました。

  • スレッド数:1 (処理時間:42秒)
  • スレッド数:3(処理時間:16秒)
  • スレッド数:5(処理時間:14秒)
  • スレッド数:10(処理時間:7秒)
  • スレッド数:20(処理時間:7秒)

という結果となりました。マルチスレッド化が効いてますね。

スレッド数が10を超えると

Connection pool is full, discarding connection

のWARNING(S3のコネクションプール数が上限に達したので接続を切ります)が出力されるようになり、処理時間が頭打ちとなりました。

サーバーレスライフ楽しんでいきましょう!

AWS導入コンサルティング

AWS導入コンサルティング

クラウド化への最適解 – AWS導入支援

2023年の現在、ビジネスの成長に欠かせない要素として、AWSやその他のクラウドサービスの活用が不可欠となっています。 しかし、多くの企業が様々な理由でクラウド化されていないインフラ環境を使用し続けており、その最適化に苦労しているのが現状です。

私たちブリスウェルは、AWS導入支援を推進し、お客様のビジネスに最適な構成をご提案、ご提供いたします。 AWS認定パートナー企業として、最新技術やサービスを常に把握し、お客様のニーズに合った最適なソリューションを提供できます。

ブリスウェルのAWS導入支援サービスの特長

私たちが提供するAWS導入支援には、以下のようなサービスが含まれています:

  • AWSの導入や移行に関する専門的なアドバイス
  • AWS導入後のシステム設計、構築、運用の最適化
  • AWSサービスの効果的な監視、保守、アップデート

Amazon Web Services構築パターン

1. 最小構成:

最小構成
最小構成

コストを抑えたスピード重視の最小構成。 ホームページや簡易的なWEBサイト、テスト環境や動作確認用の一時的利用に最適。

費用:90,000円〜

期間:3営業日〜

2. 冗長構成:

冗長構成
冗長構成
信頼性と拡張性を備えた冗長構成。 業務アプリケーションや一般的なWEBアプリ、ECサイトに最適。

費用:300,000円〜

期間:2週間〜

3. サーバレス:

サーバレス
サーバレス
サーバメンテナンスフリーの構成。 運用保守に手間を極力かけたくない場合や、使った分だけ課金するAPIサービスに最適。

費用:450,000円〜

期間:3週間〜

4. バッチジョブ管理:

バッチジョブ管理
バッチジョブ管理
柔軟なバッチジョブ管理を実現する構成。 会計システム向けの月次締処理バッチや基幹システムからの日次データ連携バッチに最適。 従量課金によるコストメリットも魅力的です。 AWS Cloud Development Kit (CDK)を利用して、新規バッチ構築と共にのInfrastructure as Code(IaC)の実現を行った事例もございます。(費用別途)

費用:600,000円〜

期間:4週間〜

5. 動画配信:

動画配信
動画配信
動画ファイルをストリーミング配信形式に変換して配信する構成。 動画配信サービスや動画共有サービスに最適。

費用:900,000円〜

期間:6週間〜

貴社のビジネスに最適なAWS導入支援を私たちが実現します。

最後に

私たちブリスウェルは、お客様のビジネスニーズに合わせた最適なソリューションを提供できるAWS認定パートナー企業です。

貴社のニーズや課題に対し、最適なAWS導入支援をご提供することで、ビジネスの効率化や競争力向上に貢献いたします。

もし、AWS導入支援についてご興味がある場合は、お気軽にお問い合わせください。

弊社のエキスパートがお客様のビジネスに最適なAWS導入支援をご提供いたします。

貴社が抱える課題や目標に対し、私たちが持つ豊富な経験と専門知識を活かして、最適なソリューションを見つけ出し、お客様の成長をサポートします。

資料請求・お問い合わせ

ご興味を持って頂けましたら、ぜひ以下より資料請求、お問い合わせください。 www.briswell.com

※ 期間・費用は投稿時点の情報であり、変更する可能性があります。

artillery.io - 負荷テストのツールを使ってみる

Web開発にとっては、負荷テストは大事なことですね。
負荷テストとはアクセス数が多い時、サーバエラーが発生するか、データ返却の速度が遅くなるか、ロードバランサーとオートスケーリングが正常に動くかという検証することです。

今回のテックブログの主人公は artillery.io です。
artillery.io は有料プランがありますが、無料でも使えます。

① artillery.ioをインストールする
artillery.ioのホームページはここです: https://www.artillery.io/
artillery.ioをインストールするため、nodejsをインストールする必要があります。
npm を使って、artillery.ioをグローバルのパッケージとしてインストールします。
npm install -g artillery@latest
artillery.ioが正常にインストールされたか、下記のコマンドで確認できます。
artillery dino
恐竜のようなASCII画像が返却されたらartillery.ioが正常にインストールできたということです。

② デモのため、負荷テスト対象の環境を構築
これから簡単なAPIサーバを作成します。検証の時、DBの様子も観察しましょう!
利用するAWSサービスは Elastic Beanstalk と RDSです。
Elastic Beanstalkの設定:

RDSの設定:

APIは処理が軽いapiと処理が重いapiの2つを用意します。処理が重いapiは約4秒でデータ返却します。

③ テストのconfigファイルを作成
config.yml

  # デプロイしたEB環境のURL
  target: "https://testartillery.com/v1"
  http:
    # デフォルトのタイムアウトを延長する
    timeout: 60
  phases:
      # テストの期間、 300秒 = 5分
    - duration: 300
      # 1秒間で2回api叩く
      arrivalRate: 2
      name: stress test

scenarios:
  - name: "fast query"
    flow:
      - get:
          url: "/fast"
  - name: "slow query"
    flow:
      - get:
          url: "/slow"


④ テスト実行
artillery run config.yml -o report.json
のコマンドでテスト実行します。
結果はこんな感じです。

テスト結果
テスト結果

大部分はタイムアウトされました。処理が重いapiは約4秒でデータ返却する想定でしたが、アクセスが多い時、ほぼタイムアウトされたように見えます。そして、スケールアップイベントも起きません。モニタリングをみてみたら、CPUの利用率は低かったです。

APIサーバのモニタリング
APIサーバのモニタリング
DBのCPUの利用率も見てみます。
DBのCPU利用率
DBのCPU利用率

DBのCPU利用率は高かったです!
この利用率が高い問題はDBのインスタンスタイプを変更するか、リードレプリカを追加するかのどちらかで解決できますね。

上記のテストは簡単ですが、artillery.ioはもっと難しいテストにも対応できそうです。色々試して、こちらのコメント欄でアドバイスや経験を共有していただけると幸いです。
では、今日の記事はここまでです。それではまた次の機会に。。

【Japan IT WeeK】AI・業務自動化 展【春】に出展します!

新年度も始まりましたね。

弊社は、明日から3日間Japan IT WeeK AI・業務自動化 展【春】に出展します。
AI・業務自動化展|Japan IT Week【春】

開催日時: 2023/4/5(水) ~ 2023/4/7(金)
会場: 東京ビッグサイト
ブース番号: E26-30

弊社の出展情報はこちらからご確認いただけます。
出展社詳細
招待状はこちらからご確認いただけます。
【e招待券_出展社】AI・業務自動化展|Japan IT Week【春】

 
今年は以下の3本柱で出展しております。

  • 「AIアナログメーター読み取り & AI画像解析」
  • AWS導入支援」
  • クラウド受発注管理システム ai-cata 導入支援」

 
また、アナログメーター読み取りと画像解析には、デモを用意しております!!
「アナログメーター読み取りってどんなものなんだろう」
「画像解析ってどういう仕組みなんだろう」
といった疑問がございましたら、是非お立ち寄りください!
 
今回は特別に、画像解析デモができるまでの流れを簡単に動画にしました。
ご参考になれば幸いです。   www.youtube.com

 
皆様のご来場、心よりお待ちしております。

SlackからChatGPTを使ってみる

最近、毎日ChatGPTの恩恵を受けている大澤です。 動かないコードの解析をさせて原因を特定してもらったり、出力されたエラーログをChatGPTに投げて原因箇所と対策を教えてもらったりしています。

もちろん100%信用できるわけではないので、最終的には自分で原因を特定しないといけませんが、そのステップをアシストしてくれるのはとても便利ですね。

ChatGPTを使用するときはOpenAIの公式からだったりするわけですが、SlackでChatGPTが使えたら便利かなと思い実装してみました。
ちなみにもともとはGPT-3のAPIを利用して作ったのですが、記事を書いているときにちょうどChatGPTのAPIが公開されたので、ChatGPTのAPIに切り替えました。

GPT-3時点では会話させるのに工夫が必要で、そこも↓こんな感じで頑張って作ったのですが無駄になってしまいましたね…

気を取り直してChatGPT for Slackを作っていきます。

■準備

  1. Slackbotの作成
  2. SlackAPIキーの取得 
  3. OpenAIのAPIキーの取得

1、2については弊社のブログで既に紹介されているので、こちらを参考にしてください。

GAS & SlackでMeetのリンク自動生成Botを作ってみた - Briswell Tech Blog

OpenAIのAPIキーの取得

1. アカウントを作成

まずはOpenAIのアカウントを取得します。 以下のリンクをクリックしてアカウント登録を行うことができます。

https://platform.openai.com/signup

2. キーを発行

アカウント登録ができたらログインし、右上の[profile]から[View API Keys]をクリックします。

[Create new secret key]のボタンをクリックし、発行されたキーをどこかに保管しておきましょう。

実装

今回はAWS LambdaでSlack API, ChatGPT APIを使用していきます。プログラムの流れとしては以下です。

  1. SlackのOutgoing Webhookを使用して「@Chatbot」というメンションをトリガーにしてLambdaにメッセージを送信する。
  2. LambdaでChatGPTのAPIを叩いてレスポンスを取得する。
  3. 取得したレスポンスをSlack APIでスレッドに投稿する。
1. OutgoingWebhookを使用する。

OutgoingWebhookの設定の仕方については以下のように多くの記事があるので、そちらを参考にしました。

englishscience.hatenablog.com

今回は次のような設定にしました。

トリガー:<botのメンバーID>
URL:AWS Lambdaの関数URL

2. ChatGPTのAPIを叩く。

ChatGPTのAPIのボディは以下の形式です。

{
    "model": "gpt-3.5-turbo", 
    "messages": [
        {"role": "system", "content": "システムプロンプト"},
        {"role": "user", "content": "ユーザー側のプロンプト"},
        {"role": "assistant", "content": "ChatGPTの返答"},
        {"role": "user", "content": "ユーザー側のプロンプト"},
        …
    ]
}

platform.openai.com

抜粋ですが、コードとしてはこのように書いてみました。openAIのライブラリではなく、requestsライブラリで直接APIを叩きに行ってます。

        import requests
        import json
        OPENAI_HEADERS = {
            'Authorization': 'Bearer 【openAIのAPIキー】'},
            'Content-type': 'application/json'
        }
        CHATGPT_URI = 'https://api.openai.com/v1/chat/completions'

        messages = [
            {"role":"system", "content":"あなたはとてもフレンドリーで賢いAIアシスタントです。ユーザーは株式会社ブリスウェルというIT企業に勤めています。ITに関する複雑な質問も来ると思いますが、step by stepで考えて回答しましょう。"}
        ]
        
~~~

        # --- ユーザーのメッセージとChatGPTのメッセージを振り分ける
        for conversation in conversations:
            message={}
            if conversation[1]=="【<botのメンバーID>】": # botならassistantに割り振る
                message['role'] = "assistant"
                message['content'] = conversation[0]
            else:
                message['role'] = "user"
                message['content'] = conversation[0].replace('【<botのメンバーID>】','')
                print(message['content'])
            messages.append(message)
        
        data = {
            "model": "gpt-3.5-turbo", 
            "messages": messages
        }
        response = requests.post(CHATGPT_URI, headers=OPENAI_HEADERS, data=json.dumps(data))
        r = response.json()

Slackのスレッドを取得してconversationsに入れ、それを辞書型に加工してChatGPTのAPIを叩きに行ってます。
ちなみにSlackのスレッドを取得するにはSlack APIのconversations.repliesを利用します。

api.slack.com

Slack APIのページには「Tester」というタブがあり、そのページでAPIのテストができます。とても便利でした。

3. ChatGPTからの回答をスレッドに投稿する。

こちらはSlack APIのchat.postmessageを叩くだけです。

        SLACK_TOKEN = "【SlackのAPIキー】"
        POST_URL = "https://slack.com/api/chat.postMessage"
        HEADERS = {"Authorization": "Bearer "+SLACK_TOKEN}

        # --- スレッドへ返信する。
        data  = {
          'channel': channel_name, # 投稿するチャンネル名
          'text': f'{messages}', # 投稿するメッセージ(ChatGPTの回答)
          'thread_ts': ts # 投稿するスレッドの親メッセージのtimestamp
        }
        r = requests.post(POST_URL, headers=HEADERS, data=data)

api.slack.com

GPT-3のときに比べてシンプルに実装できるようになっていました。

ChatGPTでslack自動投稿botを作ってみた

昨年末に2回ほど、GAS & slackの記事を書かせていただきました。

GAS & SlackでMeetのリンク自動生成Botを作ってみた - Briswell Tech Blog GAS & Slackで地震発生時の安否確認Botを作ってみた - Briswell Tech Blog

私自身プログラマではないため、記事を書くにあたってスクリプトの作成が肝だったりします。
というよりスクリプト組めなくて諦めた記事がいくつかあったりします。。。

そこで、今回は最近流行りのChatGPTを使うことで、この悩みが解消されるのでは?と思い実際にやってみた次第です。
プログラムとかよくわからないけど、ChatGPTを使って色々自動化したいなんて人の参考になれば幸いです。

今回実装したのは、定期投稿botで投稿条件は以下となります。
* 毎週木曜日にslackの特定のチャンネルに投稿する
* 木曜日が祝日だった場合は水曜日に投稿する
* 年末年始(12/28 ~ 1/3)は投稿しない
※今回の目的はChatGPTでどこまでできるかなので簡易的な要件を選んでいます。

ChatGPTでコード生成(失敗例)

今回はさっそくスクリプトの生成から行なっていきます。 ChatGPT ChatGPTにスクリプトを組んでもらうため、仕様(やりたいこと)を伝える必要があります。
実際にChatGPTに投げた内容は以下になります。

以下の要件を網羅したgoogle apps scriptのScriptを作成してください
なお、VERIFICATION_TOKENとBOT_USER_OAUTH_TOKENは既に取得済みであるものとする
本処理はgoogle apps scriptの日次トリガーで実行されるものとする
祝日の取得には以下のカレンダーIDを使用すること
ja.japanese#holiday@group.v.calendar.google.com

分岐1: 実行日が年末年始(12月28日 ~ 1月3日)ではない
 yes → 分岐2へ
 no → 処理終了

分岐2: 実行日の曜日を確認する
 実行日が水曜日である → 分岐3へ
 実行日が木曜日である → 分岐4へ
 上記以外の曜日である → 処理終了

分岐3: 実行日翌日が祝日である
 yes → 分岐4へ
 no → 処理終了

分岐4: 実行日が祝日である
 yes → 処理終了
 no → slackの特定のチャンネルへ通知を出す

通知内容は以下とする
「<!channel>\nおはようございます。\n退社までに本日分までの工数入力をお願いします。」

そして実際に作成されたスクリプトがこちらです。

function myFunction() {
  const VERIFICATION_TOKEN = "XXXXXXXXXX";
  const BOT_USER_OAUTH_TOKEN = "xoxb-XXXXXXXXXX";
  const CHANNEL_ID = "XXXXXXXXXX";
  const CALENDAR_ID = "ja.japanese#holiday@group.v.calendar.google.com";
  
  const now = new Date();
  const yearEndStart = new Date(now.getFullYear(), 11, 28); // 12月28日
  const newYearEnd = new Date(now.getFullYear() + 1, 0, 3); // 1月3日
  const isYearEnd = now >= yearEndStart && now <= newYearEnd;
  const dayOfWeek = now.getDay();
  
  if (!isYearEnd) {
    if (dayOfWeek === 3) { // 水曜日
      const tomorrow = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1);
      const calendar = CalendarApp.getCalendarById(CALENDAR_ID);
      const events = calendar.getEventsForDay(tomorrow);
      const isTomorrowHoliday = events.length > 0;
      
      if (isTomorrowHoliday) {
        sendSlackMessage("<!channel>\nおはようございます。\n退社までに本日分までの工数入力をお願いします。", VERIFICATION_TOKEN, BOT_USER_OAUTH_TOKEN, CHANNEL_ID);
      }
    } else if (dayOfWeek === 4) { // 木曜日
      sendSlackMessage("<!channel>\nおはようございます。\n退社までに本日分までの工数入力をお願いします。", VERIFICATION_TOKEN, BOT_USER_OAUTH_TOKEN, CHANNEL_ID);
      }
    }
  }
}

function sendSlackMessage(message, verificationToken, botUserOauthToken, channelId) {
  const url = "https://slack.com/api/chat.postMessage";
  const headers = {
    "Content-type": "application/json",
    "Authorization": "Bearer " + botUserOauthToken,
  };
  const payload = {
    "text": message,
    "channel": channelId,
  };
  const options = {
    "method": "post",
    "headers": headers,
    "payload": JSON.stringify(payload),
  };
  
  UrlFetchApp.fetch(url, options);
}
}

なかなか良さそうなのですが、木曜日の場合に祝日判断が抜けている。。。

ChatGPTが生成したコードに追記して完成させる

ということで、木曜日が祝日の場合にskipする処理を追記します。
追記したものがこちらになります。
※当方がプログラマではないのでコーディングに無駄があるかもしれませんがご容赦ください。

function myFunction() {
  const VERIFICATION_TOKEN = "XXXXXXXXXX";
  const BOT_USER_OAUTH_TOKEN = "xoxb-XXXXXXXXXX";
  const CHANNEL_ID = "XXXXXXXXXX";
  const CALENDAR_ID = "ja.japanese#holiday@group.v.calendar.google.com";
  
  const now = new Date();
  const yearEndStart = new Date(now.getFullYear(), 11, 28); // 12月28日
  const newYearEnd = new Date(now.getFullYear() + 1, 0, 3); // 1月3日
  const isYearEnd = now >= yearEndStart && now <= newYearEnd;
  const dayOfWeek = now.getDay();
  
  if (!isYearEnd) {
    if (dayOfWeek === 3) { // 水曜日
      const tomorrow = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1);
      const calendar = CalendarApp.getCalendarById(CALENDAR_ID);
      const events = calendar.getEventsForDay(tomorrow);
      const isTomorrowHoliday = events.length > 0;
      
      if (isTomorrowHoliday) {
        sendSlackMessage("<!channel>\nおはようございます。\n退社までに本日分までの工数入力をお願いします。", VERIFICATION_TOKEN, BOT_USER_OAUTH_TOKEN, CHANNEL_ID);
      }
    } else if (dayOfWeek === 4) { // 木曜日
      //水曜日同様祝日判定を行う
      const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
      const calendar = CalendarApp.getCalendarById(CALENDAR_ID);
      const events = calendar.getEventsForDay(today);
      const isTodayHoliday = events.length = 0;

      if (isTodayHoliday) {
      //木曜日が祝日の場合は投稿しない
      }else{
        sendSlackMessage("<!channel>\nおはようございます。\n退社までに本日分までの工数入力をお願いします。", VERIFICATION_TOKEN, BOT_USER_OAUTH_TOKEN, CHANNEL_ID);
      }
    }
  }
}

function sendSlackMessage(message, verificationToken, botUserOauthToken, channelId) {
  const url = "https://slack.com/api/chat.postMessage";
  const headers = {
    "Content-type": "application/json",
    "Authorization": "Bearer " + botUserOauthToken,
  };
  const payload = {
    "text": message,
    "channel": channelId,
  };
  const options = {
    "method": "post",
    "headers": headers,
    "payload": JSON.stringify(payload),
  };
  
  UrlFetchApp.fetch(url, options);
}
}

スクリプトを実行してみる

実際に動作確認をしてみます。
const VERIFICATION_TOKEN = "XXXXXXXXXX";
const BOT_USER_OAUTH_TOKEN = "xoxb-XXXXXXXXXX";
const CHANNEL_ID = "XXXXXXXXXX";
上記3つのパラメータ値の設定を忘れずに行いましょう(設定方法は過去の記事を参照してください)。
GAS & SlackでMeetのリンク自動生成Botを作ってみた - Briswell Tech Blog

検証は以下の日付を指定することで水曜日や木曜日を待たずに検証できます。

「実施日」
const now = new Date(); の括弧内に日付をセットすることで該当日で検証可能です。
const now = new Date(2023, 2, 21); // 2023年3月21日で検証する場合の書き方
※月は0始まり(0 = 1月、11 = 12月)でセットします

「曜日」
if (dayOfWeek === 3) の数値を変更することで曜日を変えて検証可能です。
if (dayOfWeek === 2) // 火曜日で検証したい場合の書き方
※曜日は0始まり(0 = 日曜日、6 = 土曜日)でセットします

検証してみて、投稿ができていることを確認できました。
また、祝前日投稿や祝日に投稿しない条件もバッチリでした。

苦労した点

ここからは余談になりますが、ChatGPTでスクリプト生成で苦労した点を残しておきます。

  • 生成するたびに異なるスクリプトを組んでくる
  • 回答の文字数に制限があるのでスクリプト途中で回答が終了する(有料プラン入れってことですね)
  • 完璧に仕様を網羅できていない

ちなみに、最終版のスクリプトになるまで何度も生成を繰り返してました。
また、仕様を網羅できていない点については、ChatGPTへの渡し方(伝え方)で解決できるのかもしれません。

最後に

当初は依頼を投げたらそれっぽいスクリプトを返してくれるのですごく便利なのでは?と思っていたのですが、
実際取り組んでみると、多少のコツであったり知見(生成されたスクリプトが正しいかどうかの判断)がないと厳しいかもしれません。
とはいえ、このレベルであればわざわざプログラマの方を捕まえなくてもよくなったのは非常に良いことだと思います。
ChatGPTへの橋渡しをする職業(コミュニケーター)が今後は出てきたり資格ができたりするんでしょうか。。。

なにはともあれ、ブログの記事が捗りそうですね!

流体シミュレーション

2023年が始まったと思ったら、あっという間にもう三月。年度末は何かと忙しくなりがちですね。

コーヒーを注ぐ瞬間ってなんかほっとします。お湯をかけるときの香りや、ポタポタと落ちる音、しばし待ってからの一口... 至福です。

その至福のコーヒーの再現まではできませんでしたが、今回は、Blenderの物理演算を利用して、流体シミュレーションをしてみました。

1. 障害物:立方体

障害物の立方体を配置して、流体の受け皿とします。

・物理演算プロパティ
流体 → タイプ:エフェクターエフェクタータイプ:コリジョン

2. 障害物:円柱

障害物の円柱を配置して、蛇口のように流体の流出を調整します。

・物理演算プロパティ
流体 → タイプ:エフェクターエフェクタータイプ:コリジョン

3. フロー:球

流体の発生源のオブジェクトになります。

・物理演算プロパティ
流体 → タイプ:フロー → フロータイプ:液体 → フローの挙動:流入

4. ドメイン

流体シミュレーションを行う領域を指定します。

・物理演算プロパティ
流体 → タイプ:ドメインドメインタイプ:液体

5. アニメーション再生結果

モデルを変えることで、色々なシミュレーションができそうです。