Dify × Slack Integration: Connecting AI Agents with Lambda

に公開

Hi, I’m Dang, an AI engineer at Knowledgelabo, Inc. We provide a service called "Manageboard", which supports our clients in aggregating, analyzing, and managing scattered internal business data. Manageboard is set to enhance its AI capabilities in the future.

In this article, I’ll introduce the challenges we encountered when integrating Slack with Dify via AWS Lambda and how we addressed them.

Background and Architecture

The open-source LLM platform Dify allows you to build AI agents without code and supports Slack integration. However, when the built-in Slack integration doesn’t work, the following structure can be used instead:

Reference: DifyとSlackを連携したSlack Botをつくってみた

Main Challenges and Solutions

When AI agents grow more complex due to features such as file input/output, several challenges arise. It’s important to understand common issues and how to solve them in advance.

File Upload/Download with Slack

Receiving Files

Slack events include file URLs. You can retrieve the files by accessing the URL with a token.

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)

Sending Files

Since the files.upload API is deprecated, use the following three steps instead:

  1. Get an upload URL using files.getUploadURLExternal
  2. Upload the file to that URL
  3. Complete the upload using files.completeUploadExternal
    # Step 1
    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"]
    
    # Step 2
    requests.post(
        upload_url,
        data=content_bytes,
        headers={
            "Content-Type": "application/octet-stream"
        }
    )
    
    # Step 3
    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"}]
        }
    )

Duplicate Slack Event Triggers

If Slack detects a delay in processing, it may retry the same event. In addition, when specifying the URL for “Event Subscriptions,” a challenge request will occur. To avoid duplicate processing, use pre-checks like this:

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

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

Reference: Events API

Lambda Library Dependencies

On AWS Lambda, even common libraries like requests need to be added manually.

Option 1: Use Lambda Layers

  • Zip the package and register it as a Layer
  • Easy and lightweight, but may cause environment-related errors

Reference: Working with layers for Python Lambda functions

Option 2: Use a Container Image (ECR)

  • Use Docker to install required packages
  • Ensures consistency across development and production environments, though setup is more complex

Reference: Deploy Python Lambda functions with container images

Uploading Files to Dify

The Dify Workflow API does not support direct file sending. You must first upload the file, then pass the file ID to the workflow.

    # Upload file
    upload_response = requests.post(
        upload_url,
        headers={
            "Authorization": f"Bearer {os.environ.get('DIFY_API_KEY')}"
        },
        files={
            "file": (file_name, file_content, mimetype)
        }
    )
    file_id = upload_response.json().get("id")
    
    # Call 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  # e.g., "document", "image"
                },
                "message": ""  # Optional input text
            }
        }
    )

Conclusion

To connect Slack with Dify, no-code tools alone aren’t enough. Intermediary processing using services like Lambda is necessary. By carefully handling file processing and event management, you can build more flexible and stable AI workflows.

Discussion