メインコンテンツに移動

エージェント

エージェント

ベータ版 plugin

この plugin は現在ベータ版です。マイナーリリース間で API が変更される場合があります。@databricks/appkit/beta からインポートしてください。詳細は Plugin Stability Tiers を参照してください。

agents プラグインは、Databricks AppKit app を AI エージェントのホストに変えます。ディスクからエージェント定義を検出し (server/agents/ 配下にエージェントごとに 1 つのフォルダを置き、その中に agent.md (Markdown) または agent.ts (コード) を配置) 、POST /invocations および POST /responses (非ストリーミング、エイリアス) として公開します。あわせて POST /chat (ストリーミング) や、スレッド管理・キャンセル・HITL 承認用のルートも提供します。いずれの場合もエージェントの id はフォルダ名そのものです。管理すべきマップも、改めて指定する id もありません。

このページではライフサイクル全体を扱います。手書きのプリミティブ (tool()mcpServer()) については、tools を参照してください。

要件

ストリーミング対応のサービング endpoint のみ

agents plugin は Server-Sent Events 経由で LLM を駆動します。Foundation Model API (Claude、Llama、GPT など) をはじめとするチャット形式の endpoint はストリーミングに対応しているため、そのまま利用できます。一方、単一の JSON レスポンスを返すカスタムモデル endpoint (例: 一般的な sklearn や MLflow pyfunc の deployment) はストリーミングに対応していません。こうした endpoint をエージェントに指定すると、最初のターンで "Response body is null — streaming not supported" というエラーになります。apps init でサービング endpoint を指定する場合は、chat-completions のストリーミングプロトコルを実装したモデルの endpoint を選んでください。agents plugin は、エージェント側で model: が固定されていない場合、DATABRICKS_SERVING_ENDPOINT_NAME から endpoint 名を読み取ります。

カスタム endpoint に対して非ストリーミングで呼び出したい場合は、代わりに serving plugin の /invoke ルートと useServingInvoke を使用してください。

あるいは、マネージドの Supervisor API アダプター (ベータ) を使えば、サービング endpoint のセットアップ自体が不要になります。

インストール

agents は通常の plugin です。server() や、エージェントから利用させたいツールを提供する ToolProvider plugin と並べて plugins[] に追加してください。

import { analytics, createApp, files, server } from "@databricks/appkit";
import { agents } from "@databricks/appkit/beta";

await createApp({
  plugins: [server(), analytics(), files(), agents()],
});

これだけで、Markdown 駆動のエージェントに紐付いた POST /invocations (およびそのエイリアスである POST /responses) を備えた HTTP サーバーが実際に動き出します。ストリーミングや HITL に対応したインターフェースが必要な場合は、代わりに POST /chat を使用してください。

レベル1: Markdownエージェントパッケージを配置する

各エージェントは server/agents/ 配下の専用フォルダに置かれ、エントリファイルは agent.md です。フォルダがエージェントとして認識されるのは、エントリファイル (agent.md または agent.ts) がある場合のみで、ないフォルダはスキップされます。そのため、エージェントごとのアセットフォルダをエントリファイルと同じ階層に置けます。代表的なのが、Skills (エージェントが名前を指定して必要に応じて読み込む指示パック) を格納する skills/ フォルダです。共有の server/agents/skills/ フォルダには、どのエージェントからも利用できるskillsを配置します。

my-app/ server/ server.ts agents/ assistant/ agent.md
---
endpoint: databricks-claude-sonnet-4-5
default: true
---

You are a helpful data assistant running on Databricks.

Use the available tools to query data, browse files, and help users.

起動時に plugin は次の処理を行います。

  1. server/agents/assistant/agent.md を検出し、エージェント id assistant を登録します。
  2. YAML フロントマターと Markdown 本文を解析し、エージェントの instructions として扱います。
  3. endpoint からアダプターを解決します (未指定の場合は DATABRICKS_SERVING_ENDPOINT_NAME にフォールバック) 。
  4. デフォルト名 (assistant) でエージェントをマウントします。

エージェントはツールなしの状態で起動します。ツールはオプトイン方式です。フロントマターで宣言する (下記のレベル 2) か、agents({ autoInheritTools: { file: true } }) で明示的に自動継承を有効にしてください。そのコストと、なぜデフォルトで無効になっているのかについては、後述の「自動継承のスタンス」を参照してください。

`config/agents/` からの移行

以前のバージョンでは、Markdown エージェントを config/agents/<id>/agent.md に配置していました。この場所は非推奨のフォールバックとして引き続き読み込まれます (起動時に一度だけ警告が表示されます) 。Markdown とコードの両方のエージェントを 1 か所にまとめるため、各フォルダーを server/agents/<id>/agent.md へ移動してください。

リクエストは OpenAI Responses 互換のボディで POST /invocations (またはそのエイリアスである POST /responses) に届きます。これらの endpoint はエージェントを最後まで実行し、単一の JSON レスポンスを返します (SSE は使いません) 。ストリーミングを行うクライアントは POST /chat を使用してください。すべてのツール呼び出しは自動的にトレースされます。plugin ツールキットのツール呼び出し (plugin:<name> エントリ / plugins.<name>.toolkit()) はさらに asUser(req) を経由するため、SQL はリクエスト元のユーザーとして実行され、ファイルアクセスも Unity Catalog の ACL に従います。一方、手書きの tool({ execute })ラップされません。その execute が受け取るのは検証済みのツール引数のみで (req は渡されません) 、アプリのサービスプリンシパル ID で実行されるため、OBO を利用できません。ツールをリクエスト元のユーザーとして動作させる必要がある場合は、手書きの execute ではなく plugin ツールとして公開してください。実行コンテキストを参照してください。

`/invocations` と `/responses` では HITL が使えません

非ストリーミングの invoke インターフェイスには、呼び出し中の承認 prompt を呼び出し元に返す手段がありません。approval.requireForDestructive が有効 (デフォルト) で、解決されたエージェントに変更を伴う効果が注釈されたツール (effect: "write" | "update" | "destructive"、または従来の destructive: true) が 1 つでも含まれる場合、POST /invocationsPOST /responses はアダプターの実行前に HTTP 400 でリクエストを拒否します。HITL 対応のエージェントは POST /chat に移行するか、自律的なバックオフィス向けエージェントであれば agents({ approval: { requireForDestructive: false } }) で承認を無効化してください。

レベル2: フロントマターでツールのスコープを絞る

---
endpoint: databricks-claude-sonnet-4-5
tools:
  - plugin:analytics                              # analytics.* のツールすべて
  - plugin:files: [uploads.read, uploads.list]    # files のこれらのツールのみ
  - plugin:genie: { except: [getConversation] }   # getConversation 以外すべて
  - get_weather                                   # コード内で宣言されたアンビエントツール
default: true
---

You are a read-only data analyst.

統合された tools: リストは、plugin 参照とアンビエントツールを混在して指定でき、TS の関数形式 tools(plugins) => ({ ...plugins.analytics.toolkit(), ...plugins.files.toolkit({ only: [...] }), get_weather: tool({...}) }) と同じ働きをします。各エントリは次のいずれかです。

  • plugin:<name> — 指定した plugin のすべてのツールを取り込みます。
  • plugin:<name>: [tool1, tool2] — 列挙したツールのみ ({ only: [...] } の糖衣構文) 。
  • plugin:<name>: { ...ToolkitOptions }prefix / only / except / rename のすべてのオプションを指定できます。
  • <key> (プレフィックスなし) — agents({ tools: { ... } }) の設定から解決されるアンビエントツール名。

