✒️

esa記事を自動要約するSlack Botの実装詳細【Gemini + Cloud Run】

に公開

1. はじめに

こんにちは!
TransMedia Tech Lab に所属している B1 の木村 翔竜です。

前回の記事では、私の先輩が「毎日更新される膨大な情報を効率良くインプットするには」というのを書きました。こちらでは、esaへの投稿を自動で要約してSlackに通知するbotの概要と運用結果をご紹介しました。
本記事では、その実装の詳細について解説していきます!具体的には以下の内容をカバーします。

  • システム全体の構成
  • Gemini APIを活用したプロンプト設計の工夫
  • esa API連携の実装
  • Cloud Runを用いたデプロイ方法
  • 実装中に直面した課題とその解決策

技術的な内容に興味のあるかたや、同様のシステムを作ってみたい方の参考になれば幸いです!

2. システム構成図 簡易的に

アーキテクチャ図

アーキテクチャ図

シーケンス図

esaボットの通知を受け取って自動要約する際の挙動

① 通知受信〜フィルタリング

② 要約生成〜投稿

手動で任意の記事を要約する際の挙動

技術スタック表

カテゴリ 技術 用途
言語 Python 3.13 メインの実装言語
フレームワーク Slack Bolt Slackイベント処理
LLM Gemini 2.5 Flash Lite 要約生成
外部API esa API 記事データ取得
インフラ Google Cloud 常時稼働サーバー
通信方式 Socket Mode Slack WebSocket接続
パッケージ管理 uv 依存関係管理

3. 実装時のポイント

3.1. プロンプト設計

要約の品質を左右する最も重要な部分がプロンプト設計です。何度も試行錯誤を重ねた結果、以下のようなプロンプトに落ち着きました。

実際のプロンプト全文
# ペルソナ設定
あなたは、AI分野の研究室にいる優秀なアシスタントです。

# タスク
教授が作成した技術文書や研究資料を、指定された対象読者が理解しやすいように、要点を押さえて要約してください。

# 制約事項 (Constraints)
* **冒頭の挨拶や「学部生向けにまとめます」といった導入文は一切出力しないでください。**
* **いきなりタイトルや要約の内容から書き始めてください。**

# 1. 要約の対象読者
研究室に配属された学部生

# 2. 要約の目的 (Purpose)
長い文章を要約し、サクッと概要を把握できるようにするため

# 3. 要約のポイント (Instructions)
1. **技術的詳細の保持**: 提案手法の核心、実験設定、主要な結果(重要な数値データや傾向)など、技術的な理解に不可欠な詳細を省略しません。
2. **新規性/貢献の明示**: この文書の「最も重要な貢献(Contribution)」や「従来技術との違い」が明確になるようにします。
3. **専門用語・略語**: 専門用語はそのまま使用します。ただし、文脈上重要と判断される略語(Acronym)が初出の場合は、正式名称を()で併記します。
4. **結論と行動項目**: 文書の結論、および「次に何をすべきか(行動項目)」や「今後の課題」を明確に抽出します。
5. **構造の意識**: 可能な限り、原文の論理構成(例:背景・目的、手法、結果、考察)に沿って整理します。

# 4. 出力形式 (Format)
* **長さ**: {length_instruction}
* **形式**: {style_instruction}

【タイトル】
{title}

【カテゴリ】
{category if category else "なし"}

【本文】
{body}

上記の内容を{style_instruction}で要約してください:
新旧出力比較

Diff形式で新旧の出力を比較してみましょう。(ユーザー名などは伏せています)

