Agent
Agent は、人が声でも文字でも話せて、遮ることもできる AI です。開発者の AI プロバイダのアカウントで、直接聞いて話すモデル(OpenAI Realtime か Gemini Live)を使って動き、呼ばれたブロードキャストを聞き、声と文字で答え、アプリのツールを呼び、アプリが渡す手がかりを読みます。
パス
{source} はブロードキャストのパスで、ふつうは呼ぶ人自身のもの(alice/camera.hang)です。
| 書く人 | パス | トラック | 中身 |
|---|---|---|---|
| 呼ぶ人 | {source} |
hang のカタログと音声 | Agent が聞く声: 最初の音声のレンディション |
| 呼ぶ人 | {source} |
context.json(任意) |
JSON スナップショットのトラック: モデルへの手がかり |
| 呼ぶ人 | {source} |
input.json(任意) |
各フレームが一つの JSON オブジェクト: 入力した文 {"text": string}、またはツールの結果 {"call": id, "output": value} |
| Agent | .agent/{source} |
hang のカタログと音声 | Agent の声(Opus) |
| Agent | 同じ | transcript.json |
各フレーム {"id", "role": "user" or "agent", "text", "final": boolean, "interrupted"?: true}。同じ id の後のフレームが、その項目の文を置き換える |
| Agent | 同じ | calls.json |
各フレームがツールの呼び出し {"id", "name", "arguments": object} |
| Agent | 同じ | status.json |
JSON スナップショットのトラック {"state", "code"?, "message"?}。state は connecting、listening、thinking、speaking、failed のどれか |
.agent/{source} を読むと Agent が {source} を聞くので、{source} と同じように許してください。subscribe に .agent/{id}/** があれば、各自は自分のブロードキャストで Agent を呼べます。transcript.json と calls.json は MoQ の JSON ストリーム(json.Stream)なので、後から来た読み手もそれまでの会話を読めます。
// A typed conversation with the Agent: messages go on input.json of {me}/chat, and the
// conversation comes from .agent/{me}/chat.
import { type Connection, json } from 'tablebox.io';
interface Item { id: string; role: 'user' | 'agent'; text: string; final: boolean; interrupted?: true }
export async function chat(connection: Connection, me: string, show: (item: Item) => void) {
const input = connection.publish(`${me}/chat`).createTrack('input.json');
const agent = await connection.read(`.agent/${me}/chat`);
const transcript = new json.Stream.Consumer<Item>({ track: agent.track('transcript.json').subscribe() });
// A later item with the same id replaces that item's text.
void (async () => { for await (const item of transcript) show(item); })();
return (text: string) => input.writeJson({ text });
}話す
.agent/{source} のどれかのトラックを読むと、{source} の会話が一つ始まり、そのパスの読み手全員で共有します。会話はパスが読まれている間続き、最後の読み手が去ってから 20 秒で終わります。Agent は元の音声と入力した文を聞き、声と文字で答えます。両方の言葉は、聞き取られ話されるのに合わせて transcript.json に出ます。声で話すには、使い始めるの {id}/camera.hang のように、マイクを載せたブロードキャストで呼び、.agent/{id}/camera.hang を公式のプレーヤーで再生します。音声のない元とは、入力した文だけで話します。
Agent が話している間に呼ぶ人が話し始めるか文を送ると、Agent はすぐに話すのをやめ、その答えに interrupted を付けます。プロバイダが長さの上限でセッションを終えると、Agent は新しいセッションを開き、それまでの会話を文として渡します。
ツールと手がかり
モデルは AGENT_TOOLS にあるツールを呼びます。呼び出しはそれぞれ calls.json に出て、アプリはその id を付けて input.json で答えます。10 秒以内に答えが来なければ、失敗した呼び出しとしてモデルに届きます。context.json の最新の値は、その JSON の 64 KiB までが、変わるたびにモデルに渡されます。
環境変数
| 変数 | 意味 |
|---|---|
AGENT_PROVIDER |
openai(OpenAI Realtime、既定)か gemini(Gemini Live) |
OPENAI_API_KEY, GEMINI_API_KEY |
選んだプロバイダのキー。必須 |
AGENT_MODEL |
プロバイダのモデル。設定しなければ OpenAI は gpt-realtime-2.1、Gemini は gemini-3.8-live |
AGENT_VOICE |
プロバイダの声。設定しなければプロバイダの既定 |
AGENT_INSTRUCTIONS |
モデルに渡す指示 |
AGENT_TOOLS |
{"name", "description", "parameters"} の JSON 配列。parameters は JSON Schema。設定しなければツールなし |
環境変数はプロジェクトのものなので、一つの設定がプロジェクトのすべての会話に使われます。
失敗
- プロバイダのキーがない: 状態は
failed、コードはmissing-variableで、文がその名前を示します。設定すれば会話が始まります。 - プロバイダが拒む(キーの誤り、知らないモデルや声)、または
AGENT_TOOLSが誤っている: 状態はfailed、コードはfailedかbad-requestで、文はプロバイダか Agent のものです。変数が変わると会話はまた始まります。 - プロバイダがセッションを切る:
connectingの後、長さの上限のときと同じように会話が続きます。 - インスタンスが止まる: 読み出しは終わり、読み直すと引き継いだインスタンスに届きます。そこで新しい会話が始まります。