Skip to main content
Sonzai Docs
チュートリアル·約15分

リソースインベントリ+ナレッジベース

各ユーザーが持つツール、ライセンス、サブスクリプションを追跡し、ナレッジベースからのリアルタイムコストデータで充実させます。このチュートリアルを終える頃には、ユーザーのサブスクリプション総額、コスト変動、検討すべき代替ソリューションをすべて実際のデータに基づいて回答できるエージェントが完成します。

構築するもの

  • market_priceとティア情報を持つsoftware_licenseエンティティのナレッジベーススキーマ
  • bulkUpdateを介してライブ価格データをKBにプッシュするコスト同期パイプライン
  • → 会話中に割り当てられたツールを自動追跡するinventory機能付きエージェント
  • → ユーザーの割り当てと現在のKB価格データを結合するポートフォリオクエリ

1. エンティティスキーマの定義

スキーマは、各エンティティタイプについてKBがどのフィールドを抽出・インデックス化するかを指定します。software_license用のスキーマを作成して、プラットフォームにライセンスデータの保存・検索方法を伝えます。

import { Sonzai } from "@sonzai-labs/agents";

const client = new Sonzai({ apiKey: process.env.SONZAI_API_KEY! });
const PROJECT_ID = "proj_abc123";

await client.knowledge.createSchema(PROJECT_ID, {
  entity_type: "software_license",
  display_name: "Software License",
  fields: [
    { name: "market_price",  type: "number",  indexed: true  },
    { name: "tier",          type: "string",  indexed: true  },
    { name: "category",      type: "string",  indexed: true  },
    { name: "license_type",  type: "string",  indexed: false },
    { name: "trend_30d",     type: "string",  indexed: false },
  ],
});

スキーマの作成は一度だけで十分です。それ以降、プラットフォームはそのタイプのすべてのエンティティを検証・インデックス化します。

2. 初期データのシーディング

insertFactsを使用して最初のエンティティセットを挿入します。これは本番稼働前の履歴データのロードにも使用できます。KBが代替または補完ツールの推奨を表示できるよう、リレーションシップも含めます。

await client.knowledge.insertFacts(PROJECT_ID, {
  entities: [
    {
      type: "software_license",
      label: "Figma Enterprise",
      properties: {
        market_price: 75,
        tier: "Enterprise",
        category: "Design Tools",
        license_type: "per-seat-annual",
        trend_30d: "+5%",
      },
    },
    {
      type: "software_license",
      label: "Slack Business+",
      properties: {
        market_price: 12.50,
        tier: "Business",
        category: "Communication",
        license_type: "per-seat-monthly",
        trend_30d: "+3%",
      },
    },
    {
      type: "category",
      label: "Design Tools",
      properties: { vendor_count: 18, avg_seat_cost: 45 },
    },
  ],
  relationships: [
    { from_label: "Figma Enterprise",  to_label: "Design Tools", edge_type: "belongs_to" },
    { from_label: "Slack Business+",   to_label: "Communication", edge_type: "belongs_to" },
    { from_label: "Figma Enterprise",  to_label: "Slack Business+", edge_type: "commonly_bundled" },
  ],
  source: "seed_v1",
});

3. bulkUpdateで価格を最新に保つ

スケジュール(例:日次cron)でコスト同期ジョブを実行し、ベンダーデータソースから最新の価格を取得してKBにプッシュします。bulkUpdateはラベルで一致する既存ノードにプロパティをマージします -- 削除して再挿入する必要はありません。

// cost-sync.ts — run daily
import { Sonzai } from "@sonzai-labs/agents";
import { fetchLatestPricing } from "./vendor-api"; // your data source

const client = new Sonzai({ apiKey: process.env.SONZAI_API_KEY! });
const PROJECT_ID = "proj_abc123";

async function syncPricing() {
  const pricing = await fetchLatestPricing(); // [{ name, price, trend }]

  await client.knowledge.bulkUpdate(PROJECT_ID, {
    updates: pricing.map((license) => ({
      entity_type: "software_license",
      label: license.name,
      properties: {
        market_price: license.price,
        trend_30d: license.trend,
        last_synced: new Date().toISOString(),
      },
      // upsert: true — creates the node if it doesn't exist yet
      upsert: true,
    })),
  });

  console.log(`Synced ${pricing.length} license prices`);
}

syncPricing();

バッチサイズ

100アイテム以下のバッチは同期的に処理されます(即時レスポンス)。より大きなバッチはキューに入れられ非同期で処理されます -- レスポンスには完了をポーリングできるジョブIDが含まれます。

4. エージェントでインベントリを有効化

エージェントでinventoryknowledge機能を有効にします。 これにより、エージェントにsonzai_inventory_updatesonzai_inventoryツールが自動的に付与されます -- プロンプトエンジニアリングは不要です。

