mcpField
F
functions

functions

github.com/zavudevエージェントワークフロー1174 回インストール今日更新
リポジトリ

functions について

Zavu Functionsは、Zavu Cloud上でサーバーレスのTypeScriptエージェントやツールを構築するフレームワークです。独自の処理を実装し、CLIでデプロイやテストを行いたい開発者向けです。

主な機能

  1. 01エージェントとツールの宣言的な定義
  2. 02インポートされたTypeScriptファイルのデプロイ
  3. 03CLIによる関数とシークレットの管理
  4. 04テンプレートや既成エージェントの作成
  5. 05ローカルでのハンドラー呼び出しテスト
  6. 06HTTP経由での関数公開

活用シーン

  1. 01独自のツールハンドラーを持つエージェントの構築
  2. 02ツールからデータベースや内部APIへの接続
  3. 03Gitリポジトリからのエージェント設定のデプロイ
  4. 04模擬引数を使ったツールハンドラーのテスト

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/zavudev/zavu-skills --skill functions

インストール後、Claude Code で/functionsを入力して読み込まれたか確認します。

詳細

Zavu Functions

Zavu Functions = Zavu Cloud 上のサーバーレス TypeScript + AI エージェント向けの宣言的フレームワークです。

import { defineAgent, defineTool } from "@zavudev/functions"

defineAgent({
  senderId: process.env.SENDER_ID!,
  name: "Bella",
  provider: "zavu",
  model: "openai/gpt-5.6-luna",
  prompt: "You are Bella, host at the restaurant. Be brief.",
})

defineTool({
  name: "check_availability",
  description: "Get free reservation slots for a date.",
  parameters: {
    type: "object",
    properties: { date: { type: "string" }, partySize: { type: "number" } },
    required: ["date", "partySize"],
  },
  handler: async ({ date, partySize }) => {
    return { available: true, slots: ["19:00", "21:00"] }
  },
})

これでエージェントとツールの一式が完成します。npx zavudev deploy がライブの状態を調整します。

プロジェクトの構成

何かを書く前に決めてください。後から構成を変更すると、手間のかかるやり直しになります。

my-agent/
├── index.ts        entrypoint: defineAgent + defineTool live here
├── lib/orders.ts   imported by index.ts, deployed with it
├── package.json    npm dependencies, installed at build time
└── .zavu/          created by the CLI; do not edit
  • 複数ファイルに分けても問題ありません。 deploy は index.ts から開始し、相対インポートを通じて到達したすべてのファイルをアップロードします。プロンプト、ツール、ビジネスロジックは自由に分割できます。
  • インポートされたものだけがデプロイされます。 どこからもインポートされていないファイルは手元に残るため、テストや作業用ファイルはデプロイされません。
  • npm パッケージはファイルではありません。 package.json の dependencies に宣言してください。インストールとバンドルは自動で行われます。
  • 制限事項: 合計 200 ファイル、900,000 バイトまでです。プロジェクトルートより上のパス(../shared/x.ts)、node_modules/、および __zavu で始まる名前は拒否されます。プロジェクト間でコードを共有するには、npm パッケージとして公開してください。
  • --source <file> で別のエントリーポイントを指定できます。

Functions と命令型 AI Agent API の使い分け

ユースケース使用するもの
顧客が自身の言語でカスタムツールハンドラーを実装する、コードファーストのエージェントを求めているFunctions
ツールがユーザーのデータベースを照会したり、社内 API を呼び出したり、データを変換してから返したりする必要があるFunctions
ユーザーが git リポジトリから再現可能な設定(信頼できる唯一の情報源)を求めているFunctions
ユーザーがダッシュボードからノーコードで設定したい命令型の senders.agent.create API(ai-agent スキルを参照)
ダッシュボードで設定せず、イベント駆動型ハンドラー(message.inbound、broadcast.status_changed)を利用したいFunctions

ユーザーがコードの記述、defineAgent、defineTool、npx zavudev deploy、または「サーバーレス」に言及した場合は、このスキルを使用してください。それ以外の場合は ai-agent に案内してください。

主要インターフェースとしての CLI

Functions は API 呼び出しではなく、すべて zavu CLI で管理します。インストールは一度だけです。

# `npx zavudev@latest` needs no install and always runs the current version.
# Pin nothing: an old CLI is the most common source of "the API does not
# support this".
npx zavudev@latest --version

npx zavudev login

npx zavudev login を実行するとブラウザーが開き、認証情報が ~/.zavu/credentials.json に保存されます。

ファクトリーエージェント

音声またはテキストエージェントをすばやく動作させるには、完成済みのものをコードベースに取り込んでからデプロイします。各ファクトリーエージェントは Zavu Function であり、その index.ts で defineAgent を使ってエージェントを、defineTool を使ってスキルを宣言します。つまり、取り込むのは実際のコードであり、自分で所有して編集できます。

npx zavudev agents catalog                            # list the factory agents
npx zavudev agents pull fermi --sender <senderId>     # scaffold ./fermi, register it, set SENDER_ID
cd fermi
npx zavudev deploy

npx zavudev agents catalog では、各エージェントの ID、名前、voice かどうか、ツール数、カテゴリが表示されます。npx zavudev agents pull <id> は ./<id> にひな形を作成します(--dir <path> で変更できます)。--sender <senderId> を指定すると SENDER_ID シークレットが設定されるため、すぐに npx zavudev deploy を実行できます。指定しない場合は、先に npx zavudev fn secrets set SENDER_ID <senderId> を実行してください。音声エージェントには voice ブロックがあらかじめ設定されています。index.ts を編集して再デプロイすれば、反復的に改善できます。

