前回までにやってきたこと#
- HTMLにtextareaを追加
- vscode.lm APIを使ってLLMとの通信機能を実装
- 基本的なチャット機能が動作することを確認
今回やること#
- LLMにツール使用機能を追加
- writefileツールの実装
- プロンプトにツール説明を追加
- JSON形式でのツール実行コマンド処理を実装
VSCode Extension AI Agent作成 - ファイル書き込みツール#
生成AIエージェントは生成AIに実行可能なツールを提供し、生成AIに命令を生成させることで動くアプリケーションです。 今回は生成AIにファイルを作成できるツール機能を提供、実行できるようにしてみましょう。
src/extension.ts (全体)#
extension.tsを変更していきます。
import * as vscode from 'vscode';
export function activate(context: vscode.ExtensionContext) {
const provider = new MagiViewProvider();
context.subscriptions.push(
vscode.window.registerWebviewViewProvider(
"main.view",
provider,
),
);
}
class MagiViewProvider implements vscode.WebviewViewProvider {
public async resolveWebviewView(webviewView: vscode.WebviewView) {
webviewView.webview.options = {
enableScripts: true,
};
webviewView.webview.onDidReceiveMessage(async (data) => {
if (data.type === 'promptEntered') {
webviewView.webview.postMessage({
type: 'addElement',
text: data.text
});
const models = await vscode.lm.selectChatModels({ vendor: 'copilot', family: 'gpt-4.1' });
const model = models[0];
//この行を削除 const messages = [vscode.LanguageModelChatMessage.User(data.text)];
//ここから挿入
const prompt = `ユーザーの依頼:${data.text}
ユーザーの依頼を実現するために、適切なアクションを決定してJSONで回答してください。
回答は必ず以下のJSON形式で返してください:
{"tool":"利用するツール","args":["ツールに渡すパラメータ1","ツールに渡すパラメータ2"]}
使用可能なツール:
- "writefile": ファイルを作成・編集する場合。argsは["ファイルパス","ファイル内容"]
- "message": ユーザーにメッセージを返す場合。argsは["ユーザに見せたいメッセージ"]
ユーザーの依頼内容を分析し、ファイル操作が必要な場合は"writefile"、説明やメッセージが必要な場合は"message"を選択してください。
JSON以外の文字は一切含めず、純粋なJSONのみを返してください。`;
const messages = [vscode.LanguageModelChatMessage.User(prompt)];
//ここまで挿入
const response = await model.sendRequest(messages);
let returnTextFromVscodeLm = '';
for await (const fragment of response.text) {
returnTextFromVscodeLm += fragment;
}
/* ここから削除
webviewView.webview.postMessage({
type: 'addElement',
text: returnTextFromVscodeLm
});
ここまで削除 */
//ここから挿入
try {
// 生成AIからの応答をJSONとしてパース
const returnJSON = JSON.parse(returnTextFromVscodeLm);
if (returnJSON.tool === 'message') {
// メッセージツールの場合:Webviewにメッセージを表示
webviewView.webview.postMessage({
type: 'addElement',
text: returnJSON.args[0]
});
} else if (returnJSON.tool === 'writefile') {
// ファイル書き込みツールの場合:ファイルを作成
const filePath = returnJSON.args[0];
const fileContent = returnJSON.args[1];
// 現在のワークスペースフォルダを取得
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
if (workspaceFolder) {
// ワークスペースフォルダ内のファイルパスを構築
const fullPath = vscode.Uri.joinPath(workspaceFolder.uri, filePath);
// ファイルを作成
await vscode.workspace.fs.writeFile(fullPath, Buffer.from(fileContent, 'utf8'));
// 成功メッセージを表示
webviewView.webview.postMessage({
type: 'addElement',
text: `ファイル "${filePath}" を作成しました。`
});
} else {
webviewView.webview.postMessage({
type: 'addElement',
text: 'ワークスペースが開かれていません。'
});
}
} else {
// 未知のツールの場合
webviewView.webview.postMessage({
type: 'addElement',
text: `未知のツール: ${returnJSON.tool}`
});
}
} catch (error) {
// JSONパースエラーの場合は元のテキストをそのまま表示
webviewView.webview.postMessage({
type: 'addElement',
text: `JSONパースエラー: ${returnTextFromVscodeLm}`
});
}
//ここまで挿入
}
});
webviewView.webview.html = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Agent</title>
</head>
<body>
Hello World!
<textarea id="input-textarea" data-testid="input-textarea" rows="4" style="width:100%" placeholder="Enter text and press Enter..."></textarea>
<div id="output" data-testid="output"></div>
<script>
const vscode = acquireVsCodeApi();
const textarea = document.getElementById('input-textarea');
const output = document.getElementById('output');
textarea.addEventListener('keydown', function(event) {
// Enterキーが押された時
if (event.key === 'Enter') {
// IMEの変換中(composing状態)でない場合のみ処理
if (!event.isComposing) {
event.preventDefault(); // デフォルトの改行を防ぐ
const text = textarea.value.trim();
if (text) {
// VS Codeにメッセージを送信
vscode.postMessage({
type: 'promptEntered',
text: text
});
textarea.value = '';
}
}
}
});
// VS Codeからのメッセージを受け取る
window.addEventListener('message', event => {
const message = event.data;
if (message.type === 'addElement') {
const newDiv = document.createElement('div');
newDiv.textContent = message.text;
output.appendChild(newDiv);
}
});
// composition系のイベントも念のため処理
let isComposing = false;
textarea.addEventListener('compositionstart', function() {
isComposing = true;
});
textarea.addEventListener('compositionend', function() {
isComposing = false;
});
</script>
</body>
</html>`;
}
}
動かしてみる。#
npm run compile
でプロジェクトをビルドしてから、
Visual Studio CodeのメニューのRun→Start Debuggingを押すと新しくVisuao Studio Codeが立ち上がります。
すでにExtensionを立ち上げている場合はソースを表示しているVisual Studio Codeの
で、再起動矢印を押すと反映されます。
Extensionが動いているVisual Studio Codeでいずれかのディレクトリを開いて、 textareaにファイルを作成するような命令をお願いしてみましょう。
占い結果を表示するJavaScriptのコードをuranai.jsに作成して。
入力してEnterキーを押すと、生成AIがuranai.jsを作ってくれるはずです!きっと! これで最初の生成AIエージェントが作成できました!おめでとうございます!
コードの解説#
要点を解説していきます。
const prompt#
生成AIエージェントのキモ部分です。JSON形式で応答すること、JSONの形式、利用できるツールを指定しています。 いかにこのプロンプトを書くかが、生成AIエージェントの動作を決めてくるので、様々な工夫が可能な場所です。 色々と変更してみて、どのように動きが変化するか試してみるのも面白いでしょう。
JSON.parse(returnTextFromVscodeLm)以下#
生成AIが返答したJSONを解析し、生成AIの命令を判定、命令による処理分岐を行います。ここではwritefileというツールを生成AIが読んだときに、ファイルを出力する機能をVisual Studio Codeのファイル生成機能を使って実現しています。
まとめ#
コーディング生成AIエージェントの最小機能、コードを作成する、ができました!
今回やったこと#
- 命令をJSONとして返答し、ツールを実行するようなプロンプトを作成。
- 生成AIが生成した命令をJSONとしてうけとり、解析、ツールを動かすようにする。
- writefileメソッドを実装してファイル作成機能を追加
- 生成AIツールを使ってソースコードを出力できることを確認
次にやること#
次はファイル読み込みツールを作成して生成AIに使わせてみましょう。