When Prompts Become Plugins – Generating User Extensions with Graal Script Agent

原文はこちら。
The original article was written by Christian Humer (Consulting Researcher, Oracle).
https://medium.com/graalvm/when-prompts-become-plugins-34a737d30f54

AIはソフトウェアの構築方法に変革をもたらしていますが、その恩恵の多くは依然として、すでにコーディングができる人々に集中しています。コーディングエージェントは、ソフトウェアの記述、実行、修正を行うことができるため、開発者の生産性を向上させますが、一般のアプリケーションユーザーも、自らプログラマーになることなく、同様の変革の恩恵を受けられるべきです。

アプリケーションユーザーは、多くの場合、必要な結果、例えば特定の検索、レポート、検証ルール、可視化、ルーティングポリシー、あるいはワークフロー―などを把握しています。実装方法を知らなくても、その結果を十分に説明できます。しかし、拡張ポイントを研究したり、自分たちのために生成されたコードをレビューしたりすることは期待できません。

しかし、AIによって生成されたコードを、誰のレビューも受けずに本番アプリケーションに一般ユーザーがインストールすることを許容できるでしょうか?一見したところ、誠実な答えは「ありえねー」以外にありません。ですがこれには、生成されたコードを実行する前にその信頼性を確認しなければならないという前提があります。

もし、生成されたコードが有用で、直接的な破壊的な動作を行わないとしたらどうでしょうか?

それが、私たちのGraal Script Agentライブラリの開発動機です。

From prompt to result

Graal Script Agent は、アプリケーションがエンドユーザーからのプロンプトをサンドボックス化されたプラグインに変換できるようにするライブラリです。アプリケーションの所有者が、許可される拡張ポイントを定義します。モデルは、本番データにアクセスせずに、ユーザーのプロンプトと拡張契約に基づいて各スクリプトを生成します。その後、アプリケーションは必要に応じてそれをローカルで実行できます。

最初のデモとして、誰もが親しんでいるサンプルである PetClinic に、新しい「Script Agent」タブを追加してみました。

GitHub – graalvm/micronaut-petclinic-graal-script-agent · GitHub

さて、ユーザーの立場になって考えてみましょう。Frankは、ペットの飼い主へのフォローアップを試みている獣医師です。彼は飼い主の名前を忘れてしまいましたが、その飼い主がMadisonのどこかに住んでいることは覚えています。PetClinicの組み込み検索機能が、姓のみによる飼い主の検索のみサポートしていると仮定すれば、彼は行き詰まってしまいます。

Frankは動物の扱いは抜群ですが、ソフトウェアに関しては全くの素人です。今日は彼にとって幸運な日です。なぜなら、そのままで済むからです。複雑な検索画面やクエリ言語を覚える代わりに、彼は単に覚えていることと、必要なデータを説明するだけでよいからです。

Madison在住の飼い主を電話番号付きで表示してください。

そして、これが結果です。

A generated PetClinic query showing owners in Madison and their telephone numbers

彼は望んでいたもの、つまりMadisonに住む飼い主リストを手に入れました。各飼い主の名前には電話番号が併記されているので、おそらくこれで彼の記憶が蘇ることでしょう。

何が起きたのでしょうか? エージェントはクエリ拡張ポイントを選択し、アプリケーションが公開しているクエリ専用APIに対してスクリプトを生成しました。この拡張ポイントのすべての機能は非破壊的であるため、PetClinicはスクリプトを直接実行し、その結果を表示できるのです。

話を進める前に、結果の下にあるAdd to Find Owners page(「飼い主検索」ページに追加する)ボタンに注目してください。これをクリックすると、生成されたスクリプトがプラグインとして保存され、PetClinicの通常のFind Owners(飼い主検索)ページからこのクエリを直接利用できるようになります。

保存されたクエリが実行されるたびに、プラグインは現在のデータに対してローカルで実行します。別のモデル呼び出しは行いません。本番環境のオブジェクトはアプリケーション内に留まり、その機能は削除されるまで利用可能なままです。生成されたコードを誰かがレビューすることはありません。

