Markdownコンバーターでファイルの保存場所が取得できない、出力変数が使えない?

Markdownコンバーターの出力変数には、対応する3つのキー、textfilesjsonがあります。そのうち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がいくつかあります。今後の探索に活用できますよ :grinning_face_with_smiling_eyes: @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を直接参照するのではなく、次のようにします:

  1. 「ドキュメント抽出 / Document Extractorノード」を使用してファイルコンテンツを解析する

    • 入力:アップストリームノードが出力するファイル変数(配列でも可)—例えば、Markdownコンバーターのfiles、またはStartノードでアップロードされたファイル。
    • 出力:プレーンテキスト(例:ExcelをMarkdownテーブルテキストに変換したもの)。
  2. 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 のように、複数ステップを組み合わせて使用する

小結

  1. filesファイルオブジェクトの配列であり、LLMのコンテキストとして直接使用することはできないため、Invalid context structureというエラーが発生します。
  2. LLMにxlsxの内容を読ませたい場合:
    Markdown / Startノード(ファイルを出力) → ドキュメント抽出ノード → テキストを出力 → LLMノードのコンテキスト / プロンプトとして使用。
  3. 単純にURLを取得したい場合:コードノードでfiles[i].urlから取り出すだけでよく、files全体を直接LLMに渡さないでください。

もしよろしければ、現在のワークフローの簡単な構造(各ノードのタイプ+主要な接続)を貼り付けていただければ、「ExcelのアップロードからLLM分析まで」の具体的な接続と変数参照の例を作成できます。

ご回答ありがとうございます!

私の要件は、Markdownコンバーターが返すファイルURLを抽出し、完全なファイルダウンロードURL(絶対パス)を組み立てるためだけです。

現在、コードブロックを使用してfiles[i].urlを取得できたので、問題は解決しました。

別の問題が発生しました。上位のLLMノードで、次のMarkdownコンバーターノードがExcelを生成できるように、LLMに標準のMarkdownテーブルを返してもらうよう要求しました。

LLMが返すデータ形式を確認したところ、問題はないと考えています。例えば、下図のテキストはMarkdownの構文構造に準拠していますが、Markdownコンバーターは##をタイトルとする構文に従ってExcel内の対応するシートを生成しません。どのように対処すればよいでしょうか?

image

@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を生成する

  1. LLMノードでは、Markdownを直接出力させるのではなく、構造化JSONを出力させます。例えば:

    {
      "sheets": [
        {
          "name": "SheetA",
          "table": [
            ["列1", "列2"],
            ["a1", "a2"],
            ["b1", "b2"]
          ]
        },
        {
          "name": "SheetB",
          "table": [
            ["列1", "列2"],
            ["x1", "x2"]
          ]
        }
      ]
    }
    
  2. コードノード(Pythonを推奨)でこのJSONを解析し、openpyxlpandasなどのライブラリを使って自分で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()
    }
    
  3. その後、このexcel_fileを通常のファイルとしてユーザーにダウンロードさせたり、別のノードに渡したりすることができます。

利点:

  • Markdownコンバーターの内部ルールに全く依存しません。
  • シート名、シート数、各シートの内容をすべて自分で制御できます。
  • LLMのタスクもより明確になります。「構造化計画」のみを担当し、Excelの詳細は担当しません。

アプローチ B:LLMに引き続きMarkdownを出力させ、コードで分割してからExcelを書き出す

もし現在のLLMプロンプトがMarkdown出力に固定されており、Markdownの可読性を好むのであれば、次のようにできます:

  1. LLMに引き続き同様の構造を要求します:

    ## SheetA
    
    | 列1 | 列2 |
    | --- | --- |
    | a1  | a2  |
    | b1  | b2  |
    
    ## SheetB
    
    | 列1 | 列2 |
    | --- | --- |
    | x1  | x2  |
    
  2. ダウンストリームにコードノードを追加し、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の書き込みロジックを再利用
    
  3. 最終的には、Markdownコンバーターが自動的にシートを推測するのではなく、このコードノードがExcelを生成します。


3. もしMarkdownコンバーターに依存し続ける必要がある場合はどうするか?

現在入手可能な情報では、次のような「保証された」ドキュメントはありません:

  • #####などの見出しとシート間のマッピングルール
  • または「タイトルごとにシートを分割する」を有効にするための何らかのスイッチ

したがって:

  • たとえ現在、試行錯誤の結果「見出しごとにシートを分割できるらしい」という書き方を見つけたとしても、それは実装の詳細である可能性があり、将来のバージョンで安定するとは限りません。
  • 本番環境では、未文書化の挙動に依存することを避けるため、上記の「LLM + コードによるExcel自作」の方法を推奨します。