- 承知いたしました。優秀なアシスタントとして、学部生向けに技術文書の要点を整理します。
- 
- ## GPUサーバー取扱説明書の要約
- 
- 以下に要点をまとめます...
+ 要約: 取扱説明書(随時更新します)
+ カテゴリ
+ GPUサーバー
+ 更新日時
+ 2025-11-24T18:57:24+09:00
+ 
+ • アクセス情報: ユーザー名`■■■■■■`、ホストアドレス`■■■■■■■■■`で、接続にはTailscaleが必要です。
+ • 必須ツール: IDE(VSCode/Cursor推奨)とRemoteSSHプラグイン、およびGPUサーバーの秘密鍵が必要です。
+ • 接続設定: ローカルの`.ssh/config`にサーバー情報を設定し、IDEまたはターミナルからSSHで接続します(推奨はIDE経由)。
+ • 環境: OSはUbuntu 24.04.3 LTS、CUDA 13.0環境です。
+ • ジョブ実行: 長時間ジョブは`tmux`/screenセッション内でバックグラウンド実行が必須です。
+ • データ管理: 個人ディレクトリ`/home/member/Desktop/[ユーザー名]`を利用し、データは各自でバックアップを取ってください。
+ • ライブラリ管理: pipの使用は禁止されており、パッケージ管理には`uv`を必ず使用し、仮想環境を作成してください。
+ • ハードウェア配慮: 排熱口を塞いだり、管理者の許可なく電源操作を行わないでください。
+ • リソース利用: 現在はSlack報告制ですが、将来的には予約表に基づく利用ルールが適用されます。
+ • セキュリティ: 機密情報(APIキー等)はコマンドライン引数に直接書かず、環境変数等を使用してください。
+ • 推奨ツール: tmux, Docker, wandbの利用が推奨されます。
項目 旧プロンプト 新プロンプト
導入文 「承知いたしました...」等の前置きが入る いきなり要約内容から開始
構造 冗長な説明が混在 箇条書きで整理された情報
体験 本題にたどり着くまでに時間がかかる 即座に必要な情報にアクセス可能
工夫した点
  1. 制約事項で導入文の排除を明示

「冒頭の挨拶や…導入文は一切出力しないでください」「いきなりタイトルや要約の内容から書き始めてください」

プロンプトエンジニアリングをかじったことある方なら、わかっていただけると思うのですが、LLMは通常ペルソナなどを与えて会話などを行うと、「承知いたしました。」みたいな導入文を含めがちです。これを 「否定命令(~しないでください)」と「肯定命令(~から始めてください)」のセット で強力に制御することで、すぐに要約文にアクセスできるようにしています。


  1. 出力形式セクションで長さと形式を柔軟に指定

例: 長さ: 短めに, 形式: 箇条書きで {title}, {body}, {style_instruction}

{title}, {body}, {style_instruction} などの変数を活用して柔軟性を確保しています。
これは私のわがままなのですが、この古い記事を要約をさせたいなって思った時に、長さの調整などを決めれたらめっちゃいいじゃんって思って入れてます。
このプロンプトの構造のおかげで、サクッと概要だけ理解しちゃいたいみたいなユースケースにも合致するような要約が可能になりました。

以下要約例(ユーザー名などは伏せています)

短い要約の例

カテゴリ
GPUサーバー
更新日時
2025-11-27T12:08:21+09:00
• 接続情報: ユーザー名■■■■■■■、ホストアドレス■■■■■■■■■■■■■■で、Tailscale経由でSSH接続する。RemoteSSH (VSCode/Cursor推奨)またはターミナルで接続可能。
• 環境設定: 秘密鍵(id_member)のダウンロードと.ssh/configへの設定が必要。設定ファイルには、ホスト名、HostName、User、IdentityFileなどを記述する。
• ソフトウェア利用: パッケージ管理にはpipではなくuvを使用し、仮想環境内で管理する。長時間ジョブはtmux/screen内でバックグラウンド実行する。
• リソース・データ管理: GPUリソース利用はSlack報告後、今後の予約表に従う。個人ディレクトリ(/home/member/Desktop/[ユーザー名])以外への大規模データ保存は避け、バックアップは各自で行う。
• 注意事項: ライブラリの勝手なpipインストール、サーバー電源操作、排熱口への異物設置は禁止。APIキー類は履歴に残らないよう環境変数等を使用する。

標準の要約の例

カテゴリ
GPUサーバー
更新日時
2025-11-27T12:08:21+09:00
• GPUサーバーへのログインには、ユーザー名■■■■■■■、ホストアドレス■■■■■■■■■■■■■■を使用し、Tailscaleでの接続が必須。
• VSCodeやCursorなどのIDEにはRemoteSSH拡張機能を導入し、設定ファイル(Configファイル)を用いて接続設定を行うことが推奨される。
• サーバーのOSはUbuntu 24.04.3 LTS、CUDAバージョンは13.0環境が構築されている。
• 長時間ジョブはtmuxscreenセッション内でバックグラウンド実行する必要があり、リソース利用にはSlack報告と今後の予約表利用が求められる。
• ライブラリインストールにはpip使用せずuvツールチェーンを用いてプロジェクトごとの仮想環境を作成・管理する。
• 個人ディレクトリは/home/member/Desktop/[ユーザー名]だが、大規模データは保存せず、利用者が自らバックアップを取る必要がある。
• セキュリティのため、APIキーなどはコマンドライン引数ではなく環境変数や設定ファイルで扱う。
• nvidia-smiでGPU状況を確認し、ハードウェア(排熱口、電源)の物理的な取り扱いに注意する。
• 継続的な作業のためにtmuxや実験管理のためにwandbの利用が推奨される。
• 不明点があれば担当者へ連絡すること。