npx zavudev agents init は、すべてのセットアップをガイド付きの単一コマンドで実行します。送信者を作成し(必要に応じて電話番号を購入)、ファクトリーエージェントを取り込み、SENDER_ID を設定します。

送信者のチャネルがエージェントの応答先となり、それぞれ CLI から接続します。送信者に電話番号があれば、音声と SMS はすぐに利用できます。リッチチャネルは、それぞれ次の 1 コマンドで接続できます。

npx zavudev telegram connect --sender <senderId> --token <botFatherToken>
npx zavudev email-domains add example.com                       # then publish DNS + `verify`

ライフサイクル全体

1. ひな形を作成する
npx zavudev fn init --name order-bot --template blank
cd order-bot

利用可能なテンプレート:blank、restaurant-booking。最新の一覧を確認するには zavu fn init --help を実行してください。このページではなく、こちらの一覧が正式な情報源です。

適したものがある場合は、ファクトリーエージェントを優先してください。 zavu agents catalog には、本番用エージェント(サポート、リード獲得、予約)が表示されます。zavu agents pull <id> で、プロンプト、スキル、音声エージェントの場合は音声設定を含む一式を作成できます。

init によって index.ts、package.json、およびこのディレクトリをユーザーのプロジェクト内の Function レコードに関連付ける .zavu/config.json が作成されます。一度関連付けると、以降の各コマンドで Function が自動的に特定されます。

2. シークレットを設定する

シークレットは暗号化された環境変数で、デプロイ時に Function に渡されます。

npx zavudev fn secrets set SENDER_ID jx7abc123def456
npx zavudev fn secrets set DATABASE_URL "postgres://..."
npx zavudev fn secrets list
npx zavudev fn secrets unset OLD_KEY

送信者 ID は npx zavudev senders list で確認できます。

3. エージェントとツールを作成する

index.ts を編集します:

import { defineAgent, defineTool, defineFunction } from "@zavudev/functions"

// Four fields are required: name, provider, model, and the prompt (under
// `prompt` or `systemPrompt`). A declaration missing any of them is SKIPPED —
// the deploy still succeeds, and you get a function with no agent. `description`
// and `instructions` are not fields.
defineAgent({
  senderId: process.env.SENDER_ID!,  // Optional: omit to declare it standalone
                                      // and connect senders afterwards.
  name: "Bella",
  provider: "zavu",              // Zavu's AI gateway (charged from project balance)
                                  // Or "openai" / "anthropic" / "google" / "mistral" with BYOK + apiKey
  model: "openai/gpt-5.6-luna",   // For "zavu" provider, prefix with the underlying provider
  prompt: "You are Bella…",       // System prompt
  channels: ["whatsapp"],         // Optional: default ["*"] = all channels the sender supports
                                  // Listing a channel the sender does NOT have does
                                  // not add it — it just never fires. Prefer the
                                  // default unless you are deliberately excluding one.
  // apiKey: process.env.OPENAI_API_KEY  // only for BYOK providers (openai / anthropic / google / mistral)
})

defineTool({
  name: "lookup_order",
  description: "Get current status of an order. Use when the customer asks about an order they placed.",
  parameters: {
    type: "object",
    properties: { orderId: { type: "string" } },
    required: ["orderId"],
  },
  handler: async (args, ctx) => {
    // ctx: { projectId, functionId, slug, log, memory, contactId?, conversationId?,
    //        senderId?, channel?, messageId?, contactPhone?, sessionId?, callId? }
    const res = await fetch(`https://pos.example.com/orders/${args.orderId}`, {
      headers: { Authorization: `Bearer ${process.env.POS_API_KEY}` },
    })
    return await res.json()
  },
})

// Optional: handle raw events (message.inbound from triggers, HTTP calls).
// NOT needed if you only declare agent + tools.
// The object form is what binds events — `on` is the subscription.
export default defineFunction({
  on: ["message.inbound"],
  handler: async (event, ctx) => {
    ctx.log("got event", event.type)
  },
})
4. デプロイする
npx zavudev deploy

出力:

✓ Deployed in 6.4s
  Agents synced:
    + Bella
  Tools synced:
    + lookup_order
マーカー意味
+ name作成されました
~ name既存のものに書き込みました
= name (unchanged)既存のものと変更はありません

マーカーは、次の2つのケースを区別します。編集せずに再デプロイすると = が表示され、~ はそのエージェントまたはツールに実際に書き換えがあったことを意味します。古いバックエンドでは、既存のものすべてに ~ が表示され、= は表示されませんでした。そのため、そのようなバックエンドを使用している場合、編集なしの再デプロイとプロンプトを書き換えた再デプロイは、同じように見えます。

いずれにしても、マーカーが示すのはデプロイで「書き込まれた」内容であり、エージェントが現在「何と言うか」ではありません。変更がモデルに反映されたかを確認するには、エージェントに次のように尋ねます:

npx zavudev agents test --agent <agentId> --message "<something your edit changes>" --json

編集したプロンプトに特徴的な単語を入れ、それが返ってくるか確認します。これは1つのコマンドで済み、料金もかからず、デプロイの概要とは違って、実際に知りたいことに答えてくれます。

✓ の上にある行を確認してください。 警告は成功行より前に表示され、デプロイが成功したように見えても実際にはそうでないケースを示します。マニフェストの確認で例外が発生した場合(何も同期されず、コマンドはゼロ以外の終了コードで終了します)、エージェントにツールが紐付けられていても、そのエージェントのチャンネルからは呼び出されない場合、または、すでにエージェントがいる sender に2つ目のエージェントが配置される場合(応答するのは最初のエージェントだけです)です。

