Documentation Index
Fetch the complete documentation index at: https://docs.snorbe.deskrex.ai/llms.txt
Use this file to discover all available pages before exploring further.
エージェント実行フロー
エージェントはユーザーの入力を分析し、自動的にツールを選択して実行します。一部のツール(plan / report / matrix)は、実行前に人間の確認が必要な Human-in-the-Loop(HITL)ポイントを含んでいます。
このページでは、すべての実行パターンのイベントフローと、APIユーザーが各状態でどう操作すべきかを説明します。
共通フロー
すべての実行は以下の形式で開始されます。
POST /api/v1/agent/run/stream
config → delta(複数) → [ツール分岐] → complete
| SSE イベント | タイミング |
|---|
config | ストリーム開始時に1回 |
delta | エージェントがテキストを生成するたび |
step | 各実行ステップの完了時 |
complete | 実行完了時(ストリームの最後) |
ツール分岐一覧
エージェントは chat-routing で入力を分析し、以下のツールから自動的に選択します。
| ツール | 説明 | HITL |
|---|
| 直接回答 | ツールを使わずテキストで回答 | なし |
search | Web検索(SERP + スクレイピング + 要約) | なし |
x_search | X(Twitter)投稿検索 | なし |
browse | ブラウザ操作による調査 | 条件付き |
source_summary | URLのスクレイピング・ファイル要約 | なし |
recall | 過去の調査結果・記憶の検索 | なし |
plan | 複数ステップの調査計画 | あり |
report | レポート・長文ドキュメントの生成 | あり |
matrix | 表・マトリクスの作成 | あり |
skill | サンドボックスでのコード実行 | なし |
extract_related_urls | ウェブサイト内のリンク発見 | なし |
パターン1: 直接回答
ツールを使わず、エージェントが直接テキストで回答します。
config → delta → step → complete
API操作: 不要
パターン2: search(Web検索)
config
→ delta(ツール選択)
→ step(tool-calls)
→ search-query-generation-start
→ search-query-generated(複数クエリ)
→ search-results → search-scraping
→ search-summary-start → search-summary-delta → search-summary-complete
→ delta(検索結果を基に回答)
→ step → complete
API操作: 不要(全自動)
パターン3: browse(ブラウザ操作)
config
→ browse-start(VNC接続情報付き)
→ browse-step(複数: スクリーンショット・操作)
→ browse-final(結果)
→ browse-end
→ delta → complete
API操作: 不要。maxBrowsingSteps パラメータで最大ステップ数を制御できます。
browse 中に人間の入力が必要な場合
browse は通常は全自動ですが、ログイン画面・Cookie確認・判断が必要な画面などで人間に質問することがあります。この場合は plan/report/matrix の HITL とは扱いが違います。
browse-start(websocketInfo.session_id を保持)
→ browse-step
→ browse-ask-human(question 付き)
→ ★ ブラウザ操作が回答待ち ★
この状態では /agent/run/{runId}/plan/answer などは使いません。browse-start イベントで受け取った websocketInfo.session_id を使って、ブラウザ操作APIに回答します。
curl -X POST "https://app.snorbe.deskrex.ai/api/v1/browser/answer-question" \
-H "Authorization: Bearer snorbe_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"runId": "cmo...",
"sessionId": "browser-session-id",
"input": "左側メニューの「資料」を開いてください"
}'
ファイルを添付して回答する場合は /browser/answer-question-with-files を使います。
{
"runId": "cmo...",
"sessionId": "browser-session-id",
"modelName": "gpt-5-mini-2025-08-07",
"input": "このPDFの内容を踏まえて回答してください",
"fileUrls": ["https://example.com/file.pdf"]
}
質問待ちではないが、実行中のブラウザへ追加指示を出したい場合は /browser/spontaneous-input を使います。例: 「次は価格ページを開いて」「そのフォームには入力しないで」。
GET /agent/run/{runId}/status の browseState.askHumanQuestion でも質問待ちかどうかは確認できます。ただし回答には sessionId が必要です。APIクライアントは browse-start.payload.websocketInfo.session_id を受け取った時点で保存しておいてください。
パターン4: skill(サンドボックス実行)
config
→ skill-session-start
→ skill-delta(複数: stdoutストリーミング)
→ skill-complete(outputFiles付き)
→ delta → complete
API操作: 不要(全自動)
パターン5: plan(調査計画) — HITLあり
config
→ delta
→ first_plan(goal, steps, question付き)
→ ★ ストリーム終了 ★
ステップ1: 状態確認
GET /api/v1/agent/run/{runId}/status
{
"status": "pending",
"pendingPlanDraft": true
}
ステップ2: 確認操作(いずれか1つ)
質問に回答してドラフトを修正:
POST /api/v1/agent/run/{runId}/plan/answer
{
"runId": "cmo...",
"answer": "追加で〇〇も調べて",
"modelName": "gpt-5-mini-2025-08-07"
}
→ regenerated_plan イベントが返り、再度確認待ちになります。
プランを確定:
POST /api/v1/agent/run/{runId}/plan/confirm
→ plan_confirmed が返ります。
質問をスキップして確定:
POST /api/v1/agent/run/{runId}/plan/skip
ステップ3: レジューム
POST /api/v1/agent/run/stream/{runId}
{ "modelName": "gpt-5-mini-2025-08-07" }
→ 調査が開始され、search/browse等のツールイベントがストリーミングされます。
パターン6: report(レポート生成) — HITLあり
config
→ delta
→ first_report_structure(title, sections, question付き)
→ ★ ストリーム終了 ★
ステップ1: 状態確認
GET /api/v1/agent/run/{runId}/status
{
"status": "pending",
"pendingReportDraft": true
}
ステップ2: 確認操作
質問に回答:
POST /api/v1/agent/run/{runId}/report/answer
{
"runId": "cmo...",
"answer": "第3章を追加して",
"modelName": "gpt-5-mini-2025-08-07"
}
確定:
POST /api/v1/agent/run/{runId}/report/confirm
ステップ3: レジューム → セクション自動生成
POST /api/v1/agent/run/stream/{runId}
→ report_section_start
→ report_section_delta(複数)→ report_section_complete
→ report_section_delta → report_section_complete(各セクション繰り返し)
→ report_complete(全文)
→ complete
パターン7: matrix(マトリクス生成) — HITLあり
config
→ delta
→ first_matrix_structure(title, columns, question付き)
→ ★ ストリーム終了 ★
ステップ1: 状態確認
GET /api/v1/agent/run/{runId}/status
{
"status": "pending",
"pendingMatrixDraft": true
}
ステップ2: 確認操作
質問に回答:
POST /api/v1/agent/run/{runId}/matrix/answer
{
"runId": "cmo...",
"answer": "価格の列を追加して",
"modelName": "gpt-5-mini-2025-08-07"
}
確定:
POST /api/v1/agent/run/{runId}/matrix/confirm
ステップ3: レジューム → データ自動抽出
POST /api/v1/agent/run/stream/{runId}
→ matrix-structure-draft-delta → matrix-structure-draft-complete
→ matrix-data-preview(リアルタイム)
→ matrix-data-completed
→ complete
複合パターン: plan → report
1つの実行で複数の HITL が発生する場合があります。
first_plan → [confirm] → レジューム
→ [search/browse等で調査]
→ first_report_structure → ★停止★
→ [confirm] → レジューム
→ report_section_delta(複数) → report_complete → complete
この場合、レジューム → pendingReportDraft: true を検出 → confirm → 再レジューム、という流れを繰り返します。
状態判定早見表
getStatus レスポンス | 意味 | 次のAPI操作 |
|---|
pendingPlanDraft: true | プラン確認待ち | plan/answer or plan/confirm or plan/skip → レジューム |
pendingReportDraft: true | レポート構成確認待ち | report/answer or report/confirm → レジューム |
pendingMatrixDraft: true | マトリクス構成確認待ち | matrix/answer or matrix/confirm → レジューム |
browseState.askHumanQuestion あり | browse 中の質問待ち | browser/answer-question。/agent/run/stream/{runId} は呼ばない |
browseState.isBrowsing: true | browse 実行中 | 必要なら browser/spontaneous-input。通常は待機 |
skillState.pendingSecretKeys あり | skill の secret 登録待ち | 不足キーを /secret に登録。通常はレジューム不要 |
すべて false + status: "completed" | 完了 | 不要 |
すべて false + status: "running" | 実行中 | ポーリングで待機 |
skill の secret 登録待ち
skill が外部APIキーなどを必要とする場合、SSE に skill-ask-secret が流れ、GET /agent/run/{runId}/status の skillState.pendingSecretKeys に不足キーが表示されます。
{
"skillState": {
"isRunningSkill": true,
"skillName": "patent-search",
"pendingSecretKeys": ["PATENT_API_KEY"]
}
}
不足しているキーごとに /secret へ登録します。
curl -X POST "https://app.snorbe.deskrex.ai/api/v1/secret" \
-H "Authorization: Bearer snorbe_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"key": "PATENT_API_KEY",
"value": "your-secret-value"
}'
secret 登録は待機中の skill に通知されるため、通常は /agent/run/stream/{runId} を呼び直す必要はありません。クライアントは同じSSEを読み続けるか、GET /agent/run/{runId}/status で pendingSecretKeys が空になることを確認します。
plan / report / matrix の回答API
pendingPlanDraft、pendingReportDraft、pendingMatrixDraft は、どれも「ドラフトに対する確認待ち」です。修正したい場合は /answer に回答を送り、返ってきた再生成ドラフトを確認します。問題なければ /confirm で確定し、最後に /agent/run/stream/{runId} で実行を再開します。
| 状態 | 修正・回答 | 確定 | レジューム |
|---|
pendingPlanDraft: true | POST /agent/run/{runId}/plan/answer | POST /agent/run/{runId}/plan/confirm または POST /agent/run/{runId}/plan/skip | POST /agent/run/stream/{runId} |
pendingReportDraft: true | POST /agent/run/{runId}/report/answer | POST /agent/run/{runId}/report/confirm | POST /agent/run/stream/{runId} |
pendingMatrixDraft: true | POST /agent/run/{runId}/matrix/answer | POST /agent/run/{runId}/matrix/confirm | POST /agent/run/stream/{runId} |
/answer のリクエストボディ
Plan / Report / Matrix の /answer は共通の形です。
{
"runId": "cmo...",
"answer": "このドラフトに追加してほしい内容、直してほしい観点、削ってほしい範囲を書く",
"modelName": "gpt-5-mini-2025-08-07",
"fileUrls": ["https://example.com/reference.pdf"]
}
| フィールド | 必須 | 説明 |
|---|
runId | はい | 対象の AgentRun ID。パスの {runId} と同じ値 |
answer | はい | ドラフトへの回答・修正指示 |
modelName | はい | 再生成に使うモデル |
fileUrls | いいえ | 回答時に参照させるファイルURL |
例:
curl -X POST "https://app.snorbe.deskrex.ai/api/v1/agent/run/{runId}/matrix/answer" \
-H "Authorization: Bearer snorbe_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"runId": "{runId}",
"answer": "列に価格、導入企業、主要ユースケースを追加してください",
"modelName": "gpt-5-mini-2025-08-07"
}'
/confirm と /skip のリクエストボディ
確定系APIは runId だけを送ります。
plan/skip は「追加回答なしでプランを確定する」操作です。Report と Matrix には skip はありません。
APIユーザーの基本ループ
1. POST /agent/run/stream → SSEイベントを受信
2. browse-start が来たら session_id を保存
3. browse-ask-human が来たら /browser/answer-question で回答
4. complete イベントでストリーム終了
5. GET /agent/run/{runId}/status で pending*Draft / browseState / skillState を確認
6. plan/report/matrix の pending があれば → answer or confirm → POST /agent/run/stream/{runId} でレジューム
7. skillState.pendingSecretKeys があれば → /secret に登録
8. complete まで 4-7 を繰り返す
plan / report / matrix は「エージェント実行全体が一度止まる」ため、確認後に /agent/run/stream/{runId} でレジュームします。browse の質問は「ブラウザセッションへの入力待ち」なので、/browser/answer-question に回答し、同じ実行をそのまま続けます。
実装例: 完全なエージェント実行クライアント
以下は、SSEストリーミング・HITL確認・レジュームをすべて自動で処理するPython実装例です。APIをどう組み合わせて進行させるかの参考にしてください。
シンプルな実行(HITLなし)
ツールなし or search/browse/skill など、HITLが発生しないケースです。
import requests
import json
API_KEY = "snorbe_your_api_key_here"
BASE = "https://app.snorbe.deskrex.ai/api/v1"
HEADERS = {
"Authorization": f"Bearer {API_KEY}",
"Accept": "text/event-stream",
"Content-Type": "application/json",
}
def run_agent(prompt: str) -> str:
"""エージェントを実行し、最終テキストを返す(HITLなしの場合)"""
resp = requests.post(
f"{BASE}/agent/run/stream",
headers=HEADERS,
json={
"modelName": "gpt-5-mini-2025-08-07",
"promptKey": "chat-routing",
"inputText": prompt,
"locale": "ja",
},
stream=True,
timeout=300,
)
result = {}
for line in resp.iter_lines(decode_unicode=True):
if not line or not line.startswith("data: "):
continue
event = json.loads(line[6:])
if event["type"] == "delta":
# リアルタイム表示
print(event["payload"]["deltaText"], end="", flush=True)
elif event["type"] == "complete":
result = event["payload"]
print() # 改行
return result.get("text", "")
# 使い方
text = run_agent("最新のAIニュースを3つ教えて")
HITL対応の完全ループ
plan → report → matrix など、複数回のHITL確認が発生するケースです。
import requests
import json
import time
API_KEY = "snorbe_your_api_key_here"
BASE = "https://app.snorbe.deskrex.ai/api/v1"
HEADERS = {
"Authorization": f"Bearer {API_KEY}",
"Accept": "text/event-stream",
"Content-Type": "application/json",
}
def stream_until_complete(run_url: str, body: dict) -> dict:
"""SSEストリームを読み取り、completeイベントのpayloadを返す"""
resp = requests.post(run_url, headers=HEADERS, json=body, stream=True, timeout=600)
result = {}
for line in resp.iter_lines(decode_unicode=True):
if not line or not line.startswith("data: "):
continue
event = json.loads(line[6:])
if event["type"] == "delta":
print(event["payload"].get("deltaText", ""), end="", flush=True)
elif event["type"] == "first-plan":
print(f"\n[Plan] {event['payload']['plan']['goal'][:80]}...")
elif event["type"] == "first_report_structure":
print(f"\n[Report] {event['payload']['title']}")
elif event["type"] == "first_matrix_structure":
print(f"\n[Matrix] {event['payload']['title']}")
elif event["type"] == "complete":
result = event["payload"]
print()
elif event["type"] == "error":
raise RuntimeError(event["payload"]["message"])
return result
def get_status(run_id: str) -> dict:
"""実行ステータスを取得"""
resp = requests.get(
f"{BASE}/agent/run/{run_id}/status",
headers={"Authorization": f"Bearer {API_KEY}"},
)
return resp.json()
def confirm_draft(run_id: str, draft_type: str) -> None:
"""HITL確認(plan/report/matrix)をスキップして確定"""
resp = requests.post(
f"{BASE}/agent/run/{run_id}/{draft_type}/skip",
headers={"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"},
json={"runId": run_id},
)
data = resp.json()
if data.get("status") != "confirmed":
raise RuntimeError(f"Confirm failed: {data}")
def run_agent_with_hitl(prompt: str) -> str:
"""HITLを含む完全なエージェント実行ループ"""
# Step 1: 初回実行
result = stream_until_complete(
f"{BASE}/agent/run/stream",
{
"modelName": "gpt-5-mini-2025-08-07",
"promptKey": "chat-routing",
"inputText": prompt,
"locale": "ja",
},
)
run_id = result.get("runId")
if not run_id:
return result.get("text", "")
# Step 2: HITLループ
max_iterations = 10
for _ in range(max_iterations):
status = get_status(run_id)
# 完了チェック
if status["status"] == "completed":
break
# HITL確認が必要かチェック
hitl_type = None
if status.get("pendingPlanDraft"):
hitl_type = "plan"
elif status.get("pendingReportDraft"):
hitl_type = "report"
elif status.get("pendingMatrixDraft"):
hitl_type = "matrix"
if not hitl_type:
# 実行中ならポーリング
time.sleep(5)
continue
# Step 3: 確認 → レジューム
print(f"\n[HITL] {hitl_type} confirmation required, auto-confirming...")
confirm_draft(run_id, hitl_type)
result = stream_until_complete(
f"{BASE}/agent/run/stream/{run_id}",
{"modelName": "gpt-5-mini-2025-08-07"},
)
return result.get("text", "")
# 使い方: plan → 調査 → report まで自動進行
text = run_agent_with_hitl(
"plan modeでAIエージェントの最新動向を調査して、レポートを作成して"
)
print(f"\nFinal result: {text[:200]}...")
HITL確認に人間の判断を挟む場合
自動確定ではなく、質問内容を表示して人間が判断するパターンです。
def confirm_draft_with_human(run_id: str, draft_type: str) -> None:
"""質問を表示し、人間の入力に基づいて回答または確定"""
# 初回ストリームで first_plan 等のイベントから question を抽出済みと仮定
# (上記 stream_until_complete で event を保存しておく)
answer = input(f"\n[{draft_type}] 質問に回答(空Enterでスキップ): ").strip()
if answer:
# 回答を送信 → ドラフトが再生成される
resp = requests.post(
f"{BASE}/agent/run/{run_id}/{draft_type}/answer",
headers={
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
},
json={
"runId": run_id,
"answer": answer,
"modelName": "gpt-5-mini-2025-08-07",
},
)
print(f"回答送信 → {resp.json().get('status')}")
# 再生成されたドラフトに対して再度確認が必要
else:
# スキップして確定
confirm_draft(run_id, draft_type)
非ストリーミング版(ポーリング方式)
SSEではなく、非ストリーミングAPI + ポーリングで進行する方式です。
def run_agent_polling(prompt: str) -> str:
"""非ストリーミングで実行し、ポーリングで完了を待つ"""
# Step 1: 実行開始
resp = requests.post(
f"{BASE}/agent/run",
headers={
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
},
json={
"modelName": "gpt-5-mini-2025-08-07",
"promptKey": "chat-routing",
"inputText": prompt,
"locale": "ja",
},
timeout=300,
)
data = resp.json()
run_id = data["runId"]
# Step 2: 完了 or HITL をポーリング
while True:
status = get_status(run_id)
if status["status"] == "completed":
return data.get("text", "")
# HITL確認
for draft_type in ["plan", "report", "matrix"]:
if status.get(f"pending{draft_type.capitalize()}Draft"):
print(f"[HITL] {draft_type} 確認待ち、自動スキップ...")
confirm_draft(run_id, draft_type)
# レジューム(非ストリーミング)
resp = requests.post(
f"{BASE}/agent/run/{run_id}/resume",
headers={
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
},
json={
"runId": run_id,
"modelName": "gpt-5-mini-2025-08-07",
},
timeout=300,
)
data = resp.json()
break
else:
time.sleep(5)
# 使い方
text = run_agent_polling("半導体業界の最新トレンドを調べて")
TypeScript実装例
async function* consumeSSE(
url: string,
body: Record<string, unknown>,
apiKey: string,
): AsyncGenerator<{ type: string; payload: Record<string, unknown> }> {
const resp = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
Accept: "text/event-stream",
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
const reader = resp.body!.getReader();
const decoder = new TextDecoder();
let buffer = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const parts = buffer.split("\n\n");
buffer = parts.pop() ?? "";
for (const part of parts) {
for (const line of part.split("\n")) {
if (line.startsWith("data: ")) {
yield JSON.parse(line.slice(6));
}
}
}
}
}
async function runAgent(prompt: string, apiKey: string): Promise<string> {
const BASE = "https://app.snorbe.deskrex.ai/api/v1";
let result: Record<string, unknown> = {};
// Step 1: 初回ストリーミング
let runId = "";
for await (const event of consumeSSE(
`${BASE}/agent/run/stream`,
{
modelName: "gpt-5-mini-2025-08-07",
promptKey: "chat-routing",
inputText: prompt,
locale: "ja",
},
apiKey,
)) {
if (event.type === "delta") {
process.stdout.write(event.payload.deltaText ?? "");
} else if (event.type === "complete") {
result = event.payload;
runId = event.payload.runId as string;
}
}
// Step 2: HITLループ
for (let i = 0; i < 10; i++) {
const statusResp = await fetch(`${BASE}/agent/run/${runId}/status`, {
headers: { Authorization: `Bearer ${apiKey}` },
});
const status = await statusResp.json();
if (status.status === "completed") break;
const draftType = status.pendingPlanDraft
? "plan"
: status.pendingReportDraft
? "report"
: status.pendingMatrixDraft
? "matrix"
: null;
if (!draftType) {
await new Promise((r) => setTimeout(r, 5000));
continue;
}
// 確認 → レジューム
await fetch(`${BASE}/agent/run/${runId}/${draftType}/skip`, {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ runId }),
});
for await (const event of consumeSSE(
`${BASE}/agent/run/stream/${runId}`,
{ modelName: "gpt-5-mini-2025-08-07" },
apiKey,
)) {
if (event.type === "delta") {
process.stdout.write(event.payload.deltaText ?? "");
} else if (event.type === "complete") {
result = event.payload;
}
}
}
console.log();
return (result.text as string) ?? "";
}