tools: を 1 つでも宣言すると自動継承のデフォルトは無効になり、エージェントからは列挙したツールだけが見えます。

レベル3: コードで定義するエージェント

コードエージェントは server/agents/ 配下に1フォルダにつき1つ配置し、エントリファイルは agent.ts です (Markdown の agent.md に相当します) 。エントリでは作成したエージェントをエクスポートし、その id はフォルダ名 になります (server/agents/support/agent.tssupport) 。id をあらためて記述する必要はありません。

// server/agents/support/agent.ts
import { createAgent, tool } from "@databricks/appkit/beta";
import { z } from "zod";

export default createAgent({           // id はフォルダ名から決まる: "support"
  instructions: "You help customers with data and files.",
  model: "databricks-claude-sonnet-4-5",                      // 文字列指定の糖衣構文
  tools(plugins) {
    return {
      ...plugins.analytics.toolkit(),                          // analytics のツールすべて
      ...plugins.files.toolkit({ only: ["uploads.read"] }),    // 絞り込んだサブセット
      get_weather: tool({
        description: "Weather",
        schema: z.object({ city: z.string() }),
        execute: async ({ city }) => `Sunny in ${city}`,
      }),
    };
  },
});

agents plugin は起動時にこれらのファイルを自動的に検出します。登録もマップも不要です:

// server/server.ts
import { analytics, createApp, files, server } from "@databricks/appkit";
import { agents } from "@databricks/appkit/beta";

await createApp({
  plugins: [server(), analytics(), files(), agents()],   // エージェントマップも import も不要
});

検出処理は各 server/agents/<id>/agent.ts をインポートします。開発時は tsx 配下のソース .ts を、本番ビルドではコンパイル済みの dist/agents/<id>/agent.js を読み込みます (NODE_ENV とは無関係に、ビルド済み出力がソースより優先されます) 。本番サーバーはバンドルされ、server/server.ts から到達可能なものだけをインポートするため、テンプレートの tsdown 設定では server/agents/*/agent.ts をビルドエントリとして列挙し、スキャン対象となる dist/agents/*/agent.js が出力されるようにしています。この仕組みがあるからこそ、フォルダを置くだけで本番バンドルでも生き残ります。 (Markdown の agent.md は開発時も本番時もソースから読み込まれます。コンパイル対象ではなくデータだからです。) ルートは常に server/agents であり、移動させる設定オプションはありません。config/agents/ 配下に残っている Markdown は非推奨のフォールバックとして読み込まれます (警告は一度だけ表示) 。

開発時はビルド済み出力がソースを覆い隠します

コンパイル済み出力がソースより優先されるため、以前の npm run build で残った古い dist/agents / build/agents があると、npm run dev はライブの server/agents/*.ts ではなくそちらを読み込み、編集が無視されたように見えます。コードエージェントが固まったように見えたら ビルドディレクトリを削除してください。再ビルドしても新しいスナップショットに入れ替わるだけなので、削除して初めてソースからのライブ開発リロードが復活します。Markdown は常にソースから読み込まれるため、agent.md の編集が覆い隠されることはありません。

エントリは export default createAgent({...}) でも、作成済みエージェントを単一の名前付きエクスポートとして公開しても構いません。いずれの場合も id はフォルダ名になります。エントリが作成済みエージェントをエクスポートしないフォルダ (あるいは agent.ts/agent.md がまったくないフォルダ) はスキップされます。既定のエージェントは createAgent({ default: true }) で指定します (Markdown のフロントマター default: true と同等) 。明示的な agents({ defaultAgent }) があればそちらが優先されます。

コードで定義したエージェントは、既定ではツールを一つも持たない状態から始まります。プラグインのツールを取り込む主な方法は、関数形式の tools(plugins) => Record<string, AgentTool> です。createApp({ plugins: [...] }) に登録した各プラグインは plugins パラメータ上に現れ、そこで .toolkit(opts?) を呼び出すとスプレッド可能なレコードが得られます。runtime はエージェントのセットアップ時にこの関数を一度だけ呼び出し、結果をキャッシュします。各プラグインの記述は (createApp での) ちょうど一度きりで、変数を保持したりマーカー用のインポートを行ったりする必要はありません。

インラインの tool({...}) 呼び出しも同じレコード内に記述します。その name は省略可能で、agents プラグインがレコードのキー (上記の get_weather) で上書きします。

自動継承は 両方のオリジンとも既定でオフ です。tools: を宣言していない Markdown エージェントやコードエージェントは、空のツールインデックスを持ちます。オリジンごとに agents({ autoInheritTools: { file: true } }) (または { code: true }、両方なら true) で明示的に有効化してください。

非推奨: `agents({ agents: { ... } })` マップ

手書きのエージェントマップを渡す方式は現在も動作し、後方互換性のためサポートされていますが、初回に一度だけ非推奨警告が出力され、将来のマイナーバージョンで削除されます。この方式では各エージェントの id を二重に書くことになります (createAgent 内で 1 回、マップのキーとして 1 回) 。server/agents/ からの自動検出を使えば、マップも重複記述も不要になります。移行するには、各 createAgent(...) をそれぞれの server/agents/<id>/agent.ts に移し (デフォルトエクスポートまたは単一の名前付きエクスポート) 、マップを削除してください。検出されたエージェントとマップのエントリの id が重複する場合は検出側が優先され、マップのエントリは無視されます (この場合も一度だけ警告が出ます) 。 (インラインのサブエージェント、つまり定義内の createAgent({ agents: { ... } }) は影響を受けません。非推奨となるのは plugin レベルのマップのみです。)

以降の例の一部では、スニペットを簡潔にするため、引き続きこのマップ経由でエージェントをインラインに渡しています。実際のアプリでは、これらの createAgent(...) 定義はそれぞれ専用の server/agents/<id>/agent.ts に配置され、マップは不要です。

コードでツールのスコープを絞る

plugins.<name>.toolkit(opts?) は、Markdown のフロントマターと同じ ToolkitOptions を受け取ります:

オプション意味
only{ only: ["query"] }ローカルツール名の許可リスト
except{ except: ["legacy"] }ローカルツール名の拒否リスト
prefix{ prefix: "" }${pluginName}. プレフィックスを削除する
rename{ rename: { query: "q" } }特定のローカル名を付け替える

.toolkit() メソッドを公開していないプラグイン (例: プレーンな toPlugin で作成されたサードパーティの ToolProvider プラグイン) では、ランタイムは getAgentTools() を走査して名前空間付きのキー (${pluginName}.${localName}) を生成する方式にフォールバックします。このフォールバックでも only / except / rename / prefix は同様に適用されます。

参照したプラグインが createApp({ plugins }) に登録されていない場合、agents プラグインはセットアップ時に Available: … の一覧を添えて例外をスローします。最初のリクエストが来る前に接続設定を修正できます。

レベル4: サブエージェント

const researcher = createAgent({
  instructions: "Research the question. Return concise bullets.",
  model: "databricks-claude-sonnet-4-5",
  tools: { search: tool({ /* ... */ }) },
});

const writer = createAgent({
  instructions: "Draft prose from notes.",
  model: "databricks-claude-sonnet-4-5",
});

const supervisor = createAgent({
  instructions: "Coordinate researcher and writer.",
  model: "databricks-claude-sonnet-4-5",
  agents: { researcher, writer },  // agent-researcher、agent-writer として公開される
});

// server/agents/{supervisor,researcher,writer}/agent.ts — それぞれ 1 フォルダずつ
export default supervisor;