エージェントが到達可能なチャンネルをまったく持たない場合、✓ は表示されません。エージェントが channels に sender が受信できないチャンネルを指定しているため、デプロイされて有効にはなりますが、メッセージを1件も受信できません。デプロイではエージェントと不一致が表示され、ゼロ以外の終了コードで終了します。エージェントの channels を変更するか、そのチャンネルを sender に接続して修正します(npx zavudev senders get <id> で、実際に接続されているチャンネルを確認できます)。

ローカル開発

このスキャフォールドは実際の TypeScript プロジェクトです。zavu init と zavu agents pull は、@zavudev/functions、typescript、@types/node を devDependencies として宣言する tsconfig.json と package.json を作成します。

関数を HTTP 経由で公開する

独自のエンドポイントが必要な関数(自分でホストする webhook や、自分で管理するツール URL など)は、--http を付けて作成します:

npx zavudev fn init --template blank --http
npx zavudev agents pull kepler --sender "$SENDER_ID" --http

URL は、その URL の背後に何かがデプロイされると利用可能になります。deploy で URL が表示され、いつでも確認できます:

npx zavudev fn info                 # status, and the public URL when HTTP is on
npx zavudev fn info --json          # same, machine-readable

既存の関数での切り替えも CLI の操作で行えます。すでにデプロイ済みの関数に適用されるため、再デプロイは不要です:

npx zavudev fn http enable
npx zavudev fn http disable
HTTP イベントは Zavu イベントではありません

この形状を想定してしまうと、デプロイし直すことになる唯一のケースです。HTTP 呼び出しで渡されるのは、トリガーが渡す { type, data } イベントではなく、API Gateway v2 の生のペイロードです。event.type と event.data は undefined で、body は event.body の下に文字列として渡されます:

{
  "version": "2.0",
  "rawPath": "/",
  "headers": { "content-type": "application/json" },
  "requestContext": { "http": { "method": "POST" } },
  "body": "{\"action\":\"availability\"}",
  "isBase64Encoded": false
}

そのため、次のように読み取り、requestContext からメソッドを確認します:

export default defineFunction({
  handler: async (event: any) => {
    const method = event?.requestContext?.http?.method ?? "GET"
    const raw = event?.isBase64Encoded
      ? Buffer.from(event.body ?? "", "base64").toString("utf8")
      : event?.body
    const payload = typeof raw === "string" && raw ? JSON.parse(raw) : {}
    return { statusCode: 200, body: JSON.stringify({ ok: true, method, payload }) }
  },
})

ハンドラーを Zavu イベントとして型付けすると、コンパイルは問題なく通る一方、本番環境では undefined を読み取ることになります。これはランタイムパッケージ自身の型が警告している失敗です。

ローカルで何かをする前に npm install を実行してください。 実行するまで、何も解決されません:

cd <your-function-dir>
npm install

これを実行しないと、zavu fn invoke は Cannot find module '@zavudev/functions' で失敗し、npx tsc はコンパイラーの代わりに無関係な非推奨パッケージを警告なく取得します。どちらの方法でもデプロイは動作します。本番環境ではランタイムレイヤーがモジュールを提供し、マシンからモジュールが送信されることはないためです。そのため、影響を受けるのはローカルでの作業だけですが、作業内容の検証にまさに使うのがこのローカル環境です。

.zavu/ をコミットしてください。このディレクトリには functionId が含まれており、それを調べるコマンドはありません。そのため、これを含めずにクローンしたチームメイトはデプロイできません。

zavu fn invoke は Bun でコードを実行します。https://bun.sh からインストールしてください。それ以外のコマンドはすべて通常の Node で実行されます。

5. テスト

ローカルでの呼び出し(クラウドとの往復を省略):

# Call a tool handler with synthetic args
npx zavudev fn invoke --tool lookup_order --args '{"orderId":"ORD-001"}'

# Simulate an inbound event for defineFunction
npx zavudev fn invoke --event message.inbound --data '{"from":"+14155551234","text":"hi"}'

--args はハンドラーの実行前に、ツール独自の parameters スキーマに照らして検証されます。必須フィールド、型、enum の許可値が対象です。一致しない場合は、該当フィールドと期待される形式が正確に示され、ハンドラーは呼び出されません。

✗ arguments do not match the tool's schema:
   • missing required "orderId"
   • "score" must be one of "hot" | "warm" | "cold", got "banana"
  expected: { orderId: string, score: hot|warm|cold, notes: string? }

プロジェクトの依存関係がインストールされていない場合は、Bun の生のモジュール解決エラーを表示するのではなく、npm install を実行するよう案内して停止します。

シークレットは注入されません。 API が返すのは、各シークレットのキーと末尾 4 文字であり、値は返しません。そのため、fn invoke ではシークレットを取得できません。--live を指定しても同様です。シークレットは index.ts と同じ場所にある .env に記述するか(自動的に読み込まれます)、シェルでエクスポートしてください。これを行わないと、process.env.SOMETHING を読み取るハンドラーは未設定時の分岐に入り、{ ok: false, reason: "not_configured" } のような一見もっともらしい失敗を返します。これはコードのバグのように見えますが、そうではありません。このコマンドは警告を表示し、不足しているキーを示します。

--live を指定すると、SDK の呼び出しはスタブではなく実際に実行されます。デフォルトではすべてモックされるため、指定するまでは何も送信されません。

終了コードを使い分けてください。2 は呼び出し方に誤りがある場合(不明なツール、スキーマに合わない引数)、1 はハンドラー自体が例外をスローした場合、0 は実行された場合を示します。そのため、正常終了であれば、単にクラッシュしなかっただけでなく、有効な入力でハンドラーが実際に実行されたことがわかります。

何も送信せずにエージェントの頭脳をテストする:

npx zavudev agents list                # your agents, with their ids
npx zavudev agents test --agent <agentId> --message "where is order ORD-001?"

