DifyのワークフローからPresentonをMCPで呼ぶで、テーマを1行渡すとPPTXが返ってくるところまで通しました。動くには動くのですが、出てくる資料がそれらしいだけで中身が無いのです。
理由ははっきりしています。モデルが知らないことは書けないからです。社内の事情も、過去の経緯も、実際の数字も入っていないので、一般論が並ぶだけになります。
そこで、ナレッジで記事341本をRAG化で作ったナレッジを挟み、構成を組み立てるところまでDify側でやってからPresentonに渡す形に変えました。
結果は良かったのですが、レイアウトが全スライド同じになりました。本記事はその原因と直し方が中心です。Presentonのソースを読まないと絶対に分からない挙動でした。

1. 構成の主導権を渡してくれるパラメータ
generate_presentation の引数を見直していて気づきました。
content(必須) / slides_markdown / instructions / tone / verbosity
web_search / n_slides / language / template / include_table_of_contents
include_title_slide / files / export_as / trigger_webhook
slides_markdown は文字列の配列で、説明は “The markdown for the slides” とだけ書かれています。これが何をするのか、サーバー側を読んで確かめました。
# api/v1/ppt/endpoints/presentation.py
if request.slides_markdown:
using_slides_markdown = True
request.n_slides = len(request.slides_markdown)
# ...
else:
# Setting outlines to slides markdown
presentation_outlines = PresentationOutlineModel(
slides=[SlideOutlineModel(content=slide)
for slide in request.slides_markdown])
これを渡すと、Presenton側のアウトライン生成が丸ごとスキップされます。 渡した配列がそのままアウトラインになり、1要素が1スライドになります。
押さえておく点が3つあります。
n_slidesは無視されます。 配列の長さで上書きされるので、枚数は配列側で決まりますinclude_table_of_contentsも無視されます(request.include_table_of_contents and not using_slides_markdownという条件のため)- 上限は 50枚(
MAX_NUMBER_OF_SLIDES)。超えると400で弾かれます
つまり構成はDify側で100%決められるということです。Presentonはレイアウトへの流し込みに徹します。やりたかったことにそのまま合いました。
2. ワークフローの形
8ノードになりました。前回の5ノードに、ナレッジ検索・ストーリー生成・配列化の3つを足した形です。
| ノード | 種類 | 役割 |
|---|---|---|
| 開始 | start | テーマ・枚数・聞き手を受け取る |
| 社内ナレッジを引く | knowledge-retrieval | 関係する文書を上位8件 |
| ストーリーを組む | llm | 1枚ぶんのMarkdownを枚数分 |
| スライドごとに分ける | code | 区切りで分割して配列にする |
| Presentonで生成 | tool(MCP) | slides_markdown を渡す |
| パスをURLにする | code | path からURLを組み立てる |
| PPTXを取得 | http-request | ファイルとして受け取る |
| 終了 | end | PPTXと、組んだ構成を返す |
終了ノードで構成そのものも返しています。 スライドが気に入らないときに「構成が悪いのか、流し込みが悪いのか」を切り分けられるようにするためです。これは後で効きました。
2.1 配列は type: variable で渡す
DSLでツールノードに配列を渡すとき、書き方が他と違います。
tool_parameters:
slides_markdown:
type: variable # mixed ではない
value:
- split # ノードID
- slides # 変数名
content:
type: mixed # 文字列はこちら
value: "{{#start.theme#}}"
ToolInputType は mixed / variable / constant の3つで、mixed はテンプレート文字列の展開です。配列をそのまま渡したいときは variable にして、値に変数セレクタを書きます。ここを mixed にすると配列が文字列に潰れます。
2.2 リランクは切っておく
ナレッジ検索ノードでつまずいたのがここです。
multiple_retrieval_config:
top_k: 8
reranking_mode: reranking_model
reranking_enable: false # ← 既定は true
reranking_model:
provider: ""
model: ""
MultipleRetrievalConfig の reranking_enable は既定が true です。リランクモデルを設定していない環境では、そのままだとモデル未設定で落ちます。検証環境で先に引っかかる箇所なので、明示的に false にしておくほうが確実です。
3. ストーリー側のプロンプト
ここが記事の本題の前段です。「良いストーリー」を作らせるために、構成の原則を明示しました。
- 1枚1メッセージ。 1枚に主張を2つ以上入れない
- 並び順は「結論 → 背景と課題 → 根拠 → 打ち手 → 効果と次の一歩」。結論を最後に置かない
- 見出しは体言止めではなく言い切りの一文にする。「売上の推移」ではなく「主力商品の売上は3年連続で伸びている」
- 箇条書きは1枚あたり3〜5個、1個は40字以内
- ナレッジに書かれていない数値・固有名詞を作らない。 根拠が無い主張は断定せず、その項目を落とす
最後の1点が重要です。RAGを挟むと、モデルは文脈に引きずられてそれらしい数字を作ります。禁じておかないと、社内資料として使えないものが出てきます。
出力の区切りは ---SLIDE--- にしました。JSONを出させてパースするより壊れません。Markdown本文に現れる ---(水平線)と衝突しない文字列を選ぶのがポイントです。
# Presenton 側の上限は50枚。超えると 400 で弾かれるので手前で切る。
MAX_SLIDES = 50
def main(text: str) -> dict:
parts = (text or "").split("---SLIDE---")
slides = []
for p in parts:
p = p.strip()
# 前置きやコードフェンスが混ざったときの保険
if p.startswith("```"):
p = p.strip("`").strip()
if p:
slides.append(p)
slides = slides[:MAX_SLIDES]
return {"slides": slides, "count": len(slides)}
4. レイアウトが全部同じになった
走らせると、構成は狙いどおりになりました。ナレッジの内容を踏まえた、筋の通った流れです。
ところがスライドの見た目が全部同じでした。見出しと箇条書きが並ぶだけのレイアウトが、枚数分繰り返されます。