長い要約の例

カテゴリ
GPUサーバー
更新日時
2025-11-27T12:08:21+09:00
• ログイン情報: ユーザー名は■■■■■■■、ホストアドレスは■■■■■■■■■■■■■■です。
• 必要なツール: 接続にはTailscale(研究室共有アカウントでログイン)、IDE(VSCodeやCursorなど)、およびRemoteSSH拡張機能が必要です。
• 秘密鍵とConfigファイル: GPUサーバーへのSSH接続には秘密鍵(id_member)が必要です。この鍵をローカルの.ssh/ディレクトリに保存し、SSH設定ファイル(config)に接続設定(HostName, User, IdentityFileなど)を記述することが推奨されます。
• 接続方法(推奨): VSCodeのリモート接続機能(Remote-SSH)を使用し、Configファイルで設定したホスト名を選択するのが推奨されます。
• 接続方法(代替): ターミナルからは、Tailscale起動後、ssh <Configで設定した名前>コマンドで接続できます。
• サーバー環境: OSはUbuntu 24.04.3 LTS、CUDA 13.0環境です。
• ジョブ実行: 長時間実行ジョブは必ずtmuxscreenセッション内、またはバックグラウンドで実行してください。
• GPU確認: nvidia-smiコマンドでGPU利用状況を確認できます。
• データ管理: 個人ディレクトリは/home/member/Desktop/[ユーザー名]ですが、大規模データはサーバーに保存せず、利用者が自己管理・バックアップを行う必要があります。
• ライブラリインストール: 管理者権限が不要なuvを使用してパッケージ管理を行ってください。pip installは使用禁止です。
• uvの利用手順: プロジェクトディレクトリでuv init my-projectを実行し、source .venv/bin/activateで仮想環境を有効化してからuv add <package>でライブラリを追加します。
• ハードウェアへの配慮: サーバーの排熱口を塞いだり、電源を許可なく操作したりしないでください。
• リソース利用: 現在は使用前にSlackでの報告が必要ですが、将来的には予約表での管理に移行します。
• セキュリティ: APIキーなどの機密情報は、コマンドライン引数ではなく環境変数などで管理してください。
• 異常時対応: 異音、異臭、予期せぬシャットダウンが発生した場合は、直ちに管理者に連絡してください。
• 推奨ツール: tmux(セッション管理)、Docker(環境再現性)、wandb(ログ監視)の利用が推奨されています。
• 行動項目: まずTailscaleをインストールし、秘密鍵をダウンロードしてSSH設定を行い、VSCodeから接続を試みてください。
• 次のステップ: ライブラリ導入の際はuvコマンド体系を習得してください。

段落形式の要約の例

カテゴリ
GPUサーバー
更新日時
2025-11-27T12:08:21+09:00
GPUサーバーのログイン情報はユーザー名■■■■■■■、ホストアドレス■■■■■■■■■■■■■■であり、接続にはTailscaleの導入と、VSCode + RemoteSSHまたはターミナルが必要です。接続設定には秘密鍵id_memberとSSHのConfigファイル編集が推奨されます。ソフトウェア環境はUbuntu 24.04.3 LTS、CUDA 13.0であり、長時間ジョブはtmuxやバックグラウンド実行が必須です。ライブラリインストールにはuvを使用し、pipの直接利用は禁止されています。リソース利用は現時点ではSlack報告制、将来的には予約制となり、冷却・電源操作の禁止やセキュリティ上の配慮(APIキーの隠蔽)が求められます。データ管理は個人ディレクトリ/home/member/Desktop/[ユーザー名]に限定され、重要データは利用者自身でのバックアップが必須です。自己責任での利用が前提であり、tmux、Docker、wandbの活用が推奨されます。


  1. 誰に向けるかを絞り込んだトーンの調整