クエリはアプリケーションのデータを変更できないため、単純なケースです。しかし、もしFrankがPetClinicに変更を依頼したらどうなるでしょうか?

George Franklinの飼い犬として、2022年5月1日生まれのFidoという名前の犬を追加し、2026年3月1日に年次健康診断の来院記録を追加してください。

今回、エージェントは別の拡張ポイント、つまりデータ変更用に設計されたものを選択します。生成されたスクリプトは、飼い主データを照会したり、ペットや飼い主の作成・変更を行うAPIを呼び出したりすることができます。ただし、スクリプトの実行中、ホストは要求された変更をコミットするのではなく記録し、その結果を表示します。

Frankは、提示されたペットや訪問内容を確認し、変更を実行するかどうかを選択できるようになりました。彼は、生成されたコードではなく、ドメイン固有の結果を確認します。実行ボタンを押すと、PetClinicは承認された変更内容を検証し、永続化します。

選択された拡張ポイントがセキュリティポリシーを決定します。クエリは直接実行できますが、変更には確認と承認が必要です。

これが中核となる考え方です。それでは、Graal Script Agentを使用して、このようなプラグインメカニズムを自分で構築する方法を見ていきましょう。

Defining the Extension Points

拡張ポイントとは、アプリケーションが意図的に新しい動作を組み込めるようにした箇所のことです。Javaでは、プラグインが何を返す必要があるか、またどのアプリケーションAPIを使用できるかを定義するインターフェースとして表現できます。

PetClinicのextension contract は、次のように整理できます。

sealed interface Extension
        permits QueryExtension, ModificationExtension {
}

non-sealed interface QueryExtension extends Extension {
    QueryResult execute(OwnerQueryApi ownersApi);
}

non-sealed interface ModificationExtension extends Extension {
    void execute(OwnerQueryApi ownersApi,
            ModificationApi modificationApi);
}

interface OwnerQueryApi {
    List<OwnerView> findByCityStartingWith(String cityPrefix);
}

interface ModificationApi {
    void addPet(String ownerFirstName, String ownerLastName,
            String petName, String petTypeName, String birthDate);

    void addVisit(String ownerFirstName, String ownerLastName,
            String petName, String visitDate, String description);
}

Extensionは、スキーマ検出の出発点です。そのシールドされた階層構造は利用可能な拡張シェイプを定義し、各executeメソッドはスクリプトのエントリポイントと、そこで利用可能なアプリケーションAPIを定義します。子インターフェースは非シールド状態なので、完成したスクリプトを選択されたインターフェースにバインドできます。この単一メソッドのシェイプがおそらく最も一般的ですが、拡張契約には複数のメソッドや、より複雑な型階層が含まれる場合もあります。

拡張ポイントが定義されれば、拡張スクリプトの生成に必要なのは、モデルコネクタ、ゲスト言語、およびユーザーのプロンプトだけです。

Model model = /* LangChainModel.of(...) or SpringAiModel.of(...)*/;
String prompt = "Show owners in Madison with their telephone numbers.";

Script<Extension> extensionScript;
try (ScriptAgent agent = ScriptAgent.newBuilder(model)
                           .language("js").build()) {
    extensionScript = agent.generate(
            Extension.class,
            prompt);
}

まず、Graal Script Agent は Extension からcontractを検出し、それをモデルに提示して、許可された拡張タイプのいずれかを生成するようモデルに要求します。Frankの最初のプロンプトの場合はQueryExtension、2番目のプロンプトの場合はModificationExtensionです。スキーマに基づいてリクエストを実装できない場合、無効なプラグインを返すのではなく、有益なメッセージを表示して生成が失敗します。

この例では、language(“js”)によって、スクリプト言語として JavaScript が設定されています。JavaScript はコンパクトで、組み込みの GraalJS を通じてローカルで実行されます。Graal Script Agent では、必要に応じて GraalPy を使用して Python スクリプトを作成することできます。

上記の簡略化されたcontractに基づいて、Frankの変更リクエストに対して生成された JavaScript は、次のようなものになるでしょう。