await createApp({
  plugins: [server(), agents()],  // server/agents/ から検出される
});

supervisorresearcherwriter をそれぞれ個別の server/agents/<id>/agent.ts フォルダに配置します (いずれもデフォルトエクスポート) 。Markdown の親エージェントから、agents: [helper] フロントマターを使って同階層のフォルダにあるコードの子エージェントに委譲することもできます。AgentDefinitionagents: {...} に指定した各キーは、親エージェント上で agent-<key> ツールになります。呼び出されると、agents プラグインは新しいメッセージリスト (スレッド状態は共有されません) で子エージェントのアダプタを実行し、集約されたテキストを返します。コードエージェントのインライン agents: {} グラフ内の循環は、読み込み時 (createAgent) に拒否されます。Markdown の agents: による委譲では、読み込み時に自己参照が拒否され、より深い循環は実行時に limits.maxSubAgentDepth で制限されます。

スキル

スキルはオンデマンドで読み込まれる指示パックで、Claude CodeやCursorが採用しているものと同じSKILL.md形式を使います。システムプロンプトに常駐するのは各スキルのnamedescriptionだけ (常時有効で低コスト) で、エージェント (またはユーザー) が呼び出した時点で本文全体が読み込まれます。この仕組みはDatabricksが提供するどのモデルでも動作します。段階的な開示はAppKit自身が実装しているため、プロバイダーネイティブのスキル機能に依存しません。

スキルは、SKILL.mdと付随する参照ファイルをまとめたディレクトリです。

server/agents/ skills/ # 共有プール — どのエージェントからでも利用可能 pdf-forms/ SKILL.md reference.md planner/ agent.md skills/ # `planner` エージェント専用 house-style/ SKILL.md
---
name: pdf-forms
description: Fill and validate PDF form fields from a data record.
---

To fill a PDF form:

1. Read `reference.md` for the field-name conventions.
2. ...

namedescription は必須です。licenseallowed-toolsmetadata は、他のツールで作成された スキル との互換性のために受け付けられます。不明なキーは警告が出力され、無視されます。

可視性

  • エージェント個別のスキル (server/agents/<id>/skills/) は、そのエージェントから常に参照できます。
  • グローバルスキル (server/agents/skills/ およびカタログボリュームのスキル) はオプトインです。エージェントのフロントマターに skills: [pdf-forms] のように列挙してください。プラグイン側で autoInheritSkills: true (または { file, code }) を設定すると、列挙しなくてもすべてのグローバルスキルが参照可能になります。各エージェントの常時有効なカタログを絞り込んでおくため、既定では無効です。

エージェントがスキルを使う仕組み

表示可能なカタログを持つエージェントには、読み取り専用の組み込みツールが2つ注入されます。

  • load_skill(skill) — スキルの完全な指示と、バンドルされたファイルのマニフェストを返します。
  • read_skill_file(skill, path) — バンドルされたファイルのうち1つの内容を返します。

タスクがスキルの説明に一致すると、モデルは自ら load_skill を呼び出します。ユーザーは、チャットで /skill-name プレフィックスを使う (または useAgentChatsend(message, { skill }) オプションを使う) ことで、そのターンに特定のスキルを強制的に適用できます。このとき、スキルの指示は決定論的にそのターンへ注入され、load_skill も自動選択用として引き続き利用できます。クライアントは、pluginの clientConfig() ペイロードからエージェントごとのカタログを読み取り、ピッカーを構築します。

カタログの スキル (Unity Catalog ボリューム)

skillsVolume (または環境変数 DATABRICKS_VOLUME_AGENT_SKILLS) には、同じ構成 (<volume>/<name>/SKILL.md) で配置した UC ボリュームを指定します。カタログの スキル は起動時と reload() 時に検出されて共有のグローバルプールにマージされ、サービスプリンシパルとして読み取られます (skillCredentialMode の既定値は "sp") 。これらは共有・キュレーション済みのプールとしての利用を想定しており、ユーザーごと (OBO) の スキル ボリュームには未対応です。マニフェストで任意指定の volume リソースを宣言しておくと、スキャフォールダーが SP に読み取りアクセス権を付与します。

名前の衝突

スキル の名前は修飾なしで参照されます。2 つのソースが同じ名前を提供した場合、それぞれ <scope>:name (agent:bundle:volume:) の形式で修飾され、修飾なしの名前は曖昧なものとして拒否され、代替候補が一覧表示されます。同一の ソース内に同じ名前の スキル が 2 つ存在する場合は、起動時エラーになります。

v1 の注意点

  • スクリプトは実行されません。 スキルが scripts/foo.py を参照していても、v1 が読み込むのは説明文とリファレンスドキュメントのみです。
  • allowed-tools はあくまで参考情報です。 読み込まれたスキル内にヒントとして表示されるだけで、強制力はありません。スキルを読み込んでもエージェントが呼び出せるツールが制限されることはなく、サンドボックスとして機能するものでもありません。
  • スキル本文はユーザーごとのアクセス制御の対象外です (SP の権限で読み取られます) 。ユーザーの機密情報はスキル本文に含めないでください。

レベル5: スタンドアロン(createApp なし)

import { createAgent, runAgent, tool } from "@databricks/appkit/beta";
import { z } from "zod";

const classifier = createAgent({
  instructions: "Classify tickets: billing | bug | feature.",
  model: "databricks-claude-sonnet-4-5",
  tools: {
    lookup_account: tool({ /* ... */ }),
  },
});

for (const ticket of tickets) {
  const result = await runAgent(classifier, {
    messages: [{ role: "user", content: ticket.body }],
  });
  await persistClassification(ticket.id, result.text);
}

runAgent は、createApp や HTTP を介さずにアダプターを実行します。インラインの tool() 呼び出しは、上記のとおりスタンドアロンでも動作します。スタンドアロンモードで plugin のツールを使うには、plugin ファクトリーを RunAgentInput.plugins に渡し、tools(plugins) の関数形式からアクセスしてください:

import { analytics } from "@databricks/appkit";
import { createAgent, runAgent } from "@databricks/appkit/beta";

const classifier = createAgent({
  instructions: "Classify tickets. Use analytics.query for historical data.",
  model: "databricks-claude-sonnet-4-5",
  tools(plugins) {
    return { ...plugins.analytics.toolkit() };
  },
});

const result = await runAgent(classifier, {
  messages: "is ticket 42 a duplicate?",
  plugins: [analytics()],
});

runAgentRunAgentInput.plugins 内の各 plugin を事前に構築し、標準の attachContext({}) + await setup() ライフサイクルを実行したうえで、トップレベルの run とすべてのサブエージェントのディスパッチでインスタンスを共有します。setup()createApp 専用の runtime (例: WorkspaceClientServiceContext) が必要な plugin は、ストリーム処理の途中ではなくスタンドアロン初期化の時点で「use createApp instead」という明確なメッセージとともに例外をスローします。

MCP のホスト型ツール (mcpServer(...)) には引き続き agents() が必要です (稼働中の MCP クライアントを要するため) 。一方、Supervisor API のホスト型ツール (supervisorTools.*) は スタンドアロンの runAgent でも動作します — アダプターがサーバー側で実行するために必要なものをすべて備えているためです。これにより、createApp なしで Supervisor Agent をバッチ評価や CI で利用できます。スタンドアロンモードでの plugin ツールのディスパッチはサービスプリンシパルとして実行され (OBO なし) 、agents plugin の承認ゲートをバイパスします — スタンドアロンの runAgent は、ユーザーに公開される領域ではなく、信頼できる prompt 環境 (CI、バッチ評価、内部スクリプト) として扱ってください。

