[Dify] LLMからの回答をstreaming-modeにする

response_modeをblockingからstreamingにする
requests.postの呼び出しでstream=Trueを設定。これにより、レスポンス全体がダウンロードされるのを待たずに、すぐにチャンクの読み込みを開始する

import requests
import json
import os
import time

from dotenv import load_dotenv
load_dotenv()

# --- 設定(変更なし) ---
API_KEY = os.environ.get("DIFY_API_KEY")
APP_ID = os.environ.get("APP_ID")

# BASE_URLはDify Cloud (dify.ai) の場合です。セルフホストの場合は適宜変更してください。
BASE_URL = "https://api.dify.ai/v1" 
CHAT_ENDPOINT = f"{BASE_URL}/chat-messages"

user_input = "おはようございます。今日の天気と、何か面白いニュースがあれば教えてください。"

# --- 1. ペイロードの変更 ---
payload = {
    "query": user_input,
    "inputs": {
        "context": "Null" 
    },
    "user": "user_python_script_001", 
    # ストリーミングを有効にする
    "response_mode": "streaming", 
}

headers = {
    "Authorization": f"Bearer {API_KEY}",
    "Content-Type": "application/json"
}

print("✅ ワークフローの出力 (ストリーミング回答):")
print("-" * 30)

try:
    # --- 2. API呼び出しの変更 (stream=True) ---
    response = requests.post(
        CHAT_ENDPOINT,
        headers=headers,
        data=json.dumps(payload),
        stream=True  # ストリーミング応答を有効にする
    )
    
    response.raise_for_status() # HTTPエラーが発生した場合に例外を発生させる

    # --- 3. 応答の処理ロジックの変更 ---
    # iter_lines() でチャンクごとに処理
    for line in response.iter_lines():
        if line:
            # SSE形式の行をデコード
            decoded_line = line.decode('utf-8')
            
            # SSEのデータ行は 'data: ' で始まる
            if decoded_line.startswith('data: '):
                # 'data: ' の部分を削除し、JSONとしてパース
                json_str = decoded_line[6:].strip()
                try:
                    data = json.loads(json_str)
                except json.JSONDecodeError:
                    # JSONデコードエラーはスキップまたはログに記録
                    continue

                # タイプに応じて処理
                event_type = data.get('event')
                
                # 'message' イベントが回答のテキストチャンクを含む
                if event_type == 'message':
                    # 'answer' フィールドにテキストの断片が含まれる
                    text_chunk = data.get('answer', '')
                    
                    # ターミナルにテキストを逐次出力(flush=Trueで即時表示)
                    print(text_chunk, end='', flush=True)

                # 'message_end' イベントは回答の終了を示す
                elif event_type == 'message_end':
                    # 処理終了後、改行してループを抜ける
                    print("\n" + "-" * 30)
                    print("✅ ストリーミング終了")
                    break

except requests.exceptions.HTTPError as errh:
    print(f"\nHTTP エラーが発生しました: {errh}")
    # エラーが発生した場合、response.text を表示(非ストリームとして再取得が必要な場合あり)
    # ストリームモードでエラーがすぐに発生しないこともあるため、これはシンプルなエラー表示
    try:
        print(response.json())
    except:
        print(response.text)
except requests.exceptions.RequestException as err:
    print(f"\nリクエスト中にエラーが発生しました: {err}")
finally:
    # 接続が成功した場合でも、エラーで中断した場合でも、最後に改行してプロンプトをきれいに表示
    print()

なるほど、レスポンスモードが変わるのね。了解!