Open29

WindowsでDifyを使いこなす!

shohei6117shohei6117

スクラップの目次

1.Dify環境を作る/アップデートする

2.トラブル対応

3.DifyをAPI操作する/Difyに外部から接続する

4.Difyと外部ツールを連携させる

5.Difyアプリを作る


Dify公式リンク

Dify関連記事を探す

その他リンク

shohei6117shohei6117

Dify環境を作る #1

ゴール

  • DifyをWindows PCにインストールして起動する

まとめ

  • 公式ドキュメントとか巷にあふれるブログ記事のように簡単に起動できなかった
  • 情報がなく苦労したこと
    • Docker Desktop for Windows: バイパスproxy設定
    • docker-compose.yaml: エラー回避のためのコード修正

関連リンク

大まかな手順

  1. Docker Desktop for Windowsを導入する
  2. Docker環境でDifyを起動する

もう少し詳しい手順

  1. gitをインストールする(割愛)
  2. WSL2を有効化する
  • 「Windowsの機能の有効化または無効化」を開いて「Linux用Windowsサブシステム」と「仮想マシンプラットフォーム」にチェックを入れて保存、PCを再起動する
設定画面

  1. PowerShellを管理者モードで立ち上げてubuntuをインストールする
    以下のコマンドを順番に実行する。
PowerShell
# wslが有効になっていて、バージョン2になっていることを確認
wsl --status
# wslをアップデート
wsl --update
# インストールできるUbuntu一覧を表示
wsl --list --online
# Ubuntu-24.04をインストール
wsl --install -d Ubuntu-24.04
# インストールできたか確認
wsl -l -v
# wslを起動して、ユーザー名とパスワードを設定
wsl

  1. Docker Desktop for Windowsをインストールする(割愛)
  2. Docker Desktop for Windowsの設定をする
Docker Desktop for WindowsのResources設定画面
  • wsl2との連携をONにする
  • Dockerのデフォルトネットワークサブネット。Dockerコンテナ同士の通信や、ホストマシンとDockerコンテナの間の通信用(いじらない)
  • プロキシ設定画面 大事!!
  • バイパスproxy設定の方法が分からない場合は、ipconfig /allの結果をChatGPTに貼り付けて、「Docker Desktop fow Windowsのバイパスproxy設定を教えて」と言って教えてもらう!
  • /22 とか /24の意味は「IPv4アドレス32ビット長のうち、ネットワーク部分が何ビットかを指すか」ということ。/22だとネットワークが22ビット、ホストが10ビット(=1024)なので、1024個のIPアドレス範囲を指すことになる。(、、、とChatGPTが教えてくれた)
  1. Difyのコードを公式リポジトリからダウンロードする
PowerShell
git clone https://github.com/langgenius/dify.git
  1. cloneしたdifyフォルダの中のdockerフォルダでPowerShellを起動して、DifyのDockerコンテナを起動する ⇒エラーが出る
PowerShell
docker compose up -d

⇒port80は別のアプリですでに使われているというエラーが出る

port80を使っているのはsystem

netstat -nanoコマンドでPID4のプロセスと分かる

⇒タスクマネージャーでSystemが使っていると分かる

⇒port80を使うのは諦める。

  1. port80でなく8080を使うように、docker-compose.yamlを修正する
docker-compose.yamlの修正部分
docker-compose.yaml
  nginx:
    image: nginx:latest
    restart: always
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf
      - ./nginx/proxy.conf:/etc/nginx/proxy.conf
      - ./nginx/conf.d:/etc/nginx/conf.d
      #- ./nginx/ssl:/etc/ssl
    depends_on:
      - api
      - web
    ports:
      # - "80:80" #元の設定
      - "8080:80" #変更後の設定
      #- "443:443"

  1. あらためてdocker-compose up -dする ⇒まだ駄目
    langgenius/dify-web:0.6.9postgres:15-alpineのコンテナが停止、再起動を繰り返す。。
該当するコード
docker-compose.yaml
  web:
    image: langgenius/dify-web:0.6.9
    restart: always
    environment:
      CONSOLE_API_URL: ''
      APP_API_URL: ''
      SENTRY_DSN: ''

  db:
    image: postgres:15-alpine
    restart: always
    environment:
      PGUSER: postgres
      POSTGRES_PASSWORD: difyai123456
      POSTGRES_DB: dify
      PGDATA: /var/lib/postgresql/data/pgdata
    volumes:
      - ./volumes/db/data:/var/lib/postgresql/data
    healthcheck:
      test: [ "CMD", "pg_isready" ]
      interval: 1s
      timeout: 3s
      retries: 30
  1. ChatGPTに助けてもらいながら、docker-compose.yamlをあちこち修正する。
    「エラーlogを渡す⇒yamlファイルの修正案をもらう⇒トライ」を繰り返した。。
    ⇒最終的にできたyamlファイルはこれ
ようやく完成したdocke-compose.yamlファイル
docker-compose.yaml
version: '3'

# ===========================================================
services:
# -----------------------------------------------------------
  api:
    image: langgenius/dify-api:0.6.9
    restart: always
    environment:
      MODE: api
      LOG_LEVEL: INFO
      SECRET_KEY: sk-9f73s3ljTXVcMT3Blb3ljTqtsKiGHXVcMT3BlbkFJLK7U
      CONSOLE_WEB_URL: ''
      INIT_PASSWORD: ''
      CONSOLE_API_URL: ''
      SERVICE_API_URL: ''
      APP_WEB_URL: ''
      FILES_URL: ''
      FILES_ACCESS_TIMEOUT: 300
      MIGRATION_ENABLED: 'true'
      DB_USERNAME: postgres
      DB_PASSWORD: difyai123456
      DB_HOST: db
      DB_PORT: 5432
      DB_DATABASE: dify
      REDIS_HOST: redis
      REDIS_PORT: 6379
      REDIS_USERNAME: ''
      REDIS_PASSWORD: difyai123456
      REDIS_USE_SSL: 'false'
      REDIS_DB: 0
      CELERY_BROKER_URL: redis://:difyai123456@redis:6379/1
      WEB_API_CORS_ALLOW_ORIGINS: '*'
      CONSOLE_CORS_ALLOW_ORIGINS: '*'
      STORAGE_TYPE: local
      STORAGE_LOCAL_PATH: storage
      S3_ENDPOINT: 'https://xxx.r2.cloudflarestorage.com'
      S3_BUCKET_NAME: 'difyai'
      S3_ACCESS_KEY: 'ak-difyai'
      S3_SECRET_KEY: 'sk-difyai'
      S3_REGION: 'us-east-1'
      AZURE_BLOB_ACCOUNT_NAME: 'difyai'
      AZURE_BLOB_ACCOUNT_KEY: 'difyai'
      AZURE_BLOB_CONTAINER_NAME: 'difyai-container'
      AZURE_BLOB_ACCOUNT_URL: 'https://<your_account_name>.blob.core.windows.net'
      GOOGLE_STORAGE_BUCKET_NAME: 'yout-bucket-name'
      GOOGLE_STORAGE_SERVICE_ACCOUNT_JSON_BASE64: 'your-google-service-account-json-base64-string'
      VECTOR_STORE: weaviate
      WEAVIATE_ENDPOINT: http://weaviate:8080
      WEAVIATE_API_KEY: WVF5YThaHlkYwhGUSmCRgsX3tD5ngdN8pkih
      QDRANT_URL: http://qdrant:6333
      QDRANT_API_KEY: difyai123456
      QDRANT_CLIENT_TIMEOUT: 20
      QDRANT_GRPC_ENABLED: 'false'
      QDRANT_GRPC_PORT: 6334
      MILVUS_HOST: 127.0.0.1
      MILVUS_PORT: 19530
      MILVUS_USER: root
      MILVUS_PASSWORD: Milvus
      MILVUS_SECURE: 'false'
      RELYT_HOST: db
      RELYT_PORT: 5432
      RELYT_USER: postgres
      RELYT_PASSWORD: difyai123456
      RELYT_DATABASE: postgres
      PGVECTOR_HOST: pgvector
      PGVECTOR_PORT: 5432
      PGVECTOR_USER: postgres
      PGVECTOR_PASSWORD: difyai123456
      PGVECTOR_DATABASE: dify
      MAIL_TYPE: ''
      MAIL_DEFAULT_SEND_FROM: 'YOUR EMAIL FROM (eg: no-reply <no-reply@dify.ai>)'
      SMTP_SERVER: ''
      SMTP_PORT: 465
      SMTP_USERNAME: ''
      SMTP_PASSWORD: ''
      SMTP_USE_TLS: 'true'
      SMTP_OPPORTUNISTIC_TLS: 'false'
      RESEND_API_KEY: ''
      RESEND_API_URL: https://api.resend.com
      SENTRY_DSN: ''
      SENTRY_TRACES_SAMPLE_RATE: 1.0
      SENTRY_PROFILES_SAMPLE_RATE: 1.0
      NOTION_INTEGRATION_TYPE: public
      NOTION_CLIENT_SECRET: you-client-secret
      NOTION_CLIENT_ID: you-client-id
      NOTION_INTERNAL_SECRET: you-internal-secret
      CODE_EXECUTION_ENDPOINT: "http://sandbox:8194"
      CODE_EXECUTION_API_KEY: dify-sandbox
      CODE_MAX_NUMBER: 9223372036854775807
      CODE_MIN_NUMBER: -9223372036854775808
      CODE_MAX_STRING_LENGTH: 80000
      TEMPLATE_TRANSFORM_MAX_LENGTH: 80000
      CODE_MAX_STRING_ARRAY_LENGTH: 30
      CODE_MAX_OBJECT_ARRAY_LENGTH: 30
      CODE_MAX_NUMBER_ARRAY_LENGTH: 1000
      SSRF_PROXY_HTTP_URL: 'http://ssrf_proxy:3128'
      SSRF_PROXY_HTTPS_URL: 'http://ssrf_proxy:3128'
      INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH: 1000
    depends_on:
      - db
      - redis
    volumes:
      - ./volumes/app/storage:/app/api/storage
    networks:
      - ssrf_proxy_network
      - default

# -----------------------------------------------------------
  worker:
    image: langgenius/dify-api:0.6.9
    restart: always
    environment:
      CONSOLE_WEB_URL: ''
      MODE: worker
      LOG_LEVEL: INFO
      SECRET_KEY: sk-9f73s3ljTXVcMT3Blb3ljTqtsKiGHXVcMT3BlbkFJLK7U
      DB_USERNAME: postgres
      DB_PASSWORD: difyai123456
      DB_HOST: db
      DB_PORT: 5432
      DB_DATABASE: dify
      REDIS_HOST: redis
      REDIS_PORT: 6379
      REDIS_USERNAME: ''
      REDIS_PASSWORD: difyai123456
      REDIS_DB: 0
      REDIS_USE_SSL: 'false'
      CELERY_BROKER_URL: redis://:difyai123456@redis:6379/1
      STORAGE_TYPE: local
      STORAGE_LOCAL_PATH: storage
      S3_ENDPOINT: 'https://xxx.r2.cloudflarestorage.com'
      S3_BUCKET_NAME: 'difyai'
      S3_ACCESS_KEY: 'ak-difyai'
      S3 SECRET_KEY: 'sk-difyai'
      S3_REGION: 'us-east-1'
      AZURE_BLOB_ACCOUNT_NAME: 'difyai'
      AZURE_BLOB_ACCOUNT_KEY: 'difyai'
      AZURE_BLOB_CONTAINER_NAME: 'difyai-container'
      AZURE_BLOB_ACCOUNT_URL: 'https://<your_account_name>.blob.core.windows.net'
      GOOGLE_STORAGE_BUCKET_NAME: 'yout-bucket-name'
      GOOGLE_STORAGE_SERVICE_ACCOUNT_JSON_BASE64: 'your-google-service-account-json-base64-string'
      VECTOR_STORE: weaviate
      WEAVIATE_ENDPOINT: http://weaviate:8080
      WEAVIATE_API_KEY: WVF5YThaHlkYwhGUSmCRgsX3tD5ngdN8pkih
      QDRANT_URL: http://qdrant:6333
      QDRANT_API_KEY: difyai123456
      QDRANT_CLIENT_TIMEOUT: 20
      QDRANT_GRPC_ENABLED: 'false'
      QDRANT_GRPC_PORT: 6334
      MILVUS_HOST: 127.0.0.1
      MILVUS_PORT: 19530
      MILVUS_USER: root
      MILVUS_PASSWORD: Milvus
      MILVUS_SECURE: 'false'
      MAIL_TYPE: ''
      MAIL_DEFAULT_SEND_FROM: 'YOUR EMAIL FROM (eg: no-reply <no-reply@dify.ai>)'
      SMTP_SERVER: ''
      SMTP_PORT: 465
      SMTP_USERNAME: ''
      SMTP_PASSWORD: ''
      SMTP_USE_TLS: 'true'
      SMTP_OPPORTUNISTIC_TLS: 'false'
      RESEND_API_KEY: ''
      RESEND_API_URL: https://api.resend.com
      RELYT_HOST: db
      RELYT_PORT: 5432
      RELYT_USER: postgres
      RELYT_PASSWORD: difyai123456
      RELYT_DATABASE: postgres
      PGVECTOR_HOST: pgvector
      PGVECTOR_PORT: 5432
      PGVECTOR_USER: postgres
      PGVECTOR_PASSWORD: difyai123456
      PGVECTOR_DATABASE: dify
      NOTION_INTEGRATION_TYPE: public
      NOTION_CLIENT_SECRET: you-client-secret
      NOTION_CLIENT_ID: you-client-id
      NOTION_INTERNAL_SECRET: you-internal-secret
      INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH: 1000
    depends_on:
      - db
      - redis
    volumes:
      - ./volumes/app/storage:/app/api/storage
    networks:
      - ssrf_proxy_network
      - default

# -----------------------------------------------------------
  web:
    image: langgenius/dify-web:0.6.9
    restart: always
    environment:
      CONSOLE_API_URL: ''
      APP_API_URL: ''
      SENTRY_DSN: ''
    networks:
      - ssrf_proxy_network # 修正: defaultネットワークにssrf_proxy_networkを追加
      - default

# -----------------------------------------------------------
  db:
    image: postgres:15-alpine
    restart: always
    environment:
      PGUSER: postgres
      POSTGRES_PASSWORD: difyai123456
      POSTGRES_DB: dify
      PGDATA: /var/lib/postgresql/data/pgdata
    volumes:
      - postgres_data:/var/lib/postgresql/data # 修正: ./volumes/db/dataからpostgres_dataに変更
    healthcheck:
      test: [ "CMD", "pg_isready" ]
      interval: 1s
      timeout: 3s
      retries: 30
    networks:
      - default

# -----------------------------------------------------------
  redis:
    image: redis:6-alpine
    restart: always
    volumes:
      # - redis_data:/data # 修正: ./volumes/redis/dataからredis_dataに変更
      - ./volumes/redis/data:/data
    command: redis-server --requirepass difyai123456
    healthcheck:
      test: [ "CMD", "redis-cli", "ping" ]
    networks:
      - default

# -----------------------------------------------------------
  weaviate:
    image: semitechnologies/weaviate:1.19.0
    restart: always
    volumes:
      - ./volumes/weaviate:/var/lib/weaviate
    environment:
      QUERY_DEFAULTS_LIMIT: 25
      AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED: 'false'
      PERSISTENCE_DATA_PATH: '/var/lib/weaviate'
      DEFAULT_VECTORIZER_MODULE: 'none'
      CLUSTER_HOSTNAME: 'node1'
      AUTHENTICATION_APIKEY_ENABLED: 'true'
      AUTHENTICATION_APIKEY_ALLOWED_KEYS: 'WVF5YThaHlkYwhGUSmCRgsX3tD5ngdN8pkih'
      AUTHENTICATION_APIKEY_USERS: 'hello@dify.ai'
      AUTHORIZATION_ADMINLIST_ENABLED: 'true'
      AUTHORIZATION_ADMINLIST_USERS: 'hello@dify.ai'
    networks:
      - default

# -----------------------------------------------------------
  sandbox:
    image: langgenius/dify-sandbox:0.2.0
    restart: always
    environment:
      API_KEY: dify-sandbox
      GIN_MODE: 'release'
      WORKER_TIMEOUT: 15
      ENABLE_NETWORK: 'true'
      HTTP_PROXY: 'http://ssrf_proxy:3128'
      HTTPS_PROXY: 'http://ssrf_proxy:3128'
    volumes:
      - ./volumes/sandbox/dependencies:/dependencies
    networks:
      - ssrf_proxy_network

# -----------------------------------------------------------
  ssrf_proxy:
    image: ubuntu/squid:latest
    restart: always
    volumes:
      - ./volumes/ssrf_proxy/squid.conf:/etc/squid/squid.conf
    networks:
      - ssrf_proxy_network
      - default

# -----------------------------------------------------------
  nginx:
    image: nginx:latest
    restart: always
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf
      - ./nginx/proxy.conf:/etc/nginx/proxy.conf
      - ./nginx/conf.d:/etc/nginx/conf.d
    depends_on:
      - api
      - web
    ports:
      - "8080:80"
    networks:
      - default # 修正: defaultネットワークを追加

# ===========================================================
networks:
  ssrf_proxy_network:
    driver: bridge
    internal: true
  default: # 修正: defaultネットワークを追加
    driver: bridge

# ===========================================================
volumes:
  postgres_data: # 追加: 新しいボリュームを定義
  # redis_data: # 追加: 新しいボリュームを定義```
オリジナルと最終的なdocker-compose.yamlを可視化して比較

※graphvizで可視化してみた。
オリジナル

最終版

  1. ようやくDifyが起動!
shohei6117shohei6117

Dify環境を作る#2

ゴール

  • DIfyを別のWindows PCにインストールして起動する

まとめ

  • Docker Desktopでwsl2データを全削除したらエラーが解消した

関連リンク

手順