util.implement(types.ModificationExtension, {
  execute(ownersApi, modificationApi) {
    modificationApi.addPet("George", "Franklin", "Fido",  "dog", "2022-05-01");
    modificationApi.addVisit("George", "Franklin", "Fido", "2026-03-01", "annual checkup");
  }
})

スクリプトエージェントのランタイムは、モデルがcontractを容易に満たせるよう、util.implement などのヘルパーを提供しています。これらの内部的な詳細はフレームワークが処理します。より複雑なプロンプトや大規模なスキーマでは当然、より精巧なスクリプトが生成されますが、ここでは基本的な構成を示しています。

この例では、Javaインターフェースを使用してプラグインのcontractを定義し、Graal Script Agentがそれを自動的に検出できるようにしています。拡張ポイントガイドでは、このような契約の設計方法についてさらに詳しく説明しています。

Graal Script Agent User Manual

Sandboxing the Plugin

生成された Script<Extension> をシリアライズおよび永続化できますが、アクティブなプラグインではありません。アプリケーションでこれを使用するには、実行のためにスクリプトを GraalVM polyglot contextにバインドする必要があります:

try (Context context = Sandbox.UNTRUSTED.newContext(
        extensionScript.language())) {
    Extension extension = extensionScript.bind(context);

    if (extension instanceof QueryExtension query) {
        QueryResult result = query.execute(ownerQueryApi);
        display(result);
    } else if (extension instanceof ModificationExtension modification) {
        ModificationPlan plan = new ModificatinPlan();
        modification.execute(plan, ownerQueryApi);
        if (reviewByUser(plan)) {
            validateAndPersist(plan);
        }
    }
}

Sandbox.UNTRUSTEDは、生成されたソースコードを潜在的に悪意のあるものとみなします。ホストのファイルシステム、ネットワーク、プロセス、環境、および任意のJavaのアクセスをブロックし、ゲストを隔離されたランタイム環境で実行するとともに、CPU時間、メモリ、スレッド、出力、およびプログラムの複雑さを制限します。

Graal Script Agentは、スキーマ駆動型のホストバインディングにより、そのゲストランタイムとアプリケーション間の境界を保護します。このプラグインがアクセスできるのは、拡張スキーマで宣言されたメンバーと型のみです。宣言されていないメンバーについては、基盤となる Java オブジェクトがそれ以上の機能を公開していても、アクセスできません。

いずれのレイヤーも、危険な公開 API を安全にすることはできません。公開されたホストメソッドについては、引き続き、認証、検証、テナント境界、および副作用ポリシーの適用が必須となります。サンドボックス ポリシー、リソース制限、アイソレート、および本番環境での考慮事項に関するより詳細な説明については、最近公開された サンドボックスに関するブログ記事をご覧ください。

Sandboxing Script Extensions with GraalVM | by Christian Humer | graalvm | Jul, 2026 | Medium

Giving the Model Context

コーディングエージェントは、シェルへのアクセスが可能になったことで、その有用性が飛躍的に高まりました。初期のアシスタントは主にコードの提案を行うだけでしたが、モダンなエージェントはコードを記述し、実行し、結果を確認し、修正します。コードは、エージェントとその環境をつなぐ万能なアダプターとなったのです。

Graal Script Agentは、アプリケーションによって制御される境界内で、この同じ考え方を適用します。Frankのプロンプトは、彼が望む結果を記述していますが、PetClinicのJava APIを記述していません。生成されたスキーマプロンプトがそのベースを補完します。つまり、JavaScriptやPythonといった言語固有の形式で、利用可能なメソッド、型、レコード、列挙型の値、および期待される結果の形状を記述するのです。