エージェントの返信内容に加えて、トークン数、レイテンシ、使用したナレッジのチャンク数が表示されます。何も配信されず、課金も発生しないため、プロンプトを編集するたびに安全に実行できます。

ツールはすべてのチャネルで実行されます — テキスト(SMS/WhatsApp/Telegram)、音声、フローの tool ステップ内で実行されます。テキストエージェントに何かを調べるよう依頼すると、ツールを呼び出して実際のデータを使って回答します(1 回の返信につき最大 5 回のツール呼び出し)。 デフォルトでは agents test が例外です。このコマンドはツールを提示し、エージェントが選んだツールと引数を報告したところで停止します。そのため、表示される返信は、実際には行われていない検索についてエージェントが話しているものです。返信内の数値はすべて作り出されたものであり、実行時に警告が表示されます。

実際のループ(モデルがツールを選び、ハンドラーが応答し、モデルがその結果を使って返信する)を確認するには:

npx zavudev agents test --agent <agentId> --message "where is order ORD-001?" --execute-tools

ハンドラーは実際に実行されるため、その処理(課金、書き込み、通知など)はすべて発生します。顧客には何も配信されませんが、副作用はシミュレーションされません。各呼び出しについて、応答があったかどうかも表示されます。

  ✓ get_order_status
  ✗ charge_card: HTTP 500

エラーを返すツールにも、正常に動作するツールと同じ価値があります。エージェントはそのエラーを受け取り、それを踏まえて回答しています。これは顧客に実際に返される内容です。また、エージェントが何も呼び出さなかった場合は、その旨が表示されます。ツール呼び出しが 0 回なのに自信ありげな返信をすることこそ、このフラグで検出すべき失敗です。

モデルを使わずにハンドラーを 1 つだけ個別にテストするには、fn invoke --tool を使用してください。

エンドツーエンド: 送信者の WhatsApp/SMS/Telegram 番号に実際のメッセージを送信するか、音声エージェントに電話をかけます。エージェントは LLM を実行し、同じチャネルで返信します。

6. デバッグ

問題が発生したら、チェーンを上から順に確認してください。

# 1. Did the inbound reach the agent?
npx zavudev agents executions list --sender <senderId>

# 2. Detail of any failed run
npx zavudev agents executions get <executionId> --sender <senderId>

# 3. Live tool handler logs (your console.log calls)
npx zavudev fn logs --tail

executions list の --json フラグを指定すると、解析可能な診断情報を含む全ペイロードが返されます。これには errorMessage も含まれます。

デプロイ自体が失敗した場合、ビルド出力は Build output: の下に表示されます。解決できなかったインポート、構文エラー、インストールできなかったパッケージなどを確認できます。その上に表示される 1 行のステータスは要約にすぎず、対処可能な情報はほとんど示しません。確認すべき情報は Build output: の下にあります。同じ内容はデプロイ上の buildLogs にも保持されており、REST の GET /v1/functions/deployments/{deploymentId} から確認できます(npx zavudev fn versions list で ID を確認できます)。

defineAgent リファレンス

defineAgent({
  senderId: string,              // Required. The sender that receives inbound + dispatches the agent.
  name: string,                  // Required. Displayed in dashboard.
  provider: "zavu" | "openai" | "anthropic" | "google" | "mistral",
  model: string,                 // For "zavu": prefix with underlying provider e.g. "openai/gpt-5.6-luna"
  prompt: string,                // System prompt.
  apiKey?: string,               // Required for non-"zavu" providers.
  channels?: string[],           // Default ["*"] = whatever the sender has. See below.
  messageTypes?: string[],       // Default ["text"]. Filter by message type.
  groups?: boolean,              // Answer in group chats, on channels that carry them. Default false. See below.
  temperature?: number,          // 0-2.
  maxTokens?: number,            // Cap on output tokens.
  contextWindowMessages?: number,// Past N messages included as context. Default 10.
  sessionTimeoutMinutes?: number,// Reset conversation context after N minutes. Default 60.
  includeContactMetadata?: boolean, // Inject contact's metadata into the system prompt. Default true.
  enabled?: boolean,             // Default true. Read only when the deploy creates the agent.
  voice?: VoiceConfig,           // Add to make the agent answer phone calls. See "Voice agents".
})

モデル設定は再デプロイ後も維持されます。 provider、model、temperature、 maxTokens、contextWindowMessages、includeContactMetadata、および sessionTimeoutMinutes は、ダッシュボードまたは PATCH /v1/agents/{agentId} でも変更できます。デプロイ時に書き込まれるのは、前回のデプロイ以降にコードで変更された設定のみです。それ以外は現在の値が維持され、デプロイ結果にはコードで宣言された値が表示されます。provider と model は1つの設定として扱われます。コードで指定したモデルを強制するには、コード内で変更してデプロイしてください。コードで宣言されたその他すべての項目(名前、プロンプト、チャンネル、音声)は毎回のデプロイで書き込まれ、enabled は作成後に書き換えられることはありません。

グループチャットと NO_REPLY

エージェントは、groups: true を宣言しない限り、1対1の会話にのみ応答します。宣言すると、グループチャットに対応しているチャンネルでグループチャットにも応答し、返信はメッセージを書いた人への個別送信ではなく、グループに送られます。どちらの場合も、グループメッセージは Webhook と Functions に届きます。groups を削除すると、次回のデプロイで無効になります。groups を使うには最新のランタイムが必要です。古いランタイムに固定された関数では、このフィールドは無視されてデプロイされるため、npx zavudev deploy --update-runtime を実行してデプロイしてください。

