VSCodeからOpenAIのAPI(ChatGPT)を呼び出すVSCode Macrosマクロ

この記事は公開から1年以上経過しています。

VSCodeでChatGPTを利用する方法は幾つかありますが、今回はVSCode Macros拡張とOpenAIのAPIを使い、汎用性の高いマクロを作成する方法を紹介します。

ちなみに入力データを外部へ丸投げする機能(ChatGPTや各種翻訳サービスなど)を業務で利用する場合は、必ず関連部署等からの承認を得ることが重要です。
知らずなのか、やらかしてしまっている方が非常に多いようですので…。

2024.3.23:
OpenAIのAPI仕様変更対応に伴い、サンプルソースコードにコスト計算方法の変更、およびモデル選択機能の追加対応等を行いました。

2023.4時点のOpenAIのAPI利用規約では、API経由で利用したデータはオプトアウトしなくてもAIのトレーニングに使用されることはなくなりましたが、30日間はOpenAIのサーバー上に保持されるため個人情報や機密情報の取り扱いには注意が必要です。

ChatGPT(Plus)を業務に利用する場合の注意点はこちらのエントリをご参照ください。

APIキーが漏洩して第三者に悪用された場合や、処理を誤って大量のリクエストを行ってしまうと意図しないAPI使用料が発生する可能性があります。APIキーの管理およびAPIの利用には十分に注意してください。

本マクロの不具合等により生じた損害については故意過失の有無に関わらずEXCEEDSYSTEMは一切の責任を負わないものとします(At your own risk)。


マクロの動作イメージ

このマクロを使い「日英相互翻訳」と「質問」を行うマクロの動作イメージを紹介します。

  1. Translateマクロで選択範囲の日本語を英語に翻訳してその結果をOutputウィンドウへ出力している様子です。
    例は和英変換ですが英文を渡せば英和変換もできます。

  2. QuestionマクロでVSCode APIの使い方の説明を問い合わせ、その結果をOutputウィンドウへ出力している様子です。
    (回答が長いため時間がかかっています)


npmライブラリのインストール手順

以下の手順で、マクロ内で利用するnpmライブラリを導入しておきます。

  1. マクロディレクトリにpackage.jsonが存在しない場合は、カレントをマクロディレクトリに移動して以下のコマンドでVSCodeMacrosで利用するプライベートnpmライブラリ用のpackage.jsonを生成します(npmライブラリをグローバルインストールする場合は不要)。

    echo '{"private":true}' >> package.json

    ※Windowsのコマンドプロンプト(cmd.exe)の場合はシングルクォート'は不要。

  2. 以下のコマンドでnpmライブラリをインストールします。

    npm i openai


マクロのサンプルソースコード

API使用料金は概算です。モデルを変更する場合やAPI利用料が改定された場合は、ソース中のコスト定数を修正してください。

厳密なプロンプトインジェクション対策などは行っていません。

Streamを使用していないためリアルタイムでの結果表示は行われません。

OpenAI.js

const vscode = require('vscode');
const process = require('process');
const { Configuration, OpenAIApi, OpenAI } = require('openai');

// Completion API result class
class RunCompletionResult {
  constructor(status, content, model, cost) {
    this.status = status;
    this.content = content;
    this.model = model;
    this.cost = cost;
  }
}

// Exported macro commands for translating and asking questions
module.exports.macroCommands = {
  Question: {
    no: 1,
    func: askFunc,
  },
  Translate: {
    no: 2,
    func: translateFunc,
  },
  'Select Model': {
    no: 3,
    func: selectModel,
  },

};

const COST_PER_TOKENS = 1000;
const GPT_MODELS = {
  'GPT-3.5 Turbo': {
    modelName: 'gpt-3.5-turbo-1106',
    apiCosts: { in: 0.001, out: 0.002 },
  },
  'GPT-4 Turbo': {
    modelName: 'gpt-4-1106-preview',
    apiCosts: { in: 0.01, out: 0.03 },
  },
};
const MODEL_NAMES = Object.keys(GPT_MODELS);

if (!global.gptModel_nai3ohye) {
  global.gptModel_nai3ohye = MODEL_NAMES[0];
}

// The API key for OpenAI are obtained from environment variables.
if (!process.env.OPENAI_API_KEY) {
  throw new Error(`Environment variable 'OPENAI_API_KEY' is not defined.`);
}

const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY })

// Output window for displaying results('up5ue9IC' is randomly generated variable name)
if (!global.up5ue9IC) {
  global.up5ue9IC = { outputWindow: vscode.window.createOutputChannel('OpenAI') };
}
const outputWindow = global.up5ue9IC.outputWindow;