既存のアプリにエージェントを追加する

すでにアプリがあり、そこにエージェントを追加したい場合、手を入れる箇所はエージェントの種類によって変わります。

Markdown エージェント — plugin だけで済みます。server/agents/<id>/agent.md を置き、pluginsagents() を追加すれば完了です。Markdown は dev でも prod でも runtime にソースから読み込まれるため、ビルドの変更は不要です。

コードエージェント (server/agents/<id>/agent.ts) — 本番バンドルに出力されるよう、サーバーのビルドも更新してください。コードエージェントはどこからも import されないため、server/server.ts だけをコンパイルするビルドでは dist/agents/*/agent.js が生成されず、バンドルした npm run build + 起動ではコードエージェントが 1 つも見つかりません (検出数はゼロになります) 。

dev では気づけません

npm run dev (tsx) は .ts ソースを直接 import するため、ビルドを変更しなくてもコードエージェントは動作します。この問題が表面化するのはバンドルビルドのときだけです。コードエージェントを追加したのにビルドの変更を忘れた場合、plugin は黙って失敗するのではなく、起動時に警告を出して修正方法も示します。

修正は 1 行、ビルドプリセットを採用するだけです。

// tsdown.server.config.ts
import { appkitServerConfig } from '@databricks/appkit/tsdown';

export default appkitServerConfig();

appkitServerConfig()server/agents/ を自動検出し、コードエージェントが存在する場合にのみエントリの glob と clean を追加します。オーバーライドは appkitServerConfig({ external, define, ... }) のように渡すか、完全に制御したい場合は関数形式で appkitServerConfig((base) => ({ ...base })) と指定します。しかも、このファイルを編集するのはこれが最後です。今後のビルド設定の変更はパッケージ側で提供されます。手書きの設定を維持したい場合は、次のようにエントリを自分で追加してください:

entry: ['server/server.ts', 'server/agents/*/agent.ts'],
clean: true,

マネージド エージェント: Supervisor API アダプター

DatabricksAdapter.fromSupervisorApi (ベータ) は、設定不要で エージェント を実行する方法です。model serving endpoint をプロビジョニングして指定する代わりに、AI Gateway Responses API (/ai-gateway/mlflow/v1/responses) を呼び出すことで、agentic ループを Databricks ワークスペース内で実行します。この API は、LLM とホスト型ツールを Databricks 上のマネージドサービスとして実行します。DATABRICKS_SERVING_ENDPOINT_NAME も、ストリーミング対応の確認も、一般的なユースケースにおける JS ツールの配線も不要です。

最小構成の エージェント は、Markdown エージェント に 1 行加えるだけで書けます。

import { createApp } from "@databricks/appkit";
import { agents, createAgent, DatabricksAdapter } from "@databricks/appkit/beta";

await createApp({
  plugins: [
    agents({
      agents: {
        assistant: createAgent({
          instructions: "You are a helpful assistant.",
          model: DatabricksAdapter.fromSupervisorApi({
            model: "databricks-claude-sonnet-4-5",
          }),
        }),
      },
    }),
  ],
});

createAgent({ model }) は、これまでの例で使ってきたモデル名の文字列に加えて、アダプターやアダプターの Promise も受け付けるため、ファクトリーの戻り値をそのまま渡せます。ファクトリーは SDK のチェーン (DATABRICKS_HOST、OAuth、PAT など) から資格情報を解決します。既存のクライアントを再利用する場合は workspaceClient を渡してください。

ホスト型ツール

Genie space、Unity Catalog の関数/接続、Knowledge Assistant、他の AppKit app は、エージェントツールとして宣言するだけでモデルから利用できるようになります。宣言する場所は他のツールとまったく同じです。実行はサーバー側で行われるため、ツールのコードを書く必要はありません:

import {
  createAgent,
  DatabricksAdapter,
  supervisorTools,
} from "@databricks/appkit/beta";

const assistant = createAgent({
  instructions: "You are a helpful data assistant.",
  model: DatabricksAdapter.fromSupervisorApi({
    model: "databricks-claude-sonnet-4-5",
  }),
  tools: () => ({
    nyc: supervisorTools.genieSpace({
      id: "01ABCDEF12345678",
      description: "NYC taxi trip records and zones",
    }),
    add: supervisorTools.ucFunction({
      name: "main.default.add",
      description: "Adds two integers and returns the sum.",
    }),
  }),
});

supervisorTools.* ファクトリは、名前付きオプションオブジェクトを 1 つだけ受け取ります。ルーティングを左右する文字列には呼び出し箇所でラベルが付くため、引数の順序を取り違えるバグは起こりません。

description必須かつ空文字不可 です。LLM はこれを手がかりにツール間のルーティングを判断するため、2 つの Genie space に同じ「Genie space」というラベルを付けると区別できなくなります。

ホスト型ツールの description は信頼されたアプリケーション設定です (CWE-1427)

ホスト型ツールの description は、そのツールにルーティングするかどうかを判断するために LLM が読み取ります。信頼できない入力から生成しないでください — ユーザーメッセージ、リクエストボディ、外部システムからの自由記述フィールド、攻撃者が影響を与えられる値などが該当します。description (および id / name) は、エージェントの instructions と同様にアプリケーション側で制御するものとして扱ってください。ここにユーザー制御の文字列を許可すると、プロンプトインジェクションの侵入口になります。悪意ある description は、そのエージェントが以降に処理するあらゆるリクエストにおいて、特定のツールへルーティングさせる (あるいはルーティングを回避させる) ようモデルを誘導し得ます。

同じ注意は MCP の description や、ルーティング時にモデルが読み取るその他のフィールドにも当てはまります。

ファクトリツールの種類識別子
supervisorTools.genieSpace({ id, description })Genie spacespace id
supervisorTools.ucFunction({ name, description })Unity Catalog 関数3 パート名
supervisorTools.knowledgeAssistant({ knowledgeAssistantId, description })Knowledge Assistantアシスタント ID
supervisorTools.app({ name, description })Databricks Appアプリ名
supervisorTools.ucConnection({ name, description })UC 接続接続名

Markdown エージェントでホスト型ツールを宣言する

ホスト型スーパーバイザーのツールは、Markdown 駆動のエージェントでも利用できます。ツールをコード内 (agents({ tools: { ... } }) 配下) で宣言し、そのキーをフロントマターで参照します。

// server.ts
agents({
  agents: { /* ... */ },
  tools: {
    nyc_taxi: supervisorTools.genieSpace({
      id: "01ABCDEF12345678",
      description: "NYC taxi trip records and zones",
    }),
  },
});
---
endpoint: databricks-claude-sonnet-4-5
tools:
  - nyc_taxi
---

You answer questions about NYC taxi data using the Genie space.

新しいフロントマター構文は不要です。tools: のアンビエントツール参照は、すでに素のキーを agents({ tools }) に対して解決します。また、supervisorTools.* のタグ付きレコード形式により、pluginがそれらを自動的に分類できます。

Supervisor API エージェントに 適用されない もの

