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

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

> 学習中に Arkor が発火する 5 つのコールバックと、それぞれが何に役立つか。

学習が進むにつれて Arkor は 5 つのコールバックを発火します。すべてオプションで、いずれもあなたのプロセス内で動く普通の TypeScript 関数です。これがアプリケーション本体と同じ感触のループにする鍵です。ノートブックも、外部のダッシュボードも、別の設定言語もいりません。

```ts theme={null}
createTrainer({
  name: "support-bot-v1",
  model: "unsloth/gemma-4-E4B-it",
  dataset: { type: "huggingface", name: "arkorlab/triage-demo" },
  callbacks: {
    onStarted, onLog, onCheckpoint, onCompleted, onFailed,
  },
});
```

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

```
.start()                 ── ジョブ投入、{ jobId } を返す。コールバックはまだ。
   │
   ▼
.wait()                  ── SSE イベントストリームを開く。コールバックはここから発火。
   │
   ▼
onStarted ─── 1 回。ストリームが `training.started` を報告したとき
   │
   ▼
onLog     ─── 多数回。学習ステップのメトリックスバッチごとに 1 回
   │
   ▼
onCheckpoint ── 数回。アダプタチェックポイントが保存されたとき
   │
   ▼
onCompleted  ── 1 回。正常終了時
or
onFailed     ── 1 回。バックエンドが失敗を報告したとき
```

5 つすべてのコールバックは `wait()` 内からディスパッチされます。`start()` を呼んで `wait()` を呼ばないと、学習はバックエンドで進行していてもコールバックは **一切発火しません**。`arkor start` はあなたの代わりに `wait()` を呼びます。CLI の外側で学習を自分のコードに組み込む場合は、自分でも呼ぶようにしてください。

`onCompleted` と `onFailed` は排他で、学習 1 回あたり最大 1 つしか発火しません。終端イベント到達前に `wait()` が throw した場合（例えば `abortSignal` が Abort された、再接続試行が尽きた）、どちらも発火しないことがあります。

どのコールバックも Promise を返して構いません。Arkor は次に進む前に await するので、非同期処理（DB への書き込み、Slack への投稿、`infer` の呼び出し）を競合なくこなせます。

## `onStarted({ job })`

`wait()` が開いた SSE ストリームが `training.started` イベントを報告したときに 1 回発火します。これは `start()` の resolve のタイミングと同じではありません。`start()` はジョブを投入して `jobId` を返すだけです。

```ts theme={null}
onStarted: ({ job }) => {
  console.log(`Run ${job.id} accepted`);
},
```

ログ行、メトリックスカウンター、「学習開始しました」の通知に使ってください。

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

学習が進むにつれて繰り返し発火します。各数値フィールドはバックエンドがまだそのメトリックスを出していなければ `null` になり得ます（例えば `evalLoss` は eval ステップのときだけ）。

```ts theme={null}
onLog: ({ step, loss, evalLoss }) => {
  if (loss !== null) {
    console.log(`step=${step} loss=${loss.toFixed(4)} evalLoss=${evalLoss ?? "-"}`);
  }
},
```

よくある用途:

* 自前のメトリックスパイプライン（PostHog、Datadog など）に転送。
* 早期発散の検知: `loss` が増えていれば、`abortSignal` で `wait()` を Abort し、`trainer.cancel()` でバックエンドの GPU を止める。
* カスタム Early Stopping（指標が悪化したら学習を自動で打ち切るパターン。詳しくは [Early Stopping レシピ](/ja/cookbook/early-stopping) を参照）を実装。

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

学習中にバックエンドでチェックポイントが保存されたタイミングで発火します。

```ts theme={null}
onCheckpoint: async ({ step, infer }) => {
  const res = await infer({
    messages: [{ role: "user", content: "Can't log in" }],
  });
  console.log(`step=${step} sample=`, await res.text());
},
```

`adapter` はチェックポイントを識別する小さなオブジェクト（`{ kind: "checkpoint", jobId, step }`）。`infer` は関数で、チャット形式のリクエストを取り、生の `Response` を返します。`await res.text()`（または `res.json()`、ストリーム処理）で読み取ります。

実用上もっとも価値のあるコールバックです。学習の最後まで待たずに、途中でモデルを動作確認できます。チェックポイントが既にベースモデルより悪ければ、止めるべきだとわかります。

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

成功時に 1 回発火します。`artifacts` はこの学習でバックエンドが生み出したものを並べたリストです。用途:

* 最終アダプタ ID をアプリの他の部分から見つけられる場所に保存。
* モデルを昇格させる前に最終スモークテスト。
* 「学習完了」通知の送信。

```ts theme={null}
onCompleted: ({ job, artifacts }) => {
  console.log(`Run ${job.id} done, ${artifacts.length} artifacts`);
},
```

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

バックエンドが失敗を報告した場合に 1 回発火します。`error` は `string`（バックエンドが送ってきたメッセージ）であって `Error` インスタンスではないことに注意:

```ts theme={null}
onFailed: ({ job, error }) => {
  console.error(`Run ${job.id} failed: ${error}`);
},
```

`onFailed` はバックエンド報告の失敗専用です。あなたのコールバック内で throw した例外は `onFailed` を経由しませんし、`wait()` をクリーンに失敗させる保証もありません。現行のランタイムはイベントディスパッチ中に throw されたエラーを catch し、SSE 失敗として扱って再接続ループに送ります。元の例外は予想外の場所でリトライされたりスキップされたりすることがあります。決定的な振る舞いが必要なら、コールバック内で catch して、エスケープする前に何をするか（ログ、Abort、永続化）を決めてください。

## 完成例

```ts theme={null}
import { createTrainer } from "arkor";

export const trainer = createTrainer({
  name: "support-bot-v1",
  model: "unsloth/gemma-4-E4B-it",
  dataset: { type: "huggingface", name: "arkorlab/triage-demo" },
  lora: { r: 16, alpha: 16 },
  maxSteps: 100,
  callbacks: {
    onStarted: ({ job }) => console.log(`started ${job.id}`),
    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(`done ${job.id}`),
    onFailed: ({ error }) => console.error(`failed: ${error}`),
  },
});
```

コールバックを理解してしまえば、Arkor のほとんどはその上に乗る設定にすぎません。
