(C) Questetra, Inc. (MIT License)
2025-12-19
3
2
https://support.questetra.com/bpmn-icons/service-task-openai-chatgpt-chat/
https://support.questetra.com/ja/bpmn-icons/service-task-openai-chatgpt-chat/
This item sends a message to OpenAI ChatGPT and stores the
response in the specified data item.
この工程は、OpenAI ChatGPT へメッセージを送信し、回答をデータ項目に保存します。
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
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
AAAAAElFTkSuQmCC
{
const authSetting = httpClient.createAuthSettingToken('ChatGPT', authKey);
configs.putObject('conf_Auth', authSetting);
configs.put('conf_OrganizationId', organizationId);
configs.put('conf_Model', model);
configs.put('conf_MaxTokens', maxTokens);
configs.put('conf_ReasoningEffort', reasoningEffort);
configs.put('conf_Verbosity', verbosity);
configs.put('conf_Temperature', temperature);
configs.put('conf_StopSequences', stopSequences);
configs.put('conf_GPT_Role', gptRole);
configs.put('conf_Message1', message);
const answerDef = engine.createDataDefinition('回答', 1, 'q_answer', 'STRING_TEXTAREA');
engine.setData(answerDef, '事前文字列');
configs.putObject('conf_Answer1', answerDef);
return answerDef;
};
/**
* 異常系のテスト
* @param errorMsg
*/
const assertError = (errorMsg) => {
let failed = false;
try {
main();
} catch (e) {
failed = true;
expect(e.message).toEqual(errorMsg);
}
if (!failed) {
fail('No error was thrown.');
}
};
/**
* メッセージが空
*/
test('Message is empty', () => {
prepareConfigs('key', '', 'gpt-4', '', '', '', '', '', '', '');
assertError('User Message is empty.');
});
/**
* トークン数の最大値が正の整数でない - 数字でない文字を含む場合
*/
test('Maximum number of tokens must be a positive integer - includes a non-numeric character', () => {
prepareConfigs('key', '', 'gpt-4', '1.1', '', '', '', '', '', 'こんにちは');
assertError('Maximum number of tokens must be a positive integer.');
});
/**
* トークン数の最大値が正の整数でない - ゼロ
*/
test('Maximum number of tokens must be a positive integer - 0', () => {
prepareConfigs('key', '', 'gpt-4', '0', '', '', '', '', '', 'こんにちは');
assertError('Maximum number of tokens must be a positive integer.');
});
/**
* 温度が不正 - 数字、小数点でない文字を含む
*/
test('Invalid temperature - includes a non-numeric character', () => {
prepareConfigs('key', '', 'gpt-4', '', '', '', '-1', '', '', 'こんにちは');
assertError('Temperature must be a number from 0 to 2.');
});
/**
* 温度が不正 - 2 を超える
*/
test('Invalid temperature - bigger than 2', () => {
prepareConfigs('key', '', 'gpt-4', '', '', '', '2.1', '', '', 'こんにちは');
assertError('Temperature must be a number from 0 to 2.');
});
/**
* 停止シークエンスの数が多すぎる
*/
test('Too many stop sequences', () => {
let stopSequences = '\n';
for (let i = 0; i < MAX_STOP_SEQUENCE_NUM + 1; i++) {
stopSequences += `stop${i}\n`;
}
prepareConfigs('key', '', 'gpt-4', '', '', '', '', stopSequences, '', 'こんにちは');
assertError(`Too many stop sequences. The maximum number is ${MAX_STOP_SEQUENCE_NUM}.`);
});
/**
* 指定サイズのファイルを作成
* @param name
* @param contentType
* @param size
* @return qfile
*/
const createQfile = (name, contentType, size) => {
let text = '';
if (size >= 4000) {
text = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'.repeat(100); // 40 * 100 = 4000
}
while (text.length < size) {
if (text.length !== 0 && text.length * 2 <= size) {
text += text;
} else if (text.length + 1000 <= size) {
text += 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'.repeat(25); // 40 * 25 = 1000
} else {
text += 'a';
}
}
return engine.createQfile(name, contentType, text);
};
/**
* 添付画像のサイズが大きすぎる
*/
test('Image file is too large', () => {
prepareConfigs('key', '', 'gpt-4-vision-preview', '150', '', '', '', '', '', 'こんにちは');
// 添付画像を指定
const image1 = createQfile('画像1.png', 'image/png', 100);
const image2 = createQfile('画像2.jpg', 'image/jpeg', MAX_IMAGE_SIZE + 1);
const imagesDef = engine.createDataDefinition('添付画像', 2, 'q_images', 'FILE');
engine.setData(imagesDef, [image1, image2]);
configs.putObject('conf_Images1', imagesDef);
assertError(`Attached image "画像2.jpg" is too large. Each file must be less than ${MAX_IMAGE_SIZE} bytes.`);
});
/**
* 添付画像の Content-Type がサポート外
*/
test('Content-Type of image file is not supported', () => {
prepareConfigs('key', '', 'gpt-4-vision-preview', '150', '', '', '', '', '', 'こんにちは');
// 添付画像を指定
const image1 = createQfile('画像1.png', 'image/png', 100);
const image2 = createQfile('画像2.jpg', 'image/jpeg', 100);
const image3 = createQfile('画像3.bmp', 'image/bmp', 100); // サポート外
const imagesDef = engine.createDataDefinition('添付画像', 2, 'q_images', 'FILE');
engine.setData(imagesDef, [image1, image2, image3]);
configs.putObject('conf_Images1', imagesDef);
assertError(`Content-Type of "画像3.bmp" is not supported. Supported types are: ${AVAILABLE_IMAGE_TYPES.join(', ')}.`);
});
/**
* API リクエストのテスト
* @param {Object} request
* @param request.url
* @param request.method
* @param request.contentType
* @param request.headers
* @param request.body
* @param {String} authKey
* @param {String} organizationId
* @param {String} model
* @param {Number} maxTokens
* @param {String} reasoningEffort
* @param {String} verbosity
* @param {Number} temperature
* @param {Array} stopSequences
* @param {String} gptRole
* @param {String} message
* @param {Array} imageContentTypes
* @param {String} imageDetail
*/
const assertRequest = ({
url,
method,
contentType,
headers,
body
}, authKey, organizationId, model, maxTokens, reasoningEffort, verbosity, temperature, stopSequences, gptRole, message, imageContentTypes, imageDetail = undefined) => {
expect(url).toEqual('https://api.openai.com/v1/chat/completions');
expect(method).toEqual('POST');
expect(contentType).toEqual('application/json');
expect(headers['Authorization']).toEqual(`Bearer ${authKey}`);
if (organizationId !== '') {
expect(headers['OpenAI-Organization']).toEqual(organizationId);
}
const bodyObj = JSON.parse(body);
expect(bodyObj.safety_identifier).toEqual(`m${processInstance.getProcessModelInfoId().toString()}`);
expect(bodyObj.model).toEqual(model);
expect(bodyObj.n).toEqual(1);
expect(bodyObj.max_completion_tokens).toEqual(maxTokens);
expect(bodyObj.reasoning_effort).toEqual(reasoningEffort);
expect(bodyObj.verbosity).toEqual(verbosity);
expect(bodyObj.temperature).toEqual(temperature);
if (gptRole === '') {
expect(bodyObj.messages.length).toEqual(1);
} else {
expect(bodyObj.messages.length).toEqual(2);
expect(bodyObj.messages[0]).toEqual({
role: 'system',
content: gptRole
});
}
expect(bodyObj.stop).toEqual(stopSequences);
const userMessage = bodyObj.messages[bodyObj.messages.length - 1];
expect(userMessage.role).toEqual('user');
expect(userMessage.content[0]).toEqual({
type: 'text',
text: message
});
imageContentTypes.forEach((contentType, index) => {
assertAttachment(userMessage, index, contentType, imageDetail);
});
};
/**
* API リクエストの body 内のユーザメッセージオブジェクトに添付ファイルが含まれることをテスト
* @param userMessage
* @param index
* @param fileName
* @param contentType
* @param imageDetail
*/
const assertAttachment = (userMessage, index, contentType, imageDetail) => {
const imageObj = userMessage.content[index + 1]; // 0 番目は text なので、1 加算
expect(imageObj.type).toEqual('image_url');
const url = imageObj.image_url.url;
expect(url.split(',')[0]).toEqual(`data:${contentType};base64`);
// ファイルのバイナリデータが base64 エンコードされた部分の確認は省略
expect(imageObj.image_url.detail).toEqual(imageDetail);
};
/**
* API リクエストでエラー
*/
test('Fail to request', () => {
prepareConfigs('key2', '', 'gpt-3.5-turbo', '', '', '', '', '', '', 'こんにちは');
httpClient.setRequestHandler((request) => {
assertRequest(request, 'key2', '', 'gpt-3.5-turbo', MAX_TOKENS_DEFAULT, undefined, undefined, 1, undefined, '', 'こんにちは', []);
return httpClient.createHttpResponse(400, 'application/json', '{}');
});
assertError('Failed to request. status: 400');
});
/**
* API リクエストが 200 レスポンスを返すが、トークン切れで回答が空の場合
*/
test('No response content generated', () => {
prepareConfigs('key2', '', 'chatgpt-4o-latest', '100', '', '', '', '', '', 'こんにちは');
const responseObj = {
"id": "chatcmpl-123",
"object": "chat.completion",
"created": 1677652288,
"choices": [{
"index": 0,
"message": {
"role": "assistant",
"content": "",
},
"finish_reason": "length"
}],
"usage": {
"prompt_tokens": 25,
"completion_tokens": 100,
"completion_tokens_details": {
"reasoning_tokens": 100
},
"total_tokens": 125
}
};
httpClient.setRequestHandler((request) => {
assertRequest(request, 'key2', '', 'chatgpt-4o-latest', 100, undefined, undefined, 1, undefined, '', 'こんにちは', []);
return httpClient.createHttpResponse(200, 'application/json', JSON.stringify(responseObj));
});
assertError('No response content generated. Finish Reason: length');
});
/**
* API のレスポンスボディを作成
* @param {String} answerText
* @returns {String} response
*/
const createResponse = (answerText) => {
const responseObj = {
"id": "chatcmpl-123",
"object": "chat.completion",
"created": 1677652288,
"choices": [{
"index": 0,
"message": {
"role": "assistant",
"content": answerText,
},
"finish_reason": "stop"
}],
"usage": {
"prompt_tokens": 9,
"completion_tokens": 12,
"completion_tokens_details": {
"reasoning_tokens": 5
},
"total_tokens": 26
}
}
;
return JSON.stringify(responseObj);
};
/**
* 成功
*/
test('Success to request', () => {
const answerDef = prepareConfigs('key3', '', 'gpt-4.1', '', '', '', '', '', 'Robot', 'Hello World');
// 添付画像指定なし
httpClient.setRequestHandler(request => {
assertRequest(request, 'key3', '', 'gpt-4.1', MAX_TOKENS_DEFAULT, undefined, undefined, 1, undefined, 'Robot', 'Hello World', []);
return httpClient.createHttpResponse(200, 'application/json', createResponse("\n\nHello there, how may I assist you today?"));
});
main();
// データ項目の値をチェック
expect(engine.findData(answerDef)).toEqual("\n\nHello there, how may I assist you today?");
});
/**
* 成功 - 組織 ID を指定
* reasoningEffort を指定
* verbosity は指定なし
* 回答を保存するデータ項目に、文字型 Markdown を指定
*/
test('Success to request - with organization ID', () => {
prepareConfigs('key4', 'org-1', 'gpt-5', '', 'minimal', '', '', '', '', 'Hi!');
// 回答を保存するデータ項目に、文字型 Markdown を指定
const answerDef = engine.createDataDefinition('回答', 1, 'q_answer', 'STRING_MARKDOWN');
engine.setData(answerDef, '事前文字列');
configs.putObject('conf_Answer1', answerDef);
// 添付画像はデータ項目の指定はあるが、ファイル添付なし
const imagesDef = engine.createDataDefinition('添付画像', 2, 'q_images', 'FILE');
configs.putObject('conf_Images1', imagesDef);
httpClient.setRequestHandler(request => {
assertRequest(request, 'key4', 'org-1', 'gpt-5', MAX_TOKENS_DEFAULT, 'minimal', undefined, 1, undefined, '', 'Hi!', []);
return httpClient.createHttpResponse(200, 'application/json', createResponse("Hi! How can I help you?"));
});
main();
// データ項目の値をチェック
expect(engine.findData(answerDef)).toEqual("Hi! How can I help you?");
});
/**
* 成功 - 最大トークン数を指定
* reasoning_effort は指定なし
* verbosity を指定
*/
test('Success to request - with max_tokens', () => {
const answerDef = prepareConfigs('key5', '', 'gpt-5-mini', '100', '', 'high', '', '', 'Assistant', 'Hi! How\'s the weather in Kyoto today?');
httpClient.setRequestHandler(request => {
assertRequest(request, 'key5', '', 'gpt-5-mini', 100, undefined, 'high', 1, undefined, 'Assistant', 'Hi! How\'s the weather in Kyoto today?', []);
return httpClient.createHttpResponse(200, 'application/json', createResponse("Hi! It's sunny today."));
});
main();
// データ項目の値をチェック
expect(engine.findData(answerDef)).toEqual("Hi! It's sunny today.");
});
/**
* 成功 - 温度を指定
* 回答を保存するデータ項目に、文字型 Markdown を指定
*/
test('Success to request - with temperature', () => {
prepareConfigs('key5', '', 'gpt-4.1-mini', '', '', '', '1', '', 'Assistant', 'Hi! How\'s the weather in Kyoto today?');
// 回答を保存するデータ項目に、文字型 Markdown を指定
const answerDef = engine.createDataDefinition('回答', 1, 'q_answer', 'STRING_MARKDOWN');
engine.setData(answerDef, '事前文字列');
configs.putObject('conf_Answer1', answerDef);
httpClient.setRequestHandler(request => {
assertRequest(request, 'key5', '', 'gpt-4.1-mini', MAX_TOKENS_DEFAULT, undefined, undefined,1, undefined, 'Assistant', 'Hi! How\'s the weather in Kyoto today?', []);
return httpClient.createHttpResponse(200, 'application/json', createResponse("Hi! It's sunny today."));
});
main();
// データ項目の値をチェック
expect(engine.findData(answerDef)).toEqual("Hi! It's sunny today.");
});
/**
* 成功 - 停止シーケンスを指定
*/
test('Success to request - with stop', () => {
const stopSequences = [];
for (let i = 0; i < MAX_STOP_SEQUENCE_NUM; i++) {
stopSequences.push(`stop${i}`);
}
const stopSequencesStr = stopSequences.join('\n') + '\n';
const answerDef = prepareConfigs('key5', '', 'gpt-4.1-nano', '', '', '', '', stopSequencesStr, 'Assistant', 'Hi! How\'s the weather in Kyoto today?');
httpClient.setRequestHandler(request => {
assertRequest(request, 'key5', '', 'gpt-4.1-nano', MAX_TOKENS_DEFAULT, undefined, undefined, 1, stopSequences, 'Assistant', 'Hi! How\'s the weather in Kyoto today?', []);
return httpClient.createHttpResponse(200, 'application/json', createResponse("Hi! It's sunny today."));
});
main();
// データ項目の値をチェック
expect(engine.findData(answerDef)).toEqual("Hi! It's sunny today.");
});
/**
* 成功 - 画像を添付。detail 指定なし reasoningEffort verbosity を指定
*/
test('Success to request - with attached images, no detail param', () => {
const answerDef = prepareConfigs('key5', '', 'o3-mini', '', 'high', 'low', '', '', '', 'Describe the images attached.');
configs.put('conf_Images1Detail', undefined);
// 添付画像を指定
const image1 = createQfile('画像1.png', 'image/png', 100);
const image2 = createQfile('画像2.jpg', 'image/jpeg', MAX_IMAGE_SIZE);
const image3 = createQfile('画像3.gif', 'image/gif', 100);
const image4 = createQfile('画像4.webp', 'image/webp', 100);
const imagesDef = engine.createDataDefinition('添付画像', 2, 'q_images', 'FILE');
engine.setData(imagesDef, [image1, image2, image3, image4]);
configs.putObject('conf_Images1', imagesDef);
const imageContentTypes = ['image/png', 'image/jpeg', 'image/gif', 'image/webp'];
httpClient.setRequestHandler(request => {
assertRequest(request, 'key5', '', 'o3-mini', MAX_TOKENS_DEFAULT, 'high', 'low', 1, undefined, '', 'Describe the images attached.', imageContentTypes, undefined);
return httpClient.createHttpResponse(200, 'application/json', createResponse("The first one is a blue circle and the second one is a red square."));
});
main();
// データ項目の値をチェック
expect(engine.findData(answerDef)).toEqual("The first one is a blue circle and the second one is a red square.");
});
/**
* 成功 - 画像を添付。detail を指定 reasoningEffort verbosity を指定
*/
test('Success to request - with attached images, detail param set', () => {
const answerDef = prepareConfigs('key5', '', 'gpt-5-nano', '', 'medium', 'medium', '', '', '', 'Describe the images attached.');
configs.put('conf_Images1Detail', 'low');
// 添付画像を指定
const image1 = createQfile('画像1.png', 'image/png', 100);
const image2 = createQfile('画像2.jpg', 'image/jpeg', MAX_IMAGE_SIZE);
const imagesDef = engine.createDataDefinition('添付画像', 2, 'q_images', 'FILE');
engine.setData(imagesDef, [image1, image2]);
configs.putObject('conf_Images1', imagesDef);
const imageContentTypes = ['image/png', 'image/jpeg'];
httpClient.setRequestHandler(request => {
assertRequest(request, 'key5', '', 'gpt-5-nano', MAX_TOKENS_DEFAULT, 'medium', 'medium', 1, undefined, '', 'Describe the images attached.', imageContentTypes, 'low');
return httpClient.createHttpResponse(200, 'application/json', createResponse("The first one is a blue circle and the second one is a red square."));
});
main();
// データ項目の値をチェック
expect(engine.findData(answerDef)).toEqual("The first one is a blue circle and the second one is a red square.");
});
]]>