Amazon Bedrock: Mistral AI: チャット

Overview

Auto Step icon
Configs for this Auto Step
conf_AccessKey
C1: アクセスキー *
conf_SecretKey
C2: シークレットアクセスキー *
conf_Region
C3: リージョンコード *
conf_Model
C4: モデル *
conf_MaxTokens
C5: 回答の生成に使用するトークン数の上限
conf_Temperature
C6: 温度(0.0 〜 1.0)
conf_SystemPrompt
C7: システムプロンプト#{EL}
conf_Message1
U1: ユーザメッセージ *#{EL}
conf_Images1
I1: ユーザメッセージに添付する画像
conf_Answer1
A1: 回答を保存するデータ項目 *
Script (click to open)

const SERVICE = 'bedrock';

const main = () => {
    ////// == 工程コンフィグ・ワークフローデータの参照 / Config & Data Retrieving ==
    const awsKey = configs.getObject('conf_AccessKey').getToken();
    const awsSecret = configs.getObject('conf_SecretKey').getToken();
    const region = retrieveRegion();
    const model = retrieveModel();

    const maxTokens = retrieveMaxTokens();
    const temperature = retrieveTemperature();
    const systemPrompt = configs.get('conf_SystemPrompt');
    const userMessage = retrieveUserMessage(); // 添付画像の取得も含む

    ////// == 演算 / Calculating ==
    const answer = converse(
        awsKey,
        awsSecret,
        region,
        model,
        maxTokens,
        temperature,
        systemPrompt,
        userMessage
    );

    ////// == ワークフローデータへの代入 / Data Updating ==
    saveData('conf_Answer1', answer);
}

/**
 * config からリージョンコードを読み出す
 * リージョンコードの形式として不正な場合はエラー
 * @return {String}
 */
const retrieveRegion = () => {
    const region = configs.get('conf_Region');
    // 今後リージョンが増えることも考えて、中央の最大文字数には余裕をみている
    const reg = new RegExp('^[a-z]{2}-[a-z]{4,16}-[1-9]$');
    if (!reg.test(region)) {
        throw new Error('Region Code is invalid.');
    }
    return region;
}

/**
 * config からモデル ID を読み出す
 * モデル ID として不正な場合はエラー
 * @return {String}
 */
const retrieveModel = () => {
    const model = configs.get('conf_Model');
    const reg = new RegExp('^[a-z0-9.:-]+$');
    if (!reg.test(model)) {
        throw new Error('Model is invalid. It contains an invalid character.');
    }
    const MODEL_PREFIX = 'mistral.';
    if (!model.startsWith(MODEL_PREFIX)){
        throw new Error(`Model is invalid. It must start with "${MODEL_PREFIX}".`);
    }
    return model;
}

/**
 * config から最大トークン数を読み出す
 * 正の整数でない場合、エラー
 * 未定義の場合、null を返す
 * @returns {Number}
 */
const retrieveMaxTokens = () => {
    const maxTokens = configs.get('conf_MaxTokens');
    if (maxTokens === '') {
        return null;
    }
    const regExp = new RegExp(/^[1-9][0-9]*$/);
    if (!regExp.test(maxTokens)) {
        throw new Error('Maximum number of tokens for generating output must be a positive integer.');
    }
    return parseInt(maxTokens, 10);
};

/**
 * config から温度を読み出す
 * 未定義の場合、null を返す
 * @returns {Number}
 */
const retrieveTemperature = () => {
    const temperature = configs.get('conf_Temperature');
    if (temperature === '') {
        return null;
    }
    const regExp = /^(0(\.\d+)?|1(\.0+)?)$/;
    if (!regExp.test(temperature)) {
        throw new Error('Temperature must be a number from 0 to 1.');
    }
    return parseFloat(temperature);
};

// https://docs.mistral.ai/studio-api/conversations/vision#faq
const MAX_IMAGE_NUM = 8; // Mistral AI の制限。1 リクエストにつき添付画像 8 個まで
const MAX_IMAGE_SIZE = 10000000; // Mistral AI の制限。1 ファイルにつき 10 Mb まで
const ALLOWED_MEDIA_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];

/**
 * config からユーザメッセージ(添付画像を含む)を読み出す
 * ユーザメッセージが空の場合、添付画像が不正な場合はエラー
 * @returns {Object} ユーザメッセージオブジェクト
 */
const retrieveUserMessage = () => {
    const message = configs.get('conf_Message1');
    if (message === '') {
        throw new Error('User Message is empty.');
    }
    const imageFiles = retrieveImageFiles();
    validateFiles(imageFiles, 'image', MAX_IMAGE_NUM, MAX_IMAGE_SIZE);
    const inlineImages = imageFiles.map(file => ({
        image: fileToObj(file)
    }));
    const userMessage = {
        role: 'user',
        content: [
            {
                text: message
            },
            ...inlineImages
        ]
    };
    return userMessage;

};