※ 同じ社内ネットワークにある別のPCなので、途中までは [前回](https://zenn.dev/link/comments/59a67f0070ba23) と同じで、手順6で出たエラーだけが違う

  1. gitをインストール(割愛)
  2. wsl2を有効化(割愛)
  3. Docker Desktop for Windowsを導入(割愛)
  4. Difyのコードを公式リポジトリからgit cloneする(割愛)
  5. cloneしたdifyフォルダの中のdocker-compose.yamlを書き替える
    ファイルのパスはE:\dify\docker\docker-compose.yaml
書き替えたあとのdocker-compose.yaml
docker-compose.yaml
version: '3'

# ===========================================================
services:
# -----------------------------------------------------------
  api:
    image: langgenius/dify-api:0.6.9
    restart: always
    environment:
      MODE: api
      LOG_LEVEL: INFO
      SECRET_KEY: sk-9f73s3ljTXVcMT3Blb3ljTqtsKiGHXVcMT3BlbkFJLK7U
      CONSOLE_WEB_URL: ''
      INIT_PASSWORD: ''
      CONSOLE_API_URL: ''
      SERVICE_API_URL: ''
      APP_WEB_URL: ''
      FILES_URL: ''
      FILES_ACCESS_TIMEOUT: 300
      MIGRATION_ENABLED: 'true'
      DB_USERNAME: postgres
      DB_PASSWORD: difyai123456
      DB_HOST: db
      DB_PORT: 5432
      DB_DATABASE: dify
      REDIS_HOST: redis
      REDIS_PORT: 6379
      REDIS_USERNAME: ''
      REDIS_PASSWORD: difyai123456
      REDIS_USE_SSL: 'false'
      REDIS_DB: 0
      CELERY_BROKER_URL: redis://:difyai123456@redis:6379/1
      WEB_API_CORS_ALLOW_ORIGINS: '*'
      CONSOLE_CORS_ALLOW_ORIGINS: '*'
      STORAGE_TYPE: local
      STORAGE_LOCAL_PATH: storage
      S3_ENDPOINT: 'https://xxx.r2.cloudflarestorage.com'
      S3_BUCKET_NAME: 'difyai'
      S3_ACCESS_KEY: 'ak-difyai'
      S3_SECRET_KEY: 'sk-difyai'
      S3_REGION: 'us-east-1'
      AZURE_BLOB_ACCOUNT_NAME: 'difyai'
      AZURE_BLOB_ACCOUNT_KEY: 'difyai'
      AZURE_BLOB_CONTAINER_NAME: 'difyai-container'
      AZURE_BLOB_ACCOUNT_URL: 'https://<your_account_name>.blob.core.windows.net'
      GOOGLE_STORAGE_BUCKET_NAME: 'yout-bucket-name'
      GOOGLE_STORAGE_SERVICE_ACCOUNT_JSON_BASE64: 'your-google-service-account-json-base64-string'
      VECTOR_STORE: weaviate
      WEAVIATE_ENDPOINT: http://weaviate:8080
      WEAVIATE_API_KEY: WVF5YThaHlkYwhGUSmCRgsX3tD5ngdN8pkih
      QDRANT_URL: http://qdrant:6333
      QDRANT_API_KEY: difyai123456
      QDRANT_CLIENT_TIMEOUT: 20
      QDRANT_GRPC_ENABLED: 'false'
      QDRANT_GRPC_PORT: 6334
      MILVUS_HOST: 127.0.0.1
      MILVUS_PORT: 19530
      MILVUS_USER: root
      MILVUS_PASSWORD: Milvus
      MILVUS_SECURE: 'false'
      RELYT_HOST: db
      RELYT_PORT: 5432
      RELYT_USER: postgres
      RELYT_PASSWORD: difyai123456
      RELYT_DATABASE: postgres
      PGVECTOR_HOST: pgvector
      PGVECTOR_PORT: 5432
      PGVECTOR_USER: postgres
      PGVECTOR_PASSWORD: difyai123456
      PGVECTOR_DATABASE: dify
      MAIL_TYPE: ''
      MAIL_DEFAULT_SEND_FROM: 'YOUR EMAIL FROM (eg: no-reply <no-reply@dify.ai>)'
      SMTP_SERVER: ''
      SMTP_PORT: 465
      SMTP_USERNAME: ''
      SMTP_PASSWORD: ''
      SMTP_USE_TLS: 'true'
      SMTP_OPPORTUNISTIC_TLS: 'false'
      RESEND_API_KEY: ''
      RESEND_API_URL: https://api.resend.com
      SENTRY_DSN: ''
      SENTRY_TRACES_SAMPLE_RATE: 1.0
      SENTRY_PROFILES_SAMPLE_RATE: 1.0
      NOTION_INTEGRATION_TYPE: public
      NOTION_CLIENT_SECRET: you-client-secret
      NOTION_CLIENT_ID: you-client-id
      NOTION_INTERNAL_SECRET: you-internal-secret
      CODE_EXECUTION_ENDPOINT: "http://sandbox:8194"
      CODE_EXECUTION_API_KEY: dify-sandbox
      CODE_MAX_NUMBER: 9223372036854775807
      CODE_MIN_NUMBER: -9223372036854775808
      CODE_MAX_STRING_LENGTH: 80000
      TEMPLATE_TRANSFORM_MAX_LENGTH: 80000
      CODE_MAX_STRING_ARRAY_LENGTH: 30
      CODE_MAX_OBJECT_ARRAY_LENGTH: 30
      CODE_MAX_NUMBER_ARRAY_LENGTH: 1000
      SSRF_PROXY_HTTP_URL: 'http://ssrf_proxy:3128'
      SSRF_PROXY_HTTPS_URL: 'http://ssrf_proxy:3128'
      INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH: 1000
    depends_on:
      - db
      - redis
    volumes:
      - ./volumes/app/storage:/app/api/storage
    networks:
      - ssrf_proxy_network
      - default

# -----------------------------------------------------------
  worker:
    image: langgenius/dify-api:0.6.9
    restart: always
    environment:
      CONSOLE_WEB_URL: ''
      MODE: worker
      LOG_LEVEL: INFO
      SECRET_KEY: sk-9f73s3ljTXVcMT3Blb3ljTqtsKiGHXVcMT3BlbkFJLK7U
      DB_USERNAME: postgres
      DB_PASSWORD: difyai123456
      DB_HOST: db
      DB_PORT: 5432
      DB_DATABASE: dify
      REDIS_HOST: redis
      REDIS_PORT: 6379
      REDIS_USERNAME: ''
      REDIS_PASSWORD: difyai123456
      REDIS_DB: 0
      REDIS_USE_SSL: 'false'
      CELERY_BROKER_URL: redis://:difyai123456@redis:6379/1
      STORAGE_TYPE: local
      STORAGE_LOCAL_PATH: storage
      S3_ENDPOINT: 'https://xxx.r2.cloudflarestorage.com'
      S3_BUCKET_NAME: 'difyai'
      S3_ACCESS_KEY: 'ak-difyai'
      S3 SECRET_KEY: 'sk-difyai'
      S3_REGION: 'us-east-1'
      AZURE_BLOB_ACCOUNT_NAME: 'difyai'
      AZURE_BLOB_ACCOUNT_KEY: 'difyai'
      AZURE_BLOB_CONTAINER_NAME: 'difyai-container'
      AZURE_BLOB_ACCOUNT_URL: 'https://<your_account_name>.blob.core.windows.net'
      GOOGLE_STORAGE_BUCKET_NAME: 'yout-bucket-name'
      GOOGLE_STORAGE_SERVICE_ACCOUNT_JSON_BASE64: 'your-google-service-account-json-base64-string'
      VECTOR_STORE: weaviate
      WEAVIATE_ENDPOINT: http://weaviate:8080
      WEAVIATE_API_KEY: WVF5YThaHlkYwhGUSmCRgsX3tD5ngdN8pkih
      QDRANT_URL: http://qdrant:6333
      QDRANT_API_KEY: difyai123456
      QDRANT_CLIENT_TIMEOUT: 20
      QDRANT_GRPC_ENABLED: 'false'
      QDRANT_GRPC_PORT: 6334
      MILVUS_HOST: 127.0.0.1
      MILVUS_PORT: 19530
      MILVUS_USER: root
      MILVUS_PASSWORD: Milvus
      MILVUS_SECURE: 'false'
      MAIL_TYPE: ''
      MAIL_DEFAULT_SEND_FROM: 'YOUR EMAIL FROM (eg: no-reply <no-reply@dify.ai>)'
      SMTP_SERVER: ''
      SMTP_PORT: 465
      SMTP_USERNAME: ''
      SMTP_PASSWORD: ''
      SMTP_USE_TLS: 'true'
      SMTP_OPPORTUNISTIC_TLS: 'false'
      RESEND_API_KEY: ''
      RESEND_API_URL: https://api.resend.com
      RELYT_HOST: db
      RELYT_PORT: 5432
      RELYT_USER: postgres
      RELYT_PASSWORD: difyai123456
      RELYT_DATABASE: postgres
      PGVECTOR_HOST: pgvector
      PGVECTOR_PORT: 5432
      PGVECTOR_USER: postgres
      PGVECTOR_PASSWORD: difyai123456
      PGVECTOR_DATABASE: dify
      NOTION_INTEGRATION_TYPE: public
      NOTION_CLIENT_SECRET: you-client-secret
      NOTION_CLIENT_ID: you-client-id
      NOTION_INTERNAL_SECRET: you-internal-secret
      INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH: 1000
    depends_on:
      - db
      - redis
    volumes:
      - ./volumes/app/storage:/app/api/storage
    networks:
      - ssrf_proxy_network
      - default

# -----------------------------------------------------------
  web:
    image: langgenius/dify-web:0.6.9
    restart: always
    environment:
      CONSOLE_API_URL: ''
      APP_API_URL: ''
      SENTRY_DSN: ''
    networks:
      - ssrf_proxy_network # 修正: defaultネットワークにssrf_proxy_networkを追加
      - default

# -----------------------------------------------------------
  db:
    image: postgres:15-alpine
    restart: always
    environment:
      PGUSER: postgres
      POSTGRES_PASSWORD: difyai123456
      POSTGRES_DB: dify
      PGDATA: /var/lib/postgresql/data/pgdata
    volumes:
      - postgres_data:/var/lib/postgresql/data # 修正: ./volumes/db/dataからpostgres_dataに変更
    healthcheck:
      test: [ "CMD", "pg_isready" ]
      interval: 1s
      timeout: 3s
      retries: 30
    networks:
      - default

# -----------------------------------------------------------
  redis:
    image: redis:6-alpine
    restart: always
    volumes:
      # - redis_data:/data # 修正: ./volumes/redis/dataからredis_dataに変更
      - ./volumes/redis/data:/data
    command: redis-server --requirepass difyai123456
    healthcheck:
      test: [ "CMD", "redis-cli", "ping" ]
    networks:
      - default

# -----------------------------------------------------------
  weaviate:
    image: semitechnologies/weaviate:1.19.0
    restart: always
    volumes:
      - ./volumes/weaviate:/var/lib/weaviate
    environment:
      QUERY_DEFAULTS_LIMIT: 25
      AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED: 'false'
      PERSISTENCE_DATA_PATH: '/var/lib/weaviate'
      DEFAULT_VECTORIZER_MODULE: 'none'
      CLUSTER_HOSTNAME: 'node1'
      AUTHENTICATION_APIKEY_ENABLED: 'true'
      AUTHENTICATION_APIKEY_ALLOWED_KEYS: 'WVF5YThaHlkYwhGUSmCRgsX3tD5ngdN8pkih'
      AUTHENTICATION_APIKEY_USERS: 'hello@dify.ai'
      AUTHORIZATION_ADMINLIST_ENABLED: 'true'
      AUTHORIZATION_ADMINLIST_USERS: 'hello@dify.ai'
    networks:
      - default

# -----------------------------------------------------------
  sandbox:
    image: langgenius/dify-sandbox:0.2.0
    restart: always
    environment:
      API_KEY: dify-sandbox
      GIN_MODE: 'release'
      WORKER_TIMEOUT: 15
      ENABLE_NETWORK: 'true'
      HTTP_PROXY: 'http://ssrf_proxy:3128'
      HTTPS_PROXY: 'http://ssrf_proxy:3128'
    volumes:
      - ./volumes/sandbox/dependencies:/dependencies
    networks:
      - ssrf_proxy_network

# -----------------------------------------------------------
  ssrf_proxy:
    image: ubuntu/squid:latest
    restart: always
    volumes:
      - ./volumes/ssrf_proxy/squid.conf:/etc/squid/squid.conf
    networks:
      - ssrf_proxy_network
      - default

# -----------------------------------------------------------
  nginx:
    image: nginx:latest
    restart: always
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf
      - ./nginx/proxy.conf:/etc/nginx/proxy.conf
      - ./nginx/conf.d:/etc/nginx/conf.d
    depends_on:
      - api
      - web
    ports:
      - "8080:80"
    networks:
      - default # 修正: defaultネットワークを追加

# ===========================================================
networks:
  ssrf_proxy_network:
    driver: bridge
    internal: true
  default: # 修正: defaultネットワークを追加
    driver: bridge

# ===========================================================
volumes:
  postgres_data: # 追加: 新しいボリュームを定義
  # redis_data: # 追加: 新しいボリュームを定義  ```
オリジナルと最終的なdocker-compose.yamlを可視化して比較

※graphvizで可視化してみた。
オリジナル

最終版

  1. dockerフォルダでPoserShellを立ち上げて docker-compose up -dコマンドを打つ ⇒エラーが出る

Error response from daemon: error while creating mount source path '/run/desktop/mnt/host/e/dify/docker/volumes/sandbox/dependencies': mkdir /run/desktop/mnt/host/e: file exists

  1. データをいったん全削除する
    GhatGPTに聞きながら対応しても解決しない
    ⇒ググって、この記事を見つける!

⇒同じようにやってみる

Docker Desktop for Windows操作の画面キャプチャ

Troubleshootを開く

Clean / Purge data をクリック

WSL 2を選択して delete をクリック

images も containers も全部消えた。。

  1. あらためてdocker-compose up -dする
  2. ようやくDifyが起動!
shohei6117shohei6117

Difyを0.6.10にアップデートする

ゴール

  • Dify0.6.9 から Dify0.6.10 にアップデートする

まとめ

  • Dify0.6.9をインストールしたときに作ったdocker-compose.yamlファイルを流用した

関連リンク

手順

  1. いまのdockerフォルダにあるdocker-compose.yamlの名称をdocker-compose_069backup.yamlみたいな名前にしておいて、公式docどおりに進める。
PowerShell
# dockerフォルダに移動
cd dify/docker 
# リモートリポジトリのmainブランチから最新バージョンをダウンロード
git pull origin main
# 現在のdockerコンテナを停止
docker compose down
# docker-compose.ymlで定義された最新のイメージを取得
docker compose pull
# ------ ここから先はまだしない! --------------
# dockerコンテナを再構築して起動
# docker compose up -d 
  1. 0.6.10バージョンのdocker-compose.yamlを以下のように書き換える。
docker-compose.yamlの中身
version
services:
  api:
    image: langgenius/dify-api:0.6.10
    restart: always
    environment:
      MODE: api
      LOG_LEVEL: INFO
      SECRET_KEY: sk-9f73s3ljTXVcMT3Blb3ljTqtsKiGHXVcMT3BlbkFJLK7U
      CONSOLE_WEB_URL: 'http://161.93.***.***:8080' #追加(PCの静的IPを入れる)
      INIT_PASSWORD: ''
      CONSOLE_API_URL: 'http://161.93.***.***:8080' #追加(PCの静的IPを入れる)
      SERVICE_API_URL: 'http://161.93.***.***:8080' #追加(PCの静的IPを入れる)
      APP_WEB_URL: 'http://161.93.***.***:8080' #追加(PCの静的IPを入れる)
      FILES_URL: 'http://161.93.***.***:8080' #追加(PCの静的IPを入れる)
      FILES_ACCESS_TIMEOUT: 300
      MIGRATION_ENABLED: 'true'
      DB_USERNAME: postgres
      DB_PASSWORD: difyai123456
      DB_HOST: db
      DB_PORT: 5432
      DB_DATABASE: dify
      REDIS_HOST: redis
      REDIS_PORT: 6379
      REDIS_USERNAME: ''
      REDIS_PASSWORD: difyai123456
      REDIS_USE_SSL: 'false'
      REDIS_DB: 0
      CELERY_BROKER_URL: redis://:difyai123456@redis:6379/1
      WEB_API_CORS_ALLOW_ORIGINS: '*'
      CONSOLE_CORS_ALLOW_ORIGINS: '*'
      STORAGE_TYPE: local
      STORAGE_LOCAL_PATH: storage
      S3_ENDPOINT: 'https://xxx.r2.cloudflarestorage.com'
      S3_BUCKET_NAME: 'difyai'
      S3_ACCESS_KEY: 'ak-difyai'
      S3_SECRET_KEY: 'sk-difyai'
      S3_REGION: 'us-east-1'
      AZURE_BLOB_ACCOUNT_NAME: 'difyai'
      AZURE_BLOB_ACCOUNT_KEY: 'difyai'
      AZURE_BLOB_CONTAINER_NAME: 'difyai-container'
      AZURE_BLOB_ACCOUNT_URL: 'https://<your_account_name>.blob.core.windows.net'
      GOOGLE_STORAGE_BUCKET_NAME: 'yout-bucket-name'
      GOOGLE_STORAGE_SERVICE_ACCOUNT_JSON_BASE64: 'your-google-service-account-json-base64-string'
      VECTOR_STORE: weaviate
      WEAVIATE_ENDPOINT: http://weaviate:8080
      WEAVIATE_API_KEY: WVF5YThaHlkYwhGUSmCRgsX3tD5ngdN8pkih
      QDRANT_URL: http://qdrant:6333
      QDRANT_API_KEY: difyai123456
      QDRANT_CLIENT_TIMEOUT: 20
      QDRANT_GRPC_ENABLED: 'false'
      QDRANT_GRPC_PORT: 6334
      MILVUS_HOST: 127.0.0.1
      MILVUS_PORT: 19530
      MILVUS_USER: root
      MILVUS_PASSWORD: Milvus
      MILVUS_SECURE: 'false'
      RELYT_HOST: db
      RELYT_PORT: 5432
      RELYT_USER: postgres
      RELYT_PASSWORD: difyai123456
      RELYT_DATABASE: postgres
      PGVECTOR_HOST: pgvector
      PGVECTOR_PORT: 5432
      PGVECTOR_USER: postgres
      PGVECTOR_PASSWORD: difyai123456
      PGVECTOR_DATABASE: dify
      MAIL_TYPE: ''
      MAIL_DEFAULT_SEND_FROM: 'YOUR EMAIL FROM (eg: no-reply <no-reply@dify.ai>)'
      SMTP_SERVER: ''
      SMTP_PORT: 465
      SMTP_USERNAME: ''
      SMTP_PASSWORD: ''
      SMTP_USE_TLS: 'true'
      SMTP_OPPORTUNISTIC_TLS: 'false'
      RESEND_API_KEY: ''
      RESEND_API_URL: https://api.resend.com
      SENTRY_DSN: ''
      SENTRY_TRACES_SAMPLE_RATE: 1.0
      SENTRY_PROFILES_SAMPLE_RATE: 1.0
      NOTION_INTEGRATION_TYPE: public
      NOTION_CLIENT_SECRET: you-client-secret
      NOTION_CLIENT_ID: you-client-id
      NOTION_INTERNAL_SECRET: you-internal-secret
      CODE_EXECUTION_ENDPOINT: "http://sandbox:8194"
      CODE_EXECUTION_API_KEY: dify-sandbox
      CODE_MAX_NUMBER: 9223372036854775807
      CODE_MIN_NUMBER: -9223372036854775808
      CODE_MAX_STRING_LENGTH: 80000
      TEMPLATE_TRANSFORM_MAX_LENGTH: 80000
      CODE_MAX_STRING_ARRAY_LENGTH: 30
      CODE_MAX_OBJECT_ARRAY_LENGTH: 30
      CODE_MAX_NUMBER_ARRAY_LENGTH: 1000
      SSRF_PROXY_HTTP_URL: 'http://ssrf_proxy:3128'
      SSRF_PROXY_HTTPS_URL: 'http://ssrf_proxy:3128'
      INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH: 1000
    depends_on:
      - db
      - redis
    volumes:
      - ./volumes/app/storage:/app/api/storage
    networks:
      - ssrf_proxy_network
      - default

  worker:
    image: langgenius/dify-api:0.6.10
    restart: always
    environment:
      CONSOLE_WEB_URL: ''
      MODE: worker
      LOG_LEVEL: INFO
      SECRET_KEY: sk-9f73s3ljTXVcMT3Blb3ljTqtsKiGHXVcMT3BlbkFJLK7U
      DB_USERNAME: postgres
      DB_PASSWORD: difyai123456
      DB_HOST: db
      DB_PORT: 5432
      DB_DATABASE: dify
      REDIS_HOST: redis
      REDIS_PORT: 6379
      REDIS_USERNAME: ''
      REDIS_PASSWORD: difyai123456
      REDIS_DB: 0
      REDIS_USE_SSL: 'false'
      CELERY_BROKER_URL: redis://:difyai123456@redis:6379/1
      STORAGE_TYPE: local
      STORAGE_LOCAL_PATH: storage
      S3_ENDPOINT: 'https://xxx.r2.cloudflarestorage.com'
      S3_BUCKET_NAME: 'difyai'
      S3_ACCESS_KEY: 'ak-difyai'
      S3_SECRET_KEY: 'sk-difyai'
      S3_REGION: 'us-east-1'
      AZURE_BLOB_ACCOUNT_NAME: 'difyai'
      AZURE_BLOB_ACCOUNT_KEY: 'difyai'
      AZURE_BLOB_CONTAINER_NAME: 'difyai-container'
      AZURE_BLOB_ACCOUNT_URL: 'https://<your_account_name>.blob.core.windows.net'
      GOOGLE_STORAGE_BUCKET_NAME: 'yout-bucket-name'
      GOOGLE_STORAGE_SERVICE_ACCOUNT_JSON_BASE64: 'your-google-service-account-json-base64-string'
      VECTOR_STORE: weaviate
      WEAVIATE_ENDPOINT: http://weaviate:8080
      WEAVIATE_API_KEY: WVF5YThaHlkYwhGUSmCRgsX3tD5ngdN8pkih
      QDRANT_URL: http://qdrant:6333
      QDRANT_API_KEY: difyai123456
      QDRANT_CLIENT_TIMEOUT: 20
      QDRANT_GRPC_ENABLED: 'false'
      QDRANT_GRPC_PORT: 6334
      MILVUS_HOST: 127.0.0.1
      MILVUS_PORT: 19530
      MILVUS_USER: root
      MILVUS_PASSWORD: Milvus
      MILVUS_SECURE: 'false'
      MAIL_TYPE: ''
      MAIL_DEFAULT_SEND_FROM: 'YOUR EMAIL FROM (eg: no-reply <no-reply@dify.ai>)'
      SMTP_SERVER: ''
      SMTP_PORT: 465
      SMTP_USERNAME: ''
      SMTP_PASSWORD: ''
      SMTP_USE_TLS: 'true'
      SMTP_OPPORTUNISTIC_TLS: 'false'
      RESEND_API_KEY: ''
      RESEND_API_URL: https://api.resend.com
      RELYT_HOST: db
      RELYT_PORT: 5432
      RELYT_USER: postgres
      RELYT_PASSWORD: difyai123456
      RELYT_DATABASE: postgres
      PGVECTOR_HOST: pgvector
      PGVECTOR_PORT: 5432
      PGVECTOR_USER: postgres
      PGVECTOR_PASSWORD: difyai123456
      PGVECTOR_DATABASE: dify
      NOTION_INTEGRATION_TYPE: public
      NOTION_CLIENT_SECRET: you-client-secret
      NOTION_CLIENT_ID: you-client-id
      NOTION_INTERNAL_SECRET: you-internal-secret
      INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH: 1000
    depends_on:
      - db
      - redis
    volumes:
      - ./volumes/app/storage:/app/api/storage
    networks:
      - ssrf_proxy_network
      - default

  web:
    image: langgenius/dify-web:0.6.10
    restart: always
    environment:
      CONSOLE_API_URL: ''
      APP_API_URL: ''
      SENTRY_DSN: ''
    networks: #追加
      - ssrf_proxy_network #追加
      - default #追加


  db:
    image: postgres:15-alpine
    restart: always
    environment:
      PGUSER: postgres
      POSTGRES_PASSWORD: difyai123456
      POSTGRES_DB: dify
      PGDATA: /var/lib/postgresql/data/pgdata
    volumes:
      #- ./volumes/db/data:/var/lib/postgresql/data
      - postgres_data:/var/lib/postgresql/data #変更
    healthcheck:
      test: [ "CMD", "pg_isready" ]
      interval: 1s
      timeout: 3s
      retries: 30
    networks: #追加
      - default #追加

  redis:
    image: redis:6-alpine
    restart: always
    volumes:
      - ./volumes/redis/data:/data
    command: redis-server --requirepass difyai123456
    healthcheck:
      test: [ "CMD", "redis-cli", "ping" ]
    networks: #追加
      - default #追加

  weaviate:
    image: semitechnologies/weaviate:1.19.0
    restart: always
    volumes:
      - ./volumes/weaviate:/var/lib/weaviate
    environment:
      QUERY_DEFAULTS_LIMIT: 25
      AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED: 'false'
      PERSISTENCE_DATA_PATH: '/var/lib/weaviate'
      DEFAULT_VECTORIZER_MODULE: 'none'
      CLUSTER_HOSTNAME: 'node1'
      AUTHENTICATION_APIKEY_ENABLED: 'true'
      AUTHENTICATION_APIKEY_ALLOWED_KEYS: 'WVF5YThaHlkYwhGUSmCRgsX3tD5ngdN8pkih'
      AUTHENTICATION_APIKEY_USERS: 'hello@dify.ai'
      AUTHORIZATION_ADMINLIST_ENABLED: 'true'
      AUTHORIZATION_ADMINLIST_USERS: 'hello@dify.ai'
    networks: #追加
      - default #追加

  sandbox:
    image: langgenius/dify-sandbox:0.2.1
    restart: always
    environment:
      API_KEY: dify-sandbox
      GIN_MODE: 'release'
      WORKER_TIMEOUT: 15
      ENABLE_NETWORK: 'true'
      HTTP_PROXY: 'http://ssrf_proxy:3128'
      HTTPS_PROXY: 'http://ssrf_proxy:3128'
      SANDBOX_PORT: 8194
    volumes:
      - ./volumes/sandbox/dependencies:/dependencies
    networks:
      - ssrf_proxy_network

  ssrf_proxy:
    image: ubuntu/squid:latest
    restart: always
    volumes:
      - ./volumes/ssrf_proxy/squid.conf:/etc/squid/squid.conf
    networks:
      - ssrf_proxy_network
      - default

  nginx:
    image: nginx:latest
    restart: always
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf
      - ./nginx/proxy.conf:/etc/nginx/proxy.conf
      - ./nginx/conf.d:/etc/nginx/conf.d
    depends_on:
      - api
      - web
    ports:
      - "8080:80" #80:80から変更
    networks: #追加
      - default #追加

networks:
  ssrf_proxy_network:
    driver: bridge
    internal: true
  default: #追加
    driver: bridge 

volumes: #追加
  postgres_data: #追加
docker-compose.yamlの中身を可視化

※streamlit+graphvizで可視化してみた。

  1. あたらめて起動
PowerShell
# dockerコンテナを再構築して起動
# docker compose up -d 
  1. 無事起動!
shohei6117shohei6117

Dify+Ollamaでチャットボットを作る

ゴール

  • Ollamaで動くLlama3と連携したチャットボットを作る

まとめ

  • Difyはすごい!
    • ノーコードでLLMアプリを作れる
    • 作ったアプリをホストしてくれる
    • デバッグがしやすい
    • ログ管理やセッション管理までできる
  • LLMブロックを3段階重ねて、なんとかLlama3で日本語チャットができるようになった

参考リンク

手順

まず、DifyにOllamaで動くLlama3を登録する

  1. Ollamaを以下のbatファイルから起動する
    ※ 前提:ollamaにllama3がインストールされている
    ※ OLLAM_HOSTをシステム環境変数として登録してもよい
ollama起動バッチファイル
@echo off
set OLLAMA_HOST=0.0.0.0:11434
ollama serve
  1. Difyを起動して、アクセスする。(私の場合はhttp://localhost:8080/apps
  2. LLMモデルを登録する

つぎに、Difyでチャットボットを作る

  1. 下図のようにはじめる
    スタジオ最初から作成チャットボットChatflow→アプリ名はLlama3チャットボット
  2. ブロックを追加、連結、編集していく。
完成したフロー

全体フロー

日→英翻訳:ユーザー入力を英語に変換する

  • システムプロンプト
# INSTRUCTIONS
- Translate only the Japanese parts of the "USER INPUT" sentences into English.
- Output only the translated text.
- Do not include "Here is the translation of the Japanese part: " at the beginning of the output text.
- Ensure the translation captures the full meaning of the Japanese text without adding unnecessary context. 


# GOOD SAMPLE
- USER INPUT: "moonshot meanってどういう意味?"
- YOUR OUTPUT: "What does moonshot mean?"
 

# BAD SAMPLE
- USER INPUT: "テニスのルールを教えて"
- YOUR OUTPUT: "Translation of Japanese text to English\nThe translation of the Japanese text is as follows:\n\n\"Teaching tennis rules\"
 

# USER INPUT
{{#context#}}

メインブロック:回答を生成する

英→日翻訳:LLM出力を日本語に翻訳する


システムプロンプト

以下の文章を、忠実に日本語に翻訳して出力してください。
(ただし以下は日本語に変換しないこと:固有名詞、技術用語、プログラミングコード、製品名やブランド名、特定の業界用語や略語(例えば、IT業界の「API」や「SQL」など)、ファイル名やディレクトリ名、設定や構成項目の名前、ログメッセージやエラーメッセージ、数値や特定の単位(例えば、「GB」や「Hz」など)、メールアドレスやURL )

# 翻訳する文章  
{{#context#}}

出力:出力するだけ

  1. デバッグしながら、プロンプトやLLM設定(tempretureなど)を調整する
デバッグの様子

  1. アプリとして「公開する」
アプリを公開

アプリを実行を選択すると、webアプリページが開く。

Chrome拡張機能を使って、ウェブサイトに埋め込むこともできる
※動かないサイトも多い

  1. アプリ管理機能もある
アプリ管理機能

ログ&アナウンス

概要

メッセージ数、アクティブユーザー数、セッション数、トークン出力速度、トークン使用量など

  1. (追加)最初のメッセージを設定する
最初のメッセージを設定




shohei6117shohei6117

Dify+OllamaでRAGチャットボットを作る#1

ゴール

  • OllamaのモデルだけでRAGチャットボットを作れるようになる

まとめ

  • Ollamaで使える埋め込みモデルの日本語処理性能が悪い!→使えないので中止

関連リンク

埋め込みモデルの設定方法

Ollamaで使える埋め込みモデル一覧(2024-6-9時点)

mxbai-embed-largeモデルについて

サンプルFAQデータセット

手順

Difyにmxbai-embe-largeを登録する

  1. ollamaにモデルをpullする。
PowerShell
ollama pull mxbai-embed-large
  1. ollamaを起動する
Ollama起動batファイル
@echo off
set OLLAMA_HOST=0.0.0.0:11434
ollama serve
  1. Difyでモデルを設定する
    画面右上のアカウント名をクリック → モデルプロバイダーOllamaAdd model
モデル設定画面

※ model context sizeは公式サイト(下図)を参考に、512に設定した。

埋め込むデータを準備する

  1. LINEヤフーが公開している「子育てAIチャットボット用FAQデータセット」を利用する
  2. このサイトからZIPファイルをダウンロードして展開する
解凍したデータ

エクセル(xlsx)ファイルで、レコード数は981。

質問項目は以下のとおり。

  • 母子手帳・妊婦健診
  • 子どもの健診
  • 予防接種
  • 引っ越し手続き
  • 児童手当
  • 療育医療・育成医療・養育医療
  • 保育園・延長保育・一時保育
  • 子育て支援・イベント
  • その他の手続き(住民票、婚姻届など)

データを埋め込む

  1. システムモデル設定で、埋め込みモデルをmxdbai-embed-largeに設定しておく。

  2. Difyに取り込む
    画面上部のナレッジ知識を作成テキストファイルからインポート→回答したエクセルファイルをアップロードと進む(詳細は下記)

ファイルアップロードから埋め込みまでの流れ

ファイルをアップロード

テキストの前処理とクリーニング

すべて自動でおまかせする。

埋め込みの実行

1分くらいで終わる。

  1. 検索テストをしてみる
    画面上部→ナレッジ→埋め込み完了したデータセットを選択→サイドバーの検索テストをクリック
検索テストの結果

※top-Kは10に再設定した

  • 「母子手帳」で検索 →ダメ
  • 「児童手当」で検索 →ダメ
  • 「保育園に入れたい」で検索 →ダメ

全然ダメだ。。

比較:azure-openai text-embedding-3-largeで埋め込んだときの検索テスト結果

検索テスト結果

※top-Kは6に再設定した

  • 「母子手帳」で検索 →合格
  • 「児童手当」で検索 →合格
  • 「保育園に入れたい」で検索 →合格

非常に優秀

別のモデル(nomic-embe-text、snowflake-arctic-embed)も導入する

  1. Ollamaを停止
  2. 二つのモデルをpull
PowerShell
ollama pull nomic-embed-text
ollama pull snowflake-arctic-embed
  1. Ollmaを再起動
  2. Difyと連携
  • Model context sizenomic-embed-text8192snowflake-arctic-embed4096に設定した。
  1. データを埋め込む
  2. 検証テストをする
検証テスト結果(nomic-embed-text)
  • 「母子手帳」で検索 →ダメ
  • 「児童手当」で検索 →ダメ
  • 「保育園に入れたい」で検索 →ギリギリ合格
  • 「金属ごみの出し方を知りたい」で検索 →ダメ
  • 「粗大ごみの出し方」で検索 →ダメ

→**nomic-embed-textもダメ**

検証テスト結果(snowflake-arctic-embed)
  • 「母子手帳」で検索 →ダメ
  • 「児童手当」で検索 →ダメ
  • 「保育園に入れたい」で検索 →ダメ
  • 「金属ごみの出し方を知りたい」で検索 →ダメ
  • 「粗大ごみの出し方」で検索 →ギリギリ合格

→**snowflake-arctic-embedもダメ**

shohei6117shohei6117

Difyと一緒にOllamaもDockerで動かしてみる

ゴール

  • Difyのdocker-compose.yamlにOllamaも追記して、Docker上でOllamaを稼働させる
     ※現状はwindowsアプリとして起動させている

まとめ

  • 問題なく動いたが、現状CPUしか使えていない
    • GPUで動くようにするのはまた今度

関連リンク

手順

コンテナの再構築

  1. dify/dockerフォルダでPowerShellを起動して、Difyを停止する。
PowerShell
docker-compose down

※Docker Desktopの停止ボタンを押しても良い。
2. docker-compose.yamlを次のように書き替える

修正したdocker-compose.yaml
docker-compose.yaml
version: '3'
services:
  api:
    image: langgenius/dify-api:0.6.10
    restart: always
    environment:
      MODE: api
      LOG_LEVEL: INFO
      SECRET_KEY: sk-9f73s3ljTXVcMT3Blb3ljTqtsKiGHXVcMT3BlbkFJLK7U
      CONSOLE_WEB_URL: 'http://(DifyをホストするPCの静的IPを記載):8080' #追加
      INIT_PASSWORD: ''
      CONSOLE_API_URL: 'http://(DifyをホストするPCの静的IPを記載):8080' #追加
      SERVICE_API_URL: 'http://(DifyをホストするPCの静的IPを記載):8080' #追加
      APP_WEB_URL: 'http://(DifyをホストするPCの静的IPを記載):8080' #追加
      FILES_URL: 'http://(DifyをホストするPCの静的IPを記載):8080' #追加
      FILES_ACCESS_TIMEOUT: 300
      MIGRATION_ENABLED: 'true'
      DB_USERNAME: postgres
      DB_PASSWORD: difyai123456
      DB_HOST: db
      DB_PORT: 5432
      DB_DATABASE: dify
      REDIS_HOST: redis
      REDIS_PORT: 6379
      REDIS_USERNAME: ''
      REDIS_PASSWORD: difyai123456
      REDIS_USE_SSL: 'false'
      REDIS_DB: 0
      CELERY_BROKER_URL: redis://:difyai123456@redis:6379/1
      WEB_API_CORS_ALLOW_ORIGINS: '*'
      CONSOLE_CORS_ALLOW_ORIGINS: '*'
      STORAGE_TYPE: local
      STORAGE_LOCAL_PATH: storage
      S3_ENDPOINT: 'https://xxx.r2.cloudflarestorage.com'
      S3_BUCKET_NAME: 'difyai'
      S3_ACCESS_KEY: 'ak-difyai'
      S3_SECRET_KEY: 'sk-difyai'
      S3_REGION: 'us-east-1'
      AZURE_BLOB_ACCOUNT_NAME: 'difyai'
      AZURE_BLOB_ACCOUNT_KEY: 'difyai'
      AZURE_BLOB_CONTAINER_NAME: 'difyai-container'
      AZURE_BLOB_ACCOUNT_URL: 'https://<your_account_name>.blob.core.windows.net'
      GOOGLE_STORAGE_BUCKET_NAME: 'yout-bucket-name'
      GOOGLE_STORAGE_SERVICE_ACCOUNT_JSON_BASE64: 'your-google-service-account-json-base64-string'
      VECTOR_STORE: weaviate
      WEAVIATE_ENDPOINT: http://weaviate:8080
      WEAVIATE_API_KEY: WVF5YThaHlkYwhGUSmCRgsX3tD5ngdN8pkih
      QDRANT_URL: http://qdrant:6333
      QDRANT_API_KEY: difyai123456
      QDRANT_CLIENT_TIMEOUT: 20
      QDRANT_GRPC_ENABLED: 'false'
      QDRANT_GRPC_PORT: 6334
      MILVUS_HOST: 127.0.0.1
      MILVUS_PORT: 19530
      MILVUS_USER: root
      MILVUS_PASSWORD: Milvus
      MILVUS_SECURE: 'false'
      RELYT_HOST: db
      RELYT_PORT: 5432
      RELYT_USER: postgres
      RELYT_PASSWORD: difyai123456
      RELYT_DATABASE: postgres
      PGVECTOR_HOST: pgvector
      PGVECTOR_PORT: 5432
      PGVECTOR_USER: postgres
      PGVECTOR_PASSWORD: difyai123456
      PGVECTOR_DATABASE: dify
      MAIL_TYPE: ''
      MAIL_DEFAULT_SEND_FROM: 'YOUR EMAIL FROM (eg: no-reply <no-reply@dify.ai>)'
      SMTP_SERVER: ''
      SMTP_PORT: 465
      SMTP_USERNAME: ''
      SMTP_PASSWORD: ''
      SMTP_USE_TLS: 'true'
      SMTP_OPPORTUNISTIC_TLS: 'false'
      RESEND_API_KEY: ''
      RESEND_API_URL: https://api.resend.com
      SENTRY_DSN: ''
      SENTRY_TRACES_SAMPLE_RATE: 1.0
      SENTRY_PROFILES_SAMPLE_RATE: 1.0
      NOTION_INTEGRATION_TYPE: public
      NOTION_CLIENT_SECRET: you-client-secret
      NOTION_CLIENT_ID: you-client-id
      NOTION_INTERNAL_SECRET: you-internal-secret
      CODE_EXECUTION_ENDPOINT: "http://sandbox:8194"
      CODE_EXECUTION_API_KEY: dify-sandbox
      CODE_MAX_NUMBER: 9223372036854775807
      CODE_MIN_NUMBER: -9223372036854775808
      CODE_MAX_STRING_LENGTH: 80000
      TEMPLATE_TRANSFORM_MAX_LENGTH: 80000
      CODE_MAX_STRING_ARRAY_LENGTH: 30
      CODE_MAX_OBJECT_ARRAY_LENGTH: 30
      CODE_MAX_NUMBER_ARRAY_LENGTH: 1000
      SSRF_PROXY_HTTP_URL: 'http://ssrf_proxy:3128'
      SSRF_PROXY_HTTPS_URL: 'http://ssrf_proxy:3128'
      INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH: 1000
    depends_on:
      - db
      - redis
    volumes:
      - ./volumes/app/storage:/app/api/storage
    networks:
      - ssrf_proxy_network
      - default

  worker:
    image: langgenius/dify-api:0.6.10
    restart: always
    environment:
      CONSOLE_WEB_URL: ''
      MODE: worker
      LOG_LEVEL: INFO
      SECRET_KEY: sk-9f73s3ljTXVcMT3Blb3ljTqtsKiGHXVcMT3BlbkFJLK7U
      DB_USERNAME: postgres
      DB_PASSWORD: difyai123456
      DB_HOST: db
      DB_PORT: 5432
      DB_DATABASE: dify
      REDIS_HOST: redis
      REDIS_PORT: 6379
      REDIS_USERNAME: ''
      REDIS_PASSWORD: difyai123456
      REDIS_DB: 0
      REDIS_USE_SSL: 'false'
      CELERY_BROKER_URL: redis://:difyai123456@redis:6379/1
      STORAGE_TYPE: local
      STORAGE_LOCAL_PATH: storage
      S3_ENDPOINT: 'https://xxx.r2.cloudflarestorage.com'
      S3_BUCKET_NAME: 'difyai'
      S3_ACCESS_KEY: 'ak-difyai'
      S3_SECRET_KEY: 'sk-difyai'
      S3_REGION: 'us-east-1'
      AZURE_BLOB_ACCOUNT_NAME: 'difyai'
      AZURE_BLOB_ACCOUNT_KEY: 'difyai'
      AZURE_BLOB_CONTAINER_NAME: 'difyai-container'
      AZURE_BLOB_ACCOUNT_URL: 'https://<your_account_name>.blob.core.windows.net'
      GOOGLE_STORAGE_BUCKET_NAME: 'yout-bucket-name'
      GOOGLE_STORAGE_SERVICE_ACCOUNT_JSON_BASE64: 'your-google-service-account-json-base64-string'
      VECTOR_STORE: weaviate
      WEAVIATE_ENDPOINT: http://weaviate:8080
      WEAVIATE_API_KEY: WVF5YThaHlkYwhGUSmCRgsX3tD5ngdN8pkih
      QDRANT_URL: http://qdrant:6333
      QDRANT_API_KEY: difyai123456
      QDRANT_CLIENT_TIMEOUT: 20
      QDRANT_GRPC_ENABLED: 'false'
      QDRANT_GRPC_PORT: 6334
      MILVUS_HOST: 127.0.0.1
      MILVUS_PORT: 19530
      MILVUS_USER: root
      MILVUS_PASSWORD: Milvus
      MILVUS_SECURE: 'false'
      MAIL_TYPE: ''
      MAIL_DEFAULT_SEND_FROM: 'YOUR EMAIL FROM (eg: no-reply <no-reply@dify.ai>)'
      SMTP_SERVER: ''
      SMTP_PORT: 465
      SMTP_USERNAME: ''
      SMTP_PASSWORD: ''
      SMTP_USE_TLS: 'true'
      SMTP_OPPORTUNISTIC_TLS: 'false'
      RESEND_API_KEY: ''
      RESEND_API_URL: https://api.resend.com
      RELYT_HOST: db
      RELYT_PORT: 5432
      RELYT_USER: postgres
      RELYT_PASSWORD: difyai123456
      RELYT_DATABASE: postgres
      PGVECTOR_HOST: pgvector
      PGVECTOR_PORT: 5432
      PGVECTOR_USER: postgres
      PGVECTOR_PASSWORD: difyai123456
      PGVECTOR_DATABASE: dify
      NOTION_INTEGRATION_TYPE: public
      NOTION_CLIENT_SECRET: you-client-secret
      NOTION_CLIENT_ID: you-client-id
      NOTION_INTERNAL_SECRET: you-internal-secret
      INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH: 1000
    depends_on:
      - db
      - redis
    volumes:
      - ./volumes/app/storage:/app/api/storage
    networks:
      - ssrf_proxy_network
      - default

  web:
    image: langgenius/dify-web:0.6.10
    restart: always
    environment:
      CONSOLE_API_URL: ''
      APP_API_URL: ''
      SENTRY_DSN: ''
    networks: #追加
      - ssrf_proxy_network #追加
      - default #追加


  db:
    image: postgres:15-alpine
    restart: always
    environment:
      PGUSER: postgres
      POSTGRES_PASSWORD: difyai123456
      POSTGRES_DB: dify
      PGDATA: /var/lib/postgresql/data/pgdata
    volumes:
      #- ./volumes/db/data:/var/lib/postgresql/data
      - postgres_data:/var/lib/postgresql/data #変更
    healthcheck:
      test: [ "CMD", "pg_isready" ]
      interval: 1s
      timeout: 3s
      retries: 30
    networks: #追加
      - default #追加

  redis:
    image: redis:6-alpine
    restart: always
    volumes:
      - ./volumes/redis/data:/data
    command: redis-server --requirepass difyai123456
    healthcheck:
      test: [ "CMD", "redis-cli", "ping" ]
    networks: #追加
      - default #追加

  weaviate:
    image: semitechnologies/weaviate:1.19.0
    restart: always
    volumes:
      - ./volumes/weaviate:/var/lib/weaviate
    environment:
      QUERY_DEFAULTS_LIMIT: 25
      AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED: 'false'
      PERSISTENCE_DATA_PATH: '/var/lib/weaviate'
      DEFAULT_VECTORIZER_MODULE: 'none'
      CLUSTER_HOSTNAME: 'node1'
      AUTHENTICATION_APIKEY_ENABLED: 'true'
      AUTHENTICATION_APIKEY_ALLOWED_KEYS: 'WVF5YThaHlkYwhGUSmCRgsX3tD5ngdN8pkih'
      AUTHENTICATION_APIKEY_USERS: 'hello@dify.ai'
      AUTHORIZATION_ADMINLIST_ENABLED: 'true'
      AUTHORIZATION_ADMINLIST_USERS: 'hello@dify.ai'
    networks: #追加
      - default #追加

  sandbox:
    image: langgenius/dify-sandbox:0.2.1
    restart: always
    environment:
      API_KEY: dify-sandbox
      GIN_MODE: 'release'
      WORKER_TIMEOUT: 15
      ENABLE_NETWORK: 'true'
      HTTP_PROXY: 'http://ssrf_proxy:3128'
      HTTPS_PROXY: 'http://ssrf_proxy:3128'
      SANDBOX_PORT: 8194
    volumes:
      - ./volumes/sandbox/dependencies:/dependencies
    networks:
      - ssrf_proxy_network

  ssrf_proxy:
    image: ubuntu/squid:latest
    restart: always
    volumes:
      - ./volumes/ssrf_proxy/squid.conf:/etc/squid/squid.conf
    networks:
      - ssrf_proxy_network
      - default

  nginx:
    image: nginx:latest
    restart: always
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf
      - ./nginx/proxy.conf:/etc/nginx/proxy.conf
      - ./nginx/conf.d:/etc/nginx/conf.d
    depends_on:
      - api
      - web
    ports:
      - "8080:80" #80:80から変更
    networks: #追加
      - default #追加

# ----------------------------------------
# 240609: Ollamaを追加
# ----------------------------------------
  ollama: #追加
    image: ollama/ollama
    restart: always
    container_name: ollama
    ports:
      - "11434:11434"
    volumes:
      - ollama:/root/.ollama

networks:
  ssrf_proxy_network:
    driver: bridge
    internal: true
  default: #追加
    driver: bridge 

volumes: #追加
  postgres_data: #追加
  ollama: #追加
コンテナの関係を可視化

  1. Difyを再起動
PowerShell
# ollamaイメージをダウンロード
docker-compose pull ollama
# difyを再起動。
docker-compose up -d

OllamaにLlama3導入

  1. Docker上のollamaにllama3を導入
    ollamaコンテナに入る
PowerShell
docker exec -it ollama /bin/sh

ollamaコンテナでコマンドを実行

ollama pull llama3

Docker Desktopから簡単にアクセスできる

  1. Chrome拡張機能のollama-uiでLlama3が稼働していることを確認する
確認方法
  • ollama-uiを導入
  • 問題なければllama3モデルが選択できる
  • チャットもできた

DifyとOllama(Llama3)を連携

  1. Diryにアクセス
  2. 画面右上のアカウントをクリック⇒モデルプロバイダーOllamaAdd Model⇒下図のように設定

Difyで動作検証

  1. 既存チャットボットのモデルをOllmaのLlama3に切り替えて動かしてみた(割愛)
  2. やはりCPUしか使っていないのでレスポンスが遅い。。
    タスクマネージャーの画面
shohei6117shohei6117

DifyアプリにAPIアクセスする

ゴール

  • Dify APIエンドポイントにAPIリクエストを送信する

まとめ

  • Chrome拡張機能のTalend API TesterからのPOSTリクエストが成功した

関連リンク

手順

DifyチャットアプリのAPIキーを取得する

  1. Difyでチャットアプリを作る
  2. 概要からAPIキーを取得する
  3. ChromeブラウザにTalend API Tester拡張機能を導入する
  4. Dify公式docなどを参考に、下図のように設定する
参考にしたもの

Dify公式doc

Pythonでのリクエスト方法(公式doc)
import requests
import json

url = 'https://api.dify.ai/v1/chat-messages'
headers = {
    'Authorization': 'Bearer ENTER-YOUR-SECRET-KEY',
    'Content-Type': 'application/json',
}
data = {
    "inputs": {},
    "query": "eh",
    "response_mode": "streaming",
    "conversation_id": "1c7e55fb-1ba2-4e10-81b5-30addcea2276",
    "user": "abc-123"
}

response = requests.post(url, headers=headers, data=json.dumps(data))

print(response.json())

アプリのAPIアクセスページ

curl -X POST 'http://161.93.110.41:8080/v1/chat-messages' \
--header 'Authorization: Bearer {api_key}' \
--header 'Content-Type: application/json' \
--data-raw '{
    "inputs": {},
    "query": "What are the specs of the iPhone 13 Pro Max?",
    "response_mode": "streaming",
    "conversation_id": "",
    "user": "abc-123",
    "files": [
      {
        "type": "image",
        "transfer_method": "remote_url",
        "url": "https://cloud.dify.ai/logo/logo-site.png"
      }
    ]
}'

BODYは次のとおり 
※会話IDは空欄
※テキストチャットなら"files"は不要

{
  "query": "こんにちは",
  "response_mode": "streaming",
  "conversation_id": "",
  "user": "abc-123",
  "inputs": {},
  "files": [
    {
      "type": "image",
      "transfer_method": "remote_url",
      "url": "https://cloud.dify.ai/logo/logo-site.png"
    }
  ]
}

  1. SENDを押す⇒成功!
shohei6117shohei6117

Dify+OllamaでRAGチャットボットを作る#2

ゴール

  • ローカルだけで完結するRAG検索チャットボットを作る

まとめ

  • ollama pullできる多言語対応埋め込みモデルがあったので作れたが、精度は悪く、実用には堪えない。

関連リンク

手順

  1. Ollamaトップページの検索窓でembedding multiみたいなキーワードを入力して検索する
  2. ヒットしたモデルのなかで、多言語対応の埋め込みモデルのparaphrase-multilingual-minilmと日本語LLMのFugaku-llmをollama pullする
PowerShell
ollama pull nextfire/paraphrase-multilingual-minilm
ollama run nebel/fugaku-llm:13b-instruct
  1. ollamaを起動する
  2. Difyに2つのモデルを登録する
Dify登録画面

paraphrase-multilingual-minilmを登録

Fugaku-llmの登録

  1. Difyでまずナレッジを埋め込む
  1. 埋め込みが完了したら、検索テストをしてみる
    頑張ってはいるが精度はいまいち。。
検索テストの結果



  1. RAGアプリを作る
    下図のようにつくった
アプリ作成画面

アプリ全体のアーキテクチャ

Fugaku-llmの詳細設定

  1. デバッグとプレビュー機能を使って検索してみる
検索結果


うーむ、使えたものじゃない。。

shohei6117shohei6117

Difyを0.6.10から0.6.12fix1にアップデートする

ゴール

  • Dify0.6.12fix1にバージョンアップする
  • 0.6.10環境のデータを引き継ぐ

まとめ

  • 0.6.12fix1は起動できたが、0.6.10環境のデータの引継ぎはできず。。
  • これまでのようにdocker-compose.yamlの内容をあれこれ触らなくても起動した。
    • ただしPostgresDBのデータ保管場所だけは従来と同じく変えた(後述)

関連リンク

手順

(背景)

  • 別のPCのDify0.6.10環境にDify0.6.12fixを上書きするインストールしたが、データ継承されなかった。
  • このPCのDify0.6.10環境は絶対壊したくないので、既存のDify0.6.10環境を残して、新たにDify0.6.12fix1環境を作った。以下はその手順。

PowerShellで実行するコマンド

powershell
# difyフォルダをコピー
cp dify dify_latest

# コピー先のdockerディレクトリに移動
cd dify_latest\docker\

# 手元のフォルダのタグを確認
git tag
# ⇒0.6.10までだった

# リモートのタグ情報を取得
git fetch --tags

# 再度タグを確認
git tag
# ⇒0.6.12fix1までになった

# mainブランチに切り替え
git checkout main

# リモートのmainブランチの最新内容を取得(=fetch+merge)
git pull origin main
# ⇒手元のdocker-compose.yamlをコミットしていないのでダメとエラーが出た

# docker-compose.yamlの変更を破棄
git checkout -- docker-compose.yaml
# ⇒docker-compose.yamlが上書きされるので、
#  docker-compose_0.6.10stable.yamlという名前にして、別のフォルダに保存する

# 改めてmainブランチの最新内容を取得
git pull origin main
# →今度はできた

# 手動でdocker-compose.yamlの中身を書き換える(後述)
# .env.sampleファイルを.envという名前に変えて、中身を書き換える(後述)

# コンテナをdify0_6_12という名前で起動
docker-compose -p dify0_6_12 up -d
# ⇒無事起動!
docker-compose.yaml
  db:
    volumes:
      # 変更後
      - postgres_data:/var/lib/postgresql/data #追加
      # 変更前
      # - ./volumes/db/data:/var/lib/postgresql/data

volumes:
  oradata:
  postgres_data: #追加
.env
# ------------------------------
# Common Variables
# ------------------------------
# ホストマシンのIPに変更
CONSOLE_API_URL=http://161.93.***.**:8880 #追加
CONSOLE_WEB_URL=http://161.93.***.**:8880 #追加
SERVICE_API_URL=http://161.93.***.**:8880 #追加
APP_API_URL=http://161.93.***.**:8880 #追加
APP_WEB_URL=http://161.93.***.**:8880 #追加
FILES_URL=http://161.93.***.**:8880 #追加

# ------------------------------
# Docker Compose Service Expose Host Port Configurations
# ------------------------------
EXPOSE_NGINX_PORT=8880 #80から変更
EXPOSE_NGINX_SSL_PORT=8443 #443から変更
shohei6117shohei6117

Firecrawlをセルフホストして、Difyで使う

ゴール

  • Firecrawlをセルフホストして、Difyと連携する
    • Firecrawlはサイトをクロールしてマークダウンに変換するツール
    • FirecrawlをDifyと連携させると、サイトデータをナレッジとして簡単に埋め込めるようになる

まとめ

  • Firecrawlをセルフホストできた
  • Difyと連携して、外部サイトデータ(githubリポジトリ)をナレッジにできた
  • しかし、社内サイト(SharePointOnlineサイト、OneDriveなど)はできなかった

関連リンク

手順

  1. FirecrawlをDockerから起動するまで
powershell
# リポジトリをクローン
git clone https://github.com/mendableai/firecrawl.git

# ディレクトリに移動
cd firecrawl

# 深い階層にある.env.sampleを現在のディレクトリに.envとしてコピー
cp ./apps/api/.env.example .env

# .envの中身を書き換える(後述)

# dockerコンテナを立てる
docker-compose build
#⇒かなり時間かかるが、待てば処理が終わる。

docker-compose up -d
#⇒かなり時間かかるが、無事起動!
  • GitHubリポジトリへのリンク
.env
.env
# ===== Required ENVS ======
NUM_WORKERS_PER_QUEUE=8 
PORT=3002
HOST=0.0.0.0

# 変更前
# REDIS_URL=redis://localhost:6379
# 変更後
REDIS_URL=redis://redis:6379

PLAYWRIGHT_MICROSERVICE_URL=http://playwright-service:3000/html

## To turn on DB authentication, you need to set up supabase.
# trueから変更
USE_DB_AUTHENTICATION=false

# ===== Optional ENVS ======

# Supabase Setup (used to support DB authentication, advanced logging, etc.)
# 変更後
SUPABASE_ANON_TOKEN=dummy_token 
SUPABASE_URL=http://dummy_url
SUPABASE_SERVICE_TOKEN=dummy_service_token


# Other Optionals
# 変更前
# TEST_API_KEY= # use if you've set up authentication and want to test with a real API key
# 変更後
TEST_API_KEY=fc-test

RATE_LIMIT_TEST_API_KEY_SCRAPE= # set if you'd like to test the scraping rate limit
RATE_LIMIT_TEST_API_KEY_CRAWL= # set if you'd like to test the crawling rate limit
SCRAPING_BEE_API_KEY= #Set if you'd like to use scraping Be to handle JS blocking
OPENAI_API_KEY= # add for LLM dependednt features (image alt generation, etc.)
BULL_AUTH_KEY= @
LOGTAIL_KEY= # Use if you're configuring basic logging with logtail
LLAMAPARSE_API_KEY= #Set if you have a llamaparse key you'd like to use to parse pdfs
SERPER_API_KEY= #Set if you have a serper key you'd like to use as a search api
SLACK_WEBHOOK_URL= # set if you'd like to send slack server health status messages
POSTHOG_API_KEY= # set if you'd like to send posthog events like job logs
POSTHOG_HOST= # set if you'd like to send posthog events like job logs

STRIPE_PRICE_ID_STANDARD=
STRIPE_PRICE_ID_SCALE=
STRIPE_PRICE_ID_STARTER=
STRIPE_PRICE_ID_HOBBY=
STRIPE_PRICE_ID_HOBBY_YEARLY=
STRIPE_PRICE_ID_STANDARD_NEW=
STRIPE_PRICE_ID_STANDARD_NEW_YEARLY=
STRIPE_PRICE_ID_GROWTH=
STRIPE_PRICE_ID_GROWTH_YEARLY=

HYPERDX_API_KEY=
HDX_NODE_BETA_MODE=1

FIRE_ENGINE_BETA_URL= # set if you'd like to use the fire engine closed beta

# Proxy Settings for Playwright (Alternative you can can use a proxy service like oxylabs, which rotates IPs for you on every request)
PROXY_SERVER=
PROXY_USERNAME=
PROXY_PASSWORD=
# set if you'd like to block media requests to save proxy bandwidth
BLOCK_MEDIA=

# Set this to the URL of your webhook when using the self-hosted version of FireCrawl
SELF_HOSTED_WEBHOOK_URL=

# Resend API Key for transactional emails
RESEND_API_KEY=
  • ブラウザから起動を確認する
    • http://localhost:3002/ ⇒SCRAPERS-JS: Hello, world! Fly.ioと表示される
    • http://host.docker.internal:3002/ ⇒SCRAPERS-JS: Hello, world! Fly.ioと表示される
    • http://localhost:3002/test ⇒Hello, world!が表示される
    • http://host.docker.internal:3002/test ⇒Hello, world!が表示される
  1. Difyと連携する
  • 設定項目
    • API Key: fc-test
    • Base URL: http://host.docker.internal:3002
画面キャプチャ
  • Difyトップ画面 ⇒ナレッジ ⇒ウェブサイトから同期 ⇒設定:データベース ⇒ FireCrawlのConfigureを選択
  • 以下のように設定
  • 成功!
  1. ウェブサイトをナレッジにしてみる
  • GitHubリポジトリはできた
  • 社内サイト(SharePointOnlineサイト、OneNoteなど)はクロールできず。。
画面キャプチャ
  • Difyリポジトリをナレッジにしてみる


    ※以下割愛
shohei6117shohei6117

Langfuseをセルフホストして、Difyで使う #1

ゴール

  • Langfuseをセルフホストする
  • LangfuseとDifyを連携して、DifyアプリをLangfuseでトレースする

まとめ

  • セルフホストは簡単にできた(http://localhost:3000
  • ただしDifyはhttps通信でないと連携できない!⇒ 次回に続く

関連リンク

手順

  1. Langfuseを起動する
PowerShell
# Langfuseリポジトリをcloneする
git clone https://github.com/langfuse/langfuse.git

# langufuseフォルダに移動
cd langfuse

# docker-compose.yamlをすこし書き換える(後述)

# langfuseを起動
docker-compose up -d
# ⇒特に問題なく起動!
docker-compose.yml
docker-compose.yml
services:
  langfuse-server:
    image: langfuse/langfuse:2
    depends_on:
      db:
        condition: service_healthy
    ports:
      - "3000:3000"
    environment:
      - DATABASE_URL=postgresql://postgres:postgres@db:5432/postgres
      - NEXTAUTH_SECRET=mysecret
      - SALT=mysalt
      # 変更前
      # - NEXTAUTH_URL=http://localhost:3000
      # 変更後(ホストするPCのIPアドレスに書き換える)
      - NEXTAUTH_URL=http://161.93.***.**:3000
      - TELEMETRY_ENABLED=${TELEMETRY_ENABLED:-true}
      - LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES=${LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES:-false}

  db:
    image: postgres
    restart: always
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 3s
      timeout: 3s
      retries: 10
    environment:
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=postgres
      - POSTGRES_DB=postgres
    ports:
      - 5432:5432
    volumes:
      - database_data:/var/lib/postgresql/data

volumes:
  database_data:
    driver: local
  1. Langfuseにサインアップする
画面キャプチャ
  • ブラウザでlocalhost:3000にアクセスすると、サインイン画面になる。
  • まずはサインアップする
  • あらためてサインインすると、初期画面になった。
  1. Difyと連携する
  • セルフホストしているhttpサーバを指定するとエラーになる。。(今回はここまで)
shohei6117shohei6117

API経由でナレッジを操作する

ゴール

  • APIを使ってナレッジを操作できるようになる。

まとめ

  • 簡単にできた。これを使えば、ファイルの更新や追加があるたびに埋め込み処理を実行したり、自作のwebアプリ経由でナレッジを追加したりできる。
  • ただし、ナレッジ削除も簡単にできてしまうので注意が必要。

参考リンク

手順

  • 公式docはcURLコマンドのサンプルがあるので、これをPythonコードに書き換えて実行してみる。
  • いずれも問題なく実行できた。
ナレッジ一覧(ナレッジ名とデータセットID)の取得

データセットIDはdataset_idとして後続コードでも利用します。

import requests
import json

# -------------------------------
# ユーザー設定
api_key = "dataset-**********" # APIキー
endpoint = "http://161.93.***:**:8880/v1/datasets?page=1&limit=500" # エンドポイント
# -------------------------------

headers = {"Authorization": f"Bearer {api_key}"}

# GETリクエストを送信
response = requests.get(endpoint, headers=headers)
response = response.json() # JSON形式に変換

# dataから、ナレッジのnameとidだけを取得して表示
for knowledge in response["data"]:
    print("="*100)
    print(f"ナレッジ名: {knowledge['name']}")
    print(f"データセットID: {knowledge['id']}")
新規ナレッジの作成
import requests
import json

# -------------------------------
# ユーザー設定
api_key = "dataset-**********"
endpoint = "http://161.93.***:**:8880/v1/datasets"
new_knowledge_name = "API-Knowledge 5" # 新しいナレッジの名前
# -------------------------------

headers = {
    "Authorization": f"Bearer {api_key}",
    "Content-Type": "application/json"
}
payload = {"name": new_knowledge_name} 

# POSTリクエストを送信して新しいナレッジを作成
response = requests.post(endpoint, headers=headers, data=json.dumps(payload))
response = response.json()

# dataにstatusコードが含まれていないときだけ実行する
if "status" not in response:
    # dataから、ナレッジのnameとidだけを取得して表示
    print("="*100)
    print(f"Name: {response['name']}")
    print(f"ID: {response['id']}")
else:
    # data全体を見やすく表示
    print(json.dumps(response, indent=4))
既存ナレッジの削除
import requests
import json

# -------------------------------
# ユーザー設定
api_key = "dataset-**********" # APIキー
dataset_id = "******************" # 削除するナレッジのID
# -------------------------------

endpoint = f"http://161.93.***:**:8880/v1/datasets/{dataset_id}"
headers = {"Authorization": f"Bearer {api_key}"}

# DELETEリクエストを送信してナレッジを削除
response = requests.delete(endpoint, headers=headers)

# レスポンスを表示
if response.status_code == 204:
    print("Knowledge deleted successfully.")
else:
    print(f"Failed to delete knowledge. Status code: {response.status_code}")
ファイルからドキュメントを作成
import requests
import json

# -------------------------------
# ユーザー設定
api_key = "dataset-**********" # APIキー
dataset_id = "******************" # ドキュメントを追加するナレッジのID
file_path =  r"ファイルパス" # ドキュメントとして追加するファイルのパス
# -------------------------------


endpoint = f"http://161.93.***:**:8880/v1/datasets/{dataset_id}/document/create_by_file"
headers = {"Authorization": f"Bearer {api_key}"}

# 処理方法
# 自動埋め込みならこれだけ
process_rule = {"mode": "automatic"}
# カスタム埋め込みならこちらを使う
# process_rule = {
#     "mode": "custom"
#     "rules": {
#         "pre_processing_rules": [
#             {"id": "remove_extra_spaces", "enabled": True}, # 余分なスペースを削除
#             {"id": "remove_urls_emails", "enabled": False} # URLとメールアドレスを削除
#         ],
#         "segmentation": {"separator": "###", "max_tokens": 1000} # セグメントの設定
#     }
# }
data = {
    "indexing_technique": "high_quality", # インデックス技術
    "process_rule": process_rule # プロセスルール
}
files = {
    'file': open(file_path, 'rb'),
    'data': (None, json.dumps(data), 'application/json')
}

# POSTリクエストを送信して新しいドキュメントを作成
response = requests.post(endpoint, headers=headers, files=files)
response = response.json()

# 結果を見やすく表示
print(json.dumps(response, indent=4))
テキストからドキュメントを作成
import requests
import json

# -------------------------------
# ユーザー設定
api_key = "dataset-**********"
dataset_id = "******************" # ドキュメントを追加するナレッジのID
document_name = "Sample Document"
document_text = """生成AIの業務利用には以下の点に注意が必要です。
まず、生成AIが提供する情報の正確性や信頼性を常に確認してください。
生成AIは誤った情報を生成する可能性があるため、重要な決定に使用する前に専門家によるレビューが必要です。
次に、データのプライバシーとセキュリティを確保するため、適切なアクセス制御とデータ保護対策を講じてください。
また、生成AIの利用が法令や規制に準拠していることを確認することも重要です。
最後に、生成AIが生成するコンテンツの著作権や倫理的側面についても注意し、適切な利用範囲を守るようにしてください。
これらのポイントを遵守することで、生成AIの業務利用が効果的かつ安全になります。
"""
# -------------------------------

# APIエンドポイントURL
endpoint = f"http://161.93.***:**:8880/v1/datasets/{dataset_id}/document/create_by_text"
headers = {
    "Authorization": f"Bearer {api_key}",
    "Content-Type": "application/json"
}

# POSTデータを作成
payload = {
    "name": document_name,
    "text": document_text,
    "indexing_technique": "high_quality",
    "process_rule": {"mode": "automatic"}
}

# POSTリクエストを送信して新しいドキュメントを作成
response = requests.post(endpoint, headers=headers, data=json.dumps(payload))
response = response.json()

# 結果を見やすく表示
print(json.dumps(response, indent=4))

ナレッジのドキュメント一覧を取得
import requests
import json

# APIキーとdataset_idを設定
api_key = "dataset-ydCFZztP4M**************"
dataset_id = "9d93bf86-0354-4211-af2**********"

headers = {"Authorization": f"Bearer {api_key}"}
url_get_documents = f"http://161.93.+++.+++:8880/v1/datasets/{dataset_id}/documents"

response_get_documents = requests.get(url_get_documents, headers=headers)

if response_get_documents.status_code == 200:
    documents = response_get_documents.json()
    # print("Documents:", documents) # そのまま出力
    # print(json.dumps(documents, indent=4)) # 見やすく表示
    # "id"と"name"だけ出力
    print("Document ID and Name")
    for d in documents["data"]:
        print(d["id"], d["name"])
else:
    print(f"Failed to retrieve documents. Status code: {response_get_documents.status_code}")

ナレッジのドキュメントを削除
import requests

api_key = "dataset-ydCFZztP4MSgwa**********"
dataset_id = "9d93bf86-0354-4211-af20-89**********"
document_id = "****************"  # 削除するドキュメントのID

url_delete_document = f"http://161.93.+++.+++:8880/v1/datasets/{dataset_id}/documents/{document_id}"

headers = {"Authorization": f"Bearer {api_key}"}
response_delete_document = requests.delete(url_delete_document, headers=headers)

if response_delete_document.status_code == 204:
    print("Document deleted successfully.")
else:
    print(f"Failed to delete document. Status code: {response_delete_document.status_code}")

shohei6117shohei6117

Dify0.6.12fix1から0.6.14にアップデートする

ゴール

  • Dify0.6.14にアップデートする
  • Dify0.6.12fix1のデータを引き継ぐ

まとめ

  • スムーズにアップデートできた。データ引き継ぎも問題なし。

関連リンク

手順

PowerShellで実行するコマンド

powershell
# difyのdockerフォルダに移動して
docker-compose down

# ダウンロード済みのタグを確認
git tag
# ⇒0.6.13までだった

# リモートのタグ情報を取得
git fetch --tags

# 再度タグ確認
git tag
# ⇒0.6.14までになった

# mainブランチに切り替え
git checkout main

# リモートのmainブランチの最新内容をpull(=fetch+merge)
# ※git checkput main しているので git pull でもよい
git pull origin main
# ⇒手元のdocker-compose.yamlなどの更新がコミットされていないというエラーが出る
# ⇒docker-compose.yamlと.envファイルだけ「バックアップ」フォルダを作ってコピーしておく

# スタッシュする
git stash

# あらためてpull
# ※これもgit pullでよい
git pull origin main

# docker-compose.yamlと.envファイルを書き替える(後述)

# コンテナを起動
docker-compose up -d
# ※docker-compose -p dify0_6_14 up -dのようにコンテナ名を変えるとデータ継承されない
# ⇒無事起動!データも継承されている。
  • docker-compose.yamlと.envファイルの変更点:
docker-compose.yaml
###############
# 変更1箇所目
###############
  db:
    image: postgres:15-alpine
    restart: always
    environment:
      PGUSER: ${PGUSER:-postgres}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-difyai123456}
      POSTGRES_DB: ${POSTGRES_DB:-dify}
      PGDATA: ${PGDATA:-/var/lib/postgresql/data/pgdata}
    volumes:
      # - ./volumes/db/data:/var/lib/postgresql/data #オリジナル
      - postgres_data:/var/lib/postgresql/data #追加

    healthcheck:
      test: [ "CMD", "pg_isready" ]
      interval: 1s
      timeout: 3s
      retries: 30
    ports: #追加(SQLツールからアクセスできるように)
      - "5432:5432" #追加

###############
# 変更2箇所目
###############
volumes:
  oradata: 
  postgres_data: #追加
.env
# ホストマシンのIPに変更
# ------------------------------
# Common Variables
# ------------------------------
CONSOLE_API_URL=http://161.93.+++.+++:8080
CONSOLE_WEB_URL=http://161.93.+++.+++:8080
SERVICE_API_URL=http://161.93.+++.+++:8080
APP_API_URL=http://161.93.+++.+++:8080
APP_WEB_URL=http://161.93.+++.+++:8080
FILES_URL=http://161.93.+++.+++:8080

# ------------------------------
# Docker Compose Service Expose Host Port Configurations
# ------------------------------
EXPOSE_NGINX_PORT=8080 #80から変更
EXPOSE_NGINX_SSL_PORT=8443 #443から変更
shohei6117shohei6117

DifyコンテナのPostgresデータベースにSQLツールから接続する

ゴール

  • DifyのPostgresデータベースにSQLツールのA5:SQL Mk2から接続する

まとめ

  • 簡単にできた。
  • DBに格納されているデータに簡単にアクセスできるようになった。

参考リンク

Difyが動かしている3つのデータベースについて
  1. Postgresデータベース(dbサービス)

Postgresデータベースには、Difyアプリケーションに関連する多くの永続的なデータが格納されます。
具体的には以下のような情報が含まれます。

  • ユーザーデータ: ユーザーアカウント、プロフィール情報、認証情報など。
  • アプリケーションデータ: アプリケーション設定、構成情報、使用状況ログなど。
  • メッセージデータ: チャットや会話のログ、メッセージの履歴など。
  • APIトークン: 外部サービスとの連携に使用されるAPIトークン。
  • アノテーションデータ: アノテーションに関連する設定や履歴。
  • ワークフローデータ: ワークフローの定義や実行履歴。

  1. Redis(redisサービス)

Redisはインメモリデータベースで、高速アクセスが必要なデータのキャッシュやセッション情報の格納に使用されます。具体的には以下のような情報が含まれます。

  • セッションデータ: ユーザーのセッション情報、ログイン状態など。
  • キャッシュデータ: 頻繁にアクセスされるデータのキャッシュ(例: ユーザー設定やアプリケーション設定)。
  • キュー: ジョブキューやタスクキュー(Celeryなどのバックエンドタスク処理で使用)。

  1. Weaviate(weaviateサービス)

Weaviateはベクトル検索エンジンで、ベクトルデータや意味的検索に関連する情報を格納します。具体的には以下のような情報が含まれます。

  • ベクトルデータ: テキストやその他のデータのベクトル表現。
  • メタデータ: 各ベクトルに関連するメタデータ(例: ドキュメントIDやその他の関連情報)。
  • 検索インデックス: 効率的なベクトル検索をサポートするためのインデックス。

手順

  1. docker-compose.yamlに外部ポート開放設定を追加
docker-compose.yaml
  # The postgres database.
  db:
    image: postgres:15-alpine
    restart: always
    environment:
      PGUSER: ${PGUSER:-postgres}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-difyai123456}
      POSTGRES_DB: ${POSTGRES_DB:-dify}
      PGDATA: ${PGDATA:-/var/lib/postgresql/data/pgdata}
    volumes:
      # 変更後
      # 名前付きボリューム postgres_data をコンテナ内のディレクトリ /var/lib/postgresql/data にマウントします。
      - postgres_data:/var/lib/postgresql/data #追加
      # オリジナルは下記
      # ./volumes/db/data をコンテナ内のディレクトリ /var/lib/postgresql/data にマウントします。
      # - ./volumes/db/data:/var/lib/postgresql/data
    healthcheck:
      test: [ "CMD", "pg_isready" ]
      interval: 1s
      timeout: 3s
      retries: 30
    ports: #追加
      - "5433:5432" #追加 5432でなく5433を使います!
  1. コンテナをdocker-compose up -dなどで起動。
  2. A5:SQL Mk2を起動して、以下のように設定する。


.env設定を変えていないならば、

  • データベース名:dify
  • ユーザーID:postgres
  • パスワード:difyai123456

⇒設定が正しいかは、「テスト接続」ですぐ確認できる。

  1. 接続すると、publicスキーマにあるテーブルに各種データが格納されていることがわかる。
  • accountsテーブル
    • Difyアカウント情報(id、登録名、メールアドレス、登録日など)
  • appsテーブル
    • アプリ情報(アプリid、アプリ名、テナントid、作成日など)
  • datasetsテーブル
    • ナレッジのデータセット情報(id、ファイル名、埋め込み方法など)
  • documentsテーブル
    • ナレッジに格納しているドキュメント情報
  • end_usersテーブル
    • Difyアプリを使ったユーザー情報(利用したアプリid、登録日など)
  • messagesテーブル
    • チャット履歴(id、アプリid、会話id、クエリ、トークン数、ユーザーid(from_end_user_id、from_account_idのいずれか)、作成日など)

shohei6117shohei6117

ベクトルデータベースとの接続トラブル対処記録

ゴール

  • ベクトルデータベース(weaviate)とDify本体(sandbox)が通信できない不具合を解消する。

まとめ

  • middlewareコンテナを停止、再起動したら解消した。
    • ⇒ middlewareの何が悪かったのかは未解明。

手順

  • 前日まで問題なく動いていたが、朝から次の症状が出た
    • ナレッジの埋め込みはできるが、データベースに登録できない
    • RAGアプリでナレッジ検索ブロックでデータベースに接続できない
  • 確認したこと
    • ログ ⇒問題ない
    • ストレージ ⇒十分余裕がある
    • ファイル ⇒壊れていない
    • Dify本体コンテナの停止、再起動 ⇒症状改善しない
    • Dify本体をバージョンアップ(0.6.14⇒0.6.15)⇒症状改善しない
    • Dify-middlewareを停止、再起動 ⇒ 症状改善した
shohei6117shohei6117

Dify0.6.15から0.7.0にアップデートする

ゴール

  • Dify0.7.0にアップデートする

まとめ

  • スムーズにアップデートできた。データ引き継ぎも問題なし。
  • middlewareは変化なしだったのでそのまま稼働中。

関連リンク

手順

powershell
# difyのdockerフォルダに移動して、起動中の環境を落とす
docker-compose down

# いまのdocker-compose.yamlとか.envファイルを別フォルダにコピー(バックアップ)する
# データバックアップはしない

# 最新バージョンをpullするために、現在のフォルダ状態をスタッシュ
git stash

# mainブランチに切り替え
git checkout main

# リモートのタグ情報を取得
git fetch --tags

# タグ確認
git tag
# ⇒0.7.0まであることを確認

# 最新バージョンをpull
git pull origin main

# docker-compose.yamlと.envファイルを書き換える(後述)

# コンテナを起動
docker-compose up -d
  • docker-compose.yamlと.envファイルの変更点:
docker-compose.yaml

### 変更1箇所目 #################################
  # The postgres database.
  db:
    image: postgres:15-alpine
    restart: always
    environment:
      PGUSER: ${PGUSER:-postgres}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-difyai123456}
      POSTGRES_DB: ${POSTGRES_DB:-dify}
      PGDATA: ${PGDATA:-/var/lib/postgresql/data/pgdata}
    command: >
      postgres -c 'max_connections=${POSTGRES_MAX_CONNECTIONS:-100}'
               -c 'shared_buffers=${POSTGRES_SHARED_BUFFERS:-128MB}'
               -c 'work_mem=${POSTGRES_WORK_MEM:-4MB}'
               -c 'maintenance_work_mem=${POSTGRES_MAINTENANCE_WORK_MEM:-64MB}'
               -c 'effective_cache_size=${POSTGRES_EFFECTIVE_CACHE_SIZE:-4096MB}'
    volumes:
      # - ./volumes/db/data:/var/lib/postgresql/data #オリジナル
      - postgres_data:/var/lib/postgresql/data #追加
    healthcheck:
      test: [ "CMD", "pg_isready" ]
      interval: 1s
      timeout: 3s
      retries: 30

### 変更2箇所目 #################################
volumes:
  oradata:
  dify_es01_data:
  postgres_data: #追加

.env
# ホストマシンのIPに変更
# ------------------------------
# Common Variables
# ------------------------------
CONSOLE_API_URL=http://161.93.+++.++:8880 #オリジナルは空欄
CONSOLE_WEB_URL=http://161.93.+++.++:8880 #オリジナルは空欄
SERVICE_API_URL=http://161.93.+++.++:8880 #オリジナルは空欄
APP_API_URL=http://161.93.+++.++:8880 #オリジナルは空欄
APP_WEB_URL=http://161.93.+++.++:8880 #オリジナルは空欄
FILES_URL=http://161.93.+++.++:8880 #オリジナルは空欄

# ------------------------------
# Docker Compose Service Expose Host Port Configurations
# ------------------------------
EXPOSE_NGINX_PORT=8880 #オリジナルは80
EXPOSE_NGINX_SSL_PORT=8443 #オリジナルは443
shohei6117shohei6117

Firecrawlをセルフホストして、Difyで使う #2

ゴール

  • Dify0.7.0にバージョンアップしたら動かなくなったFirecrawlを使ったチャットボットを再び動かす

まとめ

  • 次のブロックに渡すFirecrawlの出力をtextからjsonに変更して復活させた
    • textだと空データが出力されてしまう

関連リンク

手順

  1. CRAWLブロックの次のブロックに渡す変数を textからjsonに変更した。
  2. また次のテンプレートブロックで、最大1万文字でカットオフする。
{{ json[0].data|map(attribute='content')|join(' ')|truncate(10000, true, '') }}
  1. 無事復活した。
shohei6117shohei6117

Difyを0.8.3にアップデートする

ゴール

  • APIキーのロードバランシングもできるようにする 参考記事

まとめ

  • スムーズにアップデートできた。データ引き継ぎも問題なし。
  • ロードバランシング機能も有効化できた。(まだ有効にはしていない)

関連リンク

手順

powershell
# difyのdockerフォルダに移動して、起動中の環境を落とす
docker-compose down

# いまのdocker-compose.yamlとか.envファイルを別フォルダにコピー(バックアップ)する
# データバックアップはしない

# 最新バージョンをpullするために、現在のフォルダ状態をスタッシュ
git stash

# mainブランチに切り替え
git checkout main

# リモートのタグ情報を取得
git fetch --tags

# タグ確認
git tag
# ⇒0.8.3まであることを確認

# 最新バージョンをpull
git pull origin main

# docker-compose.yamlと.envファイルを書き換える(後述)

# コンテナを起動
docker-compose up -d
  • docker-compose.yamlと.envファイルの変更点:
docker-compose.yaml

### 変更1箇所目 #################################
# ロードバランシングを有効化するため

x-shared-env: &shared-api-worker-env
  LOG_LEVEL: ${LOG_LEVEL:-INFO}
  LOG_FILE: ${LOG_FILE:-}
 ## 途中省略 ##
  SSRF_PROXY_HTTP_URL: ${SSRF_PROXY_HTTP_URL:-http://ssrf_proxy:3128}
  SSRF_PROXY_HTTPS_URL: ${SSRF_PROXY_HTTPS_URL:-http://ssrf_proxy:3128}
  MODEL_LB_ENABLED: "true"  # 追加(ロードバランシングを有効化)

### 変更2,3箇所目 #################################
  # The postgres database.
  db:
    image: postgres:15-alpine
    restart: always
    environment:
      PGUSER: ${PGUSER:-postgres}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-difyai123456}
      POSTGRES_DB: ${POSTGRES_DB:-dify}
      PGDATA: ${PGDATA:-/var/lib/postgresql/data/pgdata}
    command: >
      postgres -c 'max_connections=${POSTGRES_MAX_CONNECTIONS:-100}'
               -c 'shared_buffers=${POSTGRES_SHARED_BUFFERS:-128MB}'
               -c 'work_mem=${POSTGRES_WORK_MEM:-4MB}'
               -c 'maintenance_work_mem=${POSTGRES_MAINTENANCE_WORK_MEM:-64MB}'
               -c 'effective_cache_size=${POSTGRES_EFFECTIVE_CACHE_SIZE:-4096MB}'
    volumes:
      # - ./volumes/db/data:/var/lib/postgresql/data #オリジナル
      - postgres_data:/var/lib/postgresql/data #追加(こうしないとエラーになるから)
    healthcheck:
      test: [ "CMD", "pg_isready" ]
      interval: 1s
      timeout: 3s
      retries: 30
    ports: #追加
      - "5432:5432" #追加(SQLツールからの接続用)

### 変更4箇所目 #################################
volumes:
  oradata:
  dify_es01_data:
  postgres_data: #追加

.env
# ホストマシンのIPに変更
# ------------------------------
# Common Variables
# ------------------------------
CONSOLE_API_URL=http://161.93.+++.++:8880 #オリジナルは空欄
CONSOLE_WEB_URL=http://161.93.+++.++:8880 #オリジナルは空欄
SERVICE_API_URL=http://161.93.+++.++:8880 #オリジナルは空欄
APP_API_URL=http://161.93.+++.++:8880 #オリジナルは空欄
APP_WEB_URL=http://161.93.+++.++:8880 #オリジナルは空欄
FILES_URL=http://161.93.+++.++:8880 #オリジナルは空欄

# ------------------------------
# Docker Compose Service Expose Host Port Configurations
# ------------------------------
EXPOSE_NGINX_PORT=8880 #オリジナルは80
EXPOSE_NGINX_SSL_PORT=8443 #オリジナルは443
shohei6117shohei6117

セルフホストしているFirecrawlをv0からv1にアップデートする#1

ゴール

  • ローカルPCでセルフホストしているFirecrawlをv0からv1にアップグレードする
  • Difyを0.8.3にアップデートしたらFirecrawlツールが使えなくなったため、とりあえずやってみる。

まとめ

  • v0のときと症状は変わらず。Difyでナレッジ作成はできるが、ツールが使えない。
  • (11/9追記)再構築したら治った
ナレッジ作成画面(問題なし)

改善前の症状

ツール(4種類)でエラーが出るのは解消せず。

認証はされているようだが、、

⇒認証を再設定しようとするとエラーが出る

関連リンク

手順

  • 最新バージョンをgithubからpull
  • .envファイルを書き換えて、コンテナをbuild&up
powershell
# firecrawlフォルダに移動して、起動中の環境を落とす
docker-compose down

# いまのdocker-compose.yamlと.envファイルを別フォルダにコピー(バックアップ)する
# データバックアップはしない

# 最新バージョンをpullするために、現在のフォルダ状態をスタッシュ
git stash

# mainブランチに切り替え
git checkout main

# リモートのタグ情報を取得
git fetch --tags

# タグ確認
git tag
# ⇒v1まであることを確認

# 最新バージョンをpull
git pull origin main

# .envファイルを書き換える(後述)
# docker-compose.yamlは書き換えるところなし

# buildする
docker-compose build
# ⇒時間かかる。待つ。

# コンテナを起動する
docker-compose up -d

無事起動

.envファイル(変更あり)

.env
.env
# ===== Required ENVS ======
NUM_WORKERS_PER_QUEUE=8
PORT=3002
HOST=0.0.0.0
REDIS_URL=redis://redis:6379 #for self-hosting using docker, use redis://redis:6379. For running locally, use redis://localhost:6379
REDIS_RATE_LIMIT_URL=redis://redis:6379 #for self-hosting using docker, use redis://redis:6379. For running locally, use redis://localhost:6379
PLAYWRIGHT_MICROSERVICE_URL=http://playwright-service:3000/html

## To turn on DB authentication, you need to set up supabase.
# USE_DB_AUTHENTICATION=true #オリジナル
USE_DB_AUTHENTICATION=false #変更後

# ===== Optional ENVS ======

# SearchApi key. Head to https://searchapi.com/ to get your API key
SEARCHAPI_API_KEY=
# SearchApi engine, defaults to google. Available options: google, bing, baidu, google_news, etc. Head to https://searchapi.com/ to explore more engines
SEARCHAPI_ENGINE=

# Supabase Setup (used to support DB authentication, advanced logging, etc.)
SUPABASE_ANON_TOKEN=
SUPABASE_URL=
SUPABASE_SERVICE_TOKEN=

# Other Optionals
# use if you've set up authentication and want to test with a real API key
TEST_API_KEY=fc-test #追加
# set if you'd like to test the scraping rate limit
RATE_LIMIT_TEST_API_KEY_SCRAPE=
# set if you'd like to test the crawling rate limit
RATE_LIMIT_TEST_API_KEY_CRAWL=
# set if you'd like to use scraping Be to handle JS blocking
SCRAPING_BEE_API_KEY=
# add for LLM dependednt features (image alt generation, etc.)
OPENAI_API_KEY=
BULL_AUTH_KEY=@
# set if you have a llamaparse key you'd like to use to parse pdfs
LLAMAPARSE_API_KEY=
# set if you'd like to send slack server health status messages
SLACK_WEBHOOK_URL=
# set if you'd like to send posthog events like job logs
POSTHOG_API_KEY=
# set if you'd like to send posthog events like job logs
POSTHOG_HOST=

STRIPE_PRICE_ID_STANDARD=
STRIPE_PRICE_ID_SCALE=
STRIPE_PRICE_ID_STARTER=
STRIPE_PRICE_ID_HOBBY=
STRIPE_PRICE_ID_HOBBY_YEARLY=
STRIPE_PRICE_ID_STANDARD_NEW=
STRIPE_PRICE_ID_STANDARD_NEW_YEARLY=
STRIPE_PRICE_ID_GROWTH=
STRIPE_PRICE_ID_GROWTH_YEARLY=

# set if you'd like to use the fire engine closed beta
FIRE_ENGINE_BETA_URL=

# Proxy Settings for Playwright (Alternative you can can use a proxy service like oxylabs, which rotates IPs for you on every request)
PROXY_SERVER=
PROXY_USERNAME=
PROXY_PASSWORD=
# set if you'd like to block media requests to save proxy bandwidth
BLOCK_MEDIA=

# Set this to the URL of your webhook when using the self-hosted version of FireCrawl
SELF_HOSTED_WEBHOOK_URL=

# Resend API Key for transactional emails
RESEND_API_KEY=

# LOGGING_LEVEL determines the verbosity of logs that the system will output.
# Available levels are:
# NONE - No logs will be output.
# ERROR - For logging error messages that indicate a failure in a specific operation.
# WARN - For logging potentially harmful situations that are not necessarily errors.
# INFO - For logging informational messages that highlight the progress of the application.
# DEBUG - For logging detailed information on the flow through the system, primarily used for debugging.
# TRACE - For logging more detailed information than the DEBUG level.
# Set LOGGING_LEVEL to one of the above options to control logging output.
LOGGING_LEVEL=INFO

docker-compose.yamlファイル(変更していません)

docker-compose.yaml
docker-compose.yaml
name: firecrawl

x-common-service: &common-service
  build: apps/api
  networks:
    - backend
  extra_hosts:
    - "host.docker.internal:host-gateway"

services:
  playwright-service:
    build: apps/playwright-service
    environment:
      - PORT=3000
      - PROXY_SERVER=${PROXY_SERVER}
      - PROXY_USERNAME=${PROXY_USERNAME}
      - PROXY_PASSWORD=${PROXY_PASSWORD}
      - BLOCK_MEDIA=${BLOCK_MEDIA}
    networks:
      - backend

  api:
    <<: *common-service
    environment:
      REDIS_URL: ${REDIS_URL:-redis://redis:6379}
      REDIS_RATE_LIMIT_URL: ${REDIS_URL:-redis://redis:6379}
      PLAYWRIGHT_MICROSERVICE_URL: ${PLAYWRIGHT_MICROSERVICE_URL:-http://playwright-service:3000}
      USE_DB_AUTHENTICATION: ${USE_DB_AUTHENTICATION}
      PORT: ${PORT:-3002}
      NUM_WORKERS_PER_QUEUE: ${NUM_WORKERS_PER_QUEUE}
      OPENAI_API_KEY: ${OPENAI_API_KEY}
      OPENAI_BASE_URL: ${OPENAI_BASE_URL}
      MODEL_NAME: ${MODEL_NAME:-gpt-4o}
      SLACK_WEBHOOK_URL: ${SLACK_WEBHOOK_URL}
      LLAMAPARSE_API_KEY: ${LLAMAPARSE_API_KEY}
      LOGTAIL_KEY: ${LOGTAIL_KEY}
      BULL_AUTH_KEY: ${BULL_AUTH_KEY}
      TEST_API_KEY: ${TEST_API_KEY}
      POSTHOG_API_KEY: ${POSTHOG_API_KEY}
      POSTHOG_HOST: ${POSTHOG_HOST}
      SUPABASE_ANON_TOKEN: ${SUPABASE_ANON_TOKEN}
      SUPABASE_URL: ${SUPABASE_URL}
      SUPABASE_SERVICE_TOKEN: ${SUPABASE_SERVICE_TOKEN}
      SCRAPING_BEE_API_KEY: ${SCRAPING_BEE_API_KEY}
      HOST: ${HOST:-0.0.0.0}
      SELF_HOSTED_WEBHOOK_URL: ${SELF_HOSTED_WEBHOOK_URL}
      LOGGING_LEVEL: ${LOGGING_LEVEL}
      FLY_PROCESS_GROUP: app
    depends_on:
      - redis
      - playwright-service
    ports:
      - "3002:3002"
    command: [ "pnpm", "run", "start:production" ]

  worker:
    <<: *common-service
    environment:
      REDIS_URL: ${REDIS_URL:-redis://redis:6379}
      REDIS_RATE_LIMIT_URL: ${REDIS_URL:-redis://redis:6379}
      PLAYWRIGHT_MICROSERVICE_URL: ${PLAYWRIGHT_MICROSERVICE_URL:-http://playwright-service:3000}
      USE_DB_AUTHENTICATION: ${USE_DB_AUTHENTICATION}
      PORT: ${PORT:-3002}
      NUM_WORKERS_PER_QUEUE: ${NUM_WORKERS_PER_QUEUE}
      OPENAI_API_KEY: ${OPENAI_API_KEY}
      OPENAI_BASE_URL: ${OPENAI_BASE_URL}
      MODEL_NAME: ${MODEL_NAME:-gpt-4o}
      SLACK_WEBHOOK_URL: ${SLACK_WEBHOOK_URL}
      LLAMAPARSE_API_KEY: ${LLAMAPARSE_API_KEY}
      LOGTAIL_KEY: ${LOGTAIL_KEY}
      BULL_AUTH_KEY: ${BULL_AUTH_KEY}
      TEST_API_KEY: ${TEST_API_KEY}
      POSTHOG_API_KEY: ${POSTHOG_API_KEY}
      POSTHOG_HOST: ${POSTHOG_HOST}
      SUPABASE_ANON_TOKEN: ${SUPABASE_ANON_TOKEN}
      SUPABASE_URL: ${SUPABASE_URL}
      SUPABASE_SERVICE_TOKEN: ${SUPABASE_SERVICE_TOKEN}
      SCRAPING_BEE_API_KEY: ${SCRAPING_BEE_API_KEY}
      HOST: ${HOST:-0.0.0.0}
      SELF_HOSTED_WEBHOOK_URL: ${SELF_HOSTED_WEBHOOK_URL}
      LOGGING_LEVEL: ${LOGGING_LEVEL}
      FLY_PROCESS_GROUP: worker
    depends_on:
      - redis
      - playwright-service
      - api
    command: [ "pnpm", "run", "workers" ]

  redis:
    image: redis:alpine
    networks:
      - backend
    command: redis-server --bind 0.0.0.0

networks:
  backend:
    driver: bridge
shohei6117shohei6117

Dify0.9.0でRAG検索できないエラー発生

ゴール

  • 0.8.3から0.9.0にアップデートしたら知識獲得ツールでエラーが出るようになったのを対処する。

    Run failed: '<' not supported between instances of 'NoneType' and 'NoneType'

まとめ

  • 0.9.0から0.9.1にアップデートしたら治った。

参考リンク

手順

shohei6117shohei6117

Dify0.10.0にアップデートする

ゴール

  • 0.10.0にアップデートする

まとめ

  • スムーズにアップデートできた。データ引き継ぎも問題なし。

参考リンク

手順

  • いつもは単なる最新版へのupdateだが、今回は0.10.0bata2から0.10.0にする
powershell
# firecrawlフォルダに移動して、起動中の環境を落とす
docker-compose down

# いまのdocker-compose.yamlと.envファイルを別フォルダにコピー(バックアップ)する
# データバックアップはしない

# 最新バージョンをpullするために、現在のフォルダ状態をスタッシュ
git stash

# mainブランチに切り替え
git checkout main

# リモートのタグ情報を取得
git fetch --tags

# タグ確認
git tag
# ⇒0.10.0まであることを確認

# 最新バージョンまでpull
git pull origin main

# ===最新版にするだけなら不要(ここから)===
# 0.10.0タグに切り替え
git checkout tags/0.10.0
# ===最新版にするだけなら不要(ここまで)===

# docker-compose.yamlと.envファイルを書き換える(後述)

# コンテナを起動する
docker-compose up -d

⇒無事起動。

  • docker-compose.yamlと.envファイルの変更点:
docker-compose.yaml
### 変更1箇所目 #################################
# ロードバランシングを有効化するため

x-shared-env: &shared-api-worker-env
  LOG_LEVEL: ${LOG_LEVEL:-INFO}
  LOG_FILE: ${LOG_FILE:-}
 ## 途中省略 ##
  SSRF_PROXY_HTTP_URL: ${SSRF_PROXY_HTTP_URL:-http://ssrf_proxy:3128}
  SSRF_PROXY_HTTPS_URL: ${SSRF_PROXY_HTTPS_URL:-http://ssrf_proxy:3128}
  MODEL_LB_ENABLED: "true"  # 追加(ロードバランシングを有効化)

### 変更2,3箇所目 #################################
  # The postgres database.
  db:
    image: postgres:15-alpine
    restart: always
    environment:
      PGUSER: ${PGUSER:-postgres}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-difyai123456}
      POSTGRES_DB: ${POSTGRES_DB:-dify}
      PGDATA: ${PGDATA:-/var/lib/postgresql/data/pgdata}
    command: >
      postgres -c 'max_connections=${POSTGRES_MAX_CONNECTIONS:-100}'
               -c 'shared_buffers=${POSTGRES_SHARED_BUFFERS:-128MB}'
               -c 'work_mem=${POSTGRES_WORK_MEM:-4MB}'
               -c 'maintenance_work_mem=${POSTGRES_MAINTENANCE_WORK_MEM:-64MB}'
               -c 'effective_cache_size=${POSTGRES_EFFECTIVE_CACHE_SIZE:-4096MB}'
    volumes:
      # - ./volumes/db/data:/var/lib/postgresql/data #オリジナル
      - postgres_data:/var/lib/postgresql/data #追加(こうしないとエラーになるから)
    healthcheck:
      test: [ "CMD", "pg_isready" ]
      interval: 1s
      timeout: 3s
      retries: 30
    ports: #追加
      - "5432:5432" #追加(SQLツールからの接続用)

### 変更4箇所目 #################################
volumes:
  oradata:
  dify_es01_data:
  postgres_data: #追加

.env
# ホストマシンのIPに変更
# ------------------------------
# Common Variables
# ------------------------------
CONSOLE_API_URL=http://161.93.+++.++:8880 #オリジナルは空欄
CONSOLE_WEB_URL=http://161.93.+++.++:8880 #オリジナルは空欄
SERVICE_API_URL=http://161.93.+++.++:8880 #オリジナルは空欄
APP_API_URL=http://161.93.+++.++:8880 #オリジナルは空欄
APP_WEB_URL=http://161.93.+++.++:8880 #オリジナルは空欄
FILES_URL=http://161.93.+++.++:8880 #オリジナルは空欄

# ------------------------------
# Docker Compose Service Expose Host Port Configurations
# ------------------------------
EXPOSE_NGINX_PORT=8880 #オリジナルは80
EXPOSE_NGINX_SSL_PORT=8443 #オリジナルは443
shohei6117shohei6117

Dify0.11.0にアップデートする

ゴール

  • 0.11.0にアップデートする

まとめ

  • middlewareを稼働させたままアップデートしたら、ナレッジのドキュメント取り込みができなくなった。(過去にもあった現象)
    • middlewareを停止して、Difyを再起動したら治った。
  • 並列イテレーションが実装されて、アプリが高速化した!

参考リンク

手順

shohei6117shohei6117

チャットアプリAPIをまとめる

ゴール

  • APIアクセスページのサンプルcurlコードをPythonコードにしてまとめる

まとめ

  • curlコードをPythonコードへの変換はChatGPTにしてもらった。

  • できることは以下のとおり

    1. チャットメッセージ送信
    2. ファイルアップロード
    3. 音声認識 (Speech to Text)
    4. 音声合成 (Text to Speech)
    5. 会話履歴取得
    6. 会話削除
    7. 会話の名前変更
    8. 推奨質問取得
    9. アプリ情報取得
    10. アプリメタ情報取得
  • アプリ毎に固定のAPIキー、任意に決められるuser、会話セッション毎に割り振られる(=ユーザーでなくDifyが決める)conversation_idを使えば、セッション中の会話メモリ管理、会話履歴の保存、会話履歴の呼び出しと削除がAPIでできる⇒streamlitなどにDifyチャットボットフローを埋め込んだ高度なアプリが作れる。

参考リンク

手順

  • Difyでチャットボットを作る

  • APIアクセスページで APIサーバURLをコピー

  • APIキーを作成してコピー

  • 以下のサンプルコードを使いこなす!

APIサンプルコード一覧
import requests

# APIキーとベースURL
API_KEY = "++++++++++++" #アプリ毎に違う
BASE_URL = "http://161.93.+++.+++:8880/v1" #Dify環境で固定
HEADERS = {
    "Authorization": f"Bearer {API_KEY}",
    "Content-Type": "application/json"
}

# 1. チャットメッセージ送信
def send_chat_message(query, user_id, conversation_id=None, response_mode="blocking"):
    """
    Parameters:
    - query (str): ユーザー入力
    - user_id (str): ユーザーID
    - conversation_id (str, optional): 会話ID。最初の回答で返ってくる。空にすると新しい会話になる。
    - response_mode (str): "blocking" または "streaming"
    - それ以外の引数はinputsで渡す(割愛)
    """
    url = f"{BASE_URL}/chat-messages"
    payload = {
        "query": query,
        "user": user_id,
        "conversation_id": conversation_id,
        "response_mode": response_mode
    }
    return requests.post(url, json=payload, headers=HEADERS).json()

# 2. ファイルアップロード
def upload_file(file_path, user_id):
    """
    Parameters:
    - file_path (str): ファイルのパス
    - user_id (str): ユーザーID
    """
    url = f"{BASE_URL}/files/upload"
    with open(file_path, 'rb') as file:
        files = {"file": file}
        data = {"user": user_id}
        return requests.post(url, headers={"Authorization": f"Bearer {API_KEY}"}, files=files, data=data).json()

# 3. 音声認識 (Speech to Text)
def speech_to_text(file_path, user_id):
    """
    Parameters:
    - file_path (str): 音声ファイルのパス
    - user_id (str): ユーザーID
    """
    url = f"{BASE_URL}/audio-to-text"
    with open(file_path, 'rb') as audio_file:
        files = {"file": audio_file}
        data = {"user": user_id}
        return requests.post(url, headers={"Authorization": f"Bearer {API_KEY}"}, files=files, data=data).json()

# 4. 音声合成 (Text to Speech)
def text_to_audio(text, user_id, message_id=None):
    """
    Parameters:
    - text (str): 音声化するテキスト
    - user_id (str): ユーザーID
    - message_id (str, optional): Difyが生成したメッセージID(優先使用)
    """
    url = f"{BASE_URL}/text-to-audio"
    payload = {"text": text, "user": user_id, "message_id": message_id}
    response = requests.post(url, json=payload, headers=HEADERS)
    return response.content  # 音声データ

# 5. 会話履歴取得
def get_conversation_history(user_id, conversation_id, limit=20):
    """
    Parameters:
    - user_id (str): ユーザーID
    - conversation_id (str): 会話ID
    - limit (int): 最大取得件数
    """
    url = f"{BASE_URL}/messages"
    params = {"user": user_id, "conversation_id": conversation_id, "limit": limit}
    return requests.get(url, headers=HEADERS, params=params).json()

# 6. メッセージ削除
def delete_conversation(user_id, conversation_id):
    """
    Parameters:
    - user_id (str): ユーザーID
    - conversation_id (str): 削除対象の会話ID
    """
    url = f"{BASE_URL}/conversations/{conversation_id}"
    payload = {"user": user_id}
    return requests.delete(url, json=payload, headers=HEADERS).json()

# 7. 会話の名前変更
def rename_conversation(user_id, conversation_id, new_name):
    """
    Parameters:
    - user_id (str): ユーザーID
    - conversation_id (str): 対象の会話ID
    - new_name (str): 新しい名前
    """
    url = f"{BASE_URL}/conversations/{conversation_id}/name"
    payload = {"user": user_id, "name": new_name}
    return requests.post(url, json=payload, headers=HEADERS).json()

# 8. 推奨質問の取得
def get_suggested_questions(user_id, message_id):
    """
    Parameters:
    - user_id (str): ユーザーID
    - message_id (str): メッセージID
    """
    url = f"{BASE_URL}/messages/{message_id}/suggested"
    params = {"user": user_id}
    return requests.get(url, headers=HEADERS, params=params).json()

# 9. アプリ情報取得
def get_app_info(user_id):
    """
    Parameters:
    - user_id (str): ユーザーID
    """
    url = f"{BASE_URL}/parameters"
    params = {"user": user_id}
    return requests.get(url, headers=HEADERS, params=params).json()

# 10. アプリメタ情報取得
def get_meta_info(user_id):
    """
    Parameters:
    - user_id (str): ユーザーID
    """
    url = f"{BASE_URL}/meta"
    params = {"user": user_id}
    return requests.get(url, headers=HEADERS, params=params).json()

# メイン処理例
if __name__ == "__main__":
    print(send_chat_message("What is AI?", "user123"))
    print(upload_file("path/to/file.png", "user123"))
    print(speech_to_text("path/to/audio.mp3", "user123"))
    print(text_to_audio("Hello, world!", "user123"))
    print(get_conversation_history("user123", "conversation_id_here"))
    print(delete_conversation("user123", "conversation_id_here"))
    print(rename_conversation("user123", "conversation_id_here", "New Name"))
    print(get_suggested_questions("user123", "message_id_here"))
    print(get_app_info("user123"))
    print(get_meta_info("user123"))


  • レスポンスはjsonで返ってくる。
    • たとえばsend_chat_message(blockingモード)ならば回答は answerキーに格納されているので、回答だけ表示したいときは以下のようになる。(詳細割愛)
response = send_chat_message("What is AI?", "user123")
answer = response.get("answer", "No answer found")
print(answer)
shohei6117shohei6117

Firecrawlをアップデートする

ゴール

  • セルフホストしてDockerコンテナで稼働中のFirecrawlをアップデートする
  • 実際には2024年9月のv1リリースからバージョンアップしていないので備忘録

まとめ

  • リポジトリに含まれている SELF_HOST.md を日本語にした。
SELF_HOST.md日本語訳

Firecrawl のセルフホスティング

コントリビューターですか?

Firecrawl 🔥 へようこそ!こちらでは、プロジェクトをローカルにセットアップして自分で実行し、貢献するための手順を説明します。

コントリビュートする場合、プロセスは他のオープンソースリポジトリと似ており、Firecrawl をフォークし、変更を加え、テストを実行し、プルリクエスト(PR)を送信します。

質問がある場合やセットアップのサポートが必要な場合は、こちら のDiscordコミュニティに参加するか、こちら でGitHubにイシューを提出してください!

なぜセルフホスティングをするのか?

Firecrawl をセルフホスティングすることは、データを制御された環境内に留める必要がある厳格なセキュリティポリシーを持つ組織にとって特に有益です。セルフホスティングを検討する主な理由は以下の通りです:

  • セキュリティとコンプライアンスの強化: セルフホスティングにより、すべてのデータ処理が内部および外部の規制に準拠し、機密情報を安全なインフラ内に保持できます。FirecrawlはMendable製品であり、SOC2 Type2認証に依存しているため、データセキュリティ管理において高い業界標準を遵守しています。
  • サービスのカスタマイズ: セルフホスティングにより、Playwrightサービスなどのサービスを特定のニーズや標準のクラウド提供ではサポートされていない特定のユースケースに合わせて調整できます。
  • 学習とコミュニティへの貢献: 自身のインスタンスをセットアップし維持することで、Firecrawlの仕組みをより深く理解でき、プロジェクトへのより有意義な貢献が可能になります。

考慮事項

ただし、以下の制限や追加の責任事項にも注意が必要です:

  1. Fire-engineへのアクセス制限: 現在、セルフホスティングされたFirecrawlのインスタンスはFire-engineへのアクセスがありません。Fire-engineにはIPブロックの管理やロボット検出メカニズムなどの高度な機能が含まれます。基本的なスクレイピングタスクは管理できますが、より複雑なシナリオでは追加の設定が必要となる場合やサポートされない場合があります。
  2. 手動での設定が必要: 基本的なfetchやPlaywrightオプション以外のスクレイピング方法を使用する場合、.env ファイルでこれらを手動で設定する必要があります。これは技術の深い理解を必要とし、セットアップに時間がかかる場合があります。

Firecrawlのセルフホスティングは、スクレイピングおよびデータ処理環境を完全に管理したい方に最適ですが、追加のメンテナンスおよび設定作業が伴います。

手順

  1. まず、依存関係をインストールします

  2. 環境変数を設定します

    ルートディレクトリに .env ファイルを作成し、apps/api/.env.example のテンプレートをコピーします。

    認証やオプションのサブサービス(PDFパース、JSブロッキングサポート、AI機能)は設定しません。

    .env: の内容は以下の通りです:

    # ===== 必須の環境変数 ======
    NUM_WORKERS_PER_QUEUE=8
    PORT=3002
    HOST=0.0.0.0
    REDIS_URL=redis://redis:6379
    REDIS_RATE_LIMIT_URL=redis://redis:6379
    
    ## DB認証を有効にするには、Supabaseを設定する必要があります。
    USE_DB_AUTHENTICATION=false
    
    # ===== オプションの環境変数 ======
    
    # Supabaseの設定(DB認証、詳細なロギングなどをサポート)
    SUPABASE_ANON_TOKEN=
    SUPABASE_URL=
    SUPABASE_SERVICE_TOKEN=
    
    # その他のオプション
    TEST_API_KEY= # 認証を設定し、実際のAPIキーでテストする場合に使用
    SCRAPING_BEE_API_KEY= # フォールバックスクレイパーとして使用する場合に設定
    OPENAI_API_KEY= # LLM依存機能(例:画像のalt生成)用に追加
    BULL_AUTH_KEY= @
    PLAYWRIGHT_MICROSERVICE_URL=  # Playwrightフォールバックを実行する場合に設定
    LLAMAPARSE_API_KEY= # PDFをパースするためにllamaparseキーを使用する場合に設定
    SLACK_WEBHOOK_URL= # サーバーのヘルスステータスメッセージをSlackに送信する場合に設定
    POSTHOG_API_KEY= # ジョブログなどのPosthogイベントを送信する場合に設定
    POSTHOG_HOST= # ジョブログなどのPosthogイベントを送信する場合に設定
    
  3. (オプション)TypeScript Playwrightサービスを使用する場合

    • docker-compose.yml ファイルを更新し、Playwrightサービスを変更します:

      build: apps/playwright-service
      

      から

      build: apps/playwright-service-ts
      

      に変更します。

    • .env ファイルに PLAYWRIGHT_MICROSERVICE_URL を設定します:

      PLAYWRIGHT_MICROSERVICE_URL=http://localhost:3000/scrape
      
    • 必要に応じて .env ファイルにプロキシサーバーを設定することを忘れないでください。

  4. Dockerコンテナをビルドして実行します

    docker compose build
    docker compose up
    

    これにより、http://localhost:3002 でアクセス可能なローカルインスタンスのFirecrawlが実行されます。

    http://localhost:3002/admin/@/queues でBull Queue Manager UIを確認できます。

  5. (オプション)APIをテストする

    クロールエンドポイントをテストしたい場合、以下を実行できます:

    curl -X POST http://localhost:3002/v1/crawl \
        -H 'Content-Type: application/json' \
        -d '{
          "url": "https://mendable.ai"
        }'
    

トラブルシューティング

このセクションでは、セルフホストされたFirecrawlインスタンスのセットアップや実行中に遭遇する一般的な問題への解決策を提供します。

Supabaseクライアントが設定されていない

症状:

[YYYY-MM-DDTHH:MM:SS.SSSz]ERROR - Attempted to access Supabase client when it's not configured.
[YYYY-MM-DDTHH:MM:SS.SSSz]ERROR - Error inserting scrape event: Error: Supabase client is not configured.

説明:
このエラーはSupabaseクライアントのセットアップが完了していないために発生します。スクレイプとクロールは問題なく実行できますが、現在セルフホスティングされたインスタンスではSupabaseの設定はできません。

認証をバイパスしています

症状:

[YYYY-MM-DDTHH:MM:SS.SSSz]WARN - You're bypassing authentication

説明:
このエラーはSupabaseクライアントのセットアップが完了していないために発生します。スクレイプとクロールは問題なく実行できますが、現在セルフホスティングされたインスタンスではSupabaseの設定はできません。

Dockerコンテナが起動しない

症状:
Dockerコンテナが予期せず終了する、または起動に失敗する。

解決策:
以下のコマンドを使用してDockerログを確認し、エラーメッセージを確認します:

docker logs [コンテナ名]
  • .env ファイル内のすべての必要な環境変数が正しく設定されていることを確認してください。
  • docker-compose.yml に定義されたすべてのDockerサービスが正しく設定され、必要なイメージが利用可能であることを確認してください。

Redisとの接続問題

症状:
Redisへの接続に関連するエラー(タイムアウトや「接続拒否」など)が発生する。

解決策:

  • Docker環境でRedisサービスが稼働していることを確認してください。
  • .env ファイル内の REDIS_URL および REDIS_RATE_LIMIT_URL が正しいRedisインスタンスを指していることを確認し、docker-compose.yaml ファイル内でも同じURL(redis://redis:6379)を指していることを確認してください。
  • Redisポートへの接続をブロックするネットワーク設定やファイアウォールルールがないか確認してください。

APIエンドポイントが応答しない

症状:
FirecrawlインスタンスへのAPIリクエストがタイムアウトする、または応答がない。

解決策:

  • Dockerコンテナのステータスを確認し、Firecrawlサービスが実行中であることを確認してください。
  • .env ファイル内の PORT および HOST 設定が正しく、他のサービスが同じポートを使用していないことを確認してください。
  • APIリクエストを行うクライアントからホストがアクセス可能であることを確認するためにネットワーク設定をチェックしてください。

これらの一般的な問題に対処することで、セルフホストされたFirecrawlインスタンスのセットアップと運用をスムーズに行うことができます。

KubernetesクラスターへのFirecrawlインストール(簡易版)

KubernetesクラスターにFirecrawlをインストールする方法については、examples/kubernetes/cluster-install/README.md を参照してください。


参考リンク

  • セルフホストの注意事項まとめ
セルフホストの注意事項まとめ

以下は、Firecrawl をセルフホスティングする際の注意事項の要約です。

セルフホスティング時の注意事項

  1. Fire-engineへのアクセス制限

    • セルフホストされた Firecrawl インスタンスは Fire-engine へのアクセスがありません。
    • IPブロックの管理やロボット検出メカニズムなどの高度な機能が利用できません。
    • 基本的なスクレイピングタスクは可能ですが、複雑なシナリオでは追加の設定が必要になる場合があります。
  2. 手動での設定が必要

    • 基本的な fetchPlaywright オプション以外のスクレイピング方法を使用する場合、.env ファイルで手動設定が必要です。
    • これには技術的な理解が求められ、セットアップに時間がかかる可能性があります。
  3. 追加のメンテナンスと管理

    • セルフホスティングは完全な制御を提供しますが、その分メンテナンスや管理作業が増えます。
    • 定期的なアップデートやセキュリティパッチの適用が必要です。
  4. データのバックアップ

    • バージョンアップや設定変更前に、データベースや重要なデータのバックアップを取ることを推奨します。
  5. 依存関係と環境設定の確認

    • .env ファイル以外にも、docker-compose.yml や他の設定ファイルの依存関係を確認・更新する必要があります。
    • 必要な環境変数が正しく設定されていることを確認してください。
  6. セキュリティの確保

    • データを安全に保つために、適切なセキュリティ対策(ファイアウォール設定、アクセス制御など)を講じてください。
    • 機密情報が漏洩しないよう、環境変数や設定ファイルの管理に注意が必要です。
  7. リソースの管理

    • Docker コンテナやサービスが適切に動作しているか、リソース使用状況を監視してください。
    • 必要に応じて、不要な Docker リソースのクリーンアップを行い、ディスクスペースを確保します。

これらの注意事項を遵守することで、Firecrawl のセルフホスティングを安全かつ効果的に行うことができます。問題が発生した場合は、公式ドキュメントやコミュニティリソースを参照してください。

関連リンク

手順

設定画面
PowerShell
# git cloneしたfirecrawlフォルダでPoserShellを起動してコンテナを停止
docker-compose down

# 現在のフォルダ状態をスタッシュ
git stash

# 最新リポジトリをダウンロード
git pull origin main

# .envサンプルファイルをコピー
cp ./apps/api/.env.example .env

# .envファイルを修正(後述)

# コンテナをビルド(時間かかる)
docker-compose build

# コンテナを起動
docker-compose up -d
  • docker-compose.yamlファイルはそのまま。
  • .envファイルは以下を変更。
.envファイル
# ===== Required ENVS ======
NUM_WORKERS_PER_QUEUE=8
PORT=3002
HOST=0.0.0.0
REDIS_URL=redis://redis:6379 #for self-hosting using docker, use redis://redis:6379. For running locally, use redis://localhost:6379
REDIS_RATE_LIMIT_URL=redis://redis:6379 #for self-hosting using docker, use redis://redis:6379. For running locally, use redis://localhost:6379
PLAYWRIGHT_MICROSERVICE_URL=http://playwright-service:3000/html

## To turn on DB authentication, you need to set up supabase.
USE_DB_AUTHENTICATION=false #変更 オリジナルはtrue

# ===== Optional ENVS ======

# SearchApi key. Head to https://searchapi.com/ to get your API key
SEARCHAPI_API_KEY=
# SearchApi engine, defaults to google. Available options: google, bing, baidu, google_news, etc. Head to https://searchapi.com/ to explore more engines
SEARCHAPI_ENGINE=

# Supabase Setup (used to support DB authentication, advanced logging, etc.)
SUPABASE_ANON_TOKEN=
SUPABASE_URL=
SUPABASE_SERVICE_TOKEN=

# Other Optionals
# use if you've set up authentication and want to test with a real API key
TEST_API_KEY=fc-test #テスト。オリジナルは空欄
# set if you'd like to test the scraping rate limit
RATE_LIMIT_TEST_API_KEY_SCRAPE=
# set if you'd like to test the crawling rate limit
RATE_LIMIT_TEST_API_KEY_CRAWL=
# set if you'd like to use scraping Be to handle JS blocking
SCRAPING_BEE_API_KEY=
# add for LLM dependednt features (image alt generation, etc.)
OPENAI_API_KEY=
BULL_AUTH_KEY=@
# set if you have a llamaparse key you'd like to use to parse pdfs
LLAMAPARSE_API_KEY=
# set if you'd like to send slack server health status messages
SLACK_WEBHOOK_URL=
# set if you'd like to send posthog events like job logs
POSTHOG_API_KEY=
# set if you'd like to send posthog events like job logs
POSTHOG_HOST=

STRIPE_PRICE_ID_STANDARD=
STRIPE_PRICE_ID_SCALE=
STRIPE_PRICE_ID_STARTER=
STRIPE_PRICE_ID_HOBBY=
STRIPE_PRICE_ID_HOBBY_YEARLY=
STRIPE_PRICE_ID_STANDARD_NEW=
STRIPE_PRICE_ID_STANDARD_NEW_YEARLY=
STRIPE_PRICE_ID_GROWTH=
STRIPE_PRICE_ID_GROWTH_YEARLY=

# set if you'd like to use the fire engine closed beta
FIRE_ENGINE_BETA_URL=

# Proxy Settings for Playwright (Alternative you can can use a proxy service like oxylabs, which rotates IPs for you on every request)
PROXY_SERVER=
PROXY_USERNAME=
PROXY_PASSWORD=
# set if you'd like to block media requests to save proxy bandwidth
BLOCK_MEDIA=

# Set this to the URL of your webhook when using the self-hosted version of FireCrawl
SELF_HOSTED_WEBHOOK_URL=

# Resend API Key for transactional emails
RESEND_API_KEY=

# LOGGING_LEVEL determines the verbosity of logs that the system will output.
# Available levels are:
# NONE - No logs will be output.
# ERROR - For logging error messages that indicate a failure in a specific operation.
# WARN - For logging potentially harmful situations that are not necessarily errors.
# INFO - For logging informational messages that highlight the progress of the application.
# DEBUG - For logging detailed information on the flow through the system, primarily used for debugging.
# TRACE - For logging more detailed information than the DEBUG level.
# Set LOGGING_LEVEL to one of the above options to control logging output.
LOGGING_LEVEL=INFO
shohei6117shohei6117

DifyアプリとAPI連携したstreamlitチャットボットを作る

ゴール

  • DifyチャットボットとAPI連携して、メモリ管理や会話履歴をDifyに任せるstreamlitチャットボットを作ってみる。
  • 画像をアップロードして、それとチャットできるようにする。

まとめ

  • メモリ管理、会話履歴管理をDifyがしてくれるのは楽。
  • 会話履歴からチャットを再開すると画像サムネイルが表示されない...
  • 画像とチャットするには、最初に画像をアップロードして画像IDを取得、次に画像IDを引数にして画像を参照しながらチャット、、という流れと分かった。
  • 頑張れば、ChatGPT ProjectsやGoogle NotebookLMのクローンが作れそう。

関連リンク

  • Difyアプリの「APIアクセス」
    ※いつの間にか日本語表記になっていた

手順

  1. Difyでチャットボットを作って公開する。(Difyバージョンは0.14.2)
  2. API接続を有効にして、APIキーを取得する。
  3. streamlitチャットボットのコードを書く。
streamlitチャットボットのコード
main.py
import streamlit as st
import requests
import json
import logging
import time

# -----------------------------------------------------
# もろもろの設定
# -----------------------------------------------------
st.set_page_config(page_title="Dify連携Chatbot", page_icon="🤖", layout="wide")
st.sidebar.title("🤖Dify連携Chatbot")

# ロギングの設定
logging.basicConfig(level=logging.ERROR)

# グローバル変数の設定
DIFY_URL = 'http://161.93.+++.++:8080' #ローカルホストしているDifyのURL
API_KEY = 'app-b5KlVbjKAC0wjgu+++++' #アプリ毎に振り出されるAPIキー
HEADERS = {
    'Authorization': f'Bearer {API_KEY}',
    'Content-Type': 'application/json'
}


def send_message_to_dify(
            user_message, 
            user_id,
            conversation_id="", 
            file_id=""):
    """
    Difyチャットボットのエンドポイントとメッセージを送受信する。
    """

    url = f'{DIFY_URL}/v1/chat-messages'

    if file_id != "":
        payload = {
            "inputs": {},
            "query": user_message,
            "response_mode": "blocking",
            "conversation_id": conversation_id,
            "user": user_id,
            "files": [{
                "type": "image",
                "transfer_method": "local_file",
                "upload_file_id": file_id
            }]
        }
    else:
        payload = {
            "inputs": {},
            "query": user_message,
            "response_mode": "blocking",
            "conversation_id": conversation_id,
            "user": user_id
        }

    try:
        response = requests.post(url, headers=HEADERS, json=payload) 
    except requests.exceptions.RequestException as e:
        logging.error(f"HTTPリクエストエラー: {e}")
        st.write('リクエストエラーが発生しました。')

    return response.json()


def upload_image_to_dify(upload_image, user_id):
    """
    Difyに画像をアップロードする。
    画像のIDが返される。
    """

    url = f'{DIFY_URL}/v1/files/upload'

    headers = {'Authorization': f'Bearer {API_KEY}'}

    files = {"file": (upload_image.name, upload_image.getvalue(), upload_image.type)}
    data = {'user': user_id}

    with st.spinner("画像をアップロード中..."):
        try:
            response = requests.post(url, headers=headers, files=files, data=data)
        except requests.exceptions.RequestException as e:
            st.error(f"リクエストに失敗しました: {e}")
            return None

    if response.status_code in (200, 201):
        upload_data = response.json()
        # ファイルIDを取得して返す
        return upload_data.get("id")


def dify_get_messages(user_id="tico-data-import-python", conversation_id=""):
    """
    ユーザーIDと会話IDから会話履歴を取得する。
    """

    url = f'{DIFY_URL}/v1/messages'
    params = {
        "user": user_id,
        "conversation_id": conversation_id,
        "limit": 20
    }

    response = requests.get(url, headers=HEADERS, params=params)

    if response.status_code == 200:
        return response.json().get('data', [])
    else:
        st.write(f"Error: {response.status_code}, {response.text}")
        return []


def dify_get_conversations(user_id, DIFY_URL=DIFY_URL, HEADERS=HEADERS):
    """
    指定されたユーザーIDに基づいて会話リストを取得する関数。
    """
    
    url = f'{DIFY_URL}/v1/conversations'
    params = {
        'user': user_id,
        'last_id': '',  # default is null
        'limit': 20     # 取得する会話の数
    }

    # GETリクエストを送信
    response = requests.get(url, headers=HEADERS, params=params)

    # JSON形式でレスポンスが返される場合は、以下のように解析
    try:
        json_response = response.json()
        conversation_id_list = result = [{'id': item['id'], 'name': item['name']} for item in json_response['data']]
    except ValueError:
        print("JSON形式ではないレスポンス:", response.text)

    return conversation_id_list


def dify_delete_conversation(user_id, conversation_id):
    """
    指定された会話を削除する。
    """
    
    url = f"{DIFY_URL}/v1/conversations/{conversation_id}"
    data = {"user": user_id}
    
    try:
        # DELETEリクエストを送信
        response = requests.delete(url, headers=HEADERS, data=json.dumps(data))
        
        # # # レスポンスのステータスコードと内容をログに出力
        logging.debug(f"DELETEステータスコード: {response.status_code}")
        logging.debug(f"DELETEレスポンス: {response.json()}")
        
        # ステータスコードに応じた処理
        if response.status_code in [200, 204]:
            st.success("会話を削除します。")
            time.sleep(2)
            st.session_state.conversations = dify_get_conversations(user_id=user_id)
            st.rerun()
            return True
        else:
            st.error("会話の削除に失敗しました。")
            time.sleep(2)
            return False

    except requests.exceptions.RequestException as e:
        logging.error(f"HTTPリクエストエラー: {e}")
        st.error("リクエストエラーが発生しました。")
        time.sleep(2)
        return False


def dify_rename_conversation(user_id, conversation_id, new_name):
    """
    会話名を変更する。
    """

    url = f"{DIFY_URL}/v1/conversations/{conversation_id}/name"
    payload = {
        "name": new_name,
        "user": user_id
        }
    
    try:
        response = requests.post(url, json=payload)
        logging.debug(f"POSTステータスコード: {response.status_code}")
        logging.debug(f"POSTレスポンス: {response.json()}")
        
        if response.json().get("name") == new_name:
            st.success("会話名を変更します。")
            time.sleep(2)
            st.session_state.conversations = dify_get_conversations(user_id=user_id)
            st.rerun()
            return True
        else:
            st.error("会話名の変更に失敗しました。")
            time.sleep(2)
            return False
        
    except requests.exceptions.RequestException as e:
        logging.error(f"HTTPリクエストエラー: {e}")
        st.error("リクエストエラーが発生しました。")
        time.sleep(2)
        return False


def dify_chatbot():
    """
    メイン関数
    """

    # -----------------------------------------------------
    # session_stateの初期化
    # -----------------------------------------------------
    if 'conversation_id' not in st.session_state:
        st.session_state.conversation_id = ""
    if 'messages' not in st.session_state:
        st.session_state.messages = []
    if 'file_id' not in st.session_state:
        st.session_state.file_id = ""

    # -----------------------------------------------------
    # 会話の準備
    # ------------------------------------------------
    # ユーザーIDの入力
    user_id = st.sidebar.text_input("ユーザーID", "")
    if not user_id:
        st.sidebar.info("👆会話履歴管理のため、従業員番号を入力してください。")
        st.stop()

    st.sidebar.write(f"ユーザーID: {user_id}")
    st.sidebar.write(f"会話ID: {st.session_state.conversation_id}")

    # 会話リセットボタンの追加
    if st.sidebar.button("会話をリセット"):
        st.session_state.conversation_id = ""
        st.session_state.messages = []
        st.rerun()

    # -----------------------------------------------------
    # チャットメッセージを表示
    # -----------------------------------------------------
    # for message in st.session_state.messages:
    #     avatar = "😎" if message['role'] == "user" else "🎅"
    #     st.chat_message(message['role'], avatar=avatar).write(message['content'])

    # # -----------------------------------------------------
    # # messagesをメイン画面に表示(チャット形式)
    # # -----------------------------------------------------
    for msg in st.session_state.messages:
        if msg["role"] == "user":
            if "content" in msg:
                with st.chat_message("user", avatar='😀'):
                    st.write(msg["content"])
            elif "image" in msg:
                if msg["image"]:
                    st.image(msg["image"], caption="Uploaded Image", width=400)
                
        elif msg["role"] == "assistant":
            st.chat_message("assistant", avatar='🤖').write(msg["content"])

    

    # -----------------------------------------------------
    # チャットボットのメイン処理
    # -----------------------------------------------------
    # ユーザーからの入力
    user_input = st.chat_input("メッセージを入力してください:")

    if user_input:
        st.session_state.messages.append({"role": "user", "content": user_input})
        st.chat_message("user", avatar="😎").write(user_input)
            
        with st.spinner("Difyと通信中..."):
            response = send_message_to_dify(
                                user_message=user_input, 
                                conversation_id=st.session_state.conversation_id, 
                                user_id=user_id,
                                )
            
            # conversation_idの更新
            st.session_state.conversation_id = response.get('conversation_id', st.session_state.conversation_id)
            answer = response.get('answer', '')
            
            # ボットの回答を表示
            st.session_state.messages.append({"role": "assistant", "content": answer})
            st.chat_message("assistant", avatar="🎅").write(answer)
            

    # -----------------------------------------------------
    # 画像のアップロード
    # -----------------------------------------------------
    with st.sidebar.expander("画像のアップロード", expanded=False):
        upload_image = st.file_uploader("画像をアップロード", type=["png", "jpg", "jpeg"], accept_multiple_files=False)
        
        # Difyに画像を渡して、画像IDを取得
        if upload_image:
            st.image(upload_image, caption=f"{upload_image.name}", width=200)

            if st.button("**画像をアップロード!**"):

                    # ファイルIDを取得
                    st.session_state.file_id = upload_image_to_dify(upload_image, user_id)

                    if st.session_state.file_id == "":
                        st.warning("画像のアップロードに失敗しました。")
                        st.stop()

                    st.write(f'file_id: {st.session_state.file_id}')
                
                    # 画像を説明してもらう
                    if st.session_state.file_id != "":
                        user_input = "何が写っているか説明してください。"

                        response = send_message_to_dify(
                            user_message=user_input, 
                            conversation_id=st.session_state.conversation_id,
                            user_id=user_id, 
                            file_id=st.session_state.file_id
                            )
                        
                        # conversation_idの更新
                        st.session_state.conversation_id = response.get('conversation_id', st.session_state.conversation_id)

                        # 質問、画像、回答をmessages_difyに格納
                        st.session_state.messages.append({"role": "user", "content": user_input})
                        st.session_state.messages.append({"role": "user", "image": upload_image})
                        answer = response.get('answer', '')
                        st.session_state.messages.append({"role": "assistant", "content": answer})
                        st.rerun()
                        
    # -----------------------------------------------------
    # 会話履歴の管理
    # -----------------------------------------------------
    with st.sidebar.expander("過去会話の管理", expanded=False):
        # -----------------------------------------------------
        # ユーザーIDに紐づく会話リストを取得、再取得
        # -----------------------------------------------------
        st.write("1_過去会話リストを読み込む")
        if 'conversations' not in st.session_state:
            st.session_state.conversations = dify_get_conversations(user_id=user_id)
        if st.button("リストの再取得"):
            st.session_state.conversations = dify_get_conversations(user_id=user_id)

        # key: 会話名, value: 会話ID の辞書を作成
        conversations_dict = {item['name']: item['id'] for item in st.session_state.conversations}

        # -----------------------------------------------------
        # 過去会話のロード
        # -----------------------------------------------------
        if not conversations_dict:
            st.stop()

        st.write("2_過去会話を読み込む")
        conversation_name = st.selectbox("過去会話を選択", list(conversations_dict.keys()))

        if st.button("👆これを読込む") and conversations_dict:
            # 現在の会話を削除
            del st.session_state.messages
            # 会話履歴を取得
            raw_messages = dify_get_messages(user_id=user_id, conversation_id=conversations_dict[conversation_name])
            # 会話IDを更新
            st.session_state.conversation_id = conversations_dict[conversation_name]

            # 会話履歴をst.session_state.messagesに格納
            st.session_state.messages = []
            for msg in raw_messages:
                if msg['query']: # userメッセージはこのキーで格納されている
                    st.session_state.messages.append({"role": "user", "content": msg['query']})
                if msg['answer']: # assistantメッセージはこのキーで格納されている
                    st.session_state.messages.append({"role": "assistant", "content": msg['answer']})
            st.rerun()

        # -----------------------------------------------------
        # 会話名の変更
        # -----------------------------------------------------
        st.write("3_会話名を変更")
        new_conversation_name = st.text_input("新しい会話名を入力", value=conversation_name)
        if st.button("👆これに名称変更"):
            dify_rename_conversation(user_id, conversations_dict[conversation_name], new_conversation_name)

        # -----------------------------------------------------
        # 会話の削除
        # -----------------------------------------------------
        st.write("4_選択中の会話を削除")
        if st.button("選択中の会話を削除"):
            dify_delete_conversation(user_id, conversations_dict[conversation_name])
            
if __name__ == "__main__":
    dify_chatbot()
  1. streamlitアプリを起動
    streamlit run main.py

shohei6117shohei6117

ワークフローアプリAPIをまとめる

ゴール

  • APIアクセスページのサンプルcurlコマンドをPythonコードにしてめとめる

まとめ

  • できることは以下のとおり
    1. ワークフローを実行
    2. ワークフロー実行詳細を取得
    3. 生成を停止
    4. ファイルをアップロード
    5. ワークフォローログを取得
    6. アプリ基本情報を取得
    7. アプリのパラメータ情報を取得

参考リンク

手順

  • Difyでワークフローを作る
  • APIアクセスページでAPIサーバURLをコピー
  • APIキーを作成してコピー
  • 以下のサンプルコードを使いこなす!
APIサンプルコード
sample.py
import requests
import json

# グローバル設定
API_KEY = "app-xxxxxxxx"  # アプリ毎に異なるAPIキー
BASE_URL = "http://161.93.+++.+++:++++/v1"  # DifyのベースURL
HEADERS = {
    "Authorization": f"Bearer {API_KEY}",
    "Content-Type": "application/json"
}

# 1. ワークフロー実行
def run_workflow(file_id, user, response_mode="blocking"):
    url = f"{BASE_URL}/workflows/run"
    data = {
        "inputs": {
            "orig_mail": {
                "transfer_method": "local_file",
                "upload_file_id": file_id,
                "type": "document"
            }
        },
        "response_mode": response_mode,
        "user": user
    }

    try:
        print("ワークフローを実行しています...")
        response = requests.post(url, headers=HEADERS, json=data)
        if response.status_code == 200:
            print("ワークフローが正常に実行されました")
            return response.json()
        else:
            print(f"ワークフロー実行が失敗しました。ステータスコード: {response.status_code}")
            return {"status": "error", "message": response.text}
    except Exception as e:
        print(f"エラーが発生しました: {str(e)}")
        return {"status": "error", "message": str(e)}

# 2. ワークフロー詳細取得
def get_workflow_details(workflow_id):
    url = f"{BASE_URL}/workflows/run/{workflow_id}"

    try:
        print("ワークフロー詳細を取得しています...")
        response = requests.get(url, headers=HEADERS)
        if response.status_code == 200:
            print("ワークフロー詳細が取得されました")
            return response.json()
        else:
            print(f"ワークフロー詳細取得が失敗しました。ステータスコード: {response.status_code}")
            return {"status": "error", "message": response.text}
    except Exception as e:
        print(f"エラーが発生しました: {str(e)}")
        return {"status": "error", "message": str(e)}

# 3. 生成を停止
def stop_workflow(task_id, user):
    url = f"{BASE_URL}/workflows/tasks/{task_id}/stop"
    data = {"user": user}

    try:
        print("ワークフローの生成を停止しています...")
        response = requests.post(url, headers=HEADERS, json=data)
        if response.status_code == 200:
            print("ワークフローの生成が停止されました")
            return response.json()
        else:
            print(f"ワークフロー生成停止が失敗しました。ステータスコード: {response.status_code}")
            return {"status": "error", "message": response.text}
    except Exception as e:
        print(f"エラーが発生しました: {str(e)}")
        return {"status": "error", "message": str(e)}

# 4. ファイルアップロード
def upload_file(file_path, user):
    url = f"{BASE_URL}/files/upload"

    try:
        print("ファイルをアップロードしています...")
        with open(file_path, 'rb') as file:
            files = {
                'file': (file_path, file, 'text/plain')
            }
            data = {
                "user": user,
                "type": "TXT"
            }
            response = requests.post(url, headers={"Authorization": f"Bearer {API_KEY}"}, files=files, data=data)
            if response.status_code == 201:
                print("ファイルが正常にアップロードされました")
                return response.json().get("id")
            else:
                print(f"ファイルアップロードが失敗しました。ステータスコード: {response.status_code}")
                return {"status": "error", "message": response.text}
    except Exception as e:
        print(f"エラーが発生しました: {str(e)}")
        return {"status": "error", "message": str(e)}

# 5. ワークフローログ取得
def get_workflow_logs(user, limit=10):
    url = f"{BASE_URL}/workflows/logs"
    params = {
        "user": user,
        "limit": limit
    }

    try:
        print("ワークフローログを取得しています...")
        response = requests.get(url, headers=HEADERS, params=params)
        if response.status_code == 200:
            print("ワークフローログが取得されました")
            return response.json()
        else:
            print(f"ワークフローログ取得が失敗しました。ステータスコード: {response.status_code}")
            return {"status": "error", "message": response.text}
    except Exception as e:
        print(f"エラーが発生しました: {str(e)}")
        return {"status": "error", "message": str(e)}

# 6. アプリ基本情報取得
def get_app_info(user):
    url = f"{BASE_URL}/info"
    params = {"user": user}

    try:
        print("アプリ基本情報を取得しています...")
        response = requests.get(url, headers=HEADERS, params=params)
        if response.status_code == 200:
            print("アプリ基本情報が取得されました")
            return response.json()
        else:
            print(f"アプリ基本情報取得が失敗しました。ステータスコード: {response.status_code}")
            return {"status": "error", "message": response.text}
    except Exception as e:
        print(f"エラーが発生しました: {str(e)}")
        return {"status": "error", "message": str(e)}

# 7. アプリパラメータ情報取得
def get_app_parameters(user):
    url = f"{BASE_URL}/parameters"
    params = {"user": user}

    try:
        print("アプリパラメータ情報を取得しています...")
        response = requests.get(url, headers=HEADERS, params=params)
        if response.status_code == 200:
            print("アプリパラメータ情報が取得されました")
            return response.json()
        else:
            print(f"アプリパラメータ情報取得が失敗しました。ステータスコード: {response.status_code}")
            return {"status": "error", "message": response.text}
    except Exception as e:
        print(f"エラーが発生しました: {str(e)}")
        return {"status": "error", "message": str(e)}


if __name__ == "__main__":
    user = "difyuser"
    file_path = "path/to/file.txt"

    # 1. ファイルアップロード
    print("=== ファイルアップロード ===")
    file_id = upload_file(file_path, user)
    print("アップロード結果:", file_id)

    if file_id:
        # 2. ワークフロー実行
        print("\n=== ワークフロー実行 ===")
        workflow_result = run_workflow(file_id, user)
        print("ワークフロー実行結果:", workflow_result)

        # ワークフローIDとタスクIDの取得(仮にJSONのキー名が `workflow_run_id` と `task_id` とする)
        workflow_id = workflow_result.get("workflow_run_id")
        task_id = workflow_result.get("task_id")

        # 3. ワークフロー詳細取得
        if workflow_id:
            print("\n=== ワークフロー詳細取得 ===")
            workflow_details = get_workflow_details(workflow_id)
            print("ワークフロー詳細:", workflow_details)

        # 4. ワークフローの生成停止
        if task_id:
            print("\n=== ワークフローの生成停止 ===")
            stop_result = stop_workflow(task_id, user)
            print("生成停止結果:", stop_result)

        # 5. ワークフローログ取得
        print("\n=== ワークフローログ取得 ===")
        logs = get_workflow_logs(user, limit=10)
        print("ワークフローログ:", logs)

    # 6. アプリ基本情報取得
    print("\n=== アプリ基本情報取得 ===")
    app_info = get_app_info(user)
    print("アプリ基本情報:", app_info)

    # 7. アプリパラメータ情報取得
    print("\n=== アプリパラメータ情報取得 ===")
    app_parameters = get_app_parameters(user)
    print("アプリパラメータ情報:", app_parameters)

shohei6117shohei6117

Dify v1.4.0をセルフホストする

※ 2025-07-09追記あり

ゴール

  • Windows + wsl2 + docker + DockerDesktop + proxyあり の環境で、dify v1の最新バージョンである dify v1.4.0 をセルフホストする(注意:v0環境からのアップデートではない

まとめ

  • 起動はできるがツールをインストールしようとすると発生するエラー(internal error)の解消にてこずったが、ChatGPT(o3)のお陰で無事動かせるようになった
  • 2025-07-09追記: docker-compose.yamlのapiサービスにports設定を追加。これがないとファイルアップロードが機能しなかった。

参考リンク

手順

  1. 事前準備(ここに少し書いてある)
    • gitインストール
    • wsl2有効化
    • Docker Desktop for Windowsインストール
      • proxyサーバの設定も忘れずに
  2. 最新バージョンを公式リポジトリからダウンロード
PowerShell
git clone https://github.com/langgenius/dify.git
  1. dify>dockerフォルダのdocker-compose.yaml.envファイルを一部変更する(後述)(.envファイルはenv.sampleからファイル名を変更する)
  2. あとは起動!
PowerShell
docker compose up -d
  1. 成功!
docker-compose.yaml変更箇所
docker-compose.yaml

### 変更箇所5(2025-07-09追記) ###
services:
  # API service
  api:
    ports: ##追加 2025-07-08
      - "5001:5001" ##追加 2025-07-08

### 変更箇所1,2 ###
  # The postgres database.
  db:
    volumes:
      ## 変更前
      # - ./volumes/db/data:/var/lib/postgresql/data
      ## 変更後
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: [ 'CMD', 'pg_isready', '-h', 'db', '-U', '${PGUSER:-postgres}', '-d', '${POSTGRES_DB:-dify}' ]
      interval: 1s
      timeout: 3s
      retries: 60
    ## 追加
    ports:
      - "5432:5432"

### 変更箇所3 ###

  # plugin daemon
  plugin_daemon:
    volumes:
      ## 変更前
      # - ./volumes/plugin_daemon:/app/storage
      ## 変更後
      - plugin_daemon_data:/app/storage
    depends_on:
      db:
        condition: service_healthy

### 変更箇所4 ### 
volumes:
  oradata:
  dify_es01_data:
  ## 2行追加
  postgres_data:
  plugin_daemon_data:

.env変更箇所

### 変更箇所1 ###
## オリジナルは空欄、ホストPCの固定IP、ポートは任意
CONSOLE_API_URL=http://161.93.+++.+++:8880
CONSOLE_WEB_URL=http://161.93.+++.+++:8880
SERVICE_API_URL=http://161.93.+++.+++:8880
APP_API_URL=http://161.93.+++.+++:8880
APP_WEB_URL=http://161.93.+++.+++:8880
FILES_URL=http://161.93.+++.+++:5001

### 変更箇所2 ###
# INIT_PASSWORD=
INIT_PASSWORD=passpass

### 変更箇所3 ###
# SERVER_WORKER_AMOUNT=1
SERVER_WORKER_AMOUNT=4

### 変更箇所4 ###
# CELERY_WORKER_AMOUNT=
CELERY_WORKER_AMOUNT=2

### 変更箇所5 ###
# POSTGRES_SHARED_BUFFERS=128MB
POSTGRES_SHARED_BUFFERS=512MB

### 変更箇所6 ###
# EXPOSE_NGINX_PORT=80
# EXPOSE_NGINX_SSL_PORT=443
EXPOSE_NGINX_PORT=8880
EXPOSE_NGINX_SSL_PORT=8443

### 変更箇所7 ###
# EXPOSE_PLUGIN_DEBUGGING_HOST=localhost
EXPOSE_PLUGIN_DEBUGGING_HOST=plugin_daemon
EXPOSE_PLUGIN_DEBUGGING_PORT=5003

### 変更箇所8 ###
# FORCE_VERIFYING_SIGNATURE=true
FORCE_VERIFYING_SIGNATURE=false

### 変更箇所9 ###
# PLUGIN_PYTHON_ENV_INIT_TIMEOUT=120
PLUGIN_PYTHON_ENV_INIT_TIMEOUT=320

### 変更箇所10 ###
# PLUGIN_PYTHON_ENV_INIT_TIMEOUT=120
PLUGIN_PYTHON_ENV_INIT_TIMEOUT=320

### 変更箇所11 ###  
# The maximum number of top-k value for RAG.
TOP_K_MAX_VALUE=50
docker-compose.yamlの変更点と理由まとめ
箇所 変更内容 なぜ必要だったか
db.volumes ./volumes/db/datanamed-volume postgres_data 既存バインドマウントに壊れた WAL やロックが残りコンテナを再作成しても復活してしまうため。匿名ではなく名前付き volume に切替え、docker volume rm で完全にリセットできるようにした。
db.ports 5432:5432 を追加 ホスト OS から直接 psql / BI ツールで接続・デバッグできるようにポートを公開。
plugin_daemon.volumes ./volumes/plugin_daemonnamed-volume plugin_daemon_data plugin ディレクトリと仮想環境が壊れた時に docker volume rm plugin_daemon_data で丸ごと初期化できるようにするため。
volumes: postgres_data / plugin_daemon_data を追加 上記 named-volume 用の定義。
api.environment PLUGIN_REMOTE_INSTALL_HOST/PORT を env 展開に変更 API が plugin-daemon 実ホスト名 を解決できるよう、.env 側の値をそのまま参照する形に統一。
(参考) plugin_daemon.ports "${EXPOSE_PLUGIN_DEBUGGING_PORT}:5003" デバッグ用 gRPC 5003 を外に出すことでローカル開発時に grpcurl 等で疎通確認が可能。
.envの変更点と理由まとめ
箇所 変更内容 なぜ必要だったか
CONSOLE_API_URL など4つ http://<IP>:8880 を設定 Nginx を 8880 で公開したため、フロント/SDK が正しい URL を組み立てられるようにする。
INIT_PASSWORD (コメントアウト行)# INIT_PASSWORD=INIT_PASSWORD=passpass 初回セットアップ時に管理者アカウントを作成する際、コンソールで毎回パスワード入力を求められず自動化できる。CI/CD や再デプロイ時の手間を削減。
FILES_URL http://<IP>:5001 マルチモーダル入力やファイル出力で生成される署名付き URL のホスト名を固定。
SERVER_WORKER_AMOUNT 1 → 4 Gunicorn worker を CPU コア数 x2+1 目安へ増量し、同時 API リクエスト処理性能を向上。
CELERY_WORKER_AMOUNT 空 → 2 非同期タスク(RAG 取り込み・画像処理など)が詰まりにくいように増員。
POSTGRES_SHARED_BUFFERS 128MB → 512MB VM のメモリに余裕があるため PostgreSQL のキャッシュを 4 倍にし、I/O を低減。
EXPOSE_NGINX_PORT/SSL_PORT 8880 / 8443 80/443 を別サービスで使っている環境のためポートをずらした。compose 側の publish とペア。
EXPOSE_PLUGIN_DEBUGGING_HOST localhostplugin_daemon 決定打。API が gRPC でプラグインを検証するとき localhost:5003 に繋ぎに行く → コンテナ内からは通らず “no available node” エラー。Docker DNS 解決可能な plugin_daemon:5003 に修正して解消。
FORCE_VERIFYING_SIGNATURE true → false 社内/ローカルでビルドしたプラグインに署名が無いとインストールできないため、検証をスキップ。
PLUGIN_PYTHON_ENV_INIT_TIMEOUT 120 → 320 Azure SDK など重量級プラグインの pip install が 2 分を超えることがあり、タイムアウトを延長。
(補足) SERVER_WORKER_AMOUNT, CELERY_WORKER_AMOUNT, POSTGRES_* リソース・パフォーマンス調整 いずれも「CPU/RAM 余裕があるなら増やすと快適」系のチューニング。
TOP_K_MAX_VALUE 10⇒50 知識検索ブロックで取得するチャンク数を増やす
設定変更&エラー解消方法まとめ

全体として解決できたこと

  1. プラグイン検証エラー (no available node) の根本原因
    • API → plugin-daemon 間の gRPC 経路を plugin_daemon:5003 に正しく向けたことで解消。
  2. ゴミが残る/再インストールで壊れる問題の解消
    • DB と plugin-daemon のストレージを named-volume 化し、docker compose down -v で完全クリアできるようにした。
  3. 性能改善 & 外部アクセス性向上
    • worker 数・PostgreSQL バッファ・公開ポートなどを実行環境に合わせて最適化。
shohei6117shohei6117

Difyを自己署名証明書でhttps化する

ゴール

  • httpsホストして、音声入力をできるようにする。

まとめ

  • https化はできたが、whisperは言語指定ができず、日本語で話しかけても英語で文字起こししてしまうことが分かった。。(GitHubリポジトリ Isssue)。

参考リンク

手順

1. 自己署名証明書の作成

以下のコマンドを Git Bash で実行します。

openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout server.key -out server.crt
  • 各入力項目は空EnterでOK。
  • Common Name には サーバIP(例:161.93.+++.+++) を入れるとベター。
  • server.keyserver.crt が生成されます。

2. .env ファイルの編集(http → https)

変更前:

CONSOLE_API_URL=http://161.93.+++.+++:8880
CONSOLE_WEB_URL=http://161.93.+++.+++:8880
SERVICE_API_URL=http://161.93.+++.+++:8880
APP_API_URL=http://161.93.+++.+++:8880
APP_WEB_URL=http://161.93.+++.+++:8880
NGINX_HTTPS_ENABLED=false

変更後:

CONSOLE_API_URL=https://161.93.+++.+++:8443
CONSOLE_WEB_URL=https://161.93.+++.+++:8443
SERVICE_API_URL=https://161.93.+++.+++:8443
APP_API_URL=https://161.93.+++.+++:8443
APP_WEB_URL=https://161.93.+++.+++:8443
NGINX_HTTPS_ENABLED=true

補足:

  • 8443 を使う理由は、EXPOSE_NGINX_SSL_PORT=8443 とポートを合わせているため。
  • Entra ID 連携や外部連携も :8443 を指定。
  • HTTP(8880)は EXPOSE_NGINX_PORT=8880 のままでもOK。
  • FILES_URL(ファイルアップロード用)は HTTPのままにしておく。

3. 証明書ファイルの設置

  • ファイル名を変更:

    • server.crtdify.crt
    • server.keydify.key
  • 設置先:

C:\dify-v1\docker\nginx\ssl

または docker-compose.yaml でマウントされているSSLディレクトリに配置。

  • .env に以下を追加しておくと確実:
NGINX_SSL_CERT_FILENAME=dify.crt
NGINX_SSL_CERT_KEY_FILENAME=dify.key

4. Dify コンテナの停止

docker compose -p dify-v1 down

5. Dify コンテナの再起動(HTTPS化適用)

docker compose -p dify-v1 up -d

補足:

  • .env と証明書内容が反映されて自動で再構築。

  • ポート 8443https://161.93.+++.+++:8443 にアクセスできればOK。

  • 初回は「安全ではありません」と警告されますが、証明書例外登録で利用可能。

  • 証明書更新時も同じディレクトリに上書きして再起動すればOK。

  • 社外公開や本番用途 は Let’s Encrypt証明書+自動更新を推奨。

  • 8443 以外のカスタムポートを使う場合は、すべてのURLにポート番号を明記。