アクションハンドラーの記述
Adobe LLM Apps UIでアクションを作成した後、メタデータはLLM Apps APIに保存されますが、その背後にコードはまだ存在しません。 このガイドでは、LLM プラットフォーム(ChatGPTやClaudeなど)がアクションを呼び出したときに実行されるハンドラー関数の記述について説明します。
プロジェクトのレイアウト、ローカル開発、テストの詳細については、開発を参照してください。
開発者契約
ハンドラーのみを書きます。 それ以外のすべて(アクション名、説明、入力スキーマ、注釈、ウィジェットの表示、権限、CSP)はLLM Apps UIにあり、デプロイ時に自動的にランタイムに配信されます。 リポジトリ内のメタデータを手作業で編集したり、コードにツールを登録したりすることはありません。
actions/<name>/index.jsactions.json (メタデータスナップショット)はじめに
リンクされたリポジトリーには、ハンドラーを記述する前にプロジェクト構造が必要です。 Adobe LLM Apps ボイラープレート を複製して、空の開始点から開始します。
アプリの作成中にリンクしたリポジトリにコンテンツをプッシュします(例:your-org/your-repo)。
コードを配置したら、次を実行します。
npm install
これにより、@adobe/llm-apps-runtimeを含むすべての依存関係がインストールされます。これは、MCP プロトコル通信、アクション検出、およびリクエストのルーティングを処理するランタイムです。 ランタイムは直接操作しません。ビルド時にentry.jsによって使用されます。
.claude/skills/llm-apps-action-author/にすぐに使用できるClaude スキルが含まれています。 新しいアクションの基礎を築いたり、テストファイルを生成したり、ハンドラーの形状を検証したり、ハンドラーの契約を導くことができます。 これを使用するには、Claudeに に「search-products」 というアクションを追加するよう依頼すると、正しいプロジェクト規則に自動的に従います。ハンドラーコントラクト
ハンドラーは、1つの非同期関数を書き出すactions/<name>/index.jsの1つのファイルです。
module.exports = async (args) => {
return {
content: [{ type: 'text', text: 'response for the LLM' }],
structuredContent: { /* data for the widget */ }
}
}
関数は、アクションの入力引数をプレーンオブジェクトとして受け取ります。これらは、アクションを作成ダイアログで定義したパラメーターです。 サーバーは、ハンドラーが呼び出される前に、入力スキーマに対してそれらを検証します。
content (必須)
LLMおよびテキストのみのホストに送信されるコンテンツパーツの配列。 LLM プラットフォームは、これを読んで応答を策定します。
content: [
{ type: 'text', text: 'Found 5 products matching category "bagged-coffee".' }
]
常にcontentを返します。これは、任意のホストのユニバーサルフォールバックです。
structuredContent
ウィジェットに送信されるプレーン JavaScript オブジェクト。 このデータには トークンのコストがゼロ です。このデータは、商品カルーセルやマップなどのリッチ UIをレンダリングするためにEDS ウィジェット ブロックによって消費されます。
structuredContent: {
products: [
{ name: 'Product A', category: 'bagged-coffee', imageUrl: '...' },
{ name: 'Product B', category: 'bagged-coffee', imageUrl: '...' }
],
total: 2,
category: 'bagged-coffee'
}
構造は自由です。EDS ウィジェット ブロックがbridge.toolResult経由で期待するものと一致する必要があります。
structuredContentは、ベア配列ではなくプレーンオブジェクトである必要があります。_meta(オプション)
結果と一緒に送信される追加メタデータ。 openai/widgetDescription キーは、ウィジェットの表示方法をLLM プラットフォームに伝えます。
_meta: {
'openai/widgetDescription': 'The widget displays a scrollable product carousel. '
+ 'Do NOT repeat the product list. Instead, highlight one or two recommendations.'
}
例:製品ハンドラーの検索
次に、search-products ハンドラーの例を示します。 オプションのcategory フィルターとフリーテキスト queryを受け入れ、製品カタログを検索し、LLMのテキスト概要とウィジェットカルーセルの構造化データの両方を返します。
// actions/search-products/index.js
const PRODUCTS = [
{
name: 'Product A',
description: 'A short description of Product A.',
category: 'bagged-coffee',
sub_category: 'dark-roast',
image_url: 'https://www.example.com/products/product-a/hero.jpg',
url: 'https://www.example.com/products/product-a',
productId: 'PROD-001',
rating: 4.7,
reviewCount: 58
},
// ... more products
];
const WIDGET_DESCRIPTION = 'The widget displays a scrollable product carousel '
+ 'with images, star ratings, and review counts. Do NOT repeat the product list.';
module.exports = async ({ category = '', query = '' } = {}) => {
let results = PRODUCTS;
if (category) {
const categoryLower = category.toLowerCase();
results = results.filter((p) =>
p.category.toLowerCase().includes(categoryLower)
|| p.sub_category.toLowerCase().includes(categoryLower)
);
}
if (query) {
const queryLower = query.toLowerCase();
results = results.filter((p) =>
p.name.toLowerCase().includes(queryLower)
|| p.description.toLowerCase().includes(queryLower)
);
}
const products = results.map((p) => ({
productId: p.productId,
name: p.name,
shortDescription: p.description,
category: p.category,
rating: p.rating,
reviewCount: p.reviewCount,
imageUrl: p.image_url,
productUrl: p.url,
}));
if (products.length === 0) {
return {
content: [{ type: 'text', text: `No products found for "${category}".` }],
structuredContent: { products: [], total: 0, category: null },
_meta: { 'openai/widgetDescription': WIDGET_DESCRIPTION }
};
}
return {
content: [
{ type: 'text', text: `Found ${products.length} product(s) in "${category}".` }
],
structuredContent: { products, total: products.length, category },
_meta: { 'openai/widgetDescription': WIDGET_DESCRIPTION }
};
};
実行時の処理:
- ユーザーがLLM プラットフォーム に「コーヒー製品を見せてください」と尋ねます。
- LLM プラットフォームは、インテントを 製品を検索 し、
categoryを抽出します。 - MCP サーバーが
{ category: 'bagged-coffee' }を使用してハンドラーを呼び出します。 - ハンドラーはカタログをフィルターし、
content(LLMのテキスト概要) +structuredContent(ウィジェットの製品配列)を返します。 - LLM プラットフォームは、テキスト応答を表示し、構造化データをEDS ウィジェットに渡し、製品カルーセルをレンダリングします。
ハンドラーが見つからない場合は?
UIでアクションを定義したが、まだハンドラーファイルを作成していない場合、アクションはデプロイ時に登録されたままです。 呼び出しは、実際のコードを追加するまで空のコンテンツを返すデフォルトのスタブハンドラーを使用します。 つまり、最初にUIですべてのアクションを定義し、段階的に実装できます。