3 2 2025-10-09 (C) Questetra, Inc. (MIT License) This item sends a message to a Gemini model on Google Vertex AI and stores the response in the specified data item. この工程は、Google Vertex AI 上で動作する Gemini のモデルにメッセージを送信し、回答をデータ項目に保存します。 https://support.questetra.com/bpmn-icons/service-task-google-vertexai-gemini-chat/ https://support.questetra.com/ja/bpmn-icons/service-task-google-vertexai-gemini-chat/ iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAABRlJREFUWEfF l3lsFHUUx79vttu9KbFWa4qiIqBChAQVwaDtKkcxxiCXBgFREUy0lIKAbEm20AEaiTTlEKIhAkrk FGKQS7YlYEBUUImVRqIlHlwNSHf2Pp6Z33ZxW3ZmF4T4+28yv/fe5/fe+/7mDeF/XpRt/AFuvsVk VIYQaDCYHyTQHQAKVHsmnAfzWYB+AvHeYMT+5WE3XczGd0aAx2vYYYr45jPwJhFysnHKjCiAOn/U VvW1m1r1bLQBmMm50DcRwGIAt2cTuOMeZpwlYJbHZfsYRJzOhyZAiay8DaCGgIxZ0oNjtUKMCk+l vTYrgNGb2NDyi28NAROu59SaNoza/B62mZvHUCx1z1Wnc1Yr5SAsvaHBk84Y0ztmoh3AU7IymBm7 QZBuEkCcCcPrXfY9Sf9XAIrdbJaMvmathqubYBE2ZesCmmx35hNWvGxF+foAfj0fT7uPgTNBi63b 4QoSjq4AOKuVOSAs0vK+scwqXo2t86OTBWhN4ZAIsOQCBgmQx1hQuTmAy36dHKaUIgHATCWy7y8i FGqZrXndCiJg2zdhlA8z4dklPvjDid3yGDN6dzGIwLXjLSIDJ35PnwERDjhTP9dWpEpTABRX+x+T KH5Yr+6vFuciHGXsPRHF9FIT5nwaxODeOTjeHMOj9xnQ5y4DDjVF4R5phntrEAeb2jX7Va7jLA1o qLQeEQBOWVEvm9l6AFWjzIhEGT+cjqG8NJGB7RU27PguggeKDLinQMLkD/1Qe2XGJwGcbkl776SG qPG47HMEQInsbSDQk3oAi8aaEYkBaw+G8dYQE6avD2DBaDO2HI1gsjMX9xZImLrGj5WTrJi2LoBT 57RLkCgDH6h3OYoTANXek0TUUw9g0hO5CMcYFiPhxYFGjK7z4/1XLNh0JAJfiNGryIDVnhCqRppR tS3YrknT+WXmpvpKx/1tJfD+DVCeHsBHU6yIxBiHmmKYMMiIEUt9qB1vxZajYQzqmYNeXQyYtSGA FZMyN2EiDl/2uBydkz0QBGDSAuheKKEwj3ChlXHJz0IF72wMYv9cGxp+jqKhMYo+XQ1YuS+M5/oZ 8cfFOI41xxDTrUIqQLXSDEJXLYAt06zIsxLq9oSEhlKbcM+PUXS2kZDhxFU+vPeSRTRkzechoRit 1a4EJbKiyqG/1uYehRKWjLPAZoKQnXtbEEoQmD/KLFRw7LcYBvYwYN4IM4wGoL4xCnl7SGXVBmjX hLJ3FYGm6PWA6njxC2b07WoQwWduCECV5tajEXS7TcLQh3KEShbtCOLASf07oE0Fq+tdjqmJi2iB MkySsEsPIPnu+UeMeOPpXHErqie/u0BCvp3w5yUW8rvky6h/4SoeR2nDPPtuAVBax6aQVzmXSQlJ CPWjo+rdH2LkOwg7j0exdFcoG/42AXDr2ai9oNFN4X8/RlnchqkRhvfNwYzhJrR4GWOX6X150nKJ W1B9cwWgv5s7WY3KKQKJSTebpfaFWvdrWQy+EM6xd/tqNnnbAagPJbIylBhf3MyBhAjD9rvs+5LQ 6UYy3bngWk571d5MI1nSwCl7PwDotf8UrIMxg4XsOvrUHLmLZWUGAe/emLGcyzyVjuXpDqQ78zur /SOA+DIQiq4nGww0E0sVnkrrZ1r2GX86+q1mY16LbxwYZUzomykjbT8i3zPR8lu7W9d2/A/IugTp iAct9BYYmZ5h0MNAvDcYvdqk1MgknSDwtxHinQfnOi5km7GMGcjW0fXu+web2gY/ELMpSwAAAABJ RU5ErkJggg== { // 認証設定を作成し、指定 const jwtAuth = httpClient.createAuthSettingOAuth2JwtBearer( 'JWT', scope, '', '', privateKeyId, privateKey, serviceAccount, '' ); configs.putObject('conf_Auth', jwtAuth); configs.put('conf_Region', region); configs.put('conf_ProjectId', projectId); configs.put('conf_Model', model); configs.put('conf_MaxTokens', maxTokens); configs.put('conf_Temperature', temperature); configs.put('conf_StopSequences', stopSequences); 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 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('Region Code is invalid - no hyphens', () => { const privateKeyId = 'key-12345'; const region = 'invalidregioncode'; const projectId = 'project-67890'; const model = 'gemini-2.0-flash'; const serviceAccount = 'service@questetra.com'; const message = 'Hi. How are you?'; prepareConfigs( SCOPE, privateKeyId, PRIVATE_KEY, serviceAccount, region, projectId, model, '', '', '', message ); assertError('Region Code is invalid.'); }); /** * リージョンコードの形式が不正 - ハイフンの間の文字列が長すぎる */ test('Region Code is invalid - too many characters', () => { const privateKeyId = 'key-12345'; const serviceAccount = 'service@questetra.com'; const region = 'northamerica-northamericatoooomany1'; const projectId = 'project-67890'; const model = 'gemini-2.0-flash-lite'; const message = 'Hi. How are you?'; prepareConfigs( SCOPE, privateKeyId, PRIVATE_KEY, serviceAccount, region, projectId, model, '', '', '', message ); assertError('Region Code is invalid.'); }); /** * モデルの指定が不正 - 英数字、ピリオド、ハイフン以外の文字を含む */ test('Invalid model', () => { const privateKeyId = 'key-12345'; const serviceAccount = 'service@questetra.com'; const region = 'asia-northeast1'; const projectId = 'project-67890'; const model = '不正なモデル'; const message = 'Hi. How are you?'; prepareConfigs( SCOPE, privateKeyId, PRIVATE_KEY, serviceAccount, region, projectId, model, '', '', '', message ); assertError('Model includes an invalid character.'); }); /** * トークン数の最大値が正の整数でない - 数字でない文字を含む */ test('Maximum number of tokens must be a positive integer - includes a non-numeric character', () => { const privateKeyId = 'key-12345'; const serviceAccount = 'service@questetra.com'; const region = 'asia-northeast1'; const projectId = 'project-67890'; const model = 'gemini-2.0-flash'; const maxTokens = '1.1'; const message = 'Hi. How are you?'; prepareConfigs( SCOPE, privateKeyId, PRIVATE_KEY, serviceAccount, region, projectId, model, maxTokens, '', '', message ); assertError('Maximum number of tokens must be a positive integer.'); }); /** * トークン数の最大値が正の整数でない - ゼロ */ test('Maximum number of tokens must be a positive integer - 0', () => { const privateKeyId = 'key-12345'; const serviceAccount = 'service@questetra.com'; const region = 'asia-northeast1'; const projectId = 'project-67890'; const model = 'gemini-2.0-flash-lite'; const maxTokens = '0'; const message = 'Hi. How are you?'; prepareConfigs( SCOPE, privateKeyId, PRIVATE_KEY, serviceAccount, region, projectId, model, maxTokens, '', '', message ); assertError('Maximum number of tokens must be a positive integer.'); }); /** * 温度が不正 - 数字、小数点でない文字を含む */ test('Invalid temperature - includes a non-numeric character', () => { const privateKeyId = 'key-12345'; const serviceAccount = 'service@questetra.com'; const region = 'asia-northeast1'; const projectId = 'project-67890'; const model = 'gemini-2.0-flash'; const maxTokens = '100'; const temperature = '-0.1'; const message = 'Hi. How are you?'; prepareConfigs( SCOPE, privateKeyId, PRIVATE_KEY, serviceAccount, region, projectId, model, maxTokens, temperature, '', message ); assertError('Temperature must be a number from 0 to 2.'); }); /** * 温度が不正 - 2 を超える */ test('Invalid temperature - bigger than 2', () => { const privateKeyId = 'key-12345'; const serviceAccount = 'service@questetra.com'; const region = 'asia-northeast1'; const projectId = 'project-67890'; const model = 'gemini-2.0-flash-lite'; const maxTokens = '100'; const temperature = '2.1'; const message = 'Hi. How are you?'; prepareConfigs( SCOPE, privateKeyId, PRIVATE_KEY, serviceAccount, region, projectId, model, maxTokens, temperature, '', message ); assertError('Temperature must be a number from 0 to 2.'); }); /** * 停止シークエンスの数が多すぎる */ test('Too many stop sequences', () => { const privateKeyId = 'key-12345'; const serviceAccount = 'service@questetra.com'; const region = 'asia-northeast1'; const projectId = 'project-67890'; const model = 'gemini-2.0-flash'; const maxTokens = '100'; const temperature = '1.0'; const stopSequences = 'a\n'.repeat(6); const message = 'Hi. How are you?'; prepareConfigs( SCOPE, privateKeyId, PRIVATE_KEY, serviceAccount, region, projectId, model, maxTokens, temperature, stopSequences, message ); assertError('Too many stop sequences. The maximum number is 5.'); }); /** * メッセージが空 */ test('Message is empty', () => { const privateKeyId = 'key-12345'; const serviceAccount = 'service@questetra.com'; const region = 'asia-northeast1'; const projectId = 'project-67890'; const model = 'gemini-2.0-flash'; prepareConfigs( SCOPE, privateKeyId, PRIVATE_KEY, serviceAccount, region, projectId, model, '', '', '', '' ); assertError('User Message is empty.'); }); /** * スコープが空 */ test('Scope is empty', () => { const scope = ''; const privateKeyId = 'key-12345'; const serviceAccount = 'service@questetra.com'; const region = 'asia-northeast1'; const projectId = 'project-67890'; const model = 'gemini-2.0-flash-lite'; const message = 'Hi. How are you?'; prepareConfigs( scope, privateKeyId, PRIVATE_KEY, serviceAccount, region, projectId, model, '', '', '', message ); assertError(`Scope ${SCOPE} must be included in the scope.`); }); const ANOTHER_SCOPE = 'https://www.googleapis.com/auth/drive.readonly'; /** * スコープに必須スコープを含まない */ test('Required scope is missing', () => { const privateKeyId = 'key-12345'; const serviceAccount = 'service@questetra.com'; const region = 'asia-northeast1'; const projectId = 'project-67890'; const model = 'gemini-2.0-flash'; const message = 'Hi. How are you?'; prepareConfigs( ANOTHER_SCOPE, privateKeyId, PRIVATE_KEY, serviceAccount, region, projectId, model, '', '', '', message ); assertError(`Scope ${SCOPE} must be included in the scope.`); }); /** * 秘密鍵 ID が空 */ test('Private Key ID is empty', () => { const privateKeyId = ''; const serviceAccount = 'service@questetra.com'; const region = 'asia-northeast1'; const projectId = 'project-67890'; const model = 'gemini-2.0-flash-lite'; const message = 'Hi. How are you?'; prepareConfigs( SCOPE, privateKeyId, PRIVATE_KEY, serviceAccount, region, projectId, model, '', '', '', message ); assertError('Private Key ID is required.'); }); /** * カスタム秘密情報1 に設定されている * サービスアカウントが空。 */ test('Service Account is empty', () => { const privateKeyId = 'key-12345'; const serviceAccount = ''; const region = 'asia-northeast1'; const projectId = 'project-67890'; const model = 'gemini-2.0-flash'; const message = 'Hi. How are you?'; prepareConfigs( SCOPE, privateKeyId, PRIVATE_KEY, serviceAccount, region, projectId, model, '', '', '', message ); assertError('Service Account must be set to Custom Secret 1.'); }); /** * 指定サイズのファイルを作成 * @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); }; /** * 画像・動画を添付 * @param {Array} images */ const attachImages = (images) => { const imagesDef = engine.createDataDefinition('添付画像/動画', 2, 'q_images', 'FILE'); engine.setData(imagesDef, images); configs.putObject('conf_Images1', imagesDef); }; /** * 添付ファイルのサイズが大きすぎる */ test('Attached file is too large', () => { const privateKeyId = 'key-12345'; const serviceAccount = 'service@example.com'; const region = 'asia-northeast1'; const projectId = 'project-67890'; const model = 'gemini-2.0-flash-lite'; const message = 'Please describe each image attached.'; prepareConfigs( SCOPE, privateKeyId, PRIVATE_KEY, serviceAccount, region, projectId, model, '', '', '', message ); // 添付ファイルを指定 const image1 = createQfile('画像1.png', 'image/png', 100); const image2 = createQfile('画像2.jpg', 'image/jpeg', MAX_IMAGE_SIZE + 1); attachImages([image1, image2]); assertError( `Attached file "画像2.jpg" is too large. Each file must be less than ${MAX_IMAGE_SIZE} bytes.` ); }); /** * 添付ファイルに画像/動画/音声でないファイルを含む */ test('Attached file is neither an image, video, audio, PDF, nor text', () => { const privateKeyId = 'key-12345'; const serviceAccount = 'service@example.com'; const region = 'asia-northeast1'; const projectId = 'project-67890'; const model = 'gemini-2.0-flash'; const message = 'Please describe each image attached.'; prepareConfigs( SCOPE, privateKeyId, PRIVATE_KEY, serviceAccount, region, projectId, model, '', '', '', message ); // 添付ファイルを指定 const video = createQfile('動画', 'video/mp4', 200); const csv = createQfile('CSV ファイル', 'text/csv', 100); attachImages([video, csv]); assertError('Attached file "CSV ファイル" is neither an image, video, audio, PDF, nor text.'); }); /** * トークン取得リクエストのテスト * @param {Object} request * @param request.url * @param request.method * @param request.contentType * @param request.body * @param {String} scope * @param {String} serviceAccount */ const assertTokenRequest = ({ url, method, contentType, body }, scope, serviceAccount) => { expect(url).toEqual(URL_TOKEN_REQUEST); expect(method).toEqual('POST'); expect(contentType).startsWith('application/x-www-form-urlencoded'); const query = `grant_type=${encodeURIComponent( 'urn:ietf:params:oauth:grant-type:jwt-bearer' )}&assertion=`; expect(body).startsWith(query); const assertion = decodeURIComponent(body.substring(query.length)); const publicKey = rsa.readKeyFromX509(CERTIFICATE); expect(jwt.verify(assertion, publicKey)).toEqual(true); const payloadJson = base64.decodeFromUrlSafeString(assertion.split('.')[1]); const payload = JSON.parse(payloadJson); expect(payload.iss).toEqual(serviceAccount); expect(payload.aud).toEqual(URL_TOKEN_REQUEST); expect(payload.scope).toEqual(scope); }; /** * API リクエストのテスト * @param {Object} request * @param request.url * @param request.method * @param request.contentType * @param request.headers * @param request.body * @param {String} token * @param {String} region * @param {String} projectId * @param {String} model * @param {Number} maxTokens * @param {Number} temperature * @param {Array} stopSequences * @param {String} message * @param {Array} images */ const assertRequest = ( { url, method, contentType, headers, body }, token, region, projectId, model, maxTokens, temperature, stopSequences, message, images = [] ) => { expect(url).toEqual( `https://${region}-aiplatform.googleapis.com/v1/projects/${projectId}/locations/${region}/publishers/google/models/${model}:streamGenerateContent` ); expect(method).toEqual('POST'); expect(contentType).toEqual('application/json'); expect(headers.Authorization).toEqual(`Bearer ${token}`); const bodyObj = JSON.parse(body); expect(bodyObj.contents.parts[0].text).toEqual(message); if (maxTokens !== null) { expect(bodyObj.generation_config.maxOutputTokens).toEqual(maxTokens); } else { expect(bodyObj.generation_config.maxOutputTokens).toEqual(undefined); } if (temperature !== null) { expect(bodyObj.generation_config.temperature).toEqual(temperature); } else { expect(bodyObj.generation_config.temperature).toEqual(undefined); } expect(bodyObj.generation_config.stopSequences).toEqual(stopSequences); images.forEach((image, i) => { expect(bodyObj.contents.parts[i + 1].inlineData.mimeType).toEqual(image.getContentType()); expect(bodyObj.contents.parts[i + 1].inlineData.data).toEqual( base64.encodeToString(fileRepository.readFile(image)) ); }); }; /** * API のレスポンスボディを作成 * @param {String} answers * @returns {String} response */ const createResponse = (...answers) => { const responseObj = answers.map((answer) => ({ candidates: [ { content: { parts: [ { text: answer, }, ], }, finishReason: 'FINISH_REASON_STOP ', safetyRatings: [], citationMetadata: { citations: [], }, }, ], })); return JSON.stringify(responseObj); }; /** * API リクエストでエラー */ test('Fail to request', () => { const privateKeyId = 'key-12345'; const serviceAccount = 'service@questetra.com'; const region = 'asia-northeast1'; const projectId = 'project-67890'; const model = 'gemini-2.0-flash-lite'; const message = 'Hi. How are you?'; prepareConfigs( SCOPE, privateKeyId, PRIVATE_KEY, serviceAccount, region, projectId, model, '', '', '', message ); const token = 'token'; let reqCount = 0; httpClient.setRequestHandler((request) => { if (reqCount++ === 0) { assertTokenRequest(request, SCOPE, serviceAccount); return httpClient.createHttpResponse( 200, 'application/json', JSON.stringify({ access_token: token, }) ); } assertRequest(request, token, region, projectId, model, null, null, [], message); return httpClient.createHttpResponse(400, 'application/json', '{}'); }); assertError('Failed to invoke model. status: 400'); }); /** * API リクエストが 200 レスポンスを返すが、トークン切れで回答が空の場合 * text フィールドを持つ content がない場合 */ test('No response content generated - no content with text field', () => { const privateKeyId = 'key-12345'; const serviceAccount = 'service@questetra.com'; const region = 'asia-northeast1'; const projectId = 'project-67890'; const model = 'gemini-2.0-flash'; const maxTokens = '100'; const temperature = '1'; const message = 'Hi. How are you?'; prepareConfigs( SCOPE, privateKeyId, PRIVATE_KEY, serviceAccount, region, projectId, model, maxTokens, temperature, '', message ); const token = 'token'; let reqCount = 0; httpClient.setRequestHandler((request) => { if (reqCount++ === 0) { assertTokenRequest(request, SCOPE, serviceAccount); return httpClient.createHttpResponse( 200, 'application/json', JSON.stringify({ access_token: token, }) ); } assertRequest(request, token, region, projectId, model, 100, 1, [], message); const responseObj = [ { candidates: [ { finishReason: 'MAX_TOKENS', safetyRatings: [], citationMetadata: { citations: [], }, }, ], }, ]; return httpClient.createHttpResponse(200, 'application/json', JSON.stringify(responseObj)); }); assertError('No response content generated.'); }); /** * API リクエストが 200 レスポンスを返すが、トークン切れで回答が空の場合 * text フィールドを持つ content が undefined の場合 */ test('No response content generated - content is undefined', () => { const privateKeyId = 'key-12345'; const serviceAccount = 'service@questetra.com'; const region = 'asia-northeast1'; const projectId = 'project-67890'; const model = 'gemini-2.0-flash'; const maxTokens = '100'; const temperature = '1'; const message = 'Hi. How are you?'; prepareConfigs( SCOPE, privateKeyId, PRIVATE_KEY, serviceAccount, region, projectId, model, maxTokens, temperature, '', message ); const token = 'token'; let reqCount = 0; httpClient.setRequestHandler((request) => { if (reqCount++ === 0) { assertTokenRequest(request, SCOPE, serviceAccount); return httpClient.createHttpResponse( 200, 'application/json', JSON.stringify({ access_token: token, }) ); } assertRequest(request, token, region, projectId, model, 100, 1, [], message); const responseObj = [ { candidates: [ { content: undefined, finishReason: 'FINISH_REASON_MAX_TOKENS', safetyRatings: [], citationMetadata: { citations: [], }, }, ], }, ]; return httpClient.createHttpResponse(200, 'application/json', JSON.stringify(responseObj)); }); assertError('No response content generated.'); }); /** * API リクエストが 200 レスポンスを返すが、トークン切れで回答が空の場合 * text フィールドの値が空の場合 */ test('No response content generated - text field is blank', () => { const privateKeyId = 'key-12345'; const serviceAccount = 'service@questetra.com'; const region = 'asia-northeast1'; const projectId = 'project-67890'; const model = 'gemini-2.0-flash-lite'; const maxTokens = '100'; const temperature = '1'; const message = 'Hi. How are you?'; prepareConfigs( SCOPE, privateKeyId, PRIVATE_KEY, serviceAccount, region, projectId, model, maxTokens, temperature, '', message ); const token = 'token'; let reqCount = 0; httpClient.setRequestHandler((request) => { if (reqCount++ === 0) { assertTokenRequest(request, SCOPE, serviceAccount); return httpClient.createHttpResponse( 200, 'application/json', JSON.stringify({ access_token: token, }) ); } assertRequest(request, token, region, projectId, model, 100, 1, [], message); const responseObj = [ { candidates: [ { content: { parts: [ { text: '', }, ], }, finishReason: 'FINISH_REASON_MAX_TOKENS', safetyRatings: [], citationMetadata: { citations: [], }, }, ], }, ]; return httpClient.createHttpResponse(200, 'application/json', JSON.stringify(responseObj)); }); assertError('No response content generated.'); }); /** * 成功 * gemini-2.0-flash * 必須スコープのみ * 最大トークン数、温度、停止シーケンスを指定する * 添付ファイルなし(データ項目の指定なし) */ test('Success - with max tokens, no attached files', () => { const privateKeyId = 'key-67890'; const region = 'asia-northeast1'; const projectId = 'project-12345'; const serviceAccount = 'service@example.com'; const model = 'gemini-2.0-flash'; const maxTokens = '2048'; const temperature = '0.5'; const stopSequences = 'a\nb\nc\nd\ne\n'; const message = 'Hi. How are you?'; const answerDef = prepareConfigs( SCOPE, privateKeyId, PRIVATE_KEY, serviceAccount, region, projectId, model, maxTokens, temperature, stopSequences, message ); const token = 'token-12345'; let reqCount = 0; httpClient.setRequestHandler((request) => { if (reqCount++ === 0) { assertTokenRequest(request, SCOPE, serviceAccount); return httpClient.createHttpResponse( 200, 'application/json', JSON.stringify({ access_token: token, }) ); } assertRequest( request, token, region, projectId, model, 2048, 0.5, ['a', 'b', 'c', 'd', 'e'], message ); return httpClient.createHttpResponse( 200, 'application/json', createResponse('Fine, thanks. ', 'How about you?') ); }); expect(main()).toEqual(undefined); expect(engine.findData(answerDef)).toEqual('Fine, thanks. How about you?'); }); /** * 成功 * gemini-2.0-flash-lite * 必須スコープ以外も指定 * 最大トークン数、温度を指定 * 停止シーケンス指定なし * 添付ファイルなし(データ項目は指定しているが、添付ファイルなし) */ test('Success - withmax tokens, empty attached files', () => { const scope = `${SCOPE} ${ANOTHER_SCOPE}`; const privateKeyId = 'key-12345'; const region = 'asia-southeast1'; const projectId = 'project-67890'; const serviceAccount = 'service@questetra.com'; const model = 'gemini-2.0-flash-lite'; const maxTokens = '1024'; const temperature = '0'; const message = 'こんにちは。'; const answerDef = prepareConfigs( scope, privateKeyId, PRIVATE_KEY, serviceAccount, region, projectId, model, maxTokens, temperature, '', message ); attachImages([]); const token = 'token-67890'; let reqCount = 0; httpClient.setRequestHandler((request) => { if (reqCount++ === 0) { assertTokenRequest(request, scope, serviceAccount); return httpClient.createHttpResponse( 200, 'application/json', JSON.stringify({ access_token: token, }) ); } assertRequest(request, token, region, projectId, model, 1024, 0, [], message); return httpClient.createHttpResponse( 200, 'application/json', createResponse('こんにちは。元気ですか?') ); }); expect(main()).toEqual(undefined); expect(engine.findData(answerDef)).toEqual('こんにちは。元気ですか?'); }); /** * 成功 * gemini-2.0-flash * 必須スコープ以外も指定 * 最大トークン数、温度、停止シーケンスを指定しない * 画像ファイルを複数添付 */ test('Success - with default tokens, with a number of attached images', () => { const scope = `${ANOTHER_SCOPE} ${SCOPE}`; const privateKeyId = 'key-12345'; const region = 'asia-southeast1'; const projectId = 'project-67890'; const serviceAccount = 'service@questetra.com'; const model = 'gemini-2.0-flash'; const message = 'Please describe each image attached.'; const answerDef = prepareConfigs( scope, privateKeyId, PRIVATE_KEY, serviceAccount, region, projectId, model, '', '', '', message ); // 添付ファイルを指定 const images = []; for (let i = 0; i < 10; i++) { images.push(createQfile(`画像${i}.png`, 'image/png', 100)); } attachImages(images); const token = 'token-98765'; let reqCount = 0; httpClient.setRequestHandler((request) => { if (reqCount++ === 0) { assertTokenRequest(request, scope, serviceAccount); return httpClient.createHttpResponse( 200, 'application/json', JSON.stringify({ access_token: token, }) ); } assertRequest(request, token, region, projectId, model, null, null, [], message, images); return httpClient.createHttpResponse( 200, 'application/json', createResponse('The 1st image shows a cat. The 2nd image shows a dog.') ); }); expect(main()).toEqual(undefined); expect(engine.findData(answerDef)).toEqual( 'The 1st image shows a cat. The 2nd image shows a dog.' ); }); /** * 成功 * gemini-2.0-flash-lite * 必須スコープのみ * 最大トークン数、停止シーケンスを指定しない * 温度を指定 * 動画ファイルを 1 つ添付 */ test('Success - with default tokens, with an attached video', () => { const privateKeyId = 'key-12345'; const region = 'asia-southeast1'; const projectId = 'project-67890'; const serviceAccount = 'service@questetra.com'; const model = 'gemini-2.0-flash-lite'; const temperature = '1.00'; const message = 'Please describe the video attached.'; const answerDef = prepareConfigs( SCOPE, privateKeyId, PRIVATE_KEY, serviceAccount, region, projectId, model, '', temperature, '', message ); // 添付ファイルを指定 const video = createQfile('動画.mp4', 'video/mp4', MAX_IMAGE_SIZE); attachImages([video]); const token = 'token-98765'; let reqCount = 0; httpClient.setRequestHandler((request) => { if (reqCount++ === 0) { assertTokenRequest(request, SCOPE, serviceAccount); return httpClient.createHttpResponse( 200, 'application/json', JSON.stringify({ access_token: token, }) ); } assertRequest(request, token, region, projectId, model, null, 1, [], message, [video]); return httpClient.createHttpResponse( 200, 'application/json', createResponse('The video shows a sleeping cat.') ); }); expect(main()).toEqual(undefined); expect(engine.findData(answerDef)).toEqual('The video shows a sleeping cat.'); }); /** * 成功 * gemini-2.5-pro * 必須スコープのみ * 最大トークン数、停止シーケンスを指定しない * 温度を指定(最大値の 2) * 動画ファイル、音声ファイル、テキストファイルを添付 */ test('Success - with default tokens, with a video and an audio', () => { const privateKeyId = 'key-12345'; const region = 'asia-southeast1'; const projectId = 'project-67890'; const serviceAccount = 'service@questetra.com'; const model = 'gemini-2.5-pro'; const temperature = '2.00'; const message = 'Please describe the files attached.'; const answerDef = prepareConfigs( SCOPE, privateKeyId, PRIVATE_KEY, serviceAccount, region, projectId, model, '', temperature, '', message ); // 添付ファイルを指定 const video = createQfile('動画.mov', 'video/mov', 500); const audio = createQfile('音声.mp3', 'audio/mp3', 500); const text = createQfile('テキスト.txt', 'text/plain', 500); attachImages([video, audio, text]); const token = 'token-98765'; let reqCount = 0; httpClient.setRequestHandler((request) => { if (reqCount++ === 0) { assertTokenRequest(request, SCOPE, serviceAccount); return httpClient.createHttpResponse( 200, 'application/json', JSON.stringify({ access_token: token, }) ); } assertRequest(request, token, region, projectId, model, null, 2, [], message, [video]); return httpClient.createHttpResponse( 200, 'application/json', createResponse( 'The video shows a sleeping cat, the audio sounds like a cheering voice and the text describes a cat.' ) ); }); expect(main()).toEqual(undefined); expect(engine.findData(answerDef)).toEqual( 'The video shows a sleeping cat, the audio sounds like a cheering voice and the text describes a cat.' ); }); /** * 成功 * gemini-2.5-flash * レスポンスに content プロパティが空の候補を含む */ test('Success - includes candidate without content', () => { const privateKeyId = 'key-12345'; const region = 'asia-southeast1'; const projectId = 'project-67890'; const serviceAccount = 'service@questetra.com'; const model = 'gemini-2.5-flash'; const temperature = ''; const message = 'Hi. How are you?'; const answerDef = prepareConfigs( SCOPE, privateKeyId, PRIVATE_KEY, serviceAccount, region, projectId, model, '', temperature, '', message ); const token = 'token-98765'; let reqCount = 0; httpClient.setRequestHandler((request) => { if (reqCount++ === 0) { assertTokenRequest(request, SCOPE, serviceAccount); return httpClient.createHttpResponse( 200, 'application/json', JSON.stringify({ access_token: token, }) ); } assertRequest(request, token, region, projectId, model, null, null, [], message); const responseObj = ['Hi. ', 'dummy', 'I am fine. ', 'Thank you.'].map((answer) => ({ candidates: [ { content: { parts: [ { text: answer, }, ], }, }, ], })); responseObj[1].candidates[0].content = undefined; // content プロパティを空に return httpClient.createHttpResponse(200, 'application/json', JSON.stringify(responseObj)); }); expect(main()).toEqual(undefined); expect(engine.findData(answerDef)).toEqual('Hi. I am fine. Thank you.'); }); /** * 成功 * gemini-2.5-flash-lite * 必須スコープのみ * 最大トークン数、停止シーケンスを指定しない * 温度を指定 * 画像ファイル、PDF ファイルを添付 * 回答を保存するデータ項目に、文字型 Markdown を指定 */ test('Success - with default tokens, with a image and an PDF', () => { const privateKeyId = 'key-12345'; const region = 'asia-southeast1'; const projectId = 'project-67890'; const serviceAccount = 'service@questetra.com'; const model = 'gemini-2.5-flash-lite'; const temperature = '1.00'; const message = 'Please describe the image and pdf attached.'; prepareConfigs( SCOPE, privateKeyId, PRIVATE_KEY, serviceAccount, region, projectId, model, '', temperature, '', message ); // 回答を保存するデータ項目に、文字型 Markdown を指定 const answerDef = engine.createDataDefinition('回答', 1, 'q_answer', 'STRING_MARKDOWN'); engine.setData(answerDef, '事前文字列'); configs.putObject('conf_Answer1', answerDef); // 添付ファイルを指定 const image = createQfile('画像.png', 'image/png', 1000); const pdf = createQfile('PDF.pdf', 'application/pdf', 1000); attachImages([image, pdf]); const token = 'token-98765'; let reqCount = 0; httpClient.setRequestHandler((request) => { if (reqCount++ === 0) { assertTokenRequest(request, SCOPE, serviceAccount); return httpClient.createHttpResponse( 200, 'application/json', JSON.stringify({ access_token: token, }) ); } assertRequest(request, token, region, projectId, model, null, 1, [], message, [image, pdf]); return httpClient.createHttpResponse( 200, 'application/json', createResponse('The PDF is a description of the image.') ); }); expect(main()).toEqual(undefined); expect(engine.findData(answerDef)).toEqual('The PDF is a description of the image.'); }); ]]>