グループでは、エージェントはすべてのメッセージについて質問され、各行は Name (+number): text の形式で届きます。投稿者にプロフィール名がない場合は、番号のみが届きます。プロフィール名は各ユーザーが入力したものなので、誰が話しているかを把握する必要があるプロンプトでは、番号を基準にしてください。グループではフローやハンドオフは利用できません。また、エージェントの返信はグループごとに1分あたり12回までに制限されているため、2つのエージェントが延々とループすることはありません。

応答を控えるには、エージェントは正確に NO_REPLY と返します(前後の空白は無視されます)。どのチャンネルにも何も送信されず、実行は filtered として記録されます。トークン料金は引き続き発生します。グループではこの動作がエージェントに伝えられます。いつ使うべきか、プロンプトに記載してください。

defineAgent({
  senderId: process.env.SENDER_ID!,
  name: "Host",
  provider: "zavu",
  model: "openai/gpt-5.6-luna",
  prompt: "Answer only when someone addresses you by name. Otherwise reply NO_REPLY.",
  groups: true,
})

音声エージェント

電話に応答させるには、defineAgent に voice ブロックを追加します。enabled: true を指定すると、送信者の番号への着信にエージェントが応答します。LLM はテキストの model とは独立して、最低レイテンシーを実現するため 音声ネットワーク内に配置されて実行されます。通話用のモデルを選ぶには voice.model を設定してください。省略した場合は、テキストの model から決定されます。ブロックを削除すると、次回のデプロイでエージェントはテキスト専用に戻ります。

defineAgent({
  senderId: process.env.SENDER_ID!,
  name: "Fermi",
  provider: "zavu",
  model: "openai/gpt-5.6-luna",      // text model
  channels: ["voice", "whatsapp"],
  voice: {
    enabled: true,
    model: "openai/gpt-4o",         // co-located voice model (optional; derived from `model` if omitted)
    greeting: "Hi, I'm Fermi. How can I help?",
    language: "en",                 // BCP-47; auto-detected if omitted
    interruptible: true,            // caller can barge in
    maxCallDurationMinutes: 10,
  },
  prompt: "You are Fermi…",
})
voice リファレンス
voice: {
  enabled: boolean,              // Required. true = answer/place calls; removing the block reverts to text-only.
  model?: string,                // Co-located call model, e.g. "openai/gpt-4o". Derived from the text model if omitted.
  greeting?: string,             // Opening line. Max 1000 chars. If omitted, the agent waits for the caller.
  greetings?: Record<string, string>, // Per-language greeting keyed by language tag: { es: "Hola…" }. Used when the caller's language differs from the one `greeting` is written in. Factory agents ship with this set.
  language?: string,             // BCP-47 (e.g. "en", "es", "pt-BR"). Auto-detected if omitted.
  ttsVoiceId?: string,           // Voice used for speech synthesis.
  voiceSpeed?: number,           // Speech rate 0.5–1.5. Default 1.0.
  interruptible?: boolean,       // Caller can barge in while the agent speaks. Default true.
  maxCallDurationMinutes?: number, // Hard cap on call length. Default 15.
  maxIdleSeconds?: number,       // End the call after this much silence (5–300). Default 30.
  voicemailAction?: "hangup" | "leave_message", // On answering-machine detection (outbound). Default "hangup".
  voicemailMessage?: string,     // Spoken when voicemailAction is "leave_message". Falls back to greeting.
  transferPhoneNumber?: string,  // E.164. Gives the agent a tool to transfer the call to a human.
}

音声を使うには、送信者に割り当てられた電話番号と、有効になっている音声チャンネルが必要です。範囲外の値を指定すると、npx zavudev deploy は警告を表示し、失敗する代わりにエージェントをテキスト専用としてデプロイします。値を修正して再デプロイしてください。

音声ブロック内のスペルミスのあるキーや不明なキーは別の扱いです。警告なしで破棄され、デプロイは終了コード0で終了します。そのため、interruptable を指定しても何も起きず、何も通知されません。デプロイが成功したことだけを信頼せず、npx zavudev agents get <agentId> を実行して、記述したとおりにブロックが反映されたことを確認してください。

コードでナレッジベース(RAG)を使う

defineAgent で宣言するため、ドキュメントをもとに回答するエージェントも、1つのファイルと1回のデプロイで作成できます。別途 API を呼び出す必要はありません。

defineAgent({
  senderId: process.env.SENDER_ID!,
  name: "Ada",
  provider: "zavu",
  model: "openai/gpt-5.6-luna",
  prompt: "Answer from the store policies. If they do not cover it, say so.",
  knowledgeBase: {
    name: "Store policies",
    documents: [
      { title: "Returns", content: "# Returns\n\nUnopened: 30 days. Opened: 14 days if faulty." },
      { title: "Shipping", content: "# Shipping\n\nFree over $50, 3-5 business days." },
    ],
  },
})

デプロイが正となる状態です。削除したドキュメントは削除され、内容が変更されたドキュメントは再度埋め込み処理され、変更されていないドキュメントには手を加えないため、再デプロイに費用はかかりません。ドキュメントは title で照合されるため、名前を変更すると置き換えられます。

Knowledge bases:
  + Store policies (knowledge base)
  documents sync in the background; check with `agents knowledge-bases documents list`

デプロイ結果にはナレッジベースが表示されますが、個々のドキュメントは表示されません。ドキュメントの同期はデプロイ完了後に行われるため、ドキュメントごとの行を表示しても、変更があったかどうかにかかわらず、何も変更されなかったように見えてしまいます。

取得できることを確認し、決めつけないでください。

npx zavudev agents test --sender "$SENDER_ID" --message "can I return an opened item?"

返信には、使用したナレッジチャンクの数が示されます。ドキュメントがあるエージェントでこの数が 0 の場合、回答の根拠はドキュメントにありません。それでも、回答はまるで正しいかのように読めてしまいます。