4.1 プロンプトが2種類ある
原因はPresentonの中にありました。レイアウトを選ぶのは generate_presentation_structure() ですが、システムプロンプトが2種類あり、slides_markdown の有無で切り替わります。
渡さない場合はこうです。
2. **Visual variety**: Aim for diverse slide layouts across the presentation.
- Don't use same layout for multiple slides unless necessary.
- Adjacent slide layouts should be different unless instructed/necessary otherwise.
渡した場合はこうです。
- If content contains table, then select either table layout or graph layout.
- Don't select layout with image unless content contains image
or the user explicitly requests imagery.
- Don't select table layout if content does not contain table.
- You are allowed to select same layout for multiple slides.
「変化をつけろ」という指示が消え、「同じでよい」に変わります。 構成の主導権と引き換えに、レイアウトの多様性を手放していたわけです。
これはドキュメントのどこにも書かれていません。 slides_markdown の説明は “The markdown for the slides” の一行だけです。
4.2 レイアウトは本文の「形」から選ばれる
もう一段深い理由があります。選択ルールを読むと、判断材料が本文の構造だと分かります。
- 表がある → 表レイアウトかグラフレイアウト
- 数値の表 → グラフレイアウト(さらに「n列なら n-1 個のグラフを載せられるレイアウトを選べ」)
- 文字だけの表 → 表レイアウト
- 画像レイアウト → 本文に画像があるか、利用者が明示的に要求したときだけ
私が書いたストーリー側のプロンプトは、全スライドを「## 見出し + 箇条書き」の同じ形で出させていました。表も画像も無いので、選択肢から表・グラフ・画像レイアウトがすべて外れ、残った素のテキストレイアウトだけが選ばれ続けたというわけです。
自分で選択肢を潰していたことになります。
4.3 直し方は2つある
どちらもDify側で完結します。Presentonには手を入れません。
① 本文の形を意図的に変える。 ストーリー側のプロンプトに、こう足しました。
- 数字の根拠を出す回は、数値だけのMarkdown表で書く → グラフレイアウト
- 比較・対比の回は、文字だけのMarkdown表で書く → 表レイアウト
- 残りは箇条書き
- 4枚以上なら、数値の表と文字の表を1枚以上ずつ入れる
あわせて、プロンプト内の例文も表を含む形に差し替えました。 モデルは指示より例文の形を強く真似るので、実質的にはここが一番効きます。
② instructions で変化を要求する。 レイアウト選択のプロンプトには差し込み口があり、競合時は User Instructions が勝つと明記されています。
Extract visual constraints from User Instructions and Original User Request;
User Instructions win conflicts.
そこで instructions パラメータに直接書きました。
Use a different layout for each slide wherever the content allows.
Adjacent slides must not share the same layout. Slide 1 is the opening slide,
so use a title layout. Use a chart layout for any slide whose content contains
a numeric table... I explicitly request imagery: use image-bearing layouts
for at least one slide.
最後の一文が要ります。画像レイアウトは「明示的に要求したとき」しか選ばれないので、書かない限り永久に選ばれません。
この2つを入れて、レイアウトが散るようになりました。
5. 切り分けができるようにしておく
今回いちばん役に立ったのは、終了ノードで構成そのもの(outline)も返しておいたことでした。
出来上がったPPTXだけを見ていると、「レイアウトが単調だ」までは分かっても、その先に進めません。構成のテキストが手元にあると、
- 表が出力されているのにレイアウトが単調 → 選択側の問題
- そもそも表が出ていない → ストーリー側の問題
と1回の実行で切り分けられます。生成に数分かかるワークフローでは、この差が大きいです。中間の出力を捨てないのは、この種の多段ワークフローでは基本だと思います。
6. まとめ
slides_markdownを渡すと、Presenton側のアウトライン生成は走らない。 配列がそのままアウトラインになり、構成をDify側で完全に決められる- そのとき
n_slidesとinclude_table_of_contentsは無視される。枚数は配列の長さで決まる。上限は50枚 - 代償として、レイアウト選択のプロンプトが別物に切り替わる。 「変化をつけろ」が消え、「同じレイアウトを使い回してよい」になる。ドキュメントには書かれていない
- レイアウトは本文の「形」から選ばれる。 数値だけの表はグラフ、文字だけの表は表。全部を箇条書きにすると素のテキストレイアウトしか残らない
- 画像レイアウトは明示的に要求しないと選ばれない。
instructionsに書く instructionsは User Instructions として差し込まれ、競合時に優先される- DSLで配列を渡すときは
type: variable。mixedだと文字列に潰れる - ナレッジ検索ノードの
reranking_enableは既定がtrue。リランクモデル未設定の環境では明示的に切る - 中間出力を終了ノードに残す。 構成が悪いのか流し込みが悪いのか、1回で切り分けられる
「AIに丸投げ」と「人が全部書く」の間には、構成だけ機械に組ませて、根拠は社内の文書に縛るという現実的な線があります。Presentonの slides_markdown はちょうどその線を引くための道具でした。ただしその代償はドキュメントに書かれていないので、ソースを読むまで気づけません。
本記事の内容は検証環境(Dify 1.16.1 × Presenton 3.2.4)で実際に動かして確かめたものです。LLMはAmazon Bedrockの Claude 4.5 Sonnet(jp. の推論プロファイル)を使いました。
AWSを効率的に学習する方法
私が効率的にAWSを学習するために実施した方法は以下の通りです。
①最初に書籍(ハンズオンができる)を購入、座学でAWSの基礎を学習
②AWS資格試験を取得ための学習
※私の場合は①と②を合わせて2か月でソリューションアーキテクトを取得できました。
①AWS基礎学習
最初に購入した書籍は「Amazon Web Services 基礎からのネットワーク&サーバー構築」です。
Amazon Web Services 基礎からのネットワーク&サーバー構築 改訂4版

この本では、AWSの基本サービスを利用したハンズオンを通じて、AWSの基礎を学習することができます。
また、タイトル通りAWSのネットワークやインフラに関しても網羅しているため、もともとインフラ系の技術者ではない人たちにとっても分かりやすい内容だと思います。
とりあえずAWS上にサーバーを設定して開発を行うための準備までするには最良の一冊です。
Amazon Web Services 基礎からのネットワーク&サーバー構築 改訂4版
②AWS資格取得
AWSの基礎をある程度学習することができたら、次はAWS資格を取得しましょう。まずはAWSソリューションアーキテクトを目指しましょう。

資格勉強のための問題集をひたすら解きながら、AWSの知識を積み重ねて習得していきましょう!
苦行ではありますが、この問題集を3周ほどすればAWS用語や構成に関しても習得できているはずです。
独学だと手が止まってしまう場合は、ササエル のようなインフラエンジニア向けのオンラインスクールを使う手もあります。ネットワークやサーバーの分野に絞ったカリキュラムなので、業務でインフラを触る人が学び直すのにも使えます。


コメント