// Functions for processing translate and question commands
async function translateFunc() {
  await processCommand('Translate', 'You are a Japanese-English translator and you only provide translation services', 'Translate the following:');

}

async function askFunc() {
  await processCommand('Question', 'You answer the question correctly', '');
}

// Main function for processing commands
async function processCommand(command, behavior, instruction) {
  const selectedText = await getPromptText();
  if (!selectedText) return;
  showInputText(command, selectedText);

  const result = await runCompletion(behavior, instruction + selectedText);

  if (result.status === 'error') {
    await vscode.window.showErrorMessage(result.message);
  } else if (result.status === 'ok') {
    showOutputText(result.content, result.model, result.cost);
  }
}

// Function to display input text in the output window
function showInputText(command, input) {
  outputWindow.show(false);
  outputWindow.appendLine(`--- <${command}> [${getTimeStampText()}] ---`);
  outputWindow.appendLine('Input:');
  outputWindow.appendLine(input);
  outputWindow.appendLine('');
}

// Function to display output text and token information in the output window
function showOutputText(response, model, cost) {
  outputWindow.appendLine(`Output(${model}):`);
  outputWindow.appendLine(response);
  outputWindow.appendLine('');
  outputWindow.appendLine('Tokens:');
  outputWindow.appendLine(cost);
  outputWindow.appendLine('');
}

// Function to get the prompt text
async function getPromptText() {
  const editor = vscode.window.activeTextEditor;

  if (!editor) {
    await vscode.window.showWarningMessage('Editor not found.');
    return null;
  }

  const document = editor.document;
  if (!document) {
    await vscode.window.showWarningMessage('Document not found.');
    return null;
  }

  let prompt = document.getText(editor.selection);
  if (!prompt.length) {
    prompt = await vscode.window.showInputBox({
      title: `${global.gptModel_nai3ohye} Prompt`,
    });
  }
  if (!prompt || prompt.length < 3) {
    return null;
  }

  return prompt;
}

// Function to call OpenAI's chat completion endpoint
async function runCompletion(behavior, content) {

  const { modelName, apiCosts } = GPT_MODELS[global.gptModel_nai3ohye];

  const openaiResponse = await openai.chat.completions.create({
    model: modelName,
    messages: [
      { role: 'system', content: behavior },
      { role: 'user', content: content },
    ],
    temperature: 0.3,
  });

  // Completion text
  const result = openaiResponse.choices[0].message?.content ?? '';

  // Calculate the api cost
  const { prompt_tokens, completion_tokens } = openaiResponse.usage;
  const totalAPICosts = ((prompt_tokens / COST_PER_TOKENS) * apiCosts.in
    + (completion_tokens / COST_PER_TOKENS) * apiCosts.out).toFixed(4);
  const cost = `${totalAPICosts}$(${prompt_tokens}/${completion_tokens})`;

  return new RunCompletionResult('ok', result, modelName, cost);
}

// Function to get a timestamp string
function getTimeStampText() {
  return new Date().toLocaleString('ja-JP', {
    year: 'numeric',
    month: '2-digit',
    day: '2-digit',
    hour: '2-digit',
    minute: '2-digit',
    second: '2-digit',
  });
}

// 
async function selectModel() {
  const modelName = await vscode.window.showQuickPick(MODEL_NAMES, {
    title: 'Select GPT Engine'
  });
  if (modelName) {
    global.gptModel_nai3ohye = modelName;
  }
}

GitHub Gist


OpenAI APIキーの作成

OpenAI APIを利用するためのAPIキーを作成します。
既に作成済のキーを使う場合は、この作業は不要です。

file

file

※APIキーは作成直後しか表示できないので、忘れないように注意。


環境変数の登録

前手順で取得した’API Key’をOSのユーザー環境変数に登録する。

システム環境変数に登録してしまうと当該システムを利用するすべてのユーザーがAPIキーを確認できてしまいますのでご注意ください。

環境変数名 設定値
OPENAI_API_KEY APIキーの値

環境変数の登録後にVSCodeを再起動すると、マクロからOpenAI APIを利用可能となります。

今回はOpenAIのAPIをダイレクトに利用する方法を紹介しましたが、本来APIはバックエンドから利用することが前提となっていますので、本気で作り込む場合には認証付きのWebAPIサーバー等を用意することをおすすめします。


参考ウェブサイトなど

  • OpenAI
    docs/Chat completions


以上です。

シェアする

  • このエントリーをはてなブックマークに追加

フォローする