Dify × Slack連携:LambdaでつなぐAIエージェント

に公開

こんにちは。株式会社ナレッジラボでAIエンジニアをしております、ダンと申します。
私たちは、社内に散在する経営データの集計・分析・管理を支援する「Manageboard」というサービスを提供しています。Manageboard では今後、AI機能のさらなる強化を予定しています。

この記事では、SlackとDifyをLambda経由で連携させる際に直面した課題とその対策を紹介します。

背景と構成

オープンソースのLLMプラットフォーム「Dify」は、ノーコードでAIエージェントを構築できるツールであり、Slackとの連携にも対応しています。ただし、環境によってはDify標準のSlack統合機能が利用できない場合があるため、以下の構成で対応することができます。

参考記事:DifyとSlackを連携したSlack Botをつくってみた

主な課題と対策

ファイルの入出力などによってAIエージェントが複雑になると、いくつかの課題が発生します。あらかじめ代表的な課題とその解決方法を把握しておくことが重要です。

Slackとのファイル送受信

受信(ファイル取得)

SlackイベントにはファイルのURLが含まれています。このURLにトークン付きでアクセスすることでファイルを取得することが可能です。

def lambda_handler(event, context):
    body = json.loads(event["body"])
    event_data = body.get("event", {})
    files = event_data.get("files", "")
    num_of_files = len(files)
    file_contents = []
    for idx in range(num_of_files):
        file_url = files[idx]["url_private_download"]
        file_response = requests.get(
                file_url,
                headers={
                    "Authorization": f"Bearer {os.environ.get('SLACK_TOKEN')}"
                }
            )
        file_contents.append(file_response.content)

Slackへファイルを送信

Slackでは files.upload API が廃止されたため、代わりに以下の3ステップで送信します。

  1. files.getUploadURLExternal でURLを取得
  2. URLにファイルをアップロード
  3. files.completeUploadExternal で投稿完了
    # ステップ①
    url_response = requests.get(
        "https://slack.com/api/files.getUploadURLExternal",
        headers={
            "Authorization": f"Bearer {os.environ.get('SLACK_TOKEN')}",
            "Content-Type": "application/x-www-form-urlencoded"
        },
        params={
            "filename": "result.csv",
            "length": str(len(content_bytes))
        }
    )
    response_json = url_response.json()
    upload_url = response_json["upload_url"]
    file_id = response_json["file_id"]
    
    # ステップ②
    requests.post(
        upload_url,
        data=content_bytes,
        headers={
            "Content-Type": "application/octet-stream"
        }
    )
    
    # ステップ③
    channel = event_data.get("channel", "")
    thread_ts = event_data.get("thread_ts", "")
    requests.post(
        "https://slack.com/api/files.completeUploadExternal",
        headers={
            "Authorization": f"Bearer {os.environ.get('SLACK_TOKEN')}",
            "Content-Type": "application/json"
        },
        json={
            "channel_id": channel,
            "initial_comment": "",
            "thread_ts": thread_ts,
            "files": [{"id": file_id, "title": "result.csv"}]
        }
    )

Slackイベントの重複送信

Slackは処理が遅れるとリトライを送ることがあります。その他、「Event Subscriptions」のURLを指定する際には、チャレンジリクエストが発生することがあります。処理の重複を防ぐために以下のような前処理が必要です。

def lambda_handler(event, context):
    body = json.loads(event["body"])
    if (body.get("challenge")):
        return {
            "statusCode": 200,
            "body": "challenge"
        }

    if (event["headers"].get("x-slack-retry-num")):
        return {
            "statusCode": 200,
            "body": "retry ignored"
        }

参考:Events API

Lambdaのライブラリ依存

Lambdaではrequestsなどの一般的なライブラリでも別途準備する必要があります。

方法①:Lambda Layerを利用

  • パッケージをzip化してLayer登録
  • 作成は簡単で軽量だが、環境依存エラーが起きやすい

参考:Python Lambda 関数にレイヤーを使用する

方法②:コンテナイメージ(ECR)で管理

  • Dockerで必要なパッケージをインストール
  • 開発・本番環境の整合性が取りやすいが、導入がやや複雑

参考:コンテナイメージで Python Lambda 関数をデプロイする

Difyにファイルアップロード

Dify ワークフロー APIは直接ファイルを送れないため、まずファイルをアップロードし、そのファイルIDをワークフロー呼び出しに使います。

    # ファイルをアップロード
    upload_response = requests.post(
        upload_url,
        headers={
            "Authorization": f"Bearer {os.environ.get('DIFY_API_KEY')}"
        },
        files={
            "file": (file_name, file_content, mimetype)
        }
    ) # file_name、file_content、mimetypeはファイルの情報
    file_id = upload_response.json().get("id")
    
    # Workflowを実行
    flow_response = requests.post(
        workflow_url,
        headers={
            "Authorization": f"Bearer {os.environ.get('DIFY_API_KEY')}",
            "Content-Type": "application/json"
        },
        json={
            "response_mode": "blocking",
            "user": user,
            "inputs": {
                "file": {
                    "transfer_method": "local_file",
                    "upload_file_id": file_id,
                    "type": filetype # "document"や"image"など
                },
                "message": "" # 入力テキストがあればここで編集
            }
        }
    )

まとめ

SlackとDifyを連携させるには、単なるノーコード構成では不十分であり、AWS Lambdaなどを用いた中継処理が必要となります。特に、ファイル処理やイベント管理を工夫することで、より柔軟で安定したAIワークフローを構築することが可能です。

Discussion