4. 実践的なアドバイス(最小限の変更バージョン)

既存のワークフローで、大幅な変更をしたくない場合は、次のように微調整できます:

  1. アップストリームLLM:

    • 引き続き「##付きのMarkdown」を出力させます。

    • ただし、プロンプト内でモデルに同時にJSONを出力する構造を保証させるように追加します。例えば:

      先给我 Markdown 展示,方便阅读;
      然后再给我一个 JSON 代码块,结构为:
      ```json
      { "sheets": [ { "name": "...",
                      "table": [["表头1","表头2"],["...","..."]] } ] }
      
      
      
  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つのことを比較的明確に言うことができます。

  1. 「製品の実際の動作」の観点から

    • 現在、MarkdownコンバーターはMarkdownテーブルをExcelに変換するのに問題はありません。
    • しかし、「タイトルでシートを分割し、厳密にタイトルテキストをシート名として使用する」ことは、現在の実装では信頼できる、安定した契約ではありません(特にLLMが自動的にMarkdownを生成する場合、モデルが空行、説明文、コードブロックなどを余分に生成しやすく、これらがすべて解析に影響を与えます)。
    • 実際の効果はしばしば次のようになります。
      • 時には1つのシートしか生成されない;
      • またはシート名がタイトルと一致しない;
      • 多段階の見出し/複数のテーブルの組み合わせも、あなたの期待通りに分割されない場合があります。
  2. 「テキストと実際の動作の一致」の観点から

    • あなたが見たこの説明のトーンは「必ずそうなる」というものです。

      「すべての見出しテキスト(例:#、##)は、順にワークシート名として使用されます。」

    • しかし、現在の実装は「特定のシナリオ/限られたルールのもとで試行的にタイトルをワークシート名として使用する」に近く、「無条件に保証する」ものではありません。
    • あなたのようにLLMを使ってMarkdownを自動生成するシナリオでは、この記述は確かに「##があれば、100%対応するシートが得られる」と誤解させるでしょう。

したがって、あなたが現在「コードブロックでJSONまたはMarkdownを自己解析してExcelを生成する」という方針に切り替えることは、特に複数のシートと命名を正確に制御したい場合に、エンジニアリングの実践の観点からより確実な選択です。


実装方法 & 公式への提案

  1. ご自身の実装選択(合理的なアプローチです)

    • LLMで構造化されたJSON、または合意されたMarkdown構造を出力する;
    • コードノード内で自身で:
      • シートリスト([{name, table}, ...])を解析する;
      • openpyxl / pandas などのライブラリを使用して手動でExcelを書き込み、複数のシートと命名を完全に制御する。
    • このアプローチは「文書化されていない詳細」への依存を避け、生産の安定性にとって最適です。
  2. テキスト / 製品に関するいくつかの提案(私が社内で要約して伝えます)

    • 現在のこのテキストと、実際に保証される動作との間には、確かにギャップがあります。
    • より安全な記述は次のようになるでしょう:
      • 「Markdownの構造が特定のルールを満たす場合、見出しテキストをワークシート名として使用しようとします」
        または
      • むしろ、タイトルによるシートの自動分割を約束せず、次のように強調する:
        「現在、主にMarkdownテーブルをExcelワークシートに変換することをサポートしています。複数のワークシートと命名を正確に制御する必要がある場合は、コードノードまたはプラグインを使用してExcelを自分で生成することをお勧めします。」
    • あなたが投稿したこのフィードバック自体が非常に価値があります:
      • 製品/ドキュメントチームが「テキストが与える約束 > コードが実際に保証できる動作」であることに気づくのに役立ちます;
      • また、今後より多くのユーザーが同じ落とし穴にはまるのを防ぐこともできます。

現状の確認(まとめ)

  • 「自分でコードを通じてExcelを生成する」というあなたの選択は正しいです。特にシート名/数を正確に制御したいシナリオでは。
  • あなたの現在の経験からすると、このMarkdownコンバーターのUI記述は確かに誤解を招きます。あなたの提案(このテキストを調整または弱体化する)は非常に合理的な製品改善点です。
  • もし今後、コードノードで「現在のLLM出力形式(MarkdownまたはJSON)→ 具体的なPythonサンプル」から複数のシートを持つExcelを生成する必要がある場合は、一部を匿名化した実際の出力を貼り付けていただければ、あなたの形式に合わせてすぐに使えるコードノードのサンプルを作成できます。