> ## Documentation Index
> Fetch the complete documentation index at: https://arkor-92aeef0e-eng-615.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# ライフサイクルコールバック

> バックエンドからのストリーム中に Arkor が発火する 5 つのコールバック。

# ライフサイクルコールバック

コールバックは `createTrainer({ callbacks: { ... } })` の下に渡します。バックエンドの SSE イベントストリームから `trainer.wait()` 内でディスパッチされて動きます。

## シグネチャー

```ts theme={null}
interface TrainerCallbacks {
  onStarted:    (ctx: { job: TrainingJob }) => unknown | Promise<unknown>;
  onLog:        (ctx: TrainingLogContext) => unknown | Promise<unknown>;
  onCheckpoint: (ctx: CheckpointContext) => unknown | Promise<unknown>;
  onCompleted:  (ctx: { job: TrainingJob; artifacts: unknown[] }) => unknown | Promise<unknown>;
  onFailed:     (ctx: { job: TrainingJob; error: string }) => unknown | Promise<unknown>;
}
```

interface 自体は 5 プロパティすべて必須ですが、`createTrainer` の `callbacks` フィールドは `Partial<TrainerCallbacks>` 型なので、必要なイベントだけ指定すれば OK です。コールバックの戻り値は破棄されます（`unknown | Promise<unknown>` という戻り値型は、コールバックから値を `return` しても TypeScript が文句を言わない、というだけの意味です）。

## それぞれのコールバックがいつ発火するか

```
trainer.start()    ジョブを投入し { jobId } を返す。コールバックはまだ。
   │
   ▼
trainer.wait()     SSE ストリームを開く。コールバックはここから発火。
   │
   ▼
onStarted          1 回。`training.started` イベント時
onLog              多数回。メトリックスフレームごとに 1 回
onCheckpoint       数回。チェックポイントアップロードごとに 1 回
onCompleted        1 回。`training.completed` 時
        ── または ──
onFailed           1 回。`training.failed`（バックエンド報告の失敗）時
```

`start()` を呼んで `wait()` を呼ばないと、コールバックは決して動きません。`arkor start` は両方呼んでくれます。プログラムから呼び出す側も同じことをしてください。

## パラメーター

### `onStarted({ job })`

SSE ストリームが `training.started` を報告したときに発火します。ログ行や「学習開始しました」通知に使ってください。

```ts theme={null}
onStarted: ({ job }) => {
  // job: TrainingJob (id, name, status, config, ...)
}
```

### `onLog({ step, loss, evalLoss, learningRate, epoch, samplesPerSecond, job })`

学習が進むにつれて繰り返し発火します。各数値フィールドは `number | null`: バックエンドはあるステップで持っているフィールドだけ埋めます（なので `evalLoss` は eval 以外のステップで null、`learningRate` は LR スケジューラ更新の合間に null、など）。

```ts theme={null}
onLog: ({ step, loss, evalLoss }) => {
  if (loss !== null) {
    forwardToMetrics({ step, loss, evalLoss });
  }
}
```

