true 3 2 2024-12-26 (C) Questetra, Inc. (MIT License) This item generates an image using Stability AI's Stable Diffusion 1.0 on Amazon Bedrock. この工程は、Amazon Bedrock 上で動作する Stability AI の Stable Diffusion 1.0 を用いて、画像を生成します。 https://support.questetra.com/bpmn-icons/service-task-aws-bedrock-stable-diffusion-image-generate/ https://support.questetra.com/ja/bpmn-icons/service-task-aws-bedrock-stable-diffusion-image-generate/ 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_Message', message); configs.put('conf_Negative', negative); configs.put('conf_Size', size); configs.put('conf_Style', style); configs.put('conf_FileName', fileName); // ベース画像を添付するデータ項目を作成し、指定 const initImageDef = engine.createDataDefinition('ベース画像', 1, 'q_initImage', 'FILE'); configs.putObject('conf_InitImage', initImageDef); engine.setData(initImageDef, initImages); // シードを保持している / 保存するデータ項目を作成し、指定 const seedDef = engine.createDataDefinition('シード', 2, 'q_seed', 'STRING_TEXTFIELD'); configs.putObject('conf_Seed', seedDef); engine.setData(seedDef, seed); // 生成された画像を保存するデータ項目を作成し、指定 const fileDef = engine.createDataDefinition('生成された画像', 3, 'q_file', 'FILE'); configs.putObject('conf_File', fileDef); return {seedDef, fileDef}; }; /** * 異常系のテスト * @param errorMsg */ const assertError = (errorMsg) => { try { main(); fail(); } catch (e) { expect(e.message).toEqual(errorMsg); } }; /** * リージョンコードの形式が不正 - ハイフンを含まない */ test('Region Code is invalid - no hyphens', () => { const key = 'key-12345'; const secret = 'secret-67890'; const region = 'invalidregioncode'; const message = 'masterpiece,high quality,extremely detailed face,1 girl,full body'; const negative = 'flat color, flat shading, interlocked fingers'; prepareConfigs(key, secret, region, message, negative, '1024x1024', null, '3d-model', null, '生成された画像.png'); 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'; const message = 'masterpiece,high quality,extremely detailed face,1 girl,full body'; const negative = 'flat color, flat shading, interlocked fingers'; prepareConfigs(key, secret, region, message, negative, '1024x1024', null, '3d-model', null, '生成された画像.png'); assertError('Region Code is invalid.'); }); /** * プロンプトが空 */ test('Prompt is blank', () => { const key = 'key-12345'; const secret = 'secret-67890'; const region = 'us-east-1'; const negative = 'flat color, flat shading, interlocked fingers'; prepareConfigs(key, secret, region, '', negative, '1024x1024', null, '3d-model', null, '生成された画像.png'); assertError('Prompt is blank.'); }); /** * ファイル名が空 */ test('File name is blank', () => { const key = 'key-12345'; const secret = 'secret-67890'; const region = 'us-east-1'; const message = 'masterpiece,high quality,extremely detailed face,1 girl,full body'; const negative = 'flat color, flat shading, interlocked fingers'; prepareConfigs(key, secret, region, message, negative, '1024x1024', null, '3d-model', null, ''); assertError('File name is blank.'); }); /** * ベースとする画像ファイルが 2 つ以上添付されている */ test('More than one base image files attached', () => { const key = 'key-12345'; const secret = 'secret-67890'; const region = 'us-east-1'; const message = 'masterpiece,high quality,extremely detailed face,1 girl,full body'; const negative = 'flat color, flat shading, interlocked fingers'; const initImages = new java.util.ArrayList(); initImages.add(engine.createQfile('画像1.png', 'image/png', 'aaa')); initImages.add(engine.createQfile('画像2.png', 'image/png', 'bbb')); prepareConfigs(key, secret, region, message, negative, '1024x1024', initImages, '3d-model', null, '生成された画像.png'); assertError('More than one base image files attached.'); }); /** * ベースとする画像ファイルの Content-Type が image/ で始まらない */ test('The base image file is not an image', () => { const key = 'key-12345'; const secret = 'secret-67890'; const region = 'us-east-1'; const message = 'masterpiece,high quality,extremely detailed face,1 girl,full body'; const negative = 'flat color, flat shading, interlocked fingers'; const initImages = new java.util.ArrayList(); initImages.add(engine.createQfile('画像でないファイル', 'text/plain', 'aaa')); prepareConfigs(key, secret, region, message, negative, '1024x1024', initImages, '3d-model', null, '生成された画像.png'); assertError('The base image file is not an image.'); }); /** * シードに数字でない文字を含む */ test('Seed is invalid - includes invalid character', () => { const key = 'key-12345'; const secret = 'secret-67890'; const region = 'us-east-1'; const message = 'masterpiece,high quality,extremely detailed face,1 girl,full body'; const negative = 'flat color, flat shading, interlocked fingers'; prepareConfigs(key, secret, region, message, negative, '1024x1024', null, '3d-model', '-1', '生成された画像.png'); assertError(`Seed must be an integer from 0 to ${MAX_SEED}.`); }); /** * シードが上限値を超える */ test('Seed is invalid - exceeds the limit', () => { const key = 'key-12345'; const secret = 'secret-67890'; const region = 'us-east-1'; const message = 'masterpiece,high quality,extremely detailed face,1 girl,full body'; const negative = 'flat color, flat shading, interlocked fingers'; prepareConfigs(key, secret, region, message, negative, '1024x1024', null, '3d-model', String(MAX_SEED + 1), '生成された画像.png'); assertError(`Seed must be an integer from 0 to ${MAX_SEED}.`); }); /** * API リクエストのテスト * @param {Object} request * @param request.url * @param request.method * @param request.contentType * @param request.body * @param {String} region * @param {String} message * @param {String} negative * @param {Number} width * @param {Number} height * @param {QfileView} initImage * @param {String} style * @param {Number} seedNum */ const assertRequest = ({ url, method, contentType, body }, region, message, negative, width, height, initImage, style, seedNum) => { expect(url).toEqual(`https://bedrock-runtime.${region}.amazonaws.com/model/${MODEL}/invoke`); expect(method).toEqual('POST'); expect(contentType).toEqual('application/json'); // Authorization ヘッダのテストは省略 const bodyObj = JSON.parse(body); expect(bodyObj.text_prompts[0]).toEqual({ text: message, weight: 1.0 }); if (negative !== undefined) { expect(bodyObj.text_prompts[1]).toEqual({ text: negative, weight: -0.5 }); } if (initImage !== undefined) { expect(bodyObj.init_image).toEqual(base64.encodeToString(fileRepository.readFile(initImage))); expect(bodyObj.width).toEqual(undefined); expect(bodyObj.height).toEqual(undefined); } else { expect(bodyObj.init_image).toEqual(undefined); expect(bodyObj.width).toEqual(width); expect(bodyObj.height).toEqual(height); } expect(bodyObj.style_preset).toEqual(style); expect(bodyObj.seed).toEqual(seedNum); }; /** * API リクエストでエラー */ test('Fail in API request', () => { const key = 'key-12345'; const secret = 'secret-67890'; const region = 'us-east-1'; const message = 'masterpiece,high quality,extremely detailed face,1 girl,full body'; const negative = 'flat color, flat shading, interlocked fingers'; prepareConfigs(key, secret, region, message, negative, '1024x1024', null, '3d-model', null, '生成された画像.png'); httpClient.setRequestHandler((request) => { assertRequest(request, region, message, negative, 1024, 1024, undefined, '3d-model', undefined); return httpClient.createHttpResponse(400, 'application/json', '{}'); }); assertError('Failed to invoke model. status: 400'); }); /** * API のレスポンスボディを作成 * @param {String} seed * @param {String} base64 * @returns {String} response */ const createResponse = (seed, base64) => { const responseObj = { artifacts: [{ seed, base64, finishReason: 'SUCCESS' }] }; return JSON.stringify(responseObj); }; /** * 成功 - 必須項目のみ指定。ベースとなる画像、シードはデータ項目の指定はあるが、値が空 */ test('Success - only required params', () => { const key = 'key-12345'; const secret = 'secret-67890'; const region = 'us-east-1'; const message = 'masterpiece,high quality,extremely detailed face,1 girl,full body'; const {seedDef, fileDef} = prepareConfigs(key, secret, region, message, '', '', null, '', null, '生成された画像.png'); const seedNum = 1234567890; httpClient.setRequestHandler((request) => { assertRequest(request, region, message, undefined, undefined, undefined, undefined, undefined, undefined); return httpClient.createHttpResponse(200, 'application/json', createResponse(seedNum, base64.encodeToString('aaa'))); }); expect(main()).toEqual(undefined); expect(engine.findData(seedDef)).toEqual(String(seedNum)); const files = engine.findData(fileDef); expect(files.size()).toEqual(1); expect(files.get(0).getName()).toEqual('生成された画像.png'); expect(files.get(0).getContentType()).toEqual('image/png'); }); /** * 成功 - 必須項目のみ指定。ベースとなる画像、シードはデータ項目の指定なし * 生成された画像を保存するデータ項目に、他のファイルがすでに添付されている場合 */ test('Success - only required params, data item for initImage or seed is not selected', () => { const key = 'key-12345'; const secret = 'secret-67890'; const region = 'us-east-1'; const message = 'masterpiece,high quality,extremely detailed face,1 girl,full body'; const {seedDef, fileDef} = prepareConfigs(key, secret, region, message, '', '', null, '', null, '生成された画像.png'); // データ項目の指定をはずす configs.put('conf_InitImage', ''); configs.put('conf_Seed', ''); // 生成された画像を保存するデータ項目に、他のファイルをあらかじめ添付しておく engine.setData(fileDef, [engine.createQfile('既存のファイル.txt', 'text/plain', 'aaa')]); const seedNum = 1234567890; httpClient.setRequestHandler((request) => { assertRequest(request, region, message, undefined, undefined, undefined, undefined, undefined, undefined); return httpClient.createHttpResponse(200, 'application/json', createResponse(seedNum, base64.encodeToString('123'))); }); expect(main()).toEqual(undefined); expect(engine.findData(seedDef)).toEqual(null); // データ項目の指定が外れているので const files = engine.findData(fileDef); expect(files.size()).toEqual(2); expect(files.get(0).getName()).toEqual('既存のファイル.txt'); expect(files.get(1).getName()).toEqual('生成された画像.png'); expect(files.get(1).getContentType()).toEqual('image/png'); }); /** * 成功 - ネガティブプロンプト、サイズ、スタイル、シードを指定 */ test('Success - with negative prompt, size, style and seed', () => { const key = 'key-12345'; const secret = 'secret-67890'; const region = 'us-east-1'; const message = 'masterpiece,high quality,extremely detailed face,1 girl,full body,pink hair,blue eyes'; const negative = 'flat color, flat shading, interlocked fingers'; const seedNum = MAX_SEED; const {seedDef, fileDef} = prepareConfigs(key, secret, region, message, negative, '1152x896', null, '3d-model', String(seedNum), 'Image.png'); httpClient.setRequestHandler((request) => { assertRequest(request, region, message, negative, 1152, 896, undefined, '3d-model', seedNum); return httpClient.createHttpResponse(200, 'application/json', createResponse(seedNum, base64.encodeToString('bbb'))); }); expect(main()).toEqual(undefined); expect(engine.findData(seedDef)).toEqual(String(seedNum)); const files = engine.findData(fileDef); expect(files.size()).toEqual(1); expect(files.get(0).getName()).toEqual('Image.png'); expect(files.get(0).getContentType()).toEqual('image/png'); }); /** * 成功 - ベースとなる画像を指定すると、サイズは無視される */ test('Success - with base image file, ignoring size', () => { const key = 'key-09876'; const secret = 'secret-54321'; const region = 'us-east-1'; const message = 'masterpiece,high quality,extremely detailed face,1 girl,full body,pink hair,blue eyes'; const negative = 'flat color, flat shading, interlocked fingers'; const initImage = engine.createQfile('ベースとなる画像.png', 'image/png', 'aaa'); const seedNum = 0; const {seedDef, fileDef} = prepareConfigs(key, secret, region, message, negative, '1152x896', [initImage], 'neon-punk', String(seedNum), 'Image2.png'); httpClient.setRequestHandler((request) => { assertRequest(request, region, message, negative, undefined, undefined, initImage, 'neon-punk', seedNum); return httpClient.createHttpResponse(200, 'application/json', createResponse(seedNum, base64.encodeToString('ccc'))); }); expect(main()).toEqual(undefined); expect(engine.findData(seedDef)).toEqual(String(seedNum)); const files = engine.findData(fileDef); expect(files.size()).toEqual(1); expect(files.get(0).getName()).toEqual('Image2.png'); expect(files.get(0).getContentType()).toEqual('image/png'); }); ]]>