対象読者:研究室に配属された学部生

よくある、初心者向けの説明でみたいな曖昧な指示でやると、わかりやすくしようとあまりにも冗長な文章を出力することが良くあります。そこで、研究室に配属された学部生 としてみました。基礎はわかっているけど最先端の専門用語や略語にはまだ慣れていないという絶妙なラインです。これにより専門用語は使いつつ、必要に応じて補足するという適切な設定が可能になりました。

3.2. esa API連携

esa APIとの連携では、単純なAPI呼び出しだけでなく、SlackメッセージからのURL抽出と正規化が重要なポイントになります。

基本的な記事取得

まず、記事番号を抽出します。esaのURLは https://<チーム名orユーザ名>.esa.io/posts/<記事番号> の形式で、正規表現を使用して抽出しています。以下https://team.esa.io/posts/123で表記していきます。
そして、esa APIのクライアント実装です。Bearer認証でREST APIを呼び出します。

class EsaClient:
    def __init__(self):
        self.token = ESA_ACCESS_TOKEN
        self.team_name = ESA_TEAM_NAME
        self.base_url = f"https://api.esa.io/v1/teams/{self.team_name}"
        self.headers = {
            "Authorization": f"Bearer {self.token}",
            "Content-Type": "application/json"
        }

    def extract_post_number_from_url(self, url: str) -> Optional[int]:
        """esaのURLから記事番号を抽出"""
        # https://team.esa.io/posts/123 -> 123
        
        match = re.search(r'/posts/(\d+)', url)
        # もし見つからなかったらNoneを返す
        if match:
            return int(match.group(1))
        return None
    
    def get_post_by_number(self, post_number: int) -> Optional[Dict]:
        """記事番号から記事を取得"""
        url = f"{self.base_url}/posts/{post_number}"
        try:
            response = requests.get(url, headers)
            response.raise_for_status()
            return response.json()
        except requests.exceptions.RequestException as e:
            logger.error(f"esa API Error (記事番号: {post_number}): {e}")
            return None

APIレスポンスの構造

esa APIのレスポンスは以下のような構造になっています。

# APIレスポンス例
{
    "number": 123,
    "name": "記事タイトル",
    "body_md": "本文(Markdown形式)...",
    "category": "hoge/huga/fuga",
    "updated_at": "2024-01-15T10:30:00+09:00",
    "created_by": {
        "name": "author_name",
        "screen_name": "author"
    },
    # ... その他のフィールド
}

# データ取得時の処理
post_data = post.get('post', post)  # ラップされている場合に対応
title = post_data.get('name', '')
body = post_data.get('body_md', '')
category = post_data.get('category', '')

4. CI/CD

本番環境への安全なデプロイを実現するため、GitHub Actionsを使ったCI/CDパイプラインを構築しています。

パイプライン概要

GitHub Actionsワークフロー

.github/workflows/deploy.yml を作成します。

name: Deploy to Cloud Run

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main

env:
  PROJECT_ID: ${{ secrets.GCP_PROJECT_ID }}
  REGION: asia-northeast1
  SERVICE_NAME: esa-summarizer

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.13'
      
      - name: Install uv
        uses: astral-sh/setup-uv@v4
      
      - name: Install dependencies
        run: uv sync
      
      - name: Run tests
        run: uv run pytest -v

  deploy:
    needs: test
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
    runs-on: ubuntu-latest
    
    permissions:
      contents: read
      id-token: write
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Authenticate to Google Cloud
        uses: google-github-actions/auth@v2
        with:
          workload_identity_provider: ${{ secrets.WIF_PROVIDER }}
          service_account: ${{ secrets.WIF_SERVICE_ACCOUNT }}
      
      - name: Set up Cloud SDK
        uses: google-github-actions/setup-gcloud@v2
      
      - name: Configure Docker
        run: gcloud auth configure-docker ${{ env.REGION }}-docker.pkg.dev
      
      - name: Build and Push
        run: |
          docker build -t ${{ env.REGION }}-docker.pkg.dev/${{ env.PROJECT_ID }}/esa-summarizer/${{ env.SERVICE_NAME }}:${{ github.sha }} .
          docker push ${{ env.REGION }}-docker.pkg.dev/${{ env.PROJECT_ID }}/esa-summarizer/${{ env.SERVICE_NAME }}:${{ github.sha }}
      
      - name: Deploy to Cloud Run
        run: |
          gcloud run deploy ${{ env.SERVICE_NAME }} \
            --image ${{ env.REGION }}-docker.pkg.dev/${{ env.PROJECT_ID }}/esa-summarizer/${{ env.SERVICE_NAME }}:${{ github.sha }} \
            --region ${{ env.REGION }} \
            --platform managed \
            --min-instances 1 \
            --max-instances 2
      
      - name: Notify Slack
        if: always()
        uses: 8398a7/action-slack@v3
        with:
          status: ${{ job.status }}
          fields: repo,message,commit,author,action,eventName,ref,workflow
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}