コード内のフロー

defineFlow は、エージェントと同じファイル内で決定的な会話を宣言します。モデルに即興させてはいけない場面、たとえば決まった順序で質問する場合、回答に応じて分岐する場合、収集した値を使ってツールを呼び出す場合に使用します。

import { defineAgent, defineTool, defineFlow } from "@zavudev/functions"

defineFlow({
  name: "Repair intake",
  trigger: { type: "keyword", keywords: ["broken", "repair"] },
  steps: [
    {
      id: "ask_issue",
      type: "collect",
      config: { variable: "issue", prompt: "What is wrong with it?" },
      nextStepId: "open",
    },
    {
      id: "open",
      type: "tool",
      config: { toolName: "create_ticket", params: { issue: "{{issue}}" } },
      nextStepId: "done",
    },
    { id: "done", type: "message", config: { text: "Logged." } },
  ],
})

keyword、intent、always トリガーはエンジンによって照合されます。manual は受け付けられますが、何も開始しません。ステップの形式については、ai-agent スキルを参照してください。

コードでフローを宣言する場合に固有の注意点が4つあります。

  • フローは無効の状態で作成されます。 フローは実際の会話に介入するため、作成することと有効にすることは別々の判断です。初回のデプロイで有効にするには enabled: true を渡すか、次を実行します。 npx zavudev agents flows update <flowId> --sender "$SENDER_ID" --enabled
  • 以降のデプロイで enabled が変更されることはありません。 インシデント中にフローを一時停止しても、再デプロイ後もその状態が維持されます。
  • ステップで指定するツールは、エージェントに存在する必要があります。 同じファイル内の defineTool で十分です。ツールが先に同期されるためです。存在しないツールを指定したステップは報告され、そのフローはスキップされます。デプロイのほかの部分は引き続き反映されます。
  • 決して変更されないものが2つあります。 この関数が作成していない同名のフローと、現在コンタクトが進行中のフローです。どちらも上書きまたは削除されず、代わりに報告されます。

コード内の複数の送信者

senderId がプライマリです。残りは senderIds に指定します。

defineAgent({
  senderId: process.env.SENDER_ID!,
  senderIds: [process.env.SUPPORT_SENDER_ID!],
  name: "Ada",
  provider: "zavu",
  model: "openai/gpt-5.6-luna",
  prompt: "Be brief.",
})

接続のみ行われます。指定をやめた送信者は接続されたままになり、そのことが報告されます。接続を解除すると、稼働中の番号への応答がひそかに停止してしまうためです。接続を解除する場合は、次を実行します。 DELETE /v1/agents/{agentId}/senders/{senderId}

ドキュメントはインラインで保存され、デプロイ時のソースサイズは約900KBまでです。それを超える場合は、代わりに npx zavudev agents knowledge-bases documents add --content-file ./manual.md を使用します。

関数ごとに複数のファイルを使用する

関数を複数のファイルに分割し、ファイル間でインポートできます。

// index.ts
import { formatOrder } from "./lib/orders"

npx zavudev deploy はエントリーポイント(デフォルトでは index.ts、または --source <file>)から開始し、相対インポートを通じて読み込まれるすべてのファイルをアップロードします。インポートされないファイルはデプロイされないため、テストや作業用ファイルはローカルに残ります。ビルド時に、アップロードされたファイル間のインポートが解決されます。

拒否されるパス:プロジェクトルートより上のパス(../shared/x.ts)、node_modules/、および __zavu で始まる名前。上限は、合計200ファイル、900,000バイトです。

npm の依存関係は別途管理します。package.json の dependencies に追加すると、インストールされ、バンドルされます。

代わりに、プッシュのたびにリポジトリ全体をデプロイするには、次を実行します。

npx zavudev fn git link acme/my-agent --branch main

REST 経由の場合は、任意の entrypoint とともに files(パスと内容のマップ)を送信します。1ファイルの関数には、引き続き sourceCode を簡易形式として使用できます。

defineTool リファレンス

defineTool({
  name: string,                  // Required. snake_case, max 64 chars.
  description: string,           // Required. The LLM reads this to decide WHEN to call the tool.
  parameters: {
    type: "object",
    properties: { /* JSON Schema */ },
    required?: string[],
  },
  handler: async (args, ctx) => any,  // Required. Return any JSON-serializable value.
  agent?: string,                // Optional: which agent owns this tool. Defaults to the only agent in the file.
  enabled?: boolean,             // Default true.
})
ハンドラーの ctx 形式
{
  projectId: string,
  functionId: string,
  slug: string,
  awsRequestId?: string,
  messageId?: string,            // ID of the triggering inbound (when called by agent)
  contactPhone?: string,
  sessionId?: string,            // Active flow session if any
  channel?: string,              // "whatsapp" | "sms" | "voice" | "email" | ...
  callId?: string,               // The voice call in progress. Voice channel only
  contactId?: string,            // The contact Zavu resolved, when it knows who is on the other side
  conversationId?: string,       // The inbox thread this conversation belongs to
  senderId?: string,             // The sender the conversation is running on
  log: (...args) => void,        // console.log proxy that appears in `npx zavudev fn logs --tail`
  memory: Memory,                // What the agent remembers. See below
}
ctx.memory — エージェントが記憶する内容

@zavudev/functions 0.3.0+ が必要です。1つの API で、2通りの方法で情報を呼び出せます。

// By meaning — embedded on write, retrieved semantically
await ctx.memory.contact!.add("Prefers WhatsApp over email.")
const hits = await ctx.memory.contact!.search("how they like to be reached", { minScore: 0.3 })

// By key — JSON in, JSON out
const orders = ctx.memory.contact!.collection("orders")
await orders.set("ORD-1", { status: "shipped" })
const order = await orders.get("ORD-1")   // null when absent

