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); }); ]]>