プロパティ名: DIFY_API_KEY
/**
* Dify APIのエンドポイントURL (ご自身のものに置き換えてください)
* 例: 'https://api.dify.ai/v1/workflows/run' など
*/
const DIFY_API_URL = 'https://api.dify.ai/v1/workflows/run';
/**
* Dify APIキー (ご自身のものに置き換えてください)
* セキュリティのため、スクリプトプロパティを使用することを強く推奨します。
* https://developers.google.com/apps-script/guides/properties
*/
//const DIFY_API_KEY = 'YOUR_DIFY_API_KEY_HERE';
const scriptProperties = PropertiesService.getScriptProperties();
const apiKey = scriptProperties.getProperty('DIFY_API_KEY');
if (!apiKey) {
Logger.log('エラー: Dify APIキーがスクリプトプロパティに設定されていません。「setDifyApiKey」関数を実行して設定してください。');
SpreadsheetApp.getUi().alert('Dify APIキーが設定されていません。スクリプトエディタの「setDifyApiKey」関数を実行してください。');
}
const DIFY_API_KEY = apiKey; // スクリプトプロパティの値を使用
/**
* スプレッドシートのデータを処理し、Dify APIと連携するメイン関数
*/
function processFeedbackWithDify() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getActiveSheet();
const dataRange = sheet.getDataRange();
const values = dataRange.getValues();
// Dify API キーと URL が設定されているか確認
if (DIFY_API_URL === 'YOUR_DIFY_API_ENDPOINT_HERE' || DIFY_API_KEY === 'YOUR_DIFY_API_KEY_HERE') {
SpreadsheetApp.getUi().alert('DIFY_API_URL と DIFY_API_KEY をスクリプト内で設定してください。');
Logger.log('DIFY_API_URL と DIFY_API_KEY をスクリプト内で設定してください。');
return;
}
// ヘッダー行を除き、各行を処理 (i=1 から開始)
for (let i = 1; i < values.length; i++) {
const inputText = values[i][0]; // A列: ご意見ご要望
const categoryOutput = values[i][1]; // B列: カテゴリ (既存の値)
const reasonOutput = values[i][2]; // C列: 理由 (既存の値)
// A列に入力があり、かつB列またはC列が空の場合のみ処理を実行
// (既に処理済みの行をスキップするため)
if (inputText && (!categoryOutput || !reasonOutput)) {
Logger.log(`Processing row ${i + 1}: ${inputText}`);
// Dify APIへのリクエストペイロードを作成
// 注意: Dify APIのエンドポイントや設定によってペイロードの構造が異なる場合があります。
// '/workflows/run' エンドポイントを想定した例です。
// '/chat-messages' など他のエンドポイントの場合は調整が必要です。
const payload = {
"inputs": {
"input_text": inputText // 仕様通り変数名を "input_text" にする
},
"response_mode": "blocking", // 同期的に結果を取得する場合 'blocking'、ストリーミングなら 'streaming'
"user": `spreadsheet-user-${i + 1}` // API利用者を識別するための任意のID
// 他に必要なパラメータがあれば追加してください (例: conversation_id)
};
// HTTPリクエストのオプションを設定
const options = {
'method': 'post',
'contentType': 'application/json',
'headers': {
'Authorization': 'Bearer ' + DIFY_API_KEY
},
'payload': JSON.stringify(payload),
'muteHttpExceptions': true // エラー発生時もスクリプトを止めずにレスポンスを取得する
};
try {
// Dify APIを呼び出す
const response = UrlFetchApp.fetch(DIFY_API_URL, options);
const responseCode = response.getResponseCode();
const responseBody = response.getContentText();
if (responseCode === 200) {
const jsonResponse = JSON.parse(responseBody);
Logger.log(`Row ${i + 1} API Response: ${responseBody}`);
// --- レスポンス構造の確認と値の取得 ---
// Dify APIのレスポンス構造は、設定やエンドポイントにより異なります。
// 以下は一般的な想定ですが、実際のレスポンスに合わせて調整が必要です。
let category = null;
let reason = null;
// 提供されたレスポンス構造 (data -> outputs -> category/reason) に合わせて抽出
if (jsonResponse.hasOwnProperty('data') &&
jsonResponse.data !== null &&
typeof jsonResponse.data === 'object' &&
jsonResponse.data.hasOwnProperty('outputs') &&
jsonResponse.data.outputs !== null &&
typeof jsonResponse.data.outputs === 'object' &&
jsonResponse.data.outputs.hasOwnProperty('category') &&
jsonResponse.data.outputs.hasOwnProperty('reason')) {
category = jsonResponse.data.outputs.category;
reason = jsonResponse.data.outputs.reason;
// Unicodeエスケープシーケンス (\uXXXX) は通常 setValue で自動的にデコードされますが、
// もし文字化けする場合は、明示的にデコードが必要になる可能性は低いです。
// category = decodeURIComponent(JSON.parse('"' + jsonResponse.data.outputs.category.replace(/\"/g, '\\"') + '"'));
// reason = decodeURIComponent(JSON.parse('"' + jsonResponse.data.outputs.reason.replace(/\"/g, '\\"') + '"'));
} else {
// 想定したデータ構造が見つからない場合
Logger.log(`Row ${i + 1}: Expected data structure (data.outputs.category/reason) not found in response: ${responseBody}`);
}
// --- ↑↑↑ ここまで修正 ↑↑↑ ---
// --- 値の書き込み ---
if (category !== null && reason !== null) {
sheet.getRange(i + 1, 2).setValue(category); // B列にカテゴリを書き込み (列番号は2)
sheet.getRange(i + 1, 3).setValue(reason); // C列に理由を書き込み (列番号は3)
Logger.log(`Row ${i + 1}: Successfully wrote Category='${category}', Reason='${reason}'`);
} else {
// 想定したデータが見つからない場合のエラーハンドリング
Logger.log(`Row ${i + 1}: 'category' or 'reason' not found in the expected structure of the API response.`);
sheet.getRange(i + 1, 2).setValue('Error: Data not found in response'); // B列にエラーメッセージ
}
} else {
// API呼び出しでエラーが発生した場合
Logger.log(`Row ${i + 1}: Error calling Dify API. Response Code: ${responseCode}. Response Body: ${responseBody}`);
sheet.getRange(i + 1, 2).setValue(`Error: ${responseCode}`); // B列にエラーコード
}
} catch (error) {
// 通信エラーなど、fetch自体で例外が発生した場合
Logger.log(`Row ${i + 1}: Exception during API call for row ${i + 1}: ${error}`);
sheet.getRange(i + 1, 2).setValue('Error: Exception'); // B列にエラーメッセージ
}
// API制限を避けるために少し待機する (ミリ秒単位)
Utilities.sleep(1000); // 1秒待機 (必要に応じて調整)
} else if (inputText && categoryOutput && reasonOutput) {
// 既に処理済みの行はスキップ
Logger.log(`Row ${i + 1}: Skipped (already processed).`);
} else if (!inputText) {
// A列が空の行はスキップ
Logger.log(`Row ${i + 1}: Skipped (input text is empty).`);
}
} // end for loop
SpreadsheetApp.getUi().alert('処理が完了しました。');
Logger.log('Processing finished.');
}
/**
* スプレッドシートのメニューにカスタムメニューを追加する関数
*/
function onOpen() {
SpreadsheetApp.getUi()
.createMenu('Dify連携')
.addItem('ご意見ご要望を処理', 'processFeedbackWithDify')
.addToUi();
}
/**
* (推奨) Dify APIキーをスクリプトプロパティに安全に保存するための関数
* この関数を実行する前に、YOUR_DIFY_API_KEY_HERE を実際のAPIキーに置き換えてください。
* 実行後、この関数内のAPIキーは削除しても構いません。
*/
function setDifyApiKey() {
const apiKey = 'YOUR_DIFY_API_KEY_HERE'; // ★★★ あなたのDify APIキーに置き換えてください ★★★
if (apiKey === 'YOUR_DIFY_API_KEY_HERE' || apiKey.trim() === '') {
SpreadsheetApp.getUi().alert('APIキーが入力されていません。コード内の「YOUR_DIFY_API_KEY_HERE」を実際のAPIキーに置き換えてから再度実行してください。');
return;
}
try {
PropertiesService.getScriptProperties().setProperty('DIFY_API_KEY', apiKey);
Logger.log('Dify APIキーをスクリプトプロパティに保存しました。');
SpreadsheetApp.getUi().alert('Dify APIキーをスクリプトプロパティに保存しました。\n「sendFeedbackToDify」関数を実行できます。\n(コード内のAPIキーは削除しても問題ありません)');
// セキュリティのため、コンソールにキー自体は表示しない
} catch (error) {
Logger.log(`APIキーの保存中にエラーが発生しました: ${error}`);
SpreadsheetApp.getUi().alert(`APIキーの保存中にエラーが発生しました: ${error.message}`);
}
}
<instruction>
新商品のノートの満足度アンケートを集計しました。以下の手順に従って、アンケートのご意見ご要望を指定のカテゴリに分けてください。
1. ご意見ご要望の内容を分析し、どのカテゴリに属するかを判断します。
2. 判断した理由とカテゴリをJSON形式で出力してください。
カテゴリは以下のいずれかです:
- 商品のデザイン(商品の外観やパッケージについての意見。)
- 使用感(実際に使った際の感触。)
- 機能・性能(商品の機能や期待していた性能についての評価。)
- 価格・コストパフォーマンス(価格に対する価値。)
- 購入体験(オンラインでの購入までの流れやサポートについて。)
- 配送・梱包(配送スピードや梱包状態の評価。)
- 利便性(商品の使いやすさや日常生活での便利さ。)
- 耐久性・品質(商品の耐久性や長期使用での品質に関する意見。)
- 改善要望(特定の改善点やアップデートの希望。)
- 感想・満足度全般(商品全体についての自由な感想。)
</instruction>
<input>
{{input_text}}
</input>
<example>
{
"reason": "価格に見合った品質と述べているため、商品の価格とその価値についての評価であると判断しました。",
"category": "価格・コストパフォーマンス"
}
</example>
def main(input_json_string: str) -> dict:
"""
入力された文字列から、マークダウンのJSONコードブロック表記を除去します。
Args:
input_json_string (str): Difyからの入力文字列。
この引数名はDify側で設定した **入力変数名** と
**完全に一致** させる必要があります。
Returns:
dict: 'cleaned_json_string' キーに処理後の文字列を含む辞書。
このキー名はDify側で後続のステップで使用する名前にできます。
"""
# 引数として直接渡された文字列を使用します。
# inputs辞書は使用しません。
input_str = input_json_string
# 入力が文字列でない場合に備えて型チェックと変換を行うことも検討できます
if not isinstance(input_str, str):
# エラー処理または文字列への変換を試みる (例)
input_str = str(input_str)
# またはエラーを示す値を返す
# return {'error': 'Input must be a string.'}
# 念のため、前後の空白文字(改行含む)を除去します。
cleaned_str = input_str.strip()
# 文字列が "```json" で始まり、かつ "```" で終わるか確認します。
if cleaned_str.startswith("```json") and cleaned_str.endswith("```"):
# "```json" の部分を除去します (先頭から len("```json") 文字分)
# さらに除去後の前後の空白を除去します
cleaned_str = cleaned_str[len("```json"):].strip()
# 末尾の "```" の部分を除去します (末尾から len("```") 文字分)
# さらに除去後の前後の空白を除去します
cleaned_str = cleaned_str[:-len("```")].strip()
# Python 3.9以降であれば removeprefix/removesuffix でも書けます
# cleaned_str = cleaned_str.removeprefix("```json").removesuffix("```").strip()
# 処理結果を辞書形式で返します。
# 出力変数名を 'cleaned_json_string' としています。
# このキー名はDify側で参照する際に使用します。
return {
'cleaned_json_string': cleaned_str
}