3
2
2025-12-01
(C) Questetra, Inc. (MIT License)
This item sends a message to an Anthropic Claude model on Amazon Bedrock
and stores the response in the specified data item.
この工程は、Amazon Bedrock 上で動作する Anthropic Claude
のモデルにメッセージを送信し、回答をデータ項目に保存します。
-
-
-
-
-
-
-
https://support.questetra.com/bpmn-icons/service-task-aws-bedrock-anthropic-claude-chat/
https://support.questetra.com/ja/bpmn-icons/service-task-aws-bedrock-anthropic-claude-chat/
iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAABE9JREFUWEfN
l3tMlWUcxz/v4SCXA38gtyPTiXo2m8YdEp3cUzGxOdw0E5tRNqlsMDGlRRE5kFJjqUDLKEJsYuCN
xD8qA5oMGeBhXpIdbiuRrORSkOKBt71vHuIcDnRU2un5893v8nl/z/f3e55HwMpLsDS/V1CQo6Pe
MR6EKBECAG/A5b5/D9AhQBOI5weVg+VdDQ2DlsT+VwDvBZFqpXJkpyiwFbC3JChwRxAp0OsVOR1X
v+uezGdSgLl+YVtB2CuAysLERmYiDICY2qatKZjIf0IAjX9EniiKSQ+T2NRHEIR83aWql83FMgug
8Q8vEUWenYrkhhiCwFHdpeqN4+BMP0zln1tSCaMKSHsuIORP5Z+bxhIRk8ZqYhRAUrvCdkT3sIKz
FFoS5sg9hcbQHaMAGt/wD0SBZEsDPYqdIJKra65OkWLIANKQcdCrfjPt85zMXcTGRGCjtGFo6B6f
lRwnfMkibv58i2073ubwwRw8Pdx4bUcGcbHRrIlbQfnpc2xcvwZnJxV37w6Rd7iYwuJSU947fyoH
XKVhJQNo/MISRIRiU6uQQF96evvRtXXw8YE9eKk9adG147NwPum79/Hum9txUqnI2nuQVSuiUXu6
09f/OwqFgoQtycya6SWH/PGnrnEFExA36bQ1R+4DhH8iQqKp1aJgf57bsJaZXmpEUcTe3o6S0lO8
tHkDpScqeGp5FPrhYWrrGglbEkJ9YzNKpZLVK2PQtXVypvJrjpVXMDAwfioLUKjTVr8gA8zzC2/k
7/k+uubMnkVBbha9fX18efIsq2JjUHu48Xp6Nvuz07n1y69Ms53GjZvduLtNZ4bak4MfFVF2upLl
0WGsfXoli58I5JuqC6SkZZqTTFOrtjrQAHB7zMEiG0vO72Wmcb6mls+/KCd3z1s4ONjz4qs72ZWS
xJLQICrOfUvXzW6eT1jHja5unkncxrKopVy+2iLrpCh/H38MDLA5KdUcQE+rtnq6AUA0Z7E7PZWV
yyLR6/VcbNDi4e5Kbl4hPgvmy1tTeKSU1vZOMtJSqG/UytU58P47LF0cwjRbW2739PJhwaccP3nW
bNO0aquFSQEepdUs8R0LMG4LUrdtoa5BS82Fi5bEMrKRxCkJVaqYnZ0dZacqydp3yDSO0RaME6HU
AW9sf0Uuf+mJr2Q1P8haHx/HDLUHi4IDaL58jez9eabu/4hQ42e+DVUqRzJ2JbMseinDwyNcu66j
SXuFKz+0UFNbP9pekl3Y4hCCA3zxffwx5s2ZTW9fP8fKzrDiyQhZB1Xf1xkBGLXhRIPI4KGZ603i
pnVEhoXi6uKCjY3CbDGkyXdd1yYnlioWvzoWZ2cVRUfLJh9EE41ic1mkaRcaEoC/z0KcVI6yia69
k+strXLLWriMR7HkZNXDSAKw+nEsQVj1QjIquCm8jJrqwdzl9P93Kf0vKvHA13IDhFUfJgYIqz7N
xorIao9TCyfbQ5v9BWbg5jDAzldXAAAAAElFTkSuQmCC
{
// 認証設定を作成し、指定
const keyAuth = httpClient.createAuthSettingToken('Access Key', key);
configs.putObject('conf_AccessKey', keyAuth);
const secretAuth = httpClient.createAuthSettingToken('Secret Access Key', secret);
configs.putObject('conf_SecretKey', secretAuth);
configs.put('conf_Region', region);
configs.put('conf_Model', model);
configs.putObject('conf_UseCrossRegion', useCrossRegion);
configs.put('conf_MaxTokens', maxTokens);
configs.put('conf_Temperature', temperature);
configs.put('conf_StopSequences', stopSequences);
configs.put('conf_SystemPrompt', systemPrompt);
configs.put('conf_Message1', message);
// 回答を保存するデータ項目を作成し、指定
const answerDef = engine.createDataDefinition('回答', 1, 'q_response', 'STRING_TEXTAREA');
configs.putObject('conf_Answer1', answerDef);
engine.setData(answerDef, '事前文字列');
return answerDef;
};
/**
* 拡張思考による推論を有効にし、推論に使用するトークン数上限を指定
* @param debugTokens 推論に使用するトークン数上限
*/
const enableThinking = (debugTokens) => {
configs.putObject('conf_EnableThinking', true);
configs.put('conf_BudgetTokens', debugTokens);
};
/**
* 異常系のテスト
* @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.');
}
};
const MODEL_3_HAIKU = 'anthropic.claude-3-haiku-20240307-v1:0';;
const MODEL_3_5_HAIKU = 'anthropic.claude-3-5-haiku-20241022-v1:0';
const MODEL_4_SONNET = 'anthropic.claude-sonnet-4-20250514-v1:0';
const MODEL_4_1_OPUS = 'anthropic.claude-opus-4-1-20250805-v1:0';
const MODEL_4_5_HAIKU = 'anthropic.claude-haiku-4-5-20251001-v1:0';
const MODEL_4_5_SONNET = 'anthropic.claude-sonnet-4-5-20250929-v1:0';
const MODEL_4_5_OPUS = 'anthropic.claude-opus-4-5-20251101-v1:0';
/**
* リージョンコードの形式が不正 - ハイフンを含まない
*/
test('Region Code is invalid - no hyphens', () => {
const key = 'key-12345';
const secret = 'secret-67890';
const region = 'invalidregioncode';
prepareConfigs(key, secret, region, MODEL_4_1_OPUS, false, '', '', '', '', 'Hi. How are you?');
assertError('Region Code is invalid.');
});
/**
* リージョンコードの形式が不正 - ハイフンの間の文字列が長すぎる
*/
test('Region Code is invalid - too many characters', () => {
const key = 'key-12345';
const secret = 'secret-67890';
const region = 'eu-toomanycharacters-1';
prepareConfigs(key, secret, region, MODEL_4_1_OPUS, false, '', '', '', '', 'Hi. How are you?');
assertError('Region Code is invalid.');
});
/**
* モデルの指定が不正 - 不正な文字を含む
*/
test('Model is invalid - contains an invalid character', () => {
const key = 'key-12345';
const secret = 'secret-67890';
const region = 'us-east-1';
prepareConfigs(key, secret, region, 'anthropic.claude-テスト', false, '1280', '', '', '', 'Hi. How are you?');
assertError('Model is invalid. It contains an invalid character.');
});
/**
* モデルの指定が不正 - anthropic.claude で始まらない
*/
test('Model is invalid - does not start with the prefix', () => {
const key = 'key-12345';
const secret = 'secret-67890';
const region = 'us-east-1';
prepareConfigs(key, secret, region, 'invalid-model-2.0:1', false, '1280', '', '', '', 'Hi. How are you?');
assertError('Model is invalid. It must start with "anthropic.claude".');
});
/**
* リージョンコードがクロスリージョン推論の対象でない
*/
test('Region is not supported by cross-region inference', () => {
const key = 'key-12345';
const secret = 'secret-67890';
const region = 'ca-sentral-1';
prepareConfigs(key, secret, region, MODEL_4_1_OPUS, true, '1280', '', '', '', 'Hi. How are you?');
assertError('Cross-region inference is not supported in ca-sentral-1.');
});
/**
* トークン数の最大値が正の整数でない - 数字でない文字を含む場合
*/
test('Maximum number of tokens to consume must be a positive integer - includes a non-numeric character', () => {
const key = 'key-12345';
const secret = 'secret-67890';
const region = 'us-east-1';
prepareConfigs(key, secret, region, MODEL_4_1_OPUS, false, '1.1', '', '', '', 'Hi. How are you?');
assertError('Maximum number of tokens to consume must be a positive integer.');
});
/**
* トークン数の最大値が正の整数でない - ゼロ
*/
test('Maximum number of tokens to consume must be a positive integer - 0', () => {
const key = 'key-12345';
const secret = 'secret-67890';
const region = 'us-east-1';
prepareConfigs(key, secret, region, MODEL_4_1_OPUS, false, '0', '', '', '', 'Hi. How are you?');
assertError('Maximum number of tokens to consume must be a positive integer.');
});
/**
* 推論に使用するトークン数の上限が不正 - 数字でない文字を含む場合
*/
test('Maximum number of tokens for reasoning is invalid - includes a non-numeric character', () => {
const key = 'key-12345';
const secret = 'secret-67890';
const region = 'us-east-1';
prepareConfigs(key, secret, region, MODEL_4_1_OPUS, false, '', '', '', '', 'Hi. How are you?');
enableThinking('1024.1');
assertError('Maximum number of tokens for reasoning must be an integer greater than or equal to 1024.');
});
/**
* 推論に使用するトークン数の上限が不正 - 1024 より小さい
*/
test('Maximum number of tokens for reasoning is invalid - less than 1024', () => {
const key = 'key-12345';
const secret = 'secret-67890';
const region = 'us-east-1';
prepareConfigs(key, secret, region, MODEL_4_1_OPUS, false, '', '', '', '', 'Hi. How are you?');
enableThinking('1023');
assertError('Maximum number of tokens for reasoning must be an integer greater than or equal to 1024.');
});
/**
* 推論に使用するトークン数の上限が不正 - 消費するトークン数の上限と同じ
*/
test('Maximum number of tokens for reasoning is invalid - equal to maximun number of tokens', () => {
const key = 'key-12345';
const secret = 'secret-67890';
const region = 'us-east-1';
prepareConfigs(key, secret, region, MODEL_4_1_OPUS, false, '2048', '', '', '', 'Hi. How are you?');
enableThinking('2048');
assertError('Maximum number of tokens for reasoning must be less than maximum number of tokens to consume.');
});
/**
* 推論に使用するトークン数の上限が不正 - 消費するトークン数の上限より大きい
*/
test('Maximum number of tokens for reasoning is invalid - greater than maximun number of tokens', () => {
const key = 'key-12345';
const secret = 'secret-67890';
const region = 'us-east-1';
prepareConfigs(key, secret, region, MODEL_4_1_OPUS, false, '2048', '', '', '', 'Hi. How are you?');
enableThinking('2049');
assertError('Maximum number of tokens for reasoning must be less than maximum number of tokens to consume.');
});
/**
* 温度が不正 - 数字、小数点でない文字を含む
*/
test('Invalid temperature - includes a non-numeric character', () => {
const key = 'key-12345';
const secret = 'secret-67890';
const region = 'us-east-1';
const maxTokens = '100';
const temperature = '-0.1';
const message = 'Hi. How are you?';
prepareConfigs(key, secret, region, MODEL_4_1_OPUS, false, maxTokens, temperature, '', message);
assertError('Temperature must be a number from 0 to 1.');
});
/**
* 温度が不正 - 1 を超える
*/
test('Invalid temperature - bigger than 1', () => {
const key = 'key-12345';
const secret = 'secret-67890';
const region = 'us-east-1';
const maxTokens = '100';
const temperature = '1.1';
const message = 'Hi. How are you?';
prepareConfigs(key, secret, region, MODEL_4_1_OPUS, false, maxTokens, temperature, '', message);
assertError('Temperature must be a number from 0 to 1.');
});
/**
* 温度が不正 - 拡張思考モードが有効なのに、1 でない
*/
test('Invalid temperature - not 1 when thinking is enabled', () => {
const key = 'key-12345';
const secret = 'secret-67890';
const region = 'us-east-1';
const maxTokens = '';
const temperature = '0.5';
const message = 'Hi. How are you?';
prepareConfigs(key, secret, region, MODEL_4_1_OPUS, false, maxTokens, temperature, '', message);
enableThinking('');
assertError('Temperature must be 1 when Extended Thinking Mode is enabled.');
});
/**
* 停止シークエンスの数が多すぎる
*/
test('Too many stop sequences', () => {
const key = 'key-12345';
const secret = 'secret-67890';
const region = 'us-east-1';
const maxTokens = '100';
const temperature = '1.0';
const stopSequences = 'a\n'.repeat(MAX_STOP_SEQUENCES_NUM + 1);
const message = 'Hi. How are you?';
prepareConfigs(key, secret, region, MODEL_4_1_OPUS, false, maxTokens, temperature, stopSequences, message);
assertError(`Too many stop sequences. The maximum number is ${MAX_STOP_SEQUENCES_NUM}.`);
});
/**
* メッセージが空
*/
test('Message is empty', () => {
const key = 'key-12345';
const secret = 'secret-67890';
const region = 'us-east-1';
prepareConfigs(key, secret, region, MODEL_4_1_OPUS, false, '1280', '', '', '', '');
assertError('User Message is empty.');
});
/**
* 指定サイズのファイルを作成
* @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);
};
/**
* 画像、 PDF を添付
* @param {Array} files
*/
const attachFiles = (files) => {
const filesDef = engine.createDataDefinition('添付画像', 2, 'q_files', 'FILE');
engine.setData(filesDef, files);
configs.putObject('conf_Images1', filesDef);
};
/**
* 添付画像ファイルの数が多すぎる
*/
test('Too many images attached', () => {
const key = 'key-12345';
const secret = 'secret-67890';
const region = 'us-east-1';
const message = 'Describe each image attached.';
prepareConfigs(key, secret, region, MODEL_4_1_OPUS, false, '', '', '', '', message);
// 添付画像ファイルを指定
const images = [];
for (let i = 0; i < MAX_IMAGE_NUM + 1; i++) {
images.push(createQfile(`画像${i}.png`, 'image/png', 100));
}
attachFiles(images);
assertError(`Too many images attached. The maximum number is ${MAX_IMAGE_NUM}.`);
});
/**
* 添付 PDF ファイルの数が多すぎる
*/
test('Too many pdfs attached', () => {
const key = 'key-12345';
const secret = 'secret-67890';
const region = 'us-east-1';
const message = 'Describe each image attached.';
prepareConfigs(key, secret, region, MODEL_4_1_OPUS, false, '', '', '', '', message);
// 添付 PDF ファイルを指定
const docs = [];
for (let i = 0; i < MAX_DOC_NUM + 1; i++) {
docs.push(createQfile(`DOC${i}.pdf`, 'application/pdf', 100));
}
attachFiles(docs);
assertError(`Too many PDFs attached. The maximum number is ${MAX_DOC_NUM}.`);
});
/**
* 添付画像ファイルのサイズが大きすぎる
*/
test('Attached image is too large', () => {
const key = 'key-12345';
const secret = 'secret-67890';
const region = 'us-east-1';
const message = 'Describe each image attached.';
prepareConfigs(key, secret, region, MODEL_4_1_OPUS, false, '', '', '', '', message);
// 添付ファイルを指定
const image1 = createQfile('画像1.png', 'image/png', 100);
const image2 = createQfile('画像2.jpg', 'image/jpeg', MAX_IMAGE_SIZE + 1);
attachFiles([image1, image2]);
assertError(`Attached image file "画像2.jpg" is too large. The maximum size is ${MAX_IMAGE_SIZE} bytes.`);
});
/**
* 添付 PDF ファイルのサイズが大きすぎる
*/
test('Attached pdf is too large', () => {
const key = 'key-12345';
const secret = 'secret-67890';
const region = 'us-east-1';
const message = 'Describe each image attached.';
prepareConfigs(key, secret, region, MODEL_4_1_OPUS, false, '', '', '', '', message);
// 添付ファイルを指定
const doc1 = createQfile('PDF1.pdf', 'application/pdf', 100);
const doc2 = createQfile('PDF2.pdf', 'application/pdf', MAX_DOC_SIZE + 1);
attachFiles([doc1, doc2]);
assertError(`Attached PDF file "PDF2.pdf" is too large. The maximum size is ${MAX_DOC_SIZE} bytes.`);
});
/**
* 添付ファイルに許可されていない Content-Type のファイルを含む
*/
test('Content-Type of an attached file is not allowed', () => {
const key = 'key-12345';
const secret = 'secret-67890';
const region = 'us-east-1';
const message = 'Describe each image attached.';
prepareConfigs(key, secret, region, MODEL_4_1_OPUS, false, '', '', '', '', message);
// 添付ファイルを指定
const image1 = createQfile('画像1.png', 'image/png;filename="画像1.png"', 100);
const image2 = createQfile('画像2.jpg', 'image/jpeg', 200);
const image3 = createQfile('画像3.gif', 'image/gif', 300);
const image4 = createQfile('画像4.webp', 'image/webp', 100);
const imageNotAllowed = createQfile('許可されていない画像.svg', 'image/svg+xml', 200);
attachFiles([image1, image2, image3, image4, imageNotAllowed]);
assertError('Content-Type of the attached file "許可されていない画像.svg" is not allowed.');
});
/**
* API リクエストのテスト
* @param {Object} request
* @param request.url
* @param request.method
* @param request.contentType
* @param request.body
* @param {String} region
* @param {String} model
* @param {Number} maxTokens
* @param {Boolean} enableThinking
* @param {Number} budgetTokens
* @param {Number} temperature
* @param {Array} stopSequences
* @param {String} systemPrompt
* @param {String} message
* @param {Array} files
*/
const assertRequest = ({
url,
method,
contentType,
body
}, region, model, maxTokens, enableThinking, budgetTokens, temperature, stopSequences, systemPrompt, message, files = []) => {
expect(url).toEqual(`https://bedrock-runtime.${region}.amazonaws.com/model/${model}/converse`);
expect(method).toEqual('POST');
expect(contentType).toEqual('application/json');
// Authorization ヘッダのテストは省略
const bodyObj = JSON.parse(body);
expect(bodyObj.anthropic_version).toEqual(ANTHROPIC_VERSION);
expect(bodyObj.inferenceConfig.maxTokens).toEqual(maxTokens);
if (enableThinking) {
expect(bodyObj.additionalModelRequestFields.thinking.type).toEqual('enabled');
expect(bodyObj.additionalModelRequestFields.thinking.budget_tokens).toEqual(budgetTokens);
} else {
expect(bodyObj.additionalModelRequestFields).toEqual(undefined);
}
expect(bodyObj.inferenceConfig.temperature).toEqual(temperature);
expect(bodyObj.inferenceConfig.stopSequences).toEqual(stopSequences);
if (systemPrompt !== ''){
expect(bodyObj.system[0].text).toEqual(systemPrompt);
}
expect(bodyObj.messages[0].role).toEqual('user');
expect(bodyObj.messages[0].content[0].text).toEqual(message);
expect(bodyObj.messages[0].content.length).toEqual(files.length + 1);
files.forEach((file, i) => {
if(file.getContentType().split(';')[0] === "image"){
expect(bodyObj.messages[0].content[i + 1].image.format).toEqual(file.getContentType().split(';')[0].split('/')[1]);
expect(bodyObj.messages[0].content[i + 1].image.source.bytes).toEqual(base64.encodeToString(fileRepository.readFile(file)));
} else if (file.getContentType().split(';')[0] === "pdf"){
expect(bodyObj.messages[0].content[i + 1].document.format).toEqual(file.getContentType().split(';')[0].split('/')[1]);
expect(bodyObj.messages[0].content[i + 1].document.name).toEqual(`DOC${i}`);
expect(bodyObj.messages[0].content[i + 1].document.source.bytes).toEqual(base64.encodeToString(fileRepository.readFile(file)));
}
});
};
/**
* API リクエストでエラー
* クロスリージョン推論を利用する
* 選択したモデルは、クロスリージョン推論 ID が無い
*/
test('Fail to request', () => {
const key = 'key-12345';
const secret = 'secret-67890';
const region = 'ap-northeast-1';
const temperature = '';
const stopSequences = '';
const message = 'Hi. How are you?';
prepareConfigs(key, secret, region, MODEL_4_1_OPUS, true, '1280', temperature, stopSequences, '', message);
httpClient.setRequestHandler((request) => {
assertRequest(request, region, `apac.${MODEL_4_1_OPUS}`, 1280, false, undefined, 1, [], '', message);
return httpClient.createHttpResponse(400, 'application/json', '{}');
});
assertError('Failed to converse. status: 400');
});
/**
* API のレスポンスボディを作成
* @param {String} model
* @param {String} answerText
* @returns {String} response
*/
const createResponse = (model, answerText) => {
const responseObj = {
model,
stopReason: 'end_turn',
output: {
message:{
content: [
{text: answerText}
]
}
},
usage: {
inputTokens: 1024,
outputTokens: 2048
}
};
return JSON.stringify(responseObj);
};
/**
* API のレスポンスボディを作成
* 拡張思考モードが有効で、レスポンスに reasoningContent が含まれる場合
* @param {String} model
* @param {String} answerText
* @returns {String} response
*/
const createResponseWithReasoning = (model, answerText) => {
const responseObj = {
model,
stopReason: 'end_turn',
output: {
message:{
content: [
{
reasoningContent: {
reasoningText: {
signature: "signature",
text: "Reasoning..."
}
}
}, // 拡張思考が有効な場合にのみ返る
{
text: answerText
}
]
}
},
usage: {
inputTokens: 1024,
outputTokens: 2048
}
};
return JSON.stringify(responseObj);
};
/**
* レスポンスに回答を含まないため、エラー - text フィールドを持つ content がない場合
*/
test('No response content - no content with text field', () => {
const key = 'key-12345';
const secret = 'secret-67890';
const region = 'us-east-1';
const maxTokens = '';
const temperature = '';
const stopSequences = '';
const message = 'Hi. How are you?';
prepareConfigs(key, secret, region, MODEL_3_5_HAIKU, true, maxTokens, temperature, stopSequences, '', message);
enableThinking('');
const responseObj = {
model: `us.${MODEL_3_5_HAIKU}`,
stopReason: 'max_tokens',
output: {
message:{
content: [
{reasoningContext: 'Reasoning...'}
]
}
},
usage: {
inputTokens: 1024,
outputTokens: 2048
}
};
httpClient.setRequestHandler((request) => {
assertRequest(request, region, `us.${MODEL_3_5_HAIKU}`, DEFAULT['conf_MaxTokens'], true, DEFAULT['conf_BudgetTokens'], 1, [], '', message);
return httpClient.createHttpResponse(200, 'application/json', JSON.stringify(responseObj));
});
assertError('No response content generated. Stop Reason: max_tokens');
});
/**
* レスポンスに回答を含まないため、エラー - text フィールドの値が空の場合
*/
test('No response content - no content with text field', () => {
const key = 'key-12345';
const secret = 'secret-67890';
const region = 'us-east-1';
const maxTokens = '';
const temperature = '';
const stopSequences = '';
const message = 'Hi. How are you?';
prepareConfigs(key, secret, region, MODEL_4_SONNET, true, maxTokens, temperature, stopSequences, '', message);
enableThinking('');
const responseObj = {
model: `global.${MODEL_4_SONNET}`,
stopReason: 'max_tokens',
output: {
message:{
content: [
{reasoningContext: 'Reasoning...'},
{text: ''}
]
}
},
usage: {
inputTokens: 1024,
outputTokens: 2048
}
};
httpClient.setRequestHandler((request) => {
assertRequest(request, region, `global.${MODEL_4_SONNET}`, DEFAULT['conf_MaxTokens'], true, DEFAULT['conf_BudgetTokens'], 1, [], '', message);
return httpClient.createHttpResponse(200, 'application/json', JSON.stringify(responseObj));
});
assertError('No response content generated. Stop Reason: max_tokens');
});
/**
* 成功 - 必須項目のみ指定
* 最大トークン数はデフォルト値の 2048
* 温度はデフォルト値の 1 でリクエストされる
*/
test('Success - only with required configs', () => {
const key = 'key-12345';
const secret = 'secret-67890';
const region = 'eu-central-2';
const maxTokens = '';
const temperature = '';
const stopSequences = '';
const message = 'Hi. How are you?';
const answerDef = prepareConfigs(key, secret, region, MODEL_3_HAIKU, false, maxTokens, temperature, stopSequences, '', message);
const answer = 'Fine, thanks.';
httpClient.setRequestHandler((request) => {
assertRequest(request, region, MODEL_3_HAIKU, DEFAULT['conf_MaxTokens'], false, undefined, 1, [], '', message);
return httpClient.createHttpResponse(200, 'application/json', createResponse(MODEL_3_HAIKU, answer));
});
expect(main()).toEqual(undefined);
expect(engine.findData(answerDef)).toEqual(answer);
});
/**
* 成功 - 必須項目のみ指定
* 最大トークン数はデフォルト値の 2048 を指定
* 温度はデフォルト値の 1 を指定
* クロスリージョン推論を利用する
* 回答を保存するデータ項目に、文字型 Markdown を指定
*/
test('Success - only with required configs - CrossRegionModel - Markdown', () => {
const key = 'key-abcde';
const secret = 'secret-fghij';
const region = 'us-west-1';
const maxTokens = '2048';
const temperature = '1';
const stopSequences = '';
const message = 'Hi. How are you?';
prepareConfigs(key, secret, region, MODEL_3_5_HAIKU, true, maxTokens, temperature, stopSequences, '', message);
// 回答を保存するデータ項目に、文字型 Markdown を指定
const answerDef = engine.createDataDefinition('回答', 1, 'q_answer', 'STRING_MARKDOWN');
engine.setData(answerDef, '事前文字列');
configs.putObject('conf_Answer1', answerDef);
const answer = 'Fine, thanks.';
httpClient.setRequestHandler((request) => {
assertRequest(request, region,`us.${MODEL_3_5_HAIKU}`, DEFAULT['conf_MaxTokens'], false, undefined, 1, [], '', message);
return httpClient.createHttpResponse(200, 'application/json', createResponse(`us.${MODEL_3_5_HAIKU}`, answer));
});
expect(main()).toEqual(undefined);
expect(engine.findData(answerDef)).toEqual(answer);
});
/**
* 成功 - 最大トークン数、温度を指定
*/
test('Success - with max tokens and temperature', () => {
const key = 'key-99999';
const secret = 'secret-00000';
const region = 'us-east-1';
const maxTokens = '500';
const temperature = '0.5';
const stopSequences = '';
const message = 'Are you a robot?\n\nHow old are you?';
const answerDef = prepareConfigs(key, secret, region, MODEL_4_1_OPUS, false, maxTokens, temperature, stopSequences, '', message);
const answer = "Yes, I'm an AI assistant created by Anthropic.\n\nI don't have an age.";
httpClient.setRequestHandler((request) => {
assertRequest(request, region, MODEL_4_1_OPUS, 500, false, undefined, 0.5, [], '', message);
return httpClient.createHttpResponse(200, 'application/json', createResponse(MODEL_4_1_OPUS, answer));
});
expect(main()).toEqual(undefined);
expect(engine.findData(answerDef)).toEqual(answer);
});
/**
* 成功 - 温度、停止シーケンス、システムプロンプトを指定
* 添付画像のデータ項目は指定しているが、添付ファイルなし
*/
test('Success - with temperature, stop sequences and system prompt, empty attached files', () => {
const key = 'key-12345';
const secret = 'secret-67890';
const region = 'eu-central-2';
const maxTokens = '';
const temperature = '0';
const stopSequences = 'さようなら\nありがとう\nおやすみなさい';
const systemPrompt = "You are a translator. Translate the user's input into Japanese.";
const message = 'Hi. How are you?';
const answerDef = prepareConfigs(key, secret, region, MODEL_4_SONNET, false, maxTokens, temperature, stopSequences, systemPrompt, message);
attachFiles([]);
const answer = 'こんにちは。お元気ですか?';
httpClient.setRequestHandler((request) => {
assertRequest(request, region, MODEL_4_SONNET, DEFAULT['conf_MaxTokens'], false, undefined, 0, ['さようなら', 'ありがとう', 'おやすみなさい'], systemPrompt, message);
return httpClient.createHttpResponse(200, 'application/json', createResponse(MODEL_4_SONNET, answer));
});
expect(main()).toEqual(undefined);
expect(engine.findData(answerDef)).toEqual(answer);
});
/**
* 成功 - 添付画像を最大個数指定 添付 PDF を最大個数指定
* クロスリージョン推論を利用する
* 回答を保存するデータ項目に、文字型 Markdown を指定
*/
test('Success - with attached images and pdfs - CrossRegionModel - Markdown', () => {
const key = 'key-54321';
const secret = 'secret-99999';
const region = 'ap-northeast-1';
const maxTokens = '';
const temperature = '';
const stopSequences = '';
const message = 'Describe each image attached.';
prepareConfigs(key, secret, region, MODEL_4_SONNET, true, maxTokens, temperature, stopSequences, '', message);
// 回答を保存するデータ項目に、文字型 Markdown を指定
const answerDef = engine.createDataDefinition('回答', 1, 'q_answer', 'STRING_MARKDOWN');
engine.setData(answerDef, '事前文字列');
configs.putObject('conf_Answer1', answerDef);
// 添付画像を指定
const files = [];
for (let i = 0; i < MAX_IMAGE_NUM - 1; i++) {
files.push(createQfile(`画像${i}.png`, ALLOWED_MEDIA_TYPES[i % 4], 100));
}
files.push(createQfile('最大サイズの画像.jpg', 'image/jpeg;filename="最大サイズの画像.jpg"', MAX_IMAGE_SIZE));
// 添付 PDF を指定
for (let i = 0; i < MAX_DOC_NUM - 1; i++) {
files.push(createQfile(`PDF${i}.pdf`, ALLOWED_DOCUMENT_TYPES[0], 100));
}
files.push(createQfile('最大サイズのPDF.pdf', 'application/pdf;filename="最大サイズのPDF.pdf"', MAX_DOC_SIZE));
// 画像、 PDF を添付
attachFiles(files);
const answer = 'The 1st image shows a cat. The 2nd image shows a dog.';
httpClient.setRequestHandler((request) => {
assertRequest(request, region, `global.${MODEL_4_SONNET}`, DEFAULT['conf_MaxTokens'], false, undefined, 1, [], '', message, files);
return httpClient.createHttpResponse(200, 'application/json', createResponse(`global.${MODEL_4_SONNET}`, answer));
});
expect(main()).toEqual(undefined);
expect(engine.findData(answerDef)).toEqual(answer);
});
/**
* 成功 - 拡張思考を有効にし、必須項目のみ指定
*/
test('Success - thinking enabled, only with required configs', () => {
const key = 'key-abcde';
const secret = 'secret-fghij';
const region = 'us-west-1';
const message = 'Hi. How are you?';
prepareConfigs(key, secret, region, MODEL_3_HAIKU, true, '', '', '', '', message);
enableThinking('');
// 回答を保存するデータ項目に、文字型 Markdown を指定
const answerDef = engine.createDataDefinition('回答', 1, 'q_answer', 'STRING_MARKDOWN');
engine.setData(answerDef, '事前文字列');
configs.putObject('conf_Answer1', answerDef);
const answer = 'Fine, thanks.';
httpClient.setRequestHandler((request) => {
assertRequest(request, region,`us.${MODEL_3_HAIKU}`, DEFAULT['conf_MaxTokens'], true, DEFAULT['conf_BudgetTokens'], DEFAULT['conf_Temperature'], [], '', message);
return httpClient.createHttpResponse(200, 'application/json', createResponseWithReasoning(`us.${MODEL_3_HAIKU}`, answer));
});
expect(main()).toEqual(undefined);
expect(engine.findData(answerDef)).toEqual(answer);
});
/**
* 成功 - 拡張思考を有効にし、トークン数上限、推論に使用するトークン数上限、温度(1 のみ可)を指定
*/
test('Success - thinking enabled, max tokens to consume and for reasoning specified', () => {
const key = 'key-abcde';
const secret = 'secret-fghij';
const region = 'us-west-1';
const message = 'Hi. How are you?';
prepareConfigs(key, secret, region, MODEL_4_1_OPUS, true, '2500', '1', '', '', message);
enableThinking('1600');
// 回答を保存するデータ項目に、文字型(複数行)を指定
const answerDef = engine.createDataDefinition('回答', 1, 'q_answer', 'STRING_TEXTAREA');
engine.setData(answerDef, '事前文字列');
configs.putObject('conf_Answer1', answerDef);
const answer = 'Fine, thanks.';
httpClient.setRequestHandler((request) => {
assertRequest(request, region,`us.${MODEL_4_1_OPUS}`, 2500, true, 1600, 1, [], '', message);
return httpClient.createHttpResponse(200, 'application/json', createResponseWithReasoning(`us.${MODEL_4_1_OPUS}`, answer));
});
expect(main()).toEqual(undefined);
expect(engine.findData(answerDef)).toEqual(answer);
});
// Sonnet 4 でグローバルクロスリージョン推論が使われることのテストケースはすでにある
// 4.5 系のモデルでクロスリージョン推論を ON にすると、グローバルクロスリージョン推論が使われることを確認
/*
* グローバルクロスリージョン推論が使われるかどうかのテスト
* @param model モデル
* @param isGlobal グローバルクロスリージョン推論が使われることを期待する場合、true
*/
const assertGlobalCrossRegionInference = (model, isGlobal) => {
const key = 'key-abcde';
const secret = 'secret-fghij';
const region = 'us-west-1';
const message = 'Hi. How are you?';
prepareConfigs(key, secret, region, model, true, '2500', '1', '', '', message);
const expectedModel = isGlobal ? `global.${model}` : `us.${model}`;
// 回答を保存するデータ項目に、文字型(複数行)を指定
const answerDef = engine.createDataDefinition('回答', 1, 'q_answer', 'STRING_TEXTAREA');
engine.setData(answerDef, '事前文字列');
configs.putObject('conf_Answer1', answerDef);
const answer = 'Fine, thanks.';
httpClient.setRequestHandler((request) => {
assertRequest(request, region, expectedModel, 2500, false, undefined, 1, [], '', message);
return httpClient.createHttpResponse(200, 'application/json', createResponse(expectedModel, answer));
});
expect(main()).toEqual(undefined);
expect(engine.findData(answerDef)).toEqual(answer);
}
/**
* 成功 - Haiku 4.5
*/
test('Success - Haiku 4.5, global cross region inference', () => {
assertGlobalCrossRegionInference(MODEL_4_5_HAIKU, true);
});
/**
* 成功 - Sonnet 4.5
*/
test('Success - Sonnet 4.5, global cross region inference', () => {
assertGlobalCrossRegionInference(MODEL_4_5_SONNET, true);
});
/**
* 成功 - Opus 4.5
*/
test('Success - Opus 4.5, global cross region inference', () => {
assertGlobalCrossRegionInference(MODEL_4_5_OPUS, true);
});
// deprecated 予定のため選択肢から削除済みのモデルについて、従来通りの地域別クロスリージョン推論が動作することを確認
const MODEL_4_OPUS = 'anthropic.claude-opus-4-20250514-v1:0';
const MODEL_3_7_SONNET = 'anthropic.claude-3-7-sonnet-20250219-v1:0';
const MODEL_3_5_SONNET_2 = 'anthropic.claude-3-5-sonnet-20241022-v2:0';
const MODEL_3_5_SONNET = 'anthropic.claude-3-5-sonnet-20240620-v1:0';
/**
* 成功 - Opus 4
*/
test('Success - Opus 4, regional cross region inference', () => {
assertGlobalCrossRegionInference(MODEL_4_OPUS, false);
});
/**
* 成功 - 3.7 Sonnet
*/
test('Success - 3.7 Sonnet, regional cross region inference', () => {
assertGlobalCrossRegionInference(MODEL_3_7_SONNET, false);
});
/**
* 成功 - 3.5 Sonnet v2
*/
test('Success - 3.5 Sonnet v2, regional cross region inference', () => {
assertGlobalCrossRegionInference(MODEL_3_5_SONNET_2, false);
});
/**
* 成功 - 3.5 Sonnet
*/
test('Success - 3.5 Sonnet, regional cross region inference', () => {
assertGlobalCrossRegionInference(MODEL_3_5_SONNET, false);
});
]]>