/**
 * config から画像ファイルを読み出す
 * 以下の場合はエラー
 * - Content-Type が許可されていない場合
 * @returns {Array<Qfile>} ファイルオブジェクトの配列
 */
const retrieveImageFiles = () => {
    const imageFiles = [];

    const filesDef = configs.getObject('conf_Images1');
    if (filesDef === null) {
        return imageFiles;
    }
    const files = engine.findData(filesDef);
    if (files === null) {
        return imageFiles;
    }
    files.forEach(file => {
        const contentType = file.getContentType().split(';')[0];
        if (ALLOWED_MEDIA_TYPES.includes(contentType)){
            imageFiles.push(file);
        } else {
            throw new Error(`Content-Type of the attached file "${file.getName()}" is not allowed.`);
        }
    });
    return imageFiles;
};

/**
 * ファイルの総数、サイズをチェックする
 * 以下の場合はエラー
 * - 添付ファイルの総数が多すぎる場合
 * - ファイルサイズが大きすぎる場合
 * @param {Array<Qfile>} ファイルオブジェクトの配列
 * @param errorLabel
 * @param maxNum
 * @param maxSize
 */
const validateFiles = (files, errorLabel, maxNum, maxSize) => {
    if (files.length > maxNum) {
        throw new Error(`Too many ${errorLabel}s attached. The maximum number is ${maxNum}.`);
    }
    files.forEach(file => {
        if (file.getLength() > maxSize) {
            throw new Error(`Attached ${errorLabel} file "${file.getName()}" is too large. The maximum size is ${maxSize} bytes.`);
        }
    });
}

/**
 * ファイルから inline コンテンツに必要な source, format を読み出し、ファイルオブジェクトを返す
 * @param {Qfile} file  ファイルオブジェクト
 */
const fileToObj = file => ({
    format : file.getContentType().split(';')[0].split('/')[1],
    source: {
        bytes: base64.encodeToString(fileRepository.readFile(file))
    }
});


/**
 * モデルの実行
 * @param awsKey
 * @param awsSecret
 * @param region
 * @param model
 * @param maxTokens
 * @param temperature
 * @param systemPrompt
 * @param userMessage
 * @returns {String} answer
 */
const converse = (
    awsKey,
    awsSecret,
    region,
    model,
    maxTokens,
    temperature,
    systemPrompt,
    userMessage
) => {
    const URL = `https://bedrock-runtime.${region}.amazonaws.com/model/${model}/converse`;
    const inferenceConfig = {};
    if (maxTokens !== null) {
        inferenceConfig.maxTokens = maxTokens;
    }
    if (temperature !== null) {
        inferenceConfig.temperature = temperature;
    }
    const payload = {
        inferenceConfig,
        messages: [userMessage]
    };

    if (systemPrompt !== '') {
        Object.assign(payload, {
            system: [
                {text :`${systemPrompt}`}
            ]
        });
    }

    const response = httpClient.begin()
        .awsSignV4(awsKey, awsSecret, region, SERVICE)
        .body(JSON.stringify(payload), 'application/json')
        .post(URL);

    const status = response.getStatusCode();
    const respTxt = response.getResponseAsString();
    if (status !== 200) {
        engine.log(respTxt);
        throw new Error(`Failed to converse. status: ${status}`);
    }
    const {output, stopReason, usage} = JSON.parse(respTxt);
    // 推論の要約 reasoningContent を返すモデルは今のところなし
    engine.log(`Stop Reason: ${stopReason}`);
    engine.log(`Input Tokens: ${usage.inputTokens}`);
    engine.log(`Output Tokens: ${usage.outputTokens}`);

    // text プロパティを持つ content をすべて連結して回答とする
    const answer1 = output.message.content
        .filter(content => content.text !== undefined)
        .map(content => content.text)
        .join('');
    if (answer1 === null || answer1.trim() === '') {
        throw new Error(`No response content generated. Stop Reason: ${stopReason}`);
    }
    return answer1;
};

/**
 * データ項目への保存
 * @param configName
 * @param data
 */
const saveData = (configName, data) => {
    const def = configs.getObject(configName);
    if (def === null) {
        return;
    }
    engine.setData(def, data);
};

        

Download

warning 自由改変可能な JavaScript (ECMAScript) コードです。いかなる保証もありません。
(アドオン自動工程のインストールは Professional editionでのみ可能です)

Notes

  • アクセスキー、シークレットアクセスキーの作成方法については、Amazon Web Services のドキュメントを参照してください
  • モデルによっては、日本語の応答が不自然になる場合があります

See Also