コレクションキーに使用できる文字は A-Z a-z 0-9 . _ : @ - です。+ は使えないため、E.164 電話番号は有効なキーではありません。数字のみ(56940560201)または接頭辞を付けたキー(phone:56940560201)を使い、E.164 形式の番号は値に保持してください。

すべての読み取りと書き込みは、必ず1つのスコープ内で行われ、スコープは互いに分離されています。

ハンドルスコープ利用可能な場合
ctx.memoryプロジェクト — すべての会話で共有常に
ctx.memory.contact会話の相手会話外では undefined
ctx.memory.conversation現在のスレッド会話外では undefined
ctx.memory.forContact(id) / forConversation(id)指定した相手または会話常に

デフォルトはプロジェクトスコープです。 オプションなしで ctx.memory.add(fact) を呼び出すと、ほかのすべての顧客との会話から呼び出せる情報が書き込まれます。個人に関する情報には ctx.memory.contact を使用してください。contact と conversation はどちらも省略可能なので、存在しない場合に対応してください。コンタクトが存在することを前提とするツールは、cron トリガーから初めて実行されたときに MemoryScopeError をスローします。

全機能、スコープ、上限、CLI、REST API については、memory スキルを参照してください。

defineFunction リファレンス(任意)

次の場合にのみ使用します:

  • 生の HTTP リクエスト(公開 URL で公開される関数。作成時に zavu fn init --http を実行するか、既存の関数で zavu fn http enable を実行します)
  • ネイティブイベントトリガー(message.inbound、broadcast.status_changed など。npx zavudev fn triggers add で設定します)
export default defineFunction(async (event, ctx) => {
  if (event.type === "message.inbound") {
    // event.data: { from, text, channel, messageId, ... }
  }
  return { ok: true }
})

トリガー(イベントサブスクリプション)

defineFunction が Zavu イベントに反応するようにするには:

npx zavudev fn triggers list
npx zavudev fn triggers add --events message.inbound --senders <senderId>
npx zavudev fn triggers add --events broadcast.status_changed --senders any
# Schedules: event type "cron" + a 5-field UTC expression (minimum 1 minute).
# The function receives { type: "cron", data: { cron } }. Several schedules
# per function are allowed (different expressions).
npx zavudev fn triggers add --events cron --cron '*/15 * * * *'
npx zavudev fn triggers add --events cron --cron '0 9 * * 1-5'   # weekdays 09:00 UTC
npx zavudev fn triggers toggle <triggerId>
npx zavudev fn triggers rm <triggerId>
npx zavudev fn triggers events       # list available event types

プッシュのたびにデプロイ

GitHub リポジトリを関数にリンクすると、ブランチへのプッシュでデプロイされます:

npx zavudev fn git link acme/order-bot --branch main
npx zavudev fn git link acme/monorepo --root apps/bot      # monorepos
npx zavudev fn git status                                   # link + last deploy
npx zavudev fn git deploy                                   # deploy the branch now
npx zavudev fn git set --no-auto-deploy                     # keep the link, ignore pushes
npx zavudev fn git unlink

引数には owner/repo、github.com の URL、または SSH リモートを指定します。

リンクの認証方法はサーバーが決定し、出力の connection で使用された方法を確認できます。 アカウントに Zavu GitHub App がインストールされている場合、このコマンドだけで設定が完了し、プライベートリポジトリも使用できます。インストールされていない場合は manual リンクとなり、コマンドによってペイロード URL とシークレットが出力されるので、自分でリポジトリにウェブフックとして追加します。シークレットが出力されるのは一度だけです。再リンクすると新しいシークレットが生成されます。

GitHub に対するリポジトリの確認はリンク時に行われません。確認できないためです。存在しない owner/repo や、インストールによって参照できないリポジトリも受け付けられますが、最初のデプロイ時に失敗します。リンクに成功したからといって正常に動作すると決めつけず、最初のプッシュ後に fn git status を確認してください。

トリガーでは署名付きの内部呼び出しを使用します(ハンドラー内で HMAC の検証は不要です)。

バージョンとロールバック

npx zavudev deploy を実行するたびに、変更できないバージョンが作成されます。

npx zavudev fn versions list           # alias: npx zavudev fn history
npx zavudev fn rollback 4              # go back to version 4

ダッシュボードの関数メタデータでは、有効なバージョンを確認でき、UI からロールバックもできます。

ランタイムバージョン

各関数は、最初のデプロイ時に特定のランタイムレイヤーに固定されます。安定性を保つため、その後のデプロイでも同じバージョンが維持されます(変更不可)。

npx zavudev deploy --update-runtime   # opt-in upgrade to latest runtime

セキュリティアドバイザリや必要な機能がある場合にのみオプトインしてください。フラグを付けずに npx zavudev deploy を実行すれば、今後も安全です。

料金モデル

関数の料金は、メモリ使用量に応じて重み付けされた呼び出しユニットに基づきます。

メモリ1 回の呼び出しあたりのユニット数
128 MB1
256 MB2
512 MB4
1024 MB8

各プランには月間の割り当て量が含まれます。超過分は従量課金により、次回の Stripe 請求書に加算されます。

プラン含まれるユニット数超過料金
Free100k上限あり(呼び出しがブロックされます)
Hobby1M1M あたり $5
Standard5M1M あたり $4
Growth10M1M あたり $3

メモリは関数の作成時またはダッシュボードで設定します。メモリを減らすと料金が安くなります。ほとんどのツールハンドラーは 128 MB で動作します。

よくあるパターン

手動で作成したエージェントを引き継ぐ

