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