マネージド runtime がツール実行を自ら担うため、アダプターは agents plugin のツールインデックスにある関数ツールとサブエージェントを意図的に無視しますmodel: が Supervisor アダプターであるエージェントでは、次のようになります。

  • モデルに渡るのは supervisorTools.* のエントリのみです。supervisor アダプターと併せて宣言した関数ツール (tool({...})) 、MCP ホスト型ツール (mcpServer(...)) 、ローカルのサブエージェント (agents: { ... }) は登録時に警告を出力し、モデルには公開されません。この機能チェックは、アダプター側の consumesInputTools: false によって発火します。
  • human-in-the-loop の承認ゲートは発火しません (ツール呼び出しが Node プロセスに入ることはなく、effect: "destructive" のアノテーションはホスト型ツールには無関係です) 。
  • limits.maxToolCalls は適用されません (マネージド runtime が自身の呼び出しを管理します) 。
  • 呼び出しごとの OBO はホスト型ツールには適用されません。ホスト型ツールは、マネージド runtime が対象リソースに対して使用する資格情報で実行されます。

アダプターをまたぐサブエージェントの合成

Supervisor アダプターと chat-completions アダプターは同じ agents({ agents: { ... } }) マップに共存できますが、合成できるのは一方向のみです。

  • chat-completions の親 → supervisor のサブエージェント はそのまま動作します。親は agent-{key} を通常の関数ツールとしてディスパッチし、子のアダプターはすべて AI Gateway 上で実行されます。
  • supervisor の親 → 関数ツール / ローカルのサブエージェント はまだ未対応です。登録時に機能チェックが警告を出し、それらのツールは supervisor モデルには渡されません。今後、SA の response.function_call イベントを context.executeTool 経由でルーティングすることで、この制限を解消する予定です。
非ストリーミングのツールターン向けのリカバリーパス

ホスト型ツールの種類によっては、逐次的な output_text.delta イベントを返さずに最終的なアシスタントテキストのみを返すものがあります。アダプターには response.completed.output[] からテキストを取り出すリカバリーパスが用意されており、ターンが何も出力されないまま終わるのを防ぎます。各ターンがどちらのパスを通ったかを確認したい場合は、DEBUG=appkit:agents:supervisor-api を設定して、ターンごとのイベント種別ヒストグラムをログに出力してください。

設定リファレンス

agents({
  // エージェントは server/agents/<id>/ 配下に配置します(ルートは固定)。config/agents は非推奨のフォールバックとして読み込まれます。
  agents?: Record<string, AgentDefinition>,  // 非推奨 — server/agents/<id>/ による検出を使用してください
  defaultAgent?: string,
  defaultModel?: AgentAdapter | Promise<AgentAdapter> | string,
  tools?: Record<string, AgentTool>,
  autoInheritTools?: boolean | { file?: boolean, code?: boolean },
  autoInheritSkills?: boolean | { file?: boolean, code?: boolean }, // 既定はオフ
  skillsVolume?: string,        // カタログ skills 用の UC ボリューム。未指定の場合は DATABRICKS_VOLUME_AGENT_SKILLS にフォールバック
  skillCredentialMode?: "sp" | "obo", // 既定は "sp"(Skills を参照)
  threadStore?: ThreadStore,    // 既定はインメモリ
  baseSystemPrompt?: false | string | (ctx: PromptContext) => string,
  mcp?: {
    trustedHosts?: string[],    // カスタム MCP URL で追加的に許可するホスト名
    allowLocalhost?: boolean,   // 既定: NODE_ENV !== "production"
  },
  approval?: {
    requireForDestructive?: boolean,  // 既定: true
    timeoutMs?: number,               // 既定: 60_000
  },
  limits?: {
    maxConcurrentStreamsPerUser?: number, // 既定: 5
    maxToolCalls?: number,                // 既定: 50
    maxSubAgentDepth?: number,            // 既定: 3
    toolCallTimeoutMs?: number,           // 既定: 300_000(5 分)
  },
})

autoInheritTools の既定値は { file: false, code: false } です。つまり、開発者が明示的にオプトインしない限り、どのエージェントにもツールは展開されません。オプトインした場合でも、展開されるのは plugin の作者が autoInheritable: true を指定したツールのみです。破壊的な操作や状態を変更するツールは、オプトインが有効な場合でも自動継承の対象から常に除外されます。真偽値の省略記法 (autoInheritTools: true) は両方のオリジンに適用されます。詳細は後述の「自動継承のポスチャ」を参照してください。

MCP ホストポリシー

AppKit は、ホスト型ツールとして使用されるすべての MCP URL にゼロトラストポリシーを適用します。デフォルトでアクセスできるのは、同一オリジンの Databricks ワークスペース URL (解決された DATABRICKS_HOST に一致するもの) のみです。それ以外のホストは、すべて mcp.trustedHosts で明示的に許可リストに登録する必要があります。また、ワークスペースの資格情報 (サービスプリンシパルおよびオンビハーフオブユーザートークン) がそれらのホストに転送されることは 決してありません

agents({
  agents: {
    support: createAgent({
      instructions: "",
      tools: {
        "mcp.internal": mcpServer("internal", "https://mcp.corp.internal/mcp"),
      },
    }),
  },
  mcp: {
    trustedHosts: ["mcp.corp.internal"],
  },
});

このポリシーは、MCP の connect() 実行時、1バイトも送信される前に次の4つのルールを適用します。

  1. httphttps の URL のみを受け付けます。
  2. 平文の http:// は、allowLocalhost が true の場合の localhost を除き、すべて拒否されます (開発環境では既定で有効、本番環境では無効) 。
  3. 宛先のホスト名は、ワークスペースのホストと一致するか、localhost (許可されている場合) であるか、trustedHosts に含まれている必要があります。
  4. 名前解決された DNS アドレスが、ループバック、RFC1918、CGNAT (100.64.0.0/10) 、リンクローカル (169.254.0.0/16 — クラウドのメタデータサービスを含む) 、ULA、マルチキャストの各範囲に含まれていてはなりません。

ワークスペースの資格情報を含む Authorization ヘッダーは、同一オリジンのワークスペース URL に対してのみ送信されます。信頼された外部ホストを指す mcpServer(name, url) では、認証を自身で行う必要があります (例: url に埋め込んだカスタムトークン) 。

自動継承のポリシー

AppKit は自動継承を「2つの鍵」が揃って初めて成立する操作として扱います。開発者が autoInheritTools をオプトインし、かつ plugin の作者が各ツールに autoInheritable: true を指定する必要があります。ツールが明示的な設定なしにエージェントのインデックスへ展開されるには、この両方が満たされていなければなりません。

// agents プラグインのレベルでオプトインする(いずれかを選択):
agents({ autoInheritTools: true });                   // 両方のオリジン
agents({ autoInheritTools: { file: true } });         // Markdown エージェントのみ
agents({ autoInheritTools: { file: true, code: true } });

// プラグイン内でツールごとに指定する:
defineTool({
  description: "safe read",
  schema: z.object({ ... }),
  annotations: { effect: "read", requiresUserContext: true },
  autoInheritable: true, // このツールの自動展開を明示的に許可する
  execute: (args, signal) => ...,
});

AppKit のコアプラグインは、以下の autoInheritable 設定でリリースされています。

ツールautoInheritable理由
analytics.queryありOBO スコープ。runtime に分類器で読み取り専用 SQL が強制される
files.list / files.read / files.exists / files.metadataありOBO スコープの読み取り操作
files.upload / files.deleteなし変更を伴うため、明示的に接続する
genie.getConversationあり読み取り専用の履歴
genie.sendMessageなし状態を変更する Genie の会話
lakebase.queryなしすでに exposeAsAgentTool で制御済み。多層防御として自動継承は閉じたままにする

