概念
わずかな数の言葉が、データモデル全体を支えています。以降のドキュメントはそれを知っている前提で書かれているので、このページでひとつずつ定義し、何のためにあるかを述べます。
スパン
スパンは仕事の 1 単位です。名前、種別、開始時刻、たいていは終了時刻、そしてステータスを持ちます。何が入り、何が出て、トークンをいくつ使い、いくらかかったか、失敗した場合はそのエラーも持てます。
名前はカーディナリティを低く保ってください。search_docs は名前ですが、search_docs(query=hello) は名前と値をくっつけたもので、実行が 2 件になった時点で、名前による集計が役に立たなくなります。
トレース
トレースは 1 回の実行から出たすべてのスパンで、128 ビットの trace id で結ばれています。トレースの中では parent_span_id がツリーの形を決めます。モデルのステップの中で行われたツール呼び出しはそのステップの子になるので、画面上の入れ子は、実際に起きた入れ子です。
id は OpenTelemetry と同じ形で、トレースは小文字の 16 進 32 文字、スパンは 16 文字です。これは飾りではありません。Web 層で始まりエージェントで終わったトレースが、その両方をまたいで 1 つのトレースであり続けられるのはこのためで、すでにテレメトリーを読んでいる別のものと mcpd が並んで動けるのも、このためです。
親が別のトレーシングシステムから来たスパンは、その値を external_parent_span_id に持ちます。mcpd はこの値を相関のために保持しますが、ツリーの構築には決して使いません。mcpd が受け取っていないスパンを指す親 id は、部分木ごと何もない場所にぶら下げてしまうからです。
- agent support_agent fail
- model chat
- tool returns_policy
- model chat
- tool returns_policy
- model chat
- tool returns_policy
The model called one tool three times and never answered. That is visible in four seconds here, and invisible in a log file.
mcpd は各トレースの射影も保存します。スパン数、エラー数、トークンの合計、コストの合計、モデル呼び出しの数、ツール呼び出しの数です。この行は、加算するのではなく、書き込みのたびにスパンから計算し直します。スパンは順不同で届き、再送され、その場で更新されるからです。カウンターはこの 3 つのどれでもずれますし、一覧のスパン数が明らかに間違っていれば、読む人はページ上のほかのすべての数値も信じなくなります。
ルートスパンとイベントスパン
ルートスパンは、そのトレースの中に親を持たないスパンです。ふつうは agent_run か workflow_run で、トレースの名前、タグ、相関はここから取られます。タグに意味があるのはルートの上だけで、そこでは実行全体にラベルを付けます。
イベントスパンは長さではなく時点なので、終了も所要時間も持ちません。ストリーミングのチャンクが代表例です。この区別は、終了時刻がないことから推測するのではなく、そのまま保存します。「これには最初から終わりがない」と「これはまだ終わっていない」は別の事実で、問題なのは一方だけだからです。
トレースが完了するのは、ルートが終わり、開いたままのスパンが 1 つもなくなったときです。それまでの間も、終わるまで隠すのではなく、その時点で分かる最良の名前を付けて一覧に出します。終わらなかった実行こそ、あなたが見に来たものであることが多いからです。
スパンの種別
種別は、その操作が何であるかを示します。スパンの描かれ方、そのスパンで意味を持つ属性、コスト計算の対象になるかどうかは、これで決まります。mcpd は 33 の種別を系統ごとに定義しています。
| 系統 | 種別 |
|---|---|
| エージェント | agent_run |
| モデル | model_generation, model_step,
model_inference, model_chunk |
| ツール | tool_call, client_tool_call,
provider_tool_call, mcp_tool_call |
| ワークフロー | workflow_run, workflow_step,
workflow_conditional, workflow_parallel,
workflow_loop ほか
|
| リトリーバル | rag_ingestion, rag_embedding,
rag_vector_operation, rag_action,
retrieval, memory_operation,
graph_action |
| 評価 | scorer_run, scorer_step,
guardrail, processor_run |
| その他 | mapping, skill_resolution,
workspace_action, http_request,
generic |
トークンを計上する種別は 4 つ、model_generation、model_step、model_inference、rag_embedding です。コスト計算が読むのは、このスパンたちです。generic は常に有効で、推測されることはありません。分類できないスパンには、当て推量ではなく正直にこのラベルが付きます。データモデルに、すべての種別とその意味が並んでいます。
モデルの階層
このうち 4 つの種別は互いに入れ子になり、その入れ子が、このモデルでいちばん役に立つ部分です。
model_generationは論理的な生成 1 回分です。最初のリクエストから最終的な答えまでのすべてを指します。model_stepはその中の 1 ステップです。ツールを 2 回呼ぶ生成には、ステップが 3 つあります。model_inferenceはプロバイダーとの往復だけで、プロセッサーもツールの実行も含みません。model_chunkはストリーミングのチャンク 1 つで、イベントとして記録されます。
階層が 1 つではなく 4 つある理由は、「モデルの呼び出しが遅かった」がそのままの意味で正しいことは、ほとんどないからです。ツールが絡めば、あなたのコードからの 1 回の呼び出しは HTTP の 1 往復ではありません。リクエスト、ツール、もう 1 回のリクエスト、ガードレール、さらにもう 1 回のリクエストです。これを 1 つのスパンにまとめてしまうと、自分のリトライループで 6 秒使った実行と、プロバイダーの応答を 6 秒待った実行が、まったく同じに見えます。inference と step と generation を分けているからこそ、この違いが読み取れます。そして time to first token は、最初のチャンクのイベントから分かります。
トークンの使用量は、プロバイダーを呼んだスパンに一度だけ記録してください。トレースの射影は、トレース内のすべてのスパンについて入力と出力のトークンを合計します。同じ数を generation にも各 step にも付ければ、画面上のトークン合計もコストも 2 倍になります。階層は時間のためのもので、トークンは、実際にプロバイダーと話した階層のものです。
6 つのシグナル
スパンは、mcpd が保存する 6 つのもののうちの 1 つです。ログは実行が出した行。メトリクスは数値の観測で、カウンター、ゲージ、ヒストグラムの形を取ります。スコアはスパンやトレースに対する機械の評価。フィードバックは人の評価。観点ノートは、終わった実行についての 1 文です。ほとんどはモデルが書きます。outcome のノートは、実行のステータスと、すでに持っているほかの事実から、デーモンが書きます。
-
Spans
What ran, nested, with timings
-
Logs
Lines the run emitted
-
Metrics
Numbers, with exemplars back to a run
-
Scores
Machine evaluation
-
Feedback
What a person thought
-
Aspect notes
A sentence a model wrote about the run
6 つとも、自分が属する trace id を持ち、ほとんどは span id も持ちます。そして 6 つとも、何かが書き込まれる前に同じ秘匿化のパイプラインを通ります。秘密は、プロンプトからと同じようによく、ログの 1 行からも漏れます。
スコアは必ず尺度を宣言します。値と、その値が収まる最小値と最大値です。どの SDK も呼び出し側で両方を要求します。尺度の付かない 0.7 という値だけでは、何とも比べられないからです。先週の同じスコアラーの出力とも比べられません。
フィードバックはスコアとは別の場所に置き、スコアと平均を取ることはありません。形は 5 つ、thumbs、rating、correction、tag、comment です。5 つのうちいちばん価値があるのは correction です。正しい答えを運ぶ唯一のシグナルだからです。
相関
相関とは、シグナルが属する文脈のことです。ただ積み上がった実行と、切り分けられる集合との違いが、ここにあります。
| フィールド | 保持する内容 |
|---|---|
project | テナントのキー。どのシグナルも、ちょうど 1 つに属します。 |
environment | どこで動いたか。production や staging など。 |
service_name | 送り出したサービス。 |
service_version | そのバージョン。リグレッションをリリースに結び付けられます。 |
session_id | 利用者から見たセッション。多くのトレースを 1 つの会話にまとめます。 |
thread_id | そのセッションの中のスレッド。 |
user_id | その実行の対象だったエンドユーザー。 |
resource_id | あなたのアプリケーションの中のテナントやワークスペース。 |
run_id | この実行に対する、あなた自身の識別子。 |
request_id | その仕事を引き起こした、受信したリクエスト。 |
experiment_id | 実験がその実行を生んだときに設定します。 |
release | リリース名、または git の sha。 |
必須のフィールドは project だけです。送信側が指定しなかった場合、デーモンは --project の既定値の下に振り分けるので、居場所のないまま保存されるものはありません。
冗長に見えても session_id は設定してください。エージェントの 1 回の実行はトレースですが、苦情はたいてい会話についてのものです。session id がなければ、ばらばらの 11 本のトレースを、その人が実際に交わしたやり取りに戻すものが何もありません。
トレースの一覧は、environment、session_id、thread_id、user_id でそのまま絞り込めます。ステータス、種別、名前、タグ、モデル、期間、最小の所要時間、最小のコスト、スコアも同じように使えます。実行を探すで、これらを組み合わせます。
リンク
スパンは親を 1 つと、いくつでもリンクを持てます。リンクは別のスパンや別のトレースを指し、その理由を述べます。follows_from、retry_of、replay_of、あるいはあなたが付けた名前です。
リンクがあるのは、親子関係ではファンインを表せないからです。20 件のリクエストが引き起こしたバッチ処理には、リンクが 20 本と親が 1 つあります。これを無理にツリーへ収めれば、重複した部分木を 19 個でっち上げるか、原因を 19 個捨てるかのどちらかになります。
次に
- すべてのエンティティとフィールドを網羅:データモデル。
- これらの言葉がコードでどう現れるか:SDK。
- スパンが OTLP として届いたとき mcpd が何を推測するか:OpenTelemetry。
- この用語をクエリにする:実行を探す。