よくある用途は次のとおりです。自前のパイプライン（PostHog、Datadog）へのメトリックス転送、早期発散の検知、カスタム Early Stopping（指標が悪化したら学習を自動で打ち切るパターン。詳しくは [Early Stopping レシピ](/ja/cookbook/early-stopping) を参照）の実装。Early Stopping を実装する際は、[`abortSignal`](/ja/sdk/trainer-control#abortsignal) の Abort がローカルの `wait()` を止めるだけである点に注意してください。バックエンドの GPU を実際に止めるには、その後 [`trainer.cancel()`](/ja/sdk/trainer-control#cancel) を呼ぶ必要があります。

### `onCheckpoint({ step, adapter, job, infer, artifacts })`

学習中にバックエンドでアダプターチェックポイントが保存されたときに発火します。`adapter` は `{ kind: "checkpoint", jobId, step }`。`infer` の詳細は [infer](/ja/sdk/infer) ページにあります。要するにチャット形式のリクエストを取り、生の `Response` を返す関数です。

```ts theme={null}
onCheckpoint: async ({ step, infer }) => {
  const res = await infer({
    messages: [{ role: "user", content: "Can't log in" }],
  });
  const sample = await res.text();
  // モデルが順調かどうか判定
}
```

TypeScript でファインチューニングする意義の多くはここにあります。学習途中のモデルに対して、学習完了を待たずに用意したプロンプトを試せます。

### `onCompleted({ job, artifacts })`

成功時に 1 回発火。`artifacts` は `unknown[]`: バックエンドが送った生の artifact リスト。スキーマは進化するため SDK で絞り込みません。

```ts theme={null}
onCompleted: ({ job, artifacts }) => {
  saveAdapterId({ jobId: job.id, count: artifacts.length });
}
```

### `onFailed({ job, error })`

バックエンドからの失敗報告で 1 回発火。`error` は `string`（バックエンドが送ったメッセージ）であり、`Error` インスタンスではありません。

```ts theme={null}
onFailed: ({ job, error }) => {
  // error: string
}
```

`onFailed` は **バックエンド側の** 失敗専用です。他のコールバック内で投げられた例外は `onFailed` には届きません。何が起きるかは下記の [例外ハンドリング](#例外ハンドリング) を参照してください。

## 振る舞い

### 順序

各コールバックは次のイベントがディスパッチされる前に await されます。Promise を返してよく（DB への書き込み、Slack への投稿、`infer` の呼び出しなど）、SDK は次のフレーム処理前にそれを待ちます。同一トレーナーに対する並行ディスパッチはありません。

### 例外ハンドリング

コールバック内で throw すると、その場でエラーとともに `wait()` が reject します。SDK はコールバックの失敗とトランスポートの失敗を区別します。throw されたコールバックは SSE 再接続ハンドラーには **ルートされない** ので、その失敗を黙って飛ばして続行することはありません。

これが重要なのは、コールバックが実行される時点で、その失敗イベントの `Last-Event-ID` は既に進んでいるためです。もし throw をリトライしてしまうと、再接続はそのイベントの **後** から再開してイベントをスキップし、エラーを握り潰し、（終端の `training.completed` イベントの場合は）まるで何も生成しなかったかのように空の `artifacts` で `wait()` を解決してしまいます。代わりに reject することで、対処できる場所に失敗が表面化します。

再接続ハンドラーは純粋なトランスポート失敗（接続断や一時的な 5xx）専用です。それらは指数バックオフでリトライされますが、throw されたコールバックはリトライされません。

決定的かつ非致命的なエラーハンドリングが必要なら、コールバック内で catch してください（下の 2 つ目の例を参照）。そうすれば、回復可能な副作用の失敗（不安定な Slack 投稿、メトリクス書き込みなど）で実行全体が中断されることはありません。

## 例

最小例: すべてのイベントをログに出す。

```ts theme={null}
const trainer = createTrainer({
  name: "support-bot-v1",
  model: "unsloth/gemma-4-E4B-it",
  dataset: { type: "huggingface", name: "arkorlab/triage-demo" },
  callbacks: {
    onStarted: ({ job }) => console.log(`run ${job.id} accepted`),
    onLog: ({ step, loss }) => {
      if (loss !== null) console.log(`step=${step} loss=${loss.toFixed(4)}`);
    },
    onCheckpoint: async ({ step, infer }) => {
      const res = await infer({
        messages: [{ role: "user", content: "Hello" }],
      });
      console.log(`ckpt @ ${step}:`, await res.text());
    },
    onCompleted: ({ job }) => console.log(`run ${job.id} done`),
    onFailed: ({ error }) => console.error(`failed: ${error}`),
  },
});

await trainer.start();
await trainer.wait();
```

コールバック内で catch して回復可能な失敗をローカルに閉じ込め、`wait()` が reject しないようにする:

```ts theme={null}
onCheckpoint: async ({ step, infer }) => {
  try {
    await sendToReview({ step, sample: await (await infer({ ... })).text() });
  } catch (err) {
    // ログ／メトリックス／コールバックの外側で trainer.cancel() を呼んで自分で学習を失敗させるか判断
  }
}
```

## 型定義

```ts theme={null}
interface TrainingLogContext {
  step: number;
  loss: number | null;
  evalLoss: number | null;
  learningRate: number | null;
  epoch: number | null;
  samplesPerSecond: number | null;
  job: TrainingJob;
}

interface CheckpointContext {
  step: number;
  adapter: { kind: "checkpoint"; jobId: string; step: number };
  job: TrainingJob;
  infer: (args: InferArgs) => Promise<Response>;
  artifacts?: unknown[];
}
```

`TrainingLogContext` と `CheckpointContext` は `arkor` から名前付きでエクスポートされていません。自分のコードで型付きコールバックパラメーターが必要なら、インラインで同じ形を定義してください。

## 関連項目

* [`createTrainer`](/ja/sdk/create-trainer): これらのコールバックを取り付ける関数
* [実行ライフサイクル](/ja/concepts/lifecycle): 概念的な流れ
* [`infer`](/ja/sdk/infer): `onCheckpoint` に渡される関数
* [トレーナー制御](/ja/sdk/trainer-control): `abortSignal` と `cancel()`
* [Early Stopping レシピ](/ja/cookbook/early-stopping)