toolkit() メソッドを公開しないサードパーティ製の ToolProvider plugin も、自動継承の対象から除外されます。これらのツールは tools: で明示的に接続してください。セットアップ時には、各エージェントが何を継承し何がスキップされたかを agents plugin が logs に出力するため、設定状況を把握できます。

[agents] [agent support] auto-inherited 2 tool(s): analytics.query, files.uploads.read [agents] [agent support] auto-inherit skipped 3 tool(s) not marked autoInheritable: files(2), genie(1). Wire them explicitly via `tools:` if needed.

SQL エージェントツール

LLM に代わって SQL を実行できる組み込みエージェントツールは 2 つあります。analytics.query (Databricks SQL warehouse に対して実行) と、オプトインの lakebase.query (Lakebase Postgres データベースに対して実行) です。両者は実行時の権限が異なるため、安全性の扱いも異なります。

analytics.query は呼び出し元の OBO トークン (エンドユーザーの Databricks 資格情報) で実行されます。readOnly: true アノテーションは実行時に強制され、ステートメントはトークン化されたうえで SELECTWITHSHOWEXPLAINDESCRIBEDESC のみが受け付けられます。書き込み、DDL、複数連結したステートメントは、リクエストが warehouse に届く前に拒否されます。

// 許可される
analytics.query({ query: "SELECT * FROM main.sales.orders WHERE created_at > current_date() - 7" })

// plugin の時点で拒否され、warehouse には到達しない
analytics.query({ query: "UPDATE main.sales.orders SET status = 'cancelled'" })
analytics.query({ query: "SELECT 1; DROP TABLE main.sales.orders" })

lakebase.queryデフォルトではエージェントツールとして登録されません。Lakebase プールはアプリケーションのサービスプリンシパルにひも付いているため、有効化は明示的な判断として行う必要があります。つまり、このツールを利用できるエージェントは、リクエストを開始したエンドユーザーが誰であるかにかかわらず、SP として SQL を実行できてしまいます。オプトインするには、次の承諾フラグを指定します:

lakebase({
  exposeAsAgentTool: {
    iUnderstandRunsAsServicePrincipal: true,
    readOnly: true, // デフォルト
  },
});

readOnly: true (デフォルト) の場合、analytics.query と同じ SQL 分類器が適用され、受理されたステートメントはさらに BEGIN READ ONLY; … ROLLBACK; でラップされます。これにより、分類器をすり抜けた書き込み (副作用のある関数に対する SELECT など) は Postgres サーバー側で拒否されます。ツールのアノテーションは { effect: "read" } です。

readOnly: false の場合、ツールは任意の SQL を受け付け、{ effect: "destructive" } とアノテーションされます。destructive の場合は、呼び出しのたびに human-in-the-loop の承認ゲート (後述) が発動します。

変更系ツールに対する人間による承認 (Human-in-the-loop)

変更系の効果が注釈されたツール — effect: "write" | "update" | "destructive" (推奨) または従来の destructive: true ブール値 — は、実行前に明示的なユーザー承認が必要です。デフォルトで安全側に倒れる設計です。approval.requireForDestructive: false を設定してよいのは、シングルユーザーのコンテキストで動作する完全自律型のバックオフィスエージェントの場合のみです。

フロー:

  1. ツールの実行前に、agents plugin が appkit.approval_pending SSE イベントを発行し、保留中の呼び出しの approval_idstream_idtool_nameargsannotations を伝えます。
  2. チャットクライアントが承認 prompt を表示します (リファレンスアプリの承認カードを参照) 。
  3. ストリームを開始したユーザー本人が、決定内容を POST /api/agents/approve に送信します:

    POST /api/agents/approve
    Content-Type: application/json
    X-Forwarded-User: <end-user id>
    X-Forwarded-Access-Token: <OBO token>
    
    { "streamId": "...", "approvalId": "...", "decision": "approve" | "deny" }
  4. 承認された場合、ツールは通常どおり実行され、ストリームもそのまま継続します。拒否された場合、アダプターはツールの output として文字列 "Tool execution denied by user approval gate (tool: <name>)." を受け取り、LLM は謝罪や再計画を行えます。approval.timeoutMs (デフォルト 60 秒) 以内に決定が届かない場合、ゲートが自動的に拒否します。

このルートでは、決定を行うのがストリームの所有者であることが必須です。異なる x-forwarded-user からの承認は 403 を返します。POST /api/agents/cancel でストリームをキャンセルすると、そのストリーム上の保留中の承認はすべて拒否されます。

リソース制限

このpluginは、暴走したprompt、不正な動作をするクライアント、promptインジェクションによる委譲ループから単一インスタンスのdeploymentを保護するため、いくつかの上限を設けています。一部は静的 (リクエストスキーマで強制) で、一部は agents({ limits: { ... } }) で設定できます。

静的な上限 (POST /chatPOST /invocationsPOST /responses のリクエスト解析時に適用) :

フィールド上限理由
chat.message64,000文字約16kトークン。これを超える本文はほぼ確実に不正利用です。
invocations.input 文字列64,000文字同上。
invocations.input 配列100件1回のリクエストで数百件のメッセージがスレッドストアに投入されるのを防ぎます。
invocations.input[].content 文字列64,000文字投入メッセージ1件あたりの上限です。
invocations.input[].content 配列100件投入メッセージ1件あたりの上限です。

設定可能な上限 (既定値を記載) :

agents({
  limits: {
    maxConcurrentStreamsPerUser: 5,  // 超過時は HTTP 429 + Retry-After を返す
    maxToolCalls: 50,                // 上限を使い切ったら run を中断する
    maxSubAgentDepth: 3,             // これを超えるサブエージェントの再帰は拒否する
    toolCallTimeoutMs: 300_000,      // ツール呼び出し単位のタイムアウト(5分。コールド状態の SQL/Genie を見込んだ余裕)
  },
});

maxToolCalls の予算は、最上位のアダプターと、それが委譲するすべてのサブエージェントで共有されます。そのため、prompt インジェクションによるファンアウトが階層を深くたどって制限を回避することはできません。maxConcurrentStreamsPerUser はグローバルではなくユーザーごとの設定であり、あるユーザーが上限に達しても他のユーザーには影響しません。

Runtime API

createApp の後、plugin は次を公開します。

appkit.agents.list();               // => ["support", "researcher", ...]
appkit.agents.get("support");       // => RegisteredAgent | null
appkit.agents.getDefault();         // => "support"
appkit.agents.register(name, def);  // 動的に登録
appkit.agents.reload();             // ディレクトリを再スキャン
appkit.agents.getThreads(userId);   // ユーザーのスレッド一覧を取得

エージェントの評価

AppKit には、ここで構築するエージェント向けの評価フレームワークが同梱されています。評価は TypeScript で defineEval を使って記述し、メッセージを送信してエージェントを動かし、その応答やツールの使用状況を決定論的なマッチャーまたは LLM ジャッジで検証します。評価は HTTP 経由 (--url) で稼働中のアプリに対して実行され、Databricks の認証情報と実験 (experiment) があれば、MLflow のネイティブな「評価実行 (Evaluation runs) 」として記録され、各ターンのトレースにアサーションごと・ジャッジごとのフィードバックが添付されます。評価 API はベータ機能の一部です。@databricks/appkit/beta からインポートしてください。