const AGENT_ID = "agent_xyz";

await client.agents.updateCapabilities(AGENT_ID, {
  inventory: true,   // enables sonzai_inventory_update + sonzai_inventory tools
  knowledge: true,   // enables knowledge_search tool
  project_id: PROJECT_ID,  // which KB to join against
});

ダッシュボードからも設定できます: Agents → あなたのエージェント → Capabilities Inventory をオンにしてください。

5. 会話中にエージェントにリソースを追跡させる

インベントリが有効になると、ユーザーがツールやサブスクリプションについて言及するたびに、エージェントは自動的にsonzai_inventory_updateを呼び出します。通常通りチャットするだけで、プラットフォームがKBの解決とストレージを処理します。

// Your backend chat endpoint
for await (const event of client.agents.chatStream(AGENT_ID, {
  userId: "user_123",
  messages: [
    {
      role: "user",
      content: "We just provisioned 10 Figma Enterprise seats at $75/seat.",
    },
  ],
})) {
  // The agent streams its reply — and internally calls
  // sonzai_inventory_update({ action: "add", item_type: "software_license",
  //   description: "Figma Enterprise", properties: { plan: "Enterprise",
  //   purchase_price: 75, quantity: 10 } })
  // The platform resolves the KB node, stores the link, and the agent
  // continues the conversation without interruption.
  process.stdout.write(event.choices?.[0]?.delta?.content ?? "");
}

KB解決の仕組み:プラットフォームはKB内でアイテムの説明を検索します。完全に一致するノードが1つある場合、自動的にリンクされます。複数の候補がある場合、レスポンスは候補リスト付きのstatus: "disambiguation_needed"を返し、エージェントがユーザーに確認を求めることができます。

6. エンリッチされたポートフォリオのクエリ

mode="value"を使用して、各ユーザーリソースを最新のKB価格データと結合して取得します。プラットフォームはgain_lossを自動計算します:(market_price - purchase_price) × quantity

const portfolio = await client.agents.inventory.query(AGENT_ID, "user_123", {
  mode: "value",
  project_id: PROJECT_ID,
});

// portfolio.items:
// [
//   {
//     fact_id: "fact_abc",
//     item_label: "Figma Enterprise",
//     kb_node_id: "node_xyz",
//     user_properties: { plan: "Enterprise", purchase_price: 75, quantity: 10 },
//     market_properties: { market_price: 80, tier: "Enterprise", trend_30d: "+5%" },
//     gain_loss: 50,
//   },
// ]
// portfolio.totals: { "market_price:sum": 800, "*:count": 10 }

console.log(`Portfolio value: $${portfolio.totals?.["market_price:sum"]}`);
console.log(`Total cost change: $${portfolio.items.reduce((s, i) => s + i.gain_loss, 0)}`);

mode="aggregate"aggregationsパラメーターを使用して、すべてのリソースを一覧表示せずにポートフォリオレベルの合計を取得することもできます -- 多くのサブスクリプションを持つ組織に便利です。

// Aggregate: total count + total subscription cost, grouped by item_type
const agg = await client.agents.inventory.query(AGENT_ID, "user_123", {
  mode: "aggregate",
  aggregations: "market_price:sum,*:count",
  group_by: "item_type",
  project_id: PROJECT_ID,
});
// agg.totals: { "market_price:sum": 875, "*:count": 12 }
// agg.groups: [{ group: "software_license", values: { sum: 875, count: 12 } }]

7. 既存サブスクリプションのバッチインポート

ユーザーが既にサブスクリプションのセットを持っている場合(CSV、調達システムのエクスポートなど)、エージェントが会話中に各リソースを発見するのを待つのではなく、一括でインポートします。

await client.agents.inventory.batchImport(AGENT_ID, "user_123", {
  item_type: "software_license",
  project_id: PROJECT_ID,
  items: [
    {
      description: "Figma Enterprise",
      properties: { quantity: 10, plan: "Enterprise", purchase_price: 75 },
    },
    {
      description: "GitHub Enterprise",
      properties: { quantity: 25, plan: "Enterprise", purchase_price: 21 },
    },
  ],
});

バッチあたり最大1,000アイテム

バッチエンドポイントは1回の呼び出しで最大1,000アイテムを処理します。より大きなインポートの場合は、複数の呼び出しに分割するか、ダッシュボードのCSVプライミング機能を使用してください。

次のステップ

  • → KBにレコメンデーションルールを設定して、ユーザーに代替ツールを表示
  • → トレンドトラッキング(7日/30日/90日)を追加して「最大のコスト増加」レポートを実現
  • → ユーザーごとのインベントリを Dashboard → Agents → あなたのエージェント → Users → ユーザーを選択 → Inventory / Assets でライブ確認
  • → スキーマ、分析ルール、全文検索については ナレッジベース リファレンスを参照