TECH BLOG

エルカミーの技術ブログです

【Difyで作るシリーズ 10】スライドを自動作成する

はじめに

Dify というサービスを聞いたことがあるけれど、「何ができるかわからない」、「どう使うのかわからない」などの理由で使ったことがない方は多いのではないでしょうか。

そのような方のために、「Dify で何ができるのか?」や Dify を使った活用事例を紹介する記事をシリーズ化していきます。

今回は第10回目で、Gamma APIを使用して、スライドを自動作成するワークフローを作成する方法について紹介します。

💡
Gamma とは

Gammaは、AIを活用してプレゼンテーション資料やドキュメント、Webページなどを自動で生成するクラウドサービスです。従来、資料作成はスライド一枚一枚のデザインやレイアウトを手作業で行う必要がありましたが、Gammaは簡単なテキストでの指示(プロンプト)を与えるだけで、構成の考案から文章の生成、デザインの適用までを自動で行います。

主な特徴

  • 簡単な操作: 「AIについて」といったテーマを入力するだけで、AIが最適な構成のアウトラインを提案し、それに沿った内容のスライドを自動で作成します。
  • 高品質なデザイン: 豊富なテンプレートが用意されており、専門的なデザインスキルがなくても、誰でも洗練された見た目の資料を作成できます。
  • 時間の大幅な短縮: アイデア出しから清書までのプロセスをAIがサポートするため、資料作成にかかる時間を劇的に削減します。
⚠️
今回使用する Gamma API はベータ版であり、以下のような制限があります。
  • レートリミット: 一日あたり最大50回までコンテンツを生成することができます。
  • 破壊的変更のリスク: APIの仕様が大幅に変更され、現在APIを利用しているプログラムが動作しなくなるような「破壊的変更」が、事前の十分な告知なく行われる可能性があります。
🙂
この記事の対象者
  • Dify という言葉を聞いたことがあるが、何ができるのかがわからない方
  • Dify を業務に取り入れたいと検討している方
事前準備
  • Dify へのサインアップ
  • OpenAI APIまたはGemini APIのAPIキーの取得
    • Gemini APIの場合
      1. ここにアクセスし、「APIキーを作成」を選択します。
      2. 「新しいプロジェクトでAPIキーを作成」を押します。
      3. APIキーをコピーします。
    • OpenAI APIの場合
      • サインアップした後、こちらからAPIキーを作成し、保存します。
  • APIキーのセットアップ
環境
  • Dify v1.8.0
  • Gamma Proプランに加入している
概要

AIにどのようなスライドを作ってほしいか指示を出す(プロンプトを入力する)ことで、資料を自動生成します。指示の出し方には、簡単なテーマだけを伝える方法と、各ページの内容を細かく指定する方法の2通りがあります。

1. テーマを伝えるだけのシンプル作成

作りたいプレゼンテーションのテーマを短い文章で入力するだけで、AIがそのテーマに沿った構成や内容のスライド一式を自動で生成します。最も手軽な方法です。

入力例
AIを活用した生産性向上術

2. Markdown記法で各ページを細かく指定

各ページの内容をより具体的にコントロールしたい場合は、Markdownに似た記法で指示を出します。

#」の後ろに各ページのタイトルを、「-」の後ろにそのページに含めたい箇条書きの要素を記述することで、スライドの構成と内容を正確に指定できます。

入力例
# 最後のフロンティア:深海探査
- 海の20%未満しか探索されていない
- 水深1,000メートルを超える領域はいまだに大部分が謎
- 人類は海の最深部よりも宇宙に行った人の方が多い