ユーザーがすでにダッシュボードまたは npx zavudev agents create でエージェントを作成している場合、同じ senderId + name を指定してコード内で宣言すると、そのエージェントが引き継がれます(TAKE OVER)。Zavu はそのエージェントに managedByFunctionId を設定し、ダッシュボードでの手動編集をモデル設定以外ロックします。関数のソースが信頼できる唯一の情報源になります。コードに記述されていないモデル設定(temperature、maxTokens など)は、手動で作成したエージェントの値が引き継がれます。

手動管理に戻すには、関数(npx zavudev fn delete)を削除すると、エージェントが解放されます。

作成した関数のクリーンアップ

fn delete は、Lambda、トリガー、シークレット、デプロイ履歴、および関数が所有するすべてのエージェントとツールを連鎖的に削除します。確認のため、スラッグを入力するよう求められます。

非対話モードでは、あらかじめスラッグを指定して確認します:

npx zavudev fn delete --confirm <slug>

これは意図的に単なる -y ではありません。対象の名前を指定する必要があるため、ディレクトリの間違いや古い ID が原因で別のものを削除することはなく、エラーになります。そのため、スクリプトや、後片付けを行うエージェントに安全に任せられます。

環境ごとの送信者
defineAgent({
  senderId: process.env.NODE_ENV === "production"
    ? process.env.PROD_SENDER_ID!
    : process.env.DEV_SENDER_ID!,
  // ...
})

次に、本番環境では npx zavudev fn secrets set NODE_ENV production、開発環境では ... development を実行します。同じコードで、エージェントは別々になります。

BYOK(独自のキーを持ち込む)

OpenAI、Anthropic、Google、Mistral では、apiKey を直接渡します:

defineAgent({
  senderId: process.env.SENDER_ID!,
  provider: "openai",
  model: "gpt-4o-mini",
  apiKey: process.env.OPENAI_API_KEY,
  prompt: "...",
})

npx zavudev fn secrets set OPENAI_API_KEY sk-... を実行します。エージェントはキーを直接使用するため、LLM 呼び出しで Zavu の残高は消費されません。

ツールを特定のエージェントに固定する(マルチエージェント関数)

関数で複数の defineAgent を宣言すると、ツールはデフォルトで最初のエージェントに紐付けられます。明示的に選択するには、次のようにします。

defineTool({
  name: "lookup_order",
  agent: "Bella",   // Match by agent's name field
  // ...
})
ハンドラー内から Zavu API を呼び出す

各関数にはスコープが限定された ZAVU_API_KEY が自動的に注入されます。SDK を使ってコールバックします。

import { Zavudev } from "@zavudev/sdk"

const zavu = new Zavudev({ apiKey: process.env.ZAVU_API_KEY })

defineTool({
  name: "send_followup",
  handler: async (args, ctx) => {
    await zavu.messages.send({
      to: ctx.contactPhone!,
      text: "Thanks for your order!",
    })
    return { sent: true }
  },
})

自動プロビジョニングされたキーには、messages:send、messages:read、contacts:read のスコープがあります。より広いアクセスが必要な場合は、手動で API キーを作成し、シークレットとして注入してください。

ダッシュボード

https://dashboard.zavu.dev/functions/<id> には、次のタブが表示されます。

  • コード — 現在のドラフトソース(ブラウザーで編集可能)
  • トリガー — イベントのサブスクリプション
  • エージェントとツール — この関数が管理する対象。実行へのディープリンクも表示されます
  • 依存関係 — バンドルで使用される npm パッケージ
  • シークレット — 暗号化された環境変数(値は書き込み専用)
  • バージョン — デプロイ履歴とロールバック
  • ログ — 最近の呼び出しにおけるランタイムの stdout/stderr
  • 設定 — メモリ、タイムアウト、httpEnabled、削除

リファレンスドキュメント

制約事項

  • スラッグ:小文字の英数字とハイフンのみ、最大 50 文字、プロジェクト内で一意(重複すると 409 conflict が返されます)。
  • ソースバンドル:圧縮時に 900 KB 以下。
  • 環境変数の合計サイズ:すべてのシークレットを合わせて 4 KB。
  • シークレットキーの形式:[A-Z_][A-Z0-9_]*。予約済みのプレフィックス:AWS_、LAMBDA_、_HANDLER、_X_AMZN。
  • タイムアウト:180 秒以下(設定可能、デフォルトは 30 秒)。イベントと cron による呼び出しは 非同期のため、長いタイムアウトはコストの上限を設定するだけです。実行中の 会話で呼び出されるツールは同期処理なので、応答が返るまで待機します。タイムアウトより十分短くしてください。
  • メモリ:128 / 256 / 512 / 1024 MB。
  • 課金:メモリと時間に基づきます。1 回の呼び出しは 128 MB で 1 秒間です。より大きな、 または時間のかかる関数は複数回分として計上され、1 秒未満の場合も 1 回分として 計上されます。すべてのプランで月 300,000 回の呼び出しが含まれ、それを超えると 100 万回あたり $5 です。
  • エージェントあたりのツール数:16。
  • 関数あたりのエージェント数:ハード上限はありませんが、通常は 1 つです。

アンチパターン

  • 管理対象の関数と並行して、命令型の senders.agent.create API を使わないでください。関数でエージェントを宣言すると、そのエージェントは関数が管理します。手動での編集はブロックされます。
  • ソース内に senderId をハードコードしないでください。環境をまたいで同じコードを使えるよう、必ず process.env.SENDER_ID から読み取ってください。
  • 1 つのツールでしか使わない重い依存関係をインポートしないでください。呼び出しのたびにコールドスタートが発生するため、依存関係を減らしてレイテンシを抑えてください。
  • シークレットを console.log に出力しないでください。関数ログは、プロジェクトへのアクセス権を持つ全員が閲覧できます。

コメント