評価は各エージェントと同じ場所に配置します: server/agents/<agent-id>/evals/*.eval.ts。各ファイルは defineEval({ test }) を 1 つ default export します。テスト対象のエージェントは既定で親ディレクトリの <agent-id> になります。別のエージェントを対象にする場合は agent: を指定してください。

最初の評価

// server/agents/query/evals/smoke.eval.ts
import { defineEval } from "@databricks/appkit/beta";

export default defineEval({
  description: "Query agent responds to a greeting",
  async test(t) {
    await t.send("Hi there!");
    t.succeeded(); // ゲート: エージェント/ストリームのエラーが発生せずにターンが完了したこと
  },
});

アプリを起動し、そのアプリに対して評価を実行します。

# アプリが起動しており、--url でアクセスできる状態である必要があります
appkit agent eval --url http://localhost:3000

# 部分文字列で特定のエージェント/評価に絞り込み、project のルートを指定します
appkit agent eval query --root apps/dev-playground --url http://localhost:3000

位置引数の [filter] は、<agent>/<id> に指定した部分文字列を含む eval (またはエージェント ID の完全一致) にマッチします。このコマンドは server/agents/*/evals/ 配下のすべての *.eval.ts を検出し、実行中のアプリに対してそれぞれを実行します。いずれかのゲートが失敗した場合は、ゼロ以外の終了コードで終了します。

アサーション

すべてのアサーションはチェーン可能なハンドルを返します。アサーションはデフォルトでゲートとして機能し、失敗すると評価全体が失敗します (終了コードが非ゼロになります) 。.soft() をチェーンすると記録のみのメトリクスに降格し、.gate() でソフトアサーションをゲートに戻せます。また .atLeast(n) でスコア付きアサーションの合格しきい値を設定できます。

アサーション合格条件
t.succeeded()最後のターンがエージェントエラーやストリームエラーなしで完了した場合。
t.calledTool(name)エージェントが run 中に name を呼び出した場合。
t.calledToolWith(name, expected)nameexpected を深く含む引数で呼び出された場合 (expected 内のすべてのキーが再帰的に一致すること。追加の引数は無視されます) 。
t.check(value, matcher)value がマッチャーを満たす場合。マッチャーは includes(substring)equals(expected)matches(pattern) のいずれかです。
import { defineEval, includes } from "@databricks/appkit/beta";

export default defineEval({
  description: "Helper agent answers a math question",
  agent: "helper",
  async test(t) {
    await t.send("What is 2 + 2?");
    t.succeeded();                          // ゲート
    t.check(t.reply, includes("4")).soft(); // 記録のみの指標。ゲートは失敗しない
  },
});
// ツール引数の部分一致(ディープ)チェック
await t.send("What's the weather in Brooklyn?");
t.calledTool("get_weather");
t.calledToolWith("get_weather", { city: "Brooklyn" });

評価をスキップするには t.skip("reason") を呼び出します。直近のターンを確認するには、t.replyt.toolCallst.sessionId を参照してください。

LLM-as-judge

t.judge.* は、LLM ジャッジ (Databricks サービング endpoint を指定した autoevals 経由) で最後の応答をスコアリングします。各ジャッジはスコア付きのアサーション (0..1) を返し、デフォルトでゲートとして機能します。つまり、基準に届かなければ eval は失敗します。.atLeast(n) をチェーンして合格しきい値を設定するか、.soft() で記録のみに留めることもできます。ジャッジを使うにはジャッジモデルが必要です。--judge-model <endpoint> を渡す (または APPKIT_JUDGE_MODEL を設定する) とともに、Databricks 認証を指定してください。指定がない場合、t.judge.* は明確なメッセージとともに例外をスローします。

async test(t) {
  await t.send("What's the weather in Brooklyn?");
  t.succeeded();

  // closedQA は正解データ不要で、質問に対する応答の適切さを評価します。
  (await t.judge.closedQA(
    "Does the response describe weather conditions for Brooklyn?",
  )).atLeast(0.5);
}
  • t.judge.factuality(expected) — 期待される参照回答と比較して応答を採点します。
  • t.judge.closedQA(criteria)criteria に従って、応答が質問に答えているかを採点します。
  • t.judge.custom(spec) — プロンプトテンプレート型のジャッジ ({ name, promptTemplate, choiceScores }) で、MLflow の @scorer に相当する TypeScript 版です。

ジャッジモデルを設定していない状態でも評価で駆動パスを実行したい場合は、isJudgeConfigured() でジャッジの呼び出しをガードしてください:

import { defineEval, isJudgeConfigured } from "@databricks/appkit/beta";
// ...
if (isJudgeConfigured()) {
  (await t.judge.closedQA(guideline)).atLeast(0.5);
}

会話

t.send はユーザーの 1 ターンに相当します。これらをどの順序で並べるかによって、スレッドの流れが決まります。

// ワンショット: 1ターンのみ。
await t.send("Summarize Q3 revenue.");
t.succeeded();

// マルチターン: 連続する send は同じスレッドを共有するため、エージェントが履歴を参照できる。
await t.send("Show me the orders table.");
await t.send("Now filter it to last week.");
t.succeeded();

// t.reset() は会話を破棄する。次の send は履歴のない新しいスレッドを開始する。
// 1つのテスト内で独立したワンショットの検証を複数実行したいときに使う。
await t.send("What's 2 + 2?");
t.check(t.reply, includes("4"));
t.reset();
await t.send("What's the capital of France?");
t.check(t.reply, includes("Paris"));

データセット

dataset: { table } を追加すると、Databricks の マネージド評価データセット (inputs/expectations 列を持つ Unity Catalog の catalog.schema.table) をスイープできます。評価は 1 行につき 1 回実行され、ランナーは各行の inputst.input に、expectationst.expected にバインドします。データセットの読み取りには、ワークスペースクライアントと warehouse (--warehouse-id と認証情報) が必要です。

import { defineEval, isJudgeConfigured, userTurns } from "@databricks/appkit/beta";

export default defineEval({
  description: "Query agent satisfies each dataset row's guidelines",
  dataset: { table: "main.mario.appkit_eval_dataset" }, // `limit?` は省略可能
  async test(t) {
    // 行内のすべてのユーザーターンを1つのスレッドに対して再生し、エージェントが
    // 会話の積み重ねを把握できるようにする。ユーザーターンが1つだけの行は1回送信するのみ。
    for (const turn of userTurns(t.input)) {
      await t.send(turn);
    }
    t.succeeded();

    if (isJudgeConfigured()) {
      for (const guideline of guidelines(t.expected)) {
        (await t.judge.closedQA(guideline)).atLeast(0.5);
      }
    }
  },
});

行の構造は MLflow のマネージドデータセット UI と一致します。

inputs {"messages":[{"role":"user","content":"..."}]} expectations {"guidelines":{"value":["...","..."]}} (optional)

userTurns(t.input) は、{messages:[...]} 入力から role: "user" のメッセージ内容をすべて順番に抽出します。1行に複数ターンの会話全体を含めることができ、各ユーザーターンを同一スレッドに対して順に再生することで、エージェントが履歴を積み上げていきます (間に挟まる assistant/system のターンは無視され、エージェントが自ら生成します) 。expectations.guidelines.value はご自身で読み取ってください。UI は配列を {value: [...]} の形でラップします:

function guidelines(expected: Record<string, unknown> | undefined): string[] {
  const g = (expected?.guidelines as { value?: unknown } | undefined)?.value;
  return Array.isArray(g) ? g.map(String) : [];
}

データセット評価を実行します。

appkit agent eval dataset --root apps/dev-playground --url http://localhost:3000 \
  --profile <profile> --warehouse-id <warehouse-id> --judge-model <endpoint>

eval の実行と CI