# 技術的ブレークスルー
- 極限の水圧に耐えられる高度な潜水艇
- 高解像度カメラとサンプリング装置を備えた遠隔操作探査機(ROV- 長期的なマッピング任務を行う自律型無人潜水機
- リアルタイムでデータ伝送を可能にする深海通信ネットワーク

# 生態学的発見
- 太陽光なしで繁栄する独自の生態系
- 化学合成を利用する熱水噴出孔の群集
- 生物発光や耐圧性など驚異的な適応を持つ生物
- 毎年数千もの新種が発見されている
完成イメージ
📌
イメージ
  1. 「topic」スライド作成に必要な情報を入力します。
  2. 右側にスライドのリンクが表示されます。
  3. リンク先のサイトに遷移すると、自動生成されたスライドが表示されます。
image block
image block

今回作成するワークフローの全体図です。

image block

作成手順

ここからは、ワークフロー構築の手順について説明します。

手順
  1. ワークフローの新規作成
  2. ワークフロー作成画面上での作業
  3. ワークフローの動作確認
1. ワークフローの新規作成
  1. 最初の画面の「最初から作成」を選択します。
    image block
  2. 「ワークフロー」を選択し、「アプリのアイコンと名前」を入力してから「作成する」を選択します。
    image block
2. ワークフロー作成画面上での作業
  1. Gamma API を呼び出すための APIキーを「環境変数」に設定します(キーの漏洩防止と安全な管理のため)。
    1. 画面右上にある「ENV」ボタンを押す
    2. +環境変数の追加」ボタンを押す
    3. 環境変数を以下のように設定する
      項目
      タイプ String
      変数名 gamma_api
      Gamma APIキー
      取得する方法はこちら

    以下の画像のようになっていればOKです。

    image block
  2. 「開始」ノードを選択した後、「+」ボタンを押し、下記の表のように4つの項目を追加します。
    項目
    フィールドタイプ 段落
    変数名 user_input
    ラベル名 topic
    最大長 1000
    image block
  3. 入力のタイプを分類するために「IF/ELSE」ノードを作成し、以下の表のように設定します。
    これによって、「#」で始まるテキストを各ページを細かく指定したテキストとして判定します。
    項目 入力
    IF(で始まる) 開始/user_input #

    以下の画像のようになっていればOKです。

    image block

    以降、「IF」の分岐での作業を行います。

  4. 「IF/ELSE」ノードの「+」ボタンを押し、「コード実行」ノードを作成します。
    image block

    入力変数を以下のように設定します。

    ラベル名 パラメータ
    input_text 開始/user_input

    また、「コード」部分は以下のように設定します。Markdown記法で記述されたインプットをGamma APIのプロンプト(inputTextフィールド)の書式に合うように変換しています。

    import json
    
    def convert_markdown(input_text: str) -> str:
        lines = input_text.strip().splitlines()
        sections = []
        buffer = []
    
        for line in lines:
            if line.startswith("#"):
                if buffer:
                    sections.append("\n".join(buffer))
                    buffer = []
                buffer.append(line)
            elif line.strip().startswith("- "):
                buffer.append(line.replace("- ", "* ", 1))
            else:
                buffer.append(line)
    
        if buffer:
            sections.append("\n".join(buffer))
    
        # セクションごとに \n---\n で結合(実体は“本当の改行”)
        return "\n---\n".join(sections)
    
    
    def main(input_text: str):
        # 1) 生の改行を含む文字列
        raw = convert_markdown(input_text)
    
        # 2) HTTP JSON ボディに安全な「エスケープ済み」文字列(\n など)
        escaped_for_json = json.dumps(raw, ensure_ascii=False)[1:-1]
    
        return {
            "result": escaped_for_json  # HTTPリクエストの Body に使う
        }

    以下の画像のようになっていればOKです。(わかりやすいようにノード名を「コンテンツ作成(Long_ver)」としています。)

    image block
  5. 「コンテンツ作成(Long_ver)」ノードの「+」ボタンを押し、「HTTPリクエスト」ノードを作成します。
    Gamma APIを使ってスライドの作成をリクエストします。
    image block

    APIの設定で「認証なし」を選択し、以下のように値を設定します。

    項目
    認証タイプ APIキー
    API認証タイプ カスタム
    ヘッダー X-API-KEY
    APIキー gamma_api(環境変数)
    image block

    また、「HTTPリクエスト」の設定を以下の表のように設定します。

    項目
    メソッド POST
    URL https://public-api.gamma.app/v0.2/generations
    キー(ヘッダー) Content-Type
    値(ヘッダー) application/json
    image block

    また、「ボディ」には「raw」を選択し、以下のコードを記述します。(「input_text」には「コンテンツ作成/result」を入れます。)

    {
      "inputText": "{ここに「コンテンツ作成/result」を入れる}",
      "textMode": "generate",
      "format": "presentation",
      "cardSplit": "inputTextBreaks",
      "textOptions": {
        "amount": "medium",
        "language": "ja"
      },
      "imageOptions": {
        "source": "aiGenerated"
      },
      "cardOptions": {
        "dimensions": "16x9"
      },
      "sharingOptions": {
        "externalAccess": "edit"
      }
    }

    今回は、デフォルトの設定にしていますが、Gamma APIではこのbodyを用いて様々なパラメータが調整できます。

    (参考)Gamma APIで調整できる項目
    パラメータ データ型 必須/任意 デフォルト値 許容値/フォーマット 説明
    inputText String 必須 - 1~750,000文字 コンテンツ生成の基盤となるテキスト。\n---\nでカード分割を制御可能。
    textMode Enum 任意 generate generate, condense, preserve inputTextの処理方法を定義。preserveはテキストをそのまま維持する。
    format Enum 任意 presentation presentation, document, social 生成される成果物の種類(プレゼン、ドキュメント等)を決定
    themeName String 任意 ワークスペースのデフォルト 標準またはカスタムテーマ名 ビジュアルテーマ(色、フォント)を指定
    numCards Integer 任意 10 Proプラン: 1~50, Ultraプラン: 1~75 cardSplitがautoの場合に生成されるカード数
    cardSplit Enum 任意 auto auto, inputTextBreaks コンテンツのカード分割方法inputTextBreaksはinputText内の\n---\nを使用
    additionalInstructions String 任意 - 1~500文字 コンテンツやレイアウトに関する追加の指示
    exportAs Enum 任意 - pdf, pptx 生成物と同時にPDFまたはPPTXファイルも出力
    textOptions.amount Enum 任意 medium brief, medium, detailed, extensive カードあたりのテキスト量に影響
    textOptions.tone String 任意 - 1~500文字 テキストのトーンや雰囲気を定義
    textOptions.audience String 任意 - 1~500文字 想定される読者層を記述
    textOptions.language String 任意 en 言語コード 出力言語を指定
    imageOptions.source Enum 任意 aiGenerated aiGenerated, unsplash, noImages, etc. 画像のソースを指定。商用利用可能なオプションも存在
    imageOptions.model String 任意 - AI画像生成モデル名 sourceがaiGeneratedの場合に使用するモデル
    imageOptions.style String 任意 - 1~500文字 sourceがaiGeneratedの場合の画像スタイル
    cardOptions.dimensions Enum 任意 - fluid, 16x9, 4x3 (プレゼン用) カードのアスペクト比を決定。formatに依存
    sharingOptions.workspaceAccess Enum 任意 ワークスペース設定 noAccess, view, comment, edit, fullAccess ワークスペースメンバーのアクセス権限
    sharingOptions.externalAccess Enum 任意 ワークスペース設定 noAccess, view, comment, edit 外部ユーザーのアクセス権限
    image block

    次に「ELSE」分岐に移動し、作業を行います。

  6. 「IF/ELSE」ノードの「+」ボタンを押し、「LLM」ノードを作成します。
    ユーザのプロンプトをもとにスライドのタイトルを生成します。
    image block

    AIモデルには「gpt-4o-mini」を選択し、「コンテキスト」には「開始/user_input」を選択します。
    「SYSTEM」に入力するプロンプトは以下です。

    ユーザはスライド資料の作成を依頼しています。
    ユーザのインプット({{#context#}})をもとにスライドのタイトルを作成して、スライドのタイトルのみを出力してください。
    
    必ずタイトルのみを出力してください。

    以下の画像のようになっていればOKです。(わかりやすいようにノードの名前を「トピック作成(Short_ver)」にしています。)

    image block
  7. 「トピック作成(Short_ver)」ノードから「HTTPリクエスト」ノードを作成し、「IF」の分岐と同じように設定しますが「raw」の「ボディ」には以下のように記述します。
    {
      "inputText": "ここに「トピック作成/text」を入れる",
      "textMode": "generate",
      "format": "presentation",
      "cardSplit": "auto",
      "textOptions": {
        "amount": "medium",
        "language": "ja"
      },
      "imageOptions": {
        "source": "aiGenerated"
      },
      "cardOptions": {
        "dimensions": "16x9"
      },
      "sharingOptions": {
        "externalAccess": "edit"
      }
    }

    以下の画像のようになっていれば大丈夫です。

    image block
  8. 変数集約器」ノードを作成し、「IF/ELSE」の分岐を合流させます。
    image block

    IF・ELSEそれぞれの分岐にある「HTTPリクエスト/body」を変数代入し、集約させます。

    image block
  9. 「変数集約器」ノードの「+」ボタンを押し、「コード実行」ノードを作成します。
    image block

    「入力変数」にはラベル名を「body」に、パラメータを「変数集約器/output」に設定します。スライド作成の識別番号である「generationId」を取得するために、コード部分の記述は以下のようにします。

    import json
    
    def main(body):
        body = json.loads(body)
    
        return {
            "generationId": body.get('generationId')
        }

    最後に「出力変数」を「generationId」とします。最終的に以下のような画像に設定できていたらOKです。(わかりやすくするためにノード名を「generationId取得」としています。)

    image block
  10. generationId取得」ノードの「+」ボタンを押し、「ループ」ノードを作成します。
    💡
    ループ」ノード

    終了条件が達成できるまで、または反復上限回数まで特定のワークフローを繰り返します。
    スライド作成のHTTPリクエストを送信してから実際にスライドおよびそのリンク(gammaUrl)が作成されるまでには時間がかかります。所要時間はスライドの量に依存するため、定期的にリクエスト(ポーリング処理)するために「ループ」ノードを追加しています。

    image block

    「ループ変数」の「+」ボタンを押し、設定を以下の表のようにします。

    項目
    ラベル名 gammaUrl
    データ型 String
    入力モード Constant
    URL https://gamma.app/

    こちらで設定した「URL」は、ループが終了し正常にスライド作成のリンクが生成された後に、gammaUrlで上書きされます。また、万が一上書きされない場合(ループが最大回数に達するなど)に備えて
    gammaワークスペース」のURLにしています。

    以下の画像のようになっていればOKです。

    image block

    これ以後は、「ループ」ノード内でワークフローを構築していきます。

  11. 「ループ」ノード内にある「+ブロックを追加」ボタンを押し、「HTTPリクエスト」ノードを作成します。
    このノードで、Gamma側でのスライド作成の進捗状況を確認し、実際に作成されたスライドのリンク(gammaUrl)を取得します。
    image block

    APIキーの認証については先ほどと同じように設定し、「メソッド」を「GET」にします。また、リクエストするURLを以下のように設定します。(「generation_Id」の部分では、自身の「コード実行」で取得したIdを入力します。「」は不要です。)

    https://public-api.gamma.app/v0.2/generations/「generation_Id取得/generation_Id(この部分を変数に変更)」
    image block
  12. 「HTTPリクエスト」ノードで取得した情報から、「進捗状況(status)」と「gammaUrl」を抽出します。「HTTPリクエスト」ノードから「コード実行」ノードを作成し、入力変数を以下のように設定します。
    ラベル名 パラメータ
    body httpリクエスト(URL取得)/body

    また、コードの「ボディ」部分には以下のように記述します。

    import json
    
    def main(body):
        body = json.loads(body)
    
        return {
            "status": body.get('status'),
            "gammaUrl": body.get('gammaUrl', "")
        }

    また、出力変数に「gammaUrl」と「status」を設定します。
    以下の画像のようになっていればOKです。(ノード名を「GammaURL取得」に変更)

    image block
  13. スライド作成が完了しているかどうかを確認するために、「GammaURL取得」ノードから「IF/ELSE」ノードを作成し、分岐を以下のように設定します。
    分岐名 項目 条件
    CASE 1 IF「GammaUrl取得/status」(である) completed(完了)
    CASE 2 ELIF「GammaUrl取得/status」(である) pending(作成中)
    image block
  14. 「IF(CASE 1)」の「+」ボタンを押し「変数代入」ノードを作成します。
    image block

    変数代入の設定を以下の表のように設定します。

    代入する変数 操作 パラメータ
    ループ/GammaUrl 上書き GammaURL取得/gammaUrl
    image block

    最後に「ループ完了」ノードを作成してループを終了します。

  15. 「IF/ELSE」ノードに戻り、「ELIF(CASE 2)」の「+」ボタンを押し、「コード実行」ノードを作成します。(ここで「ELSE」の分岐でないことに注意します。)
    image block

    「入力変数」は何も設定せず、「コード」部分を以下のように記述します。(ノード名を「10秒待機」に変更)

    import time
    
    def main():
        time.sleep(10)
        return {
            "result": ""
        }
    image block

    ここで分岐は終了し、メインループに戻ります。これでループ内のワークフローは完成したので、「ループ」ノードから「終了」ノードを作成し、「ループ/gammaUrl」を出力します。これでワークフローは完成です。

3. ワークフローの動作確認

実際にワークフローが動作しているか確認します。「公開する」を選択し、「更新を公開」した後、「アプリを実行」を選択します。

image block

動作確認では、「スライドのページごとの内容」をインプットにした場合を試していますが、Gammaスライドが作成されたサイトのURLが正常に表示されました。

入力例(再掲)
# 最後のフロンティア:深海探査

- 海の20%未満しか探索されていない
- 水深1,000メートルを超える領域はいまだに大部分が謎
- 人類は海の最深部よりも宇宙に行った人の方が多い

# 技術的ブレークスルー

- 極限の水圧に耐えられる高度な潜水艇
- 高解像度カメラとサンプリング装置を備えた遠隔操作探査機(ROV- 長期的なマッピング任務を行う自律型無人潜水機
- リアルタイムでデータ伝送を可能にする深海通信ネットワーク

# 生態学的発見

- 太陽光なしで繁栄する独自の生態系
- 化学合成を利用する熱水噴出孔の群集
- 生物発光や耐圧性など驚異的な適応を持つ生物
- 毎年数千もの新種が発見されている

image block

また出力されたリンクに飛んでみると、正常にスライドが生成されていることが確認できました。

image block

「スライドのテーマ」のみをインプットにした場合も、正常にスライドが生成されていることが確認できました。(テスト実行ではタイムアウトが起きてしまい結果が出力されていませんが、ログを参照すると、正常にスライドが生成されていることが確認できます。)

image block
image block

出力されたリンク先を参照すると正常にスライドが生成されていることが確認できました。

image block

応用

今回はGamma APIに対して基本的なリクエストしか飛ばしていませんが、想定される読者の設定や、スライド出力言語の設定など、より場面にあったスライドを作成することができます。

おわりに

今回は、インプットに合わせてスライドを自動作成するワークフローを作成しました。今後もDifyを利用したアプリの作成手順を紹介していきます。ぜひ引き続きご覧ください。

サービス紹介

Dify の構築や、ワークフローの作成は、見た目以上に複雑で思っていたより大変な部分も多いんです。でも、ご安心ください。弊社のサービスで、そんな面倒な作業も丸投げできちゃいます。

「自分たちで全部やるのは時間もないし無理だな」と感じたとき、ぜひお任せください。本当にやりたいことに集中できるよう、しっかりサポートいたします。お気軽にご相談ください!