Dify:結果のファイル出力 (HTTPリクエスト)

Difyの出力として、画面に表示するだけでなく、ファイルに出力したい場合もあるかと思います。

こちらの記事で作った検索ボットの結果をファイルに出力したいと思います。

方法として、プラグインを使用する方法と、GoogleのCloud Runを使う方法の2通りをやってみます。

プラグインを使う

Difyではマーケットプレイスから便利なプラグインをインストールして使うことができます。
今回使うのは、Markdown Exporterです。


LLMの出力テキストは、Markdownの形式で表現されており、見出しや強調文字などを使って読みやすくされています。このプラグインでは、Markdownを入力として、さまざまな形式へ変換することができます。

Difyの設定

先ほどの記事では、Perplexityを使って、最近のニュースを検索して回答するボットを作りました。今回は、LLMと回答との間に、Markdown Exporterを配置します。Markdown ExporterはLLMの出力をファイルに変換して、回答ブロックに渡します。

Markdown Exporterは出力形式別に12個のブロックがありますが、今回はPDFに出力するブロックを選択しました。

設定は、Markdown to PDF fileの入力変数をLLMの出力textにすることと、回答ブロックの回答にMarkdown to PDF fileのfiles変数を追加するだけです。

実行結果

実行すると、Perplexityの検索結果をLLMが整形して画面に表示します。また、同じ内容がpdfファイルとなって、左下に表示されます。ファイルの右下にマウスをフォーカスすると、ダウンロードマークが表示されますので、保存しておきたい場合は、クリックして下さい。

Google CloudRun

プラグインを使う方法は非常に簡単ですが、非公式なものも多く、業務で使うことに抵抗感があるかもしれません。また、バージョンが変わって動作が変わってしまうかもしれません。その場合は、外部にファイル作成用のAPIを自作し、Difyからそれを呼び出すことで、同様なことを実現できます。

今回は、LLMの出力をそのままMarkdown形式のテキストファイルに出力する方法を実践してみます。

CloudRun

GoogleのCloudRunは、関数レベルの小さなタスクをサーバーレスで実行する環境です。簡単なエディタが付いていますので、短いコードであれば、GUI環境でそのままソースコードを書いてデプロイすることも可能です。

以下は、ファイルへの変換コード(Python)です。

import functions_framework
import base64
from flask import send_file, make_response
import io

@functions_framework.http
def generate_text_file(request):
    """HTTP Cloud Functionでダウンロード可能なテキストファイルを生成します。

    Args:
        request (flask.Request): リクエストオブジェクト。
        
    Returns:
        ファイルを直接返すレスポンス、またはエラーレスポンス。
    """
    # CORS ヘッダー (後でレスポンスに追加)
    cors_headers = {
        'Access-Control-Allow-Origin': '*',
        'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
        'Access-Control-Allow-Headers': 'Content-Type'
    }
    
    # OPTIONS リクエスト(プリフライトリクエスト)への対応
    if request.method == 'OPTIONS':
        # OPTIONS リクエストにはボディがないため、空のタプルと204ステータス、ヘッダーを返す
        return ('', 204, cors_headers)
        
    # リクエストからテキストとファイル名を取得
    request_json = request.get_json(silent=True)
    
    if not request_json:
        # エラーレスポンスを作成し、CORSヘッダーを追加
        response = make_response(('リクエストにJSONデータが含まれていません', 400))
        response.headers.extend(cors_headers)
        return response
    
    text = request_json.get('text', '')
    # デフォルトファイル名を設定し、明示的に文字列に変換
    filename = str(request_json.get('filename', 'output.txt')) 
    
    if not text:
        # エラーレスポンスを作成し、CORSヘッダーを追加
        response = make_response(('テキストが指定されていません', 400))
        response.headers.extend(cors_headers)
        return response
    
    # テキストをUTF-8でエンコード
    text_bytes = text.encode('utf-8')
    
    # メモリ上にファイルとして保存
    file_stream = io.BytesIO(text_bytes)
    file_stream.seek(0) # ストリームの開始位置に戻す
    
    try:
        # send_file を使用してレスポンスを生成
        # download_name を指定すれば Content-Disposition は自動設定される
        response = send_file(
            file_stream,
            mimetype='text/plain',
            as_attachment=True,
            download_name=filename  # ここでファイル名を指定
        )
        
        # send_fileが生成したResponseオブジェクトにCORSヘッダーとカスタムヘッダーを追加
        response.headers.extend(cors_headers)
        response.headers['X-Filename'] = filename 

        return response

    except Exception as e:
        # ファイル送信中のエラー処理
        print(f"Error sending file: {e}") # ログにエラーを出力
        response = make_response(('ファイルの生成または送信中にエラーが発生しました', 500))
        response.headers.extend(cors_headers)
        return response

CloudRunのコードでは、出力するfilenameを指定していますが、DifyからCloudRunのAPIを呼んだ場合、内部でファイル名を変換するようです。これは、Dify内部で同じファイルが被らない様にユニークなファイル名を付与する処理をしているためと思われます。

Difyの設定

プラグインの場合と同様に、LLMと回答との間にHTTPリクエストブロックを挿入します。

HTTPリクエスト

以下の項目について設定します。エラー時の再試行などは、適宜設定して下さい。

API

POST + CloudRunのURL

ボディ

JSONを選択

回答

回答として、HTTPブロックのfilesを追加します。

実行結果

実行結果は、以下のようになります。
プラグインの時と同様に、回答欄の左下にテキストファイルが付加されており、ダウンロードすることができます。

まとめ

Difyでは、結果をファイルに出力し、ローカルのパソコンにダウンロードすることができます。分析資料などを作成した場合には、ファイルに保存してゆっくり読むことができるでしょう。

目次