appkit agent eval [filter] — 実行中のアプリに対してエージェントの eval (server/agents/<id>/evals/*.eval.ts) を実行します。

フラグ説明
[filter]<agent>/<id> にこの部分文字列を含む eval のみを実行します (エージェント id の完全一致も可)
--url <url>実行中のアプリのベース URL (既定値 http://localhost:3000)
--strictソフトアサーションの失敗も不合格として扱います
--root <dir>server/agents/ を含むプロジェクトルート (既定値: カレントディレクトリ)
--header <header...>'Key: value' 形式の追加リクエストヘッダー (繰り返し指定可)
--tag <tag...>指定したタグのいずれかが付いた eval のみを実行します (繰り返し指定可)
--profile <name>OAuth による認証に使用する Databricks CLI プロファイル (既定値: DATABRICKS_CONFIG_PROFILE)
--databricks-host <host>MLflow アセスメントの書き込みに使用する Databricks ホスト (既定値: DATABRICKS_HOST)
--databricks-token <token>MLflow アセスメントの書き込みに使用する Databricks トークン (既定値: DATABRICKS_TOKEN)
--experiment <id>評価 run に使用する MLflow エクスペリメント id (既定値: MLFLOW_EXPERIMENT_ID)
--warehouse-id <id>マネージド評価データセットの読み取りに使用する SQL warehouse id (既定値: DATABRICKS_WAREHOUSE_ID)
--judge-model <endpoint>t.judge.* の LLM ジャッジとして使用する Databricks サービング endpoint (既定値: APPKIT_JUDGE_MODEL)
--concurrency <n>同時に実行する eval/dataset 行の最大数 (既定値: 4)
--timeout <ms>eval ごとの既定タイムアウト (ミリ秒。eval 個別の timeoutMs が優先されます)
--retries <n>インフラ起因のエラー (ターン/タイムアウト) で失敗した eval を最大 N 回まで再実行します。アサーションの失敗は再試行されません
--min-pass-rate <rate>すべての eval の合格を要求する代わりに、全体の合格率 (0..1) でゲートします。下回った場合は終了コード 1 を返します
--reporter <format>レポート形式: text (ライブコンソール) 、json (dashboard 向け) 、junit (CI テストレポーター向け)
--output <file>json/junit レポートを標準出力ではなくこのファイルに書き出します (text では無視されます)

注意事項:

  • 認証は OAuth が基本です。 --profile <name> は Databricks CLI プロファイルから OAuth token を発行するため、PAT は不要です。--databricks-host--databricks-token を明示的に指定した場合 (または DATABRICKS_* 環境変数がある場合) は、プロファイルより優先されます。
  • --retries が吸収するのはインフラ起因の不安定さのみです。 再試行が走るのは、eval が例外をスローするかタイムアウトした場合 (result.error が設定された場合) のみです。誤った応答は意味のあるシグナルであり、再試行されることはありません。試行ごとに新しいドライバーが割り当てられます。
  • ゲーティング。 既定では、いずれかの eval が失敗した時点で run は非ゼロで終了します。--min-pass-rate 0.9 を指定するとしきい値モードに切り替わり、全体の合格率がしきい値を下回った場合にのみ非ゼロで終了します。さらに --strict を指定すると、ソフトアサーションの失敗も不合格として扱われます。
  • CI レポート。 --reporter junit --output results.xml は CI テストレポーター向けに JUnit ファイルを書き出し、--reporter json は機械可読な結果を出力します。機械可読レポーターでは、人間向けの出力は標準エラー出力に送られるため、標準出力はレポート専用のクリーンな状態に保たれます。
# CI: 合格率90%でゲートし、JUnitを出力、認証してMLflowへレポート
appkit agent eval --url "$APP_URL" \
  --profile ci \
  --experiment "$MLFLOW_EXPERIMENT_ID" \
  --concurrency 4 --retries 1 \
  --min-pass-rate 0.9 \
  --reporter junit --output eval-results.xml

ディレクトリ単位の設定

エージェントの評価ファイルと同じ場所に evals.config.ts を配置すると、そのエージェントの run に対するデフォルト値を設定できます。

// server/agents/query/evals/evals.config.ts
import { defineEvalConfig } from "@databricks/appkit/beta";

export default defineEvalConfig({
  maxConcurrency: 4,   // 最大4件の評価/行を並列実行
  timeoutMs: 30_000,   // 評価ごとのデフォルトタイムアウト
});

優先順位: CLI フラグevals.config.ts の値より優先され、さらにその値が組み込みのデフォルト(同時実行数 4、タイムアウトなし)より優先されます。個々の eval に指定した def.timeoutMs は、その eval に限り両方を上書きします。

MLflow レポート

--experiment <id> (または MLFLOW_EXPERIMENT_ID) を Databricks 認証と併せて設定すると、ランナーは最初にネイティブな MLflow の Evaluation run を作成します。各評価がアプリに対して実行されるたびに、そのターンのトレースが run にリンクされ、すべてのアサーションとジャッジスコアがそのトレースへのフィードバックとして書き戻されます。

  • 決定的なアサーションは、真偽値を持つ CODE ソースの Feedback になります。
  • ジャッジによるアサーションは、0〜1 の数値スコアと根拠を持つ LLM_JUDGE ソースの Feedback になります。
  • 評価ごとに全体的な合否を示す appkit_eval の Feedback が付与され、run の終了時に集計メトリクスが記録されます。

--experiment (および MLFLOW_EXPERIMENT_ID) を指定しなければ、MLflow への副作用なしに評価を完全にローカルで実行できます。この場合、CLI は評価 run をスキップした旨のメッセージを表示します。

Frontmatter スキーマ

キー備考
endpointstringModel サービング endpoint 名。model のショートカット。
modelstringendpoint と同じ。どちらを使っても構いません。
toolsarray統合ツールリスト。エントリは、plugin ツールの場合は plugin:<name> / plugin:<name>: [t1, t2] / plugin:<name>: { only, except, rename, prefix }、アンビエントツールの場合は agents({ tools: {...} }) に対して解決される単独の <key> です。例は上記の「レベル 2: frontmatter でツールをスコープする」を参照してください。
skillsarrayこのエージェントに見せるグローバル skills (共有の skills/ プールまたはカタログボリューム) の名前。<id>/skills/ 配下のエージェント個別の skills は常に見えます。Skills を参照してください。
defaultbooleandefault: true が指定されたエージェント id のうち、ソート順で最初のものがデフォルトエージェントになります。
agentsarray委譲先のサブエージェント id (兄弟フォルダー) 。それぞれが agent-<id> ツールになります。他の Markdown エージェントおよびコードエージェントに対して解決されます。
maxStepsnumberアダプターの最大ステップ数のヒント。
maxTokensnumberアダプターの最大トークン数のヒント。
generationParamsobjectAppKit がアダプターを構築する際に引き渡されるアダプターの生成パラメーター (例: temperaturetop_p) 。
baseSystemPromptfalsestringエージェント単位の上書き。false にすると AppKit のベース prompt を無効化します。
ephemeralbooleantrue の場合、このエージェントへのチャットリクエストで作成されたスレッドは、ストリーム終了後に ThreadStore から削除されます。履歴が蓄積されたり以降の呼び出しに影響したりしないよう、ステートレスな単発エージェント (例: オートコンプリート) に使用します。デフォルトは false

不明なキーはログに記録されたうえで無視されます。不正な YAML や存在しない plugin/ツールの参照は、起動時に例外を送出します。

Databricks Developer Hub

次のエージェント型アプリを数分でリリースする準備はできていますか?

ドキュメントを読む