ガイド
エージェントとツール
Oloproof はエージェントを動かしません。あなたのエージェントが自身のループを回し、自身のツールを呼び出し、起きたことを agent_trajectory/v1 アーティファクトとして記録します。エージェントのメトリクスはすべてその記録から読み取られるため、記録こそが統合のすべてです。
このページで実行するプロジェクトは examples/support_agent/ です。40 件の返金リクエスト、決定的なエージェントからなり、プロバイダーの認証情報は不要です。
軌跡の記録
システムの内部で、ステップを起きた順に組み立て、ケースレコーダーに渡します。
from oloproof import AgentConstraintCheck, AgentStep, AgentTrajectory, current_case, system
@system(name="support-agent", version="slice-e-example", records=("agent_trajectory/v1",))
def run(case):
steps = []
for name, arguments in plan(case):
steps.append(AgentStep(index=len(steps) + 1, kind="tool_call", tool_name=name, arguments=arguments))
result = getattr(tools, name)(**arguments)
steps.append(AgentStep(index=len(steps) + 1, kind="tool_result", tool_name=name, result=result))
current_case().agent_trajectory(
AgentTrajectory(
steps=tuple(steps),
terminal_status="success" if refunded else "failure",
truncated=truncated,
step_limit=STEP_LIMIT if truncated else None,
constraints=(
AgentConstraintCheck(name="no_deletion", passed=deletion is None, step_index=...),
),
)
)
return {"answer": "refunded" if refunded else "unresolved"}各部分の役割は次のとおりです。
| フィールド | 記録するもの |
|---|---|
| AgentStep.kind | message、tool_call、tool_result、observation、decision、final のいずれか |
| AgentStep.tool_name、arguments、result | 呼び出しと、返ってきたもの |
| terminal_status | success、failure、unknown のいずれか |
| truncated、step_limit | エージェントが上限に達し、トレースが途中で終わっていること |
| constraints | 環境が行ったチェック。それぞれ観測したステップを伴います |
| checkpoints | 再生を再開できる地点。AgentCheckpoint として記録します |
システムは、軌跡を記録することを、デコレーターと oloproof.yaml の両方で宣言します。
system:
name: support-agent
version: slice-e-example
callable: app:run
records: [agent_trajectory/v1]records: がないと、エージェントの評価器は、すべてのケースを欠測として数えるのではなく、実行を拒否します。
ケースが宣言するもの
特定のツールを特定の順序で呼び出すべきケースは、そのことを expected.tools の下に記述します。
{"id": "case_001", "input": {"order_id": "ord-002", "behaviour": "clean"}, "expected": {"answer": "refunded", "tools": ["lookup_order", "issue_refund"]}, "metadata": {"surface": "chat", "behaviour": "clean"}}expected.tools: [] は、そのケースがツール呼び出しをまったく想定していないことを表し、その点で判定されます。キーを省略すると、そのケースにはツール選択が当てはまらないことを表します。agent_tool_sequence と agent_no_undeclared_tool はそのケースを no_declared_tools として除外します。合格として数えるのではなく分母から外れるのは、一度も測定されなかったケースで水増しされた割合は割合ではないからです。ツール名のリストでない値は、実行の前に拒否されます。
評価器
evaluators:
- {type: contains, criterion: answer_correct, field: answer, expected_field: answer}
- {type: agent_tool_called, tool_name: lookup_order}
- {type: agent_no_tool_loop, max_repeats: 2}
- {type: agent_tool_sequence}
- {type: agent_constraints_satisfied, constraints: [no_deletion]}
- {type: agent_max_steps, max_steps: 10}
metrics:
- {id: steps_p95, type: quantile, source: agent_steps, quantile: 0.95}
- {id: tool_calls_p50, type: quantile, source: agent_tool_calls, quantile: 0.5}
slices: [metadata.surface, first_tool, repeated_action, "trajectory_length:4,8"]
min_slice_support: 3| 種類 | 合格する条件 | とるもの |
|---|---|---|
| agent_tool_called | ツールが少なくとも min_calls 回呼び出された | tool_name、min_calls(デフォルト 1) |
| agent_no_tool_loop | 同一の呼び出し(同じツールと引数)が max_repeats 回を超えて連続しない | max_repeats(デフォルト 2) |
| agent_tool_sequence | 呼び出されたツールが expected.tools と一致する | ordered(デフォルト true) |
| agent_no_undeclared_tool | expected.tools にないツールが呼び出されなかった | なし |
| agent_constraints_satisfied | 環境が記録した、名前付きのすべての制約が合格した | constraints |
| agent_max_steps | トレースのステップ数が max_steps 以下だった | max_steps |
agent_steps と agent_tool_calls は、最後の評価器の裏にある分布を分位点メトリクスとして扱うものです。
oloproof run│ answer_correct │ 82.5% │ [67.2%, 92.7%] │ 33 / 40 observed · 0 missing · 0 excluded │
│ agent_tool_lookup_order_called │ 100.0% │ [86.8%, 100.0%] │ 39 / 39 observed · 1 missing · 0 excluded │
│ agent_no_tool_loop │ 74.4% │ [56.1%, 87.4%] │ 29 / 39 observed · 1 missing · 0 excluded │
│ agent_tool_sequence │ 60.0% │ [43.3%, 75.2%] │ 24 / 40 observed · 0 missing · 0 excluded │
│ agent_constraints_satisfied │ 92.5% │ [79.6%, 98.5%] │ 37 / 40 observed · 0 missing · 0 excluded │
│ agent_steps_le_10 │ 97.5% │ [86.8%, 100.0%] │ 39 / 40 observed · 0 missing · 0 excluded │
│ steps_p95 │ 10 steps │ [10, no bound] steps │ p95 of 39 observed · 1 missing · 0 excluded │
│ tool_calls_p50 │ 2 calls │ [2, 3] calls │ p50 of 39 observed · 1 missing · 0 excluded ││ no-deletion │ agent_constraints_satisfied │ FAIL │ observed_failures_exceed_limit │制約は他の基準と同じようにゲートになります。no-deletion は max_failures: 0 を持つ観測件数ルールです。3 件のケースが delete_customer を呼び出しました。「実行したスイートの中でこれが起きてはならない」を判定するのに区間は必要ありません。
途中で終わるトレース
1 件のケースがエージェントのステップ上限に達したため、そのトレースは truncated として記録されます。切り詰められたトレースに対する件数はすべて下限であり、下限で決着する問いもあれば、決着しない問いもあります。
| 基準 | 切り詰められたケース | 理由 |
|---|---|---|
| agent_steps_le_10 | 失敗 | 記録されたステップだけで、上限 10 を超えたことがすでに証明されている |
| agent_tool_lookup_order_called | 欠測 | その呼び出しは記録されなかった部分にあるかもしれない |
| agent_no_tool_loop | 欠測 | トレースの末尾に達した繰り返しは、その先も続いているかもしれない |
| steps_p95、tool_calls_p50 | 欠測 | 下限に対する分位点は分位点ではない |
除外ではなく欠測です。そのケースは観測されないまま分母に残り、区間はそれがどちらに転んでもよいことを許容します。agent_tool_lookup_order_called が 39 件の観測ケースに対して、39 件だけで得られるより狭い区間ではなく [86.8%, 100.0%] となっているのはそのためです。
軌跡に対するスライス
first_tool は、実行がどのツールを呼び出したかにかかわらず、エージェントが最初に手を伸ばしたツールでケースをグループ化します。repeated_action は、同一の呼び出しを繰り返したケースとそうでないケースを分けます。trajectory_length:4,8 はケースを 1-4、5-8、9 以上のステップに区分します。境界を自分で宣言するのは、区分の境界によってスライスの語る内容が変わるからです。
│ first_tool=search │ agent_no_tool_loop │ 0.0% │ [0.0%, 57.9%] │ 0 / 6 observed · 1 missing · 0 excluded · │
│ first_tool=search │ agent_tool_sequence │ 0.0% │ [0.0%, 41.0%] │ 0 / 7 observed · 0 missing · 0 excluded · │
│ repeated_action=false │ answer_correct │ 93.1% │ [77.2%, 99.2%] │ 27 / 29 observed · 0 missing · 0 excluded · │
│ repeated_action=true │ answer_correct │ 60.0% │ [26.2%, 87.9%] │ 6 / 10 observed · 0 missing · 0 excluded · │ループはシグナルであって、説明ではありません。出力のどこにも、繰り返された呼び出しがケースの失敗の理由だとは書かれていません。それを示せるのは、その呼び出しを取り除いた再生だけです。
複数のエージェント
examples/triage_agents/ は 3 つのエージェントからなるチームを実行します。triage が各リクエストを billing または tech に渡し、billing が発行できない返金は人に渡されます。複数のエージェントからなるシステムでは、各ステップがそれを実行したエージェントを名指しし、制御の移譲は handoff ステップになります。
steps.append(AgentStep(index=1, kind="message", agent="triage", arguments={"request": request}))
steps.append(AgentStep(index=2, kind="handoff", agent="triage", to_agent="billing"))
steps.append(AgentStep(index=3, kind="tool_call", agent="billing", tool_name="lookup_order"))軌跡は、すべてのステップのエージェントを名指しするか、どれも名指ししないかのどちらかです。一部だけを名指しする記録は拒否され、どれも名指ししない記録は、以下の基準のすべてについて、合格ではなく欠測となります。ハンドオフのステップは任意です。次のステップを別のエージェントが実行した時点でも制御は移るからです。ハンドオフは、人のようにステップを実行しないエージェントへの移譲をトレースが記録する手段です。
ケースは、経由すべきエージェントを expected.route の下に宣言します。
{"id": "case_012", "input": {"topic": "billing", "request": "I was charged twice for order ord-012", "order_id": "ord-012", "behaviour": "escalated"}, "expected": {"answer": "escalated", "route": ["triage", "billing", "person"]}, "metadata": {"topic": "billing", "behaviour": "escalated"}}evaluators:
- {type: contains, criterion: answer_correct, field: answer, expected_field: answer}
- {type: agent_route}
- type: agent_tool_permissions
permissions:
triage: []
billing: [lookup_order, issue_refund]
tech: [search_kb]
- {type: agent_max_handoffs, max_handoffs: 2}
slices: [route]| 種類 | 合格する条件 | とるもの |
|---|---|---|
| agent_route | 制御を持ったエージェント(繰り返しはまとめる)がケースの expected.route と一致する | なし |
| agent_tool_permissions | すべてのツール呼び出しが、マップ上でそのツールの呼び出しを許可されたエージェントによって行われた | permissions |
| agent_max_handoffs | 制御の移譲が max_handoffs 回以下だった | max_handoffs |
ルートにはハンドオフの受け手が含まれるため、人へのエスカレーションで終わるトレースのルートは人に至ります。expected.route のないケースは agent_route では数えられません。権限マップは閉じています。マップに載っていないエージェントはどのツールも呼び出せません。
│ answer_correct │ 96.7% │ [82.7%, 100.0%] │ 29 / 30 observed · 0 missing · 0 excluded │
│ agent_route │ 86.7% │ [69.2%, 96.3%] │ 26 / 30 observed · 0 missing · 0 excluded │
│ agent_tool_permissions │ 93.1% │ [73.4%, 99.2%] │ 27 / 29 observed · 1 missing · 0 excluded │
│ agent_handoffs_le_2 │ 86.7% │ [69.2%, 96.3%] │ 26 / 30 observed · 0 missing · 0 excluded │2 件のリクエストで tech が返金を発行しました。どちらも answer_correct には合格し、agent_tool_permissions には失敗します。顧客は、自らの権限を破ったシステムから正しい答えを受け取ったのです。1 件のリクエストは、ループの上限に達するまで billing と tech の間を行き来します。そのトレースは切り詰められているため、ルートとハンドオフ上限には失敗し(これらは記録された先頭部分だけですでに決着します)、権限については欠測となります(こちらは決着しません)。
route は、エージェントがたどったルートでケースをグループ化し、triage>billing のように書きます。切り詰められたトレースはどのルートのグループにも入りません。そのルートは、エージェントがその後どこへ向かったにせよ、その先頭部分にすぎないからです。
出力のどこにも、どのエージェントに責任があるかは書かれていません。ルートの分岐は、2 つのルートがどこで分かれるかを示すだけです。あるエージェントが失敗を引き起こしたというのは、そのエージェントが別の行動をとっていたら何が起きたかについての主張であり、それを示せるのはそのエージェントを置き換えた再生だけです。Oloproof はそのような再生を実行しません。
次に読むページ
- システムが行ったことを記録する では、その他の型付きアーティファクトを説明しています。
- スライス では、スライスのサポートと、スライスが決してゲートにならない理由を説明しています。