ジョブの説明

ジョブ トリガー 内容
test Push / PR pytest実行、Lintチェック
deploy mainブランチへのPushのみ Docker Build → Push → Cloud Runデプロイ

GitHub Secretsの設定

リポジトリの Settings → Secrets and variables → Actions から以下を設定します。

Secret名 内容
GCP_PROJECT_ID Google CloudのプロジェクトID
WIF_PROVIDER Workload Identity Federationのプロバイダ
WIF_SERVICE_ACCOUNT デプロイ用サービスアカウント
SLACK_WEBHOOK_URL デプロイ通知用のWebhook URL

ブランチ戦略

シンプルな運用として、以下のフローを採用しています。

feature/* → PR → main → 自動デプロイ
  1. 機能開発は feature/* ブランチで実施
  2. Pull Requestを作成し、テストが通ることを確認
  3. レビュー後に main へマージ
  4. マージをトリガーに自動デプロイが実行
手動デプロイしたい場合

GitHub Actionsの workflow_dispatch を追加すると、手動トリガーも可能です。

on:
  push:
    branches:
      - main
  workflow_dispatch:  # 手動実行を許可
    inputs:
      environment:
        description: 'Deploy environment'
        required: true
        default: 'production'

5. デプロイ

本botはSlack Socket Modeを使用しているため、常時稼働のサーバーが必要です。Google CloudのCloud Runを選択した理由は以下の通りです。

比較表

項目 Cloud Run AWS Lambda AWS EC2 / GCE ローカル実行
常時稼働 min-instances=1で対応 タイムアウト制限あり 可能 可能
Socket Mode対応 対応 非対応(HTTP専用) 対応 対応
月額コスト 約500〜1,000円 約2,000〜5,000円 電気代のみ
運用負荷 ◎ フルマネージド ◎ フルマネージド △ OS管理が必要 × 常時起動・監視が必要
スケーラビリティ ◎ 自動スケール ◎ 自動スケール △ 手動設定 × 単一マシン
デプロイ容易性 ◎ コンテナPush ◎ ZIP/コンテナ △ SSH/スクリプト
障害復旧 ◎ 自動再起動 ◎ 自動 △ 設定次第 × 手動対応

なぜCloud Runを選んだか

AWS Lambdaが使えない理由

LambdaはHTTPリクエスト/レスポンス型のアーキテクチャに最適化されており、Socket Modeの常時WebSocket接続には対応できません。Slack Events APIのHTTPエンドポイント方式であればLambdaも選択肢になりますが、以下の理由からSocket Modeを採用しました。

  • 固定IPやドメインの設定が不要
  • ファイアウォール設定が不要
  • ローカル開発時のngrok等が不要

EC2/GCEを選ばなかった理由

フルスペックのVMは柔軟性が高い反面、OS管理やセキュリティパッチの適用など運用負荷が増えます。研究室の小規模なbotにはオーバースペックと判断しました。

ローカル実行を選ばなかった理由

開発・テスト時はローカルで十分ですが、本番運用では以下の問題があります。

  • PCの電源を常時ONにする必要がある
  • ネットワーク障害や再起動時に手動復旧が必要
  • 研究室のネットワーク環境に依存する

結論:Cloud Runのメリット

  • コスト効率: min-instances=1 でも月額数百円程度
  • 信頼性: 障害時の自動再起動、ログの自動収集
  • スケーラビリティ: 将来的に複数チームで使う場合も自動スケール

デプロイ

Cloud Runへのデプロイは、以下の記事を参考に行いました。

環境変数設定

Cloud Runのコンソールまたはgcloud CLIから、以下の環境変数を設定します。

# Slack設定
SLACK_BOT_TOKEN=xoxb-xxxx        # Bot User OAuth Token
SLACK_APP_TOKEN=xapp-xxxx        # App-Level Token (Socket Mode用)

# esa設定
ESA_ACCESS_TOKEN=xxxx            # esa Personal Access Token
ESA_TEAM_NAME=your-team          # チーム名(URLの先頭部分)

# Gemini設定
GEMINI_API_KEY=xxxx              # Google AI Studio APIキー

# チャンネル設定
ESA_WATCH_CHANNEL_ID=C0123456789         # esa通知を監視するチャンネル
ESA_SUMMARY_CHANNEL_ID=C9876543210       # 要約を投稿するチャンネル(カンマ区切りで複数指定可)

6. ハマったポイント

6.1. esaのSlack通知でtextが空になる問題

症状: esaからSlackへの通知メッセージで、event.get('text') が空文字になることがある

原因: esaの通知はSlackの blocks 形式のみで送信される場合があり、text フィールドが空になるケースがある

解決策: blocks からテキストを再構築するヘルパー関数を実装

def _extract_text_from_blocks(self, blocks):
    block_texts = []
    
    # blocksがNoneの場合のガード
    if not blocks:
        return ''

    for block in blocks:
        if block.get('type') != 'rich_text':
            continue

        for el in block.get('elements', []):
            if el.get('type') != 'rich_text_section':
                continue

            for sub in el.get('elements', []):
                sub_type = sub.get('type')
                if sub_type == 'text':
                    block_texts.append(sub.get('text', ''))
                elif sub_type == 'link' and sub.get('url'):
                    block_texts.append(sub.get('url', ''))

    return ' '.join(block_texts).strip()

6.2. Slack APIによるURLの省略

症状: esaのURLを正規表現で抽出すると、URLの末尾に余分な文字列が付いてしまい、esa APIへのリクエストが失敗する

原因: Slack APIはメッセージ内のURLを <URL|表示テキスト> 形式に変換して送信する

# 元のURL
https://team.esa.io/posts/123

# Slack APIが変換した形式
<https://team.esa.io/posts/123|team.esa.io/posts/123>

| の後ろはSlack上での表示用テキストであり、実際のURLではありません。この形式のまま正規表現でURLを抽出すると、| 以降も含まれてしまい、不正なURLになります。

# 単純な正規表現だと失敗する例
import re
text = "<https://team.esa.io/posts/123|team.esa.io/posts/123>"
# ❌ これだとパイプ以降も取れてしまう
match = re.search(r'https?://[^\s>]+', text)
print(match.group())  # https://team.esa.io/posts/123|team.esa.io/posts/123

解決策: | でsplitしてURL部分のみを取得する

def _clean_slack_url(self, url: str) -> str:
    """<https://...|表示テキスト> 形式から実際のURLを抽出"""
    # パイプ以降(表示テキスト)を除去
    url = url.split('|', 1)[0]
    
    # 山括弧を除去
    return url.strip('<>')

使用例:

# 入力(Slack APIから受け取った形式)
raw_url = "<https://team.esa.io/posts/123|team.esa.io/posts/123>"
# クリーンアップ
clean = self._clean_slack_url(raw_url)
print(clean)  # https://team.esa.io/posts/123 ✅

7. まとめ

本記事では、esaの記事を自動要約してSlackに通知するbotの実装詳細を解説しました。

ポイントのおさらい:

  • プロンプト設計: ペルソナ設定と制約事項の明示で、安定した品質の要約を生成
  • esa API連携: URLからの記事番号抽出と、Slackメッセージの複数箇所からのURL収集
  • CI/CD: GitHub Actionsでテスト→ビルド→デプロイを自動化
  • Cloud Runデプロイ: Socket Mode使用時は min-instances=1 が必要
  • ハマりポイント: Slackのblocks形式や文字数制限には要注意

このbotは小さなツールですが、日々の情報キャッチアップの時間を削減し、研究活動に集中できる環境づくりに貢献しています。同じような課題を抱えている方の参考になれば幸いです。

  • リポジトリリンク

質問やフィードバックがあれば、ぜひコメントやIssueでお知らせください!

Discussion