より複雑な拡張については、Graal Script Agentは、シェルアクセスではなく、アプリケーションが制御するオーサリングツールを使用した反復的なフィードバックループを追加します。

  • ドラフト編集機能により、モデルは1つのスクリプトを作成・修正できます。
  • スキーマ検査機能は、APIが膨大すぎてすべてを提示できない場合に、必要に応じて型の詳細情報を提供します。
  • サンプル検査機能は、代表的な合成またはサニタイズされたアプリケーションオブジェクトをモデルに提示し、モデルはスクリプトを使用してそれを検査できます。
  • モック実行機能は、ドラフトを制限された環境で実行し、モデルが失敗を観察して修正できるようにします。
  • アプリケーション定義のテストは、プラグインを受け入れる前にその動作を検証し、テストに失敗した際にモデルにフィードバックを提供します。

すべての拡張機能にすべてのツールが必要なわけではありません。Madison(に住む飼い主の)クエリの場合、明確なプロンプトと小規模なスキーマだけで十分かもしれません。より大規模な、あるいはコンテキストに依存する拡張ポイントでは、サンプル、実行フィードバック、およびテストが有用です。アプリケーション定義のテストが設定されている場合、それらすべてにパスして初めて完了が成功します。

デフォルトでは、モデルは実際のアプリケーションデータではなく、ユーザーのプロンプトと拡張APIを記述するスキーマを受け取ります。サンプル検査は明示的なオプトイン機能であり、選択されたアプリケーションオブジェクトをモデルに可視化します。機密データを扱うアプリケーションでは、合成またはサニタイズされたサンプル、あるいはローカルモデルを使用する必要があります。

Turn on the music!

子供の頃、私はWinampのmusic visualizationに夢中でした。何時間もそれを見つめていたのを覚えています。自分で作るのは手の届かないことのように感じていました。グラフィックプログラミングも数学も難しすぎたからです。

今はだいぶ年を取りましたが、Graal Script Agentを使えば、次のようなごく小さな拡張ポイントを公開するだけで、自然言語を使って独自のWinampビジュアライゼーションを生成できるプラグインシステムを構築できます。

void draw(PixelBuffer buffer, int tick, AudioFrame audio);

生成されたプラグインは、毎フレーム、ピクセルバッファと簡単なオーディオのサマリーを受け取ります。このごくわずかな仕様だけで、UIツールキットやオーディオシステム、ファイル、スレッドを一切公開せずに、バー、波形、パルス、ビートフラッシュ、スペクトル効果などを生成できます。

それでは、two rotating dodecahedra (2つの回転する十二面体)と指示してみましょう。

このモデルはレンダリングループの一部ではなく、生成されたスクリプトのみがループに含まれます。これらのスクリプトはローカルで実行され、デモが1秒あたり30フレームを目標とする中、320×180ピクセルのバッファを埋めていきます。GraalJSがホットな描画関数をコンパイルするため、サンドボックス内でも可視化はスムーズに動作します。

ソースコードは以下からどうぞ。

graal-script-agent/examples/src/main/java/org/graalvm/scriptagent/examples/WinampDemo.java at main · graalvm/graal-script-agent · GitHub

Filtering compiler graphs!

Graal Script Agentを、GraalVMチームがコンパイラグラフの検査に使用するツールである、Ideal Graph Visualizer(IGV)にも統合しました。

Ideal Graph Visualizer

これらのグラフは非常に大規模になることがあるため、調査に関連する少数のノードやパスを見つける目的で、手作業でカスタムフィルタを作成することがよくありますが、Graal Script Agentを使えば、代わりに探したいものや強調表示したいものを記述するだけで、対応するフィルタプラグインが生成できます。

IGVは、極めて小さいWinampのcontractとは正反対の事例を示しています。IGVは充実したグラフAPIを公開していますが、具体的なノードクラス、プロパティ、および関係性は、調査対象のコンパイラグラフによって異なります。静的なスキーマは安定したグラフ操作を記述していますが、データの正確な形状が明らかになるのは、実際に作成する段階になってからです。サンプル検査機能により、モデルは代表的なグラフを検証し、一般的な規約から推測するのではなく、観測されたノード名、プロパティ、依存関係、ブロック構造、および実行頻度に基づいて生成されたフィルタを確立できます。

以下は、グラフの中で最も一般的な制御フローパスをフィルタリングしたIGVのスクリーンショットです。これは、私たちのワークフローにとって非常に有用な拡張機能です。

Below, a generated filter has reduced a large compiler graph to its most frequently executed path.(以下で、生成されたフィルタによって、大規模なコンパイラグラフが、最も頻繁に実行されるパスに絞り込まれた例を示します)

この統合機能のソースコードは、まもなく公開される予定です。

Where It Fits and Where It Doesn’t

一部の動作については、プロンプトで作成される拡張機能として公開すべきではありません。コアとなる価格計算、承認決定、あるいは取り消し不可能な一括処理などは、より強力な保証と正規の開発プロセスを必要とする場合があります。

保存済み検索、アドホックレポート、ルーティングルール、UIフィルター、ダッシュボードカード、可視化、データ探索、検証ロジックなど、範囲が限定されたカスタマイズが最適な候補です。これらは多くの場合、特定のユーザー、チーム、テナント、またはタスクに固有のものであり、重要であるほど価値はあるものの、専門的すぎて開発を行うには不向きです。

拡張機能は変更を提案することもありますが、その場合、アプリケーションにはより厳格なワークフローが必要です。つまり、提案された操作をレビューし、ドメイン用語で提示し、書き込みを有効にする前に確認を求める必要があります。正直なところ、この領域についてはまだ十分に検討しきれていないため、その限界がどこにあるのか正確にはわかっていません。

有用な拡張機能がすべて恒久的なプラグインになる必要はありません。多くの場合、最大の価値は複雑な単発タスクから生まれます。ユーザーは必要な結果は分かっているものの、アプリケーションの既存の画面やコントロールを通じてそれを達成する方法が分からないのです。生成された拡張機能は一度実行すればよく、その後は破棄してもかまいません。

チャットボットと同様に、やり取りは自然言語によるリクエストから始まります。一般的なチャットボットとは異なり、結果はアプリケーションの型、データ、UI、および安全性のワークフローと統合されます。アプリケーションで定義されたAPIを通じてタスクを実行し、ユーザーが普段作業している場所で結果を表示できます。

Try it yourself

Graal Script Agentはプレビュー版ですが、今すぐお試しいただけます!

ドキュメントには、スキーマの検出、スクリプトの生成、サンドボックス内での実行、および作成ツールを網羅した最小限のアプリケーションを用いた入門ガイドが含まれています。

Graal Script Agent User Manual

まずは小規模から始めましょう。クエリ専用のJavaインターフェースを1つ定義します。信頼できないコードによる呼び出しを許可しても問題ないメソッドのみを公開してください。その後、エージェントにその実装を促し、適切に構成されたサンドボックス内で結果を実行します。

Sandboxing Script Extensions with GraalVM | by Christian Humer | graalvm | Jul, 2026 | Medium

現時点では、これを使って構築したアプリケーションはごくわずかです。そのため、この段階でのフィードバックは非常に重要です。プロンプトで作成する拡張機能が、現在開発中のプロジェクトに役立つと思われる場合は、ぜひご連絡いただき、当社のコミュニティSlack内の #script-agent チャンネルにご参加ください。また、遭遇した問題については、遠慮なくGitHubでissueを登録してください。

Slack でgraalvmに参加する | Slack
GitHub – graalvm/graal-script-agent: Graal Script Agent is a Java library for adding AI-authored extensions to applications. · GitHub

Further reading

Graal Script Agent User Manualセットアップ、スキーマ検出、ランタイムバインディング、およびオーサリングツールについては、ここから始めてください
Sandboxing Script Extensions with GraalVM | by Christian Humer | graalvm | Jul, 2026 | Medium
Sandboxing Script Extensions with GraalVM – Logico Inside
サンドボックスポリシー、リソース制限、アイソレート、および本番環境での運用に関する考慮事項について詳しく解説しています。
GitHub – graalvm/micronaut-petclinic-graal-script-agent · GitHub冒頭で紹介したデモ
GitHub – graalvm/graal-script-agent: Graal Script Agent is a Java library for adding AI-authored extensions to applications. · GitHubContributionはこちらから

コメントを残す

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください