Markdownコンバーターの出力変数には、対応する3つのキー、text、files、jsonがあります。そのうちfilesは配列で、ファイルに関する基本情報とURLが含まれています。
次のノードでfilesをコンテキストとして使用すると、次のようなエラーが発生します。
Run failed: Invalid context structure: dify_model_identity=‘dify__file’ id=None tenant_id=‘ccd7d781-3643-4d21-a0e9-72ad023’ type=\u003cFileType.DOCUMENT: ‘document’\u003e transfer_method=\u003cFileTransferMethod.TOOL_FILE: ‘tool_file’\u003e remote_url=None related_id=‘9eb8576-436c-b83e-529cba96c66c’ filename=‘20260121_170237.xlsx’ extension=‘.xlsx’ mime_type=‘application/vnd.openxmlformats-officedocument.spreadsheetml.sheet’ size=9845
なぜ、私が欲しいURLを抽出できないだけでなく、Difyがエラーを出すのでしょうか?どなたかご指導ください!
スクリーンショットを見ると、xlsxファイルの内容を抽出してLLMに分析させたいようですね?「コンテキスト」を使ってfilesを参照しないでください。正しい方法は以下を参照してください: Document Extractor - Dify Docs
@Dify_Forum_Helper さん、基本的な知識とストーリーポイントを交えながら解説してください。
また、MarketplaceにはExcelに関する興味深いPluginsがいくつかあります。今後の探索に活用できますよ
@Dify_Forum_Helper さんも検索してみてください。
ここには確かに2つの混同しやすい点があります:「ファイルオブジェクト」と「LLMが使用できるテキストコンテンツ」。
1. なぜ files を「コンテキスト」として使用するとエラーになるのですか?
Markdownコンバーターの出力は、おおよそ以下の構造になります:
{
"text": "...",
"files": [
{
"dify_model_identity": "__dify__file__",
"type": "document",
"filename": "20260121_170237.xlsx",
"extension": ".xlsx",
"mime_type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"size": 9845,
"url": "https://...署名付きリンク..."
}
],
"json": [...]
}
files 内の各項目は、本質的にDify内部のファイルオブジェクト/ハンドルであり、__dify__file__、FileType.DOCUMENTなどの情報を含んでいます。
- LLMノードの「コンテキスト」は、このような「ファイルオブジェクト」ではなく、文字列テキストまたは特定の構造化テキストを期待しています。
したがって、次のLLMノードでfiles配列全体を直接「コンテキスト」にバインドすると、LLMノードは「ファイルオブジェクト配列」を受け取ることになり、その構造は期待されるものと一致しないため、次のようなエラーが表示されます:
Run failed: Invalid context structure: dify_model_identity='__dify__file__' ... type=<FileType.DOCUMENT: 'document'> ...
urlが取得できないということではなく、このオブジェクト全体がLLMのコンテキストとして渡されるべきではないということです。
例えるなら、LLMに渡しているのは「表の内容」ではなく、「Excelファイルハンドル+メタデータ」であり、モデルは当然「理解できません」。
2. LLMにxlsxの内容を分析させたい場合、正しい方法は何ですか?
スクリーンショット/説明から判断すると、あなたの目標は次のとおりです:
LLMにxlsx内の表の内容を読み込ませ、分析させる。
この場合、「コンテキスト」でfilesを直接参照するのではなく、次のようにします:
-
「ドキュメント抽出 / Document Extractorノード」を使用してファイルコンテンツを解析する
- 入力:アップストリームノードが出力するファイル変数(配列でも可)—例えば、Markdownコンバーターの
files、またはStartノードでアップロードされたファイル。
- 出力:プレーンテキスト(例:ExcelをMarkdownテーブルテキストに変換したもの)。
-
LLMノードで、ドキュメント抽出ノードが出力したテキストを、プロンプト/コンテキストとして使用する
例えば、システムプロンプトやユーザープロンプトに次のように記述します:
以下はユーザーがアップロードしたExcelの内容です(Markdownテーブルに変換済み):
{{ doc_extractor.text }}
上記のテーブルに基づいて、ユーザーの質問に答えてください……
これにより、LLMが受け取るのはプレーンテキストのテーブルとなり、Invalid context structureが再度トリガーされることはありません。
3. ファイルのURLだけを取得したい場合はどうすればよいですか?
もしfiles[0].urlのようなフィールドを取得したいだけで、LLMにこのオブジェクトを直接「食べさせる」のではない場合は、次のようにできます:
-
コードノード(Python / JS)で解析する:
file_url = inputs["markdown_node"]["files"][0]["url"]
-
その後、このfile_urlを後続のノードで使用するために出力します(例えば、自身のサービスを呼び出すなど)。
しかし、これもLLMの「コンテキスト」として直接使用するのには適していません。なぜなら、URLが指しているのはバイナリのExcelファイルであり、LLM自体はそれを読み取ることができないため、中間の「解析ノード」がテキストに変換するのを助ける必要があります。
4. Excelの拡張機能について
「ドキュメント抽出ノード」のような一般的な方法の他に、DifyのMarketplaceにはExcel向けのプラグインもいくつかあります(例えば、より柔軟な読み込み、書き込み、シート名/セル範囲によるクエリなど)。
今後、より複雑なExcel処理を行いたい場合は、以下を検討してください:
- Excel関連プラグインを使用して構造化処理を完了する
- または、プラグイン → ドキュメント抽出 → LLM のように、複数ステップを組み合わせて使用する
小結
filesはファイルオブジェクトの配列であり、LLMのコンテキストとして直接使用することはできないため、Invalid context structureというエラーが発生します。
- LLMにxlsxの内容を読ませたい場合:
Markdown / Startノード(ファイルを出力) → ドキュメント抽出ノード → テキストを出力 → LLMノードのコンテキスト / プロンプトとして使用。
- 単純にURLを取得したい場合:コードノードで
files[i].urlから取り出すだけでよく、files全体を直接LLMに渡さないでください。
もしよろしければ、現在のワークフローの簡単な構造(各ノードのタイプ+主要な接続)を貼り付けていただければ、「ExcelのアップロードからLLM分析まで」の具体的な接続と変数参照の例を作成できます。
ご回答ありがとうございます!
私の要件は、Markdownコンバーターが返すファイルURLを抽出し、完全なファイルダウンロードURL(絶対パス)を組み立てるためだけです。
現在、コードブロックを使用してfiles[i].urlを取得できたので、問題は解決しました。
別の問題が発生しました。上位のLLMノードで、次のMarkdownコンバーターノードがExcelを生成できるように、LLMに標準のMarkdownテーブルを返してもらうよう要求しました。
LLMが返すデータ形式を確認したところ、問題はないと考えています。例えば、下図のテキストはMarkdownの構文構造に準拠していますが、Markdownコンバーターは##をタイトルとする構文に従ってExcel内の対応するシートを生成しません。どのように対処すればよいでしょうか?

@Dify_Forum_Helper
結論から言うと、現在のあなたの使い方(LLMに##タイトル付きのMarkdownを出力させ、Markdownコンバーターが##で自動的にシートを分割することを期待する)はDifyでは動作が保証されません。Markdownコンバーターには現在、##が必ずExcelのワークシート名になるという「公式に定められた」ルールがありません。
言い換えれば、
- MarkdownテーブルをExcelに変換することはできます。
- しかし、「複数シート + シート命名ロジック」は現在、比較的「ブラックボックス」であり、外部に文書化されたり、設定可能な機能ではありません。そのため、「Markdownは問題なさそうに見えるのに、
##でシートが分割されない」という現象は、現在の実装状況に合致しています。
以下に、どのように対処できるかについていくつか説明します。
1. 現在の挙動の理解
あなたが貼り付けた構造と合わせて:
{
"text": "## XXX\n\n| 列1 | 列2 |\n| --- | --- |\n| ... | ... |"
}
あなたの期待は:
## XXX → ExcelにXXXという名前のシートが出現する
## YYY → 別のシートが新規作成される
しかし、現在のMarkdownコンバーターは、むしろ次のような挙動をします:
- Markdown全体を一つのドキュメントとして扱います。
- 「タイトル階層 → シート」ではなく、「テーブルそのもの」に重点を置いています。
そのため、よくある現象は次のとおりです:
- 1つのシートしか生成されない可能性があります。
- または、
##内のテキストではなく、デフォルトのシート名(例:Sheet1)が使用されます。
これはあなたのMarkdownの記述が間違っているのではなく、現在の製品設計において##が「シートの境界 + シート名」の正式なプロトコルとして扱われていないためです。
2. 「タイトルごとにシートを分割する」を実現するための検討可能なアプローチ
もし「##でシートを分割する」という強い要件がある場合、Markdownコンバーターのデフォルトルールを迂回し、「コードノード + Excelライブラリ」を使用して、目的の構造を明示的に複数シートとして書き出すことを検討できます。
アプローチ A:LLMが構造化JSONを出力し、その後コードでExcelを生成する
-
LLMノードでは、Markdownを直接出力させるのではなく、構造化JSONを出力させます。例えば:
{
"sheets": [
{
"name": "SheetA",
"table": [
["列1", "列2"],
["a1", "a2"],
["b1", "b2"]
]
},
{
"name": "SheetB",
"table": [
["列1", "列2"],
["x1", "x2"]
]
}
]
}
-
コードノード(Pythonを推奨)でこのJSONを解析し、openpyxlやpandasなどのライブラリを使って自分でExcelを作成します。複数テーブル、複数シートを完全に制御できます。
疑似コード例:
import io
from openpyxl import Workbook
import json
data = json.loads(inputs["llm"]["text"]) # LLMの出力が上記のJSONであると仮定
wb = Workbook()
# デフォルトシートを削除
default_ws = wb.active
wb.remove(default_ws)
for sheet in data["sheets"]:
ws = wb.create_sheet(title=sheet["name"][:31]) # Excelシート名は最大31文字
for row in sheet["table"]:
ws.append(row)
# メモリに保存し、後続のノードにファイルとして返す
buffer = io.BytesIO()
wb.save(buffer)
buffer.seek(0)
outputs["excel_file"] = {
"type": "document",
"filename": "result.xlsx",
"content": buffer.read()
}
-
その後、このexcel_fileを通常のファイルとしてユーザーにダウンロードさせたり、別のノードに渡したりすることができます。
利点:
- Markdownコンバーターの内部ルールに全く依存しません。
- シート名、シート数、各シートの内容をすべて自分で制御できます。
- LLMのタスクもより明確になります。「構造化計画」のみを担当し、Excelの詳細は担当しません。
アプローチ B:LLMに引き続きMarkdownを出力させ、コードで分割してからExcelを書き出す
もし現在のLLMプロンプトがMarkdown出力に固定されており、Markdownの可読性を好むのであれば、次のようにできます:
-
LLMに引き続き同様の構造を要求します:
## SheetA
| 列1 | 列2 |
| --- | --- |
| a1 | a2 |
| b1 | b2 |
## SheetB
| 列1 | 列2 |
| --- | --- |
| x1 | x2 |
-
ダウンストリームにコードノードを追加し、2つのことを行います:
- 正規表現 / Markdown解析ライブラリを使用して、テキストを
##タイトルごとにブロックに分割します。
- 各ブロック内の最初のテーブルを二次元配列として抽出し、その後上記と同様の方法でExcelの複数シートを書き出します。
例えば、シンプルな正規表現のアプローチ(疑似コード):
import re
md = inputs["llm"]["text"]
# 各シートのブロックに分割
blocks = re.split(r'^##\s+', md, flags=re.MULTILINE)
# blocks[0]は##より前の内容である可能性があり、無視できます
sheet_blocks = blocks[1:]
sheets = []
for block in sheet_blocks:
# 最初の行から改行前までがシート名
lines = block.splitlines()
sheet_name = lines[0].strip()
sheet_body = "\n".join(lines[1:])
# さらにsheet_bodyから最初のMarkdownテーブルを二次元配列として解析
# (自分でパーサーを書くことも、既存のライブラリを使うことも可能)
table = parse_markdown_table(sheet_body)
sheets.append({"name": sheet_name, "table": table})
# その後、上記のopenpyxlの書き込みロジックを再利用
-
最終的には、Markdownコンバーターが自動的にシートを推測するのではなく、このコードノードがExcelを生成します。
3. もしMarkdownコンバーターに依存し続ける必要がある場合はどうするか?
現在入手可能な情報では、次のような「保証された」ドキュメントはありません:
##、###などの見出しとシート間のマッピングルール
- または「タイトルごとにシートを分割する」を有効にするための何らかのスイッチ
したがって:
- たとえ現在、試行錯誤の結果「見出しごとにシートを分割できるらしい」という書き方を見つけたとしても、それは実装の詳細である可能性があり、将来のバージョンで安定するとは限りません。
- 本番環境では、未文書化の挙動に依存することを避けるため、上記の「LLM + コードによるExcel自作」の方法を推奨します。
4. 実践的なアドバイス(最小限の変更バージョン)
既存のワークフローで、大幅な変更をしたくない場合は、次のように微調整できます:
-
アップストリームLLM:
-
引き続き「##付きのMarkdown」を出力させます。
-
ただし、プロンプト内でモデルに同時にJSONを出力する構造を保証させるように追加します。例えば:
先给我 Markdown 展示,方便阅读;
然后再给我一个 JSON 代码块,结构为:
```json
{ "sheets": [ { "name": "...",
"table": [["表头1","表头2"],["...","..."]] } ] }
-
ダウンストリームにコードノードを新規追加:
- JSONの部分だけを読み込み、Markdownの部分は無視します。
- JSON → 複数シートExcelのロジックでファイルを生成します。
- Markdownコンバーターを使ってExcelに変換することはもうしません。
これにより:
- 人間がチャット履歴を見る際は依然として洗練されたMarkdownです。
- マシンがExcelに落とし込む際は、「構造化 + 制御可能」なパスを辿ります。
まとめ
- あなたが現在直面している問題の本質は、Markdownコンバーターに「
## → Excelシート」という信頼できる、外部に公開されたルールがないため、期待するような方法で複数シートを作成しないということです。
- シート名と構造を安定して制御したい場合は、次のアプローチを推奨します:
- 「LLMが構造化JSONを出力 → コードノードがExcelを生成」;または
- 「LLMが
##付きのMarkdownを出力 → コードノードが##で分割 → 自分でExcelを書き出す」。
- これにより、内部の未文書化された実装の詳細に依存する必要がなくなり、将来のバージョンアップグレードもより安全になります。
もしよろしければ、現在のLLMの完全な出力例(匿名化後)を少し貼り付けていただければ、その出力を複数シートのExcelに変換するための対応するコードノードの例を直接作成できます。
コードブロックに切り替えて、JSONまたはMarkdownを自分で解析し、Excelを生成することを検討しています。
もしMarkdownコンバーターがこのような使い方を確実にサポートできないのであれば、ユーザーが使用上で困惑しないように、Difyのここにある説明情報を少なくとも修正すべきだと思います。
@Dify_Forum_Helper
あなたのこの補足フィードバックは非常に重要であり、あなたがスクリーンショットを撮ったこのテキストは確かに人々に「強い約束だと誤解させる」可能性があります。
「すべての見出しテキスト(例:#、##)は、順にワークシート名として使用されます。」
先ほどのあなたの実測状況を合わせると、2つのことを比較的明確に言うことができます。
-
「製品の実際の動作」の観点から
- 現在、MarkdownコンバーターはMarkdownテーブルをExcelに変換するのに問題はありません。
- しかし、「タイトルでシートを分割し、厳密にタイトルテキストをシート名として使用する」ことは、現在の実装では信頼できる、安定した契約ではありません(特にLLMが自動的にMarkdownを生成する場合、モデルが空行、説明文、コードブロックなどを余分に生成しやすく、これらがすべて解析に影響を与えます)。
- 実際の効果はしばしば次のようになります。
- 時には1つのシートしか生成されない;
- またはシート名がタイトルと一致しない;
- 多段階の見出し/複数のテーブルの組み合わせも、あなたの期待通りに分割されない場合があります。
-
「テキストと実際の動作の一致」の観点から
- あなたが見たこの説明のトーンは「必ずそうなる」というものです。
「すべての見出しテキスト(例:#、##)は、順にワークシート名として使用されます。」
- しかし、現在の実装は「特定のシナリオ/限られたルールのもとで試行的にタイトルをワークシート名として使用する」に近く、「無条件に保証する」ものではありません。
- あなたのようにLLMを使ってMarkdownを自動生成するシナリオでは、この記述は確かに「
##があれば、100%対応するシートが得られる」と誤解させるでしょう。
したがって、あなたが現在「コードブロックでJSONまたはMarkdownを自己解析してExcelを生成する」という方針に切り替えることは、特に複数のシートと命名を正確に制御したい場合に、エンジニアリングの実践の観点からより確実な選択です。
実装方法 & 公式への提案
-
ご自身の実装選択(合理的なアプローチです)
- LLMで構造化されたJSON、または合意されたMarkdown構造を出力する;
- コードノード内で自身で:
- シートリスト(
[{name, table}, ...])を解析する;
- openpyxl / pandas などのライブラリを使用して手動でExcelを書き込み、複数のシートと命名を完全に制御する。
- このアプローチは「文書化されていない詳細」への依存を避け、生産の安定性にとって最適です。
-
テキスト / 製品に関するいくつかの提案(私が社内で要約して伝えます)
- 現在のこのテキストと、実際に保証される動作との間には、確かにギャップがあります。
- より安全な記述は次のようになるでしょう:
- 「Markdownの構造が特定のルールを満たす場合、見出しテキストをワークシート名として使用しようとします」
または
- むしろ、タイトルによるシートの自動分割を約束せず、次のように強調する:
「現在、主にMarkdownテーブルをExcelワークシートに変換することをサポートしています。複数のワークシートと命名を正確に制御する必要がある場合は、コードノードまたはプラグインを使用してExcelを自分で生成することをお勧めします。」
- あなたが投稿したこのフィードバック自体が非常に価値があります:
- 製品/ドキュメントチームが「テキストが与える約束 > コードが実際に保証できる動作」であることに気づくのに役立ちます;
- また、今後より多くのユーザーが同じ落とし穴にはまるのを防ぐこともできます。
現状の確認(まとめ)
- 「自分でコードを通じてExcelを生成する」というあなたの選択は正しいです。特にシート名/数を正確に制御したいシナリオでは。
- あなたの現在の経験からすると、このMarkdownコンバーターのUI記述は確かに誤解を招きます。あなたの提案(このテキストを調整または弱体化する)は非常に合理的な製品改善点です。
- もし今後、コードノードで「現在のLLM出力形式(MarkdownまたはJSON)→ 具体的なPythonサンプル」から複数のシートを持つExcelを生成する必要がある場合は、一部を匿名化した実際の出力を貼り付けていただければ、あなたの形式に合わせてすぐに使えるコードノードのサンプルを作成できます。