概要 / architecture

設計パターン

Jev は、より大きなシステムの中で判断を担う部品です。「複雑な振る舞いを、離散的で原子的な判断の組み合わせとして考える」ことが、使いこなしの鍵になります。

まず全体像:ワークフロー設計の7ステップ

要約すると、「普通のソフトウェアのワークフローを作り、AI が必要な場所にだけ System One を差し込む」。

1

コードでできることはコードで

決定的な仕事は安くて確実なコードに。days_overdue > 30 にモデルは要らない。エージェントの while ループも避ける。

2

入力 state を絞る

今の質問に関係する文脈だけを入れる。注意散漫と文脈の劣化を防ぐ。

3

state に構造を使う

入れ子の JSON にし、質問からはバッククォートのパスで特定の値を指す。

4

質問を分解する

できるだけ明示的で、狭く、具体的で、原子的な質問に。最も重要なステップ。

5

質問にも構造を使う

複数種類のガイダンスが要るときは、密な散文にせず名前付きフィールドに分ける。

6

たくさん質問する

同じ state への独立した質問を1リクエストに。コストあたりの知性を最大化する方法。

7a

コードで合成する

決定的なルールか重み付き和で。学習させたいなら、確率を古典的 ML モデルの特徴量に。

7b

不確かさで分岐する

確信のある答えとない答えで別の行動を。不確かなケースは人か、より高価な推論モデルへ。

Pattern 1 · cost, speed

投機的ファンアウト

システムが必要とする質問を、使うか分からないものも含めて1回のリクエストに全部入れ、どれが関係あるかは後からコードで決めます。全質問が並列に評価されるので、質問を足しても通常レイテンシは増えません。

✕ 直列に聞く
call 1: カテゴリは?
if bug_report …
call 2: 重大度は?
call 3: 再現手順はある?
往復のたびにレイテンシが積み上がり、state のトークンも毎回払い直す。
○ 1回で扇状に聞く
1 call / 同じ state
category常に使う
bug_severitybug_report のときだけ
has_reproducible_stepsbug_report のときだけ
refund_requestedbilling のときだけ
frustration常に使う
コードが、関係ある答えだけを読む
機能要望のチケットだったら、重大度の答えは単に無視される。
triage.py公式パターンより
if category.choice == "bug_report":
    if bug_severity.score > 1.5 and bug_repro.noul > 0.6:
        escalate_to_engineering(ticket_id, severity="high")
    else:
        add_to_bug_backlog(ticket_id)

elif category.choice == "billing":
    if refund.noul > 0.7:
        route_to_billing_with_flag(ticket_id, refund_likely=True)
    else:
        route_to_billing(ticket_id)

elif category.choice == "feature_request":
    log_feature_request(ticket_id)

# いら立ちはカテゴリに関係なく役に立つ
if frustration.score > 1.5:
    flag_for_priority_response(ticket_id)
公式クックブックの計測例
約10倍

GDPR の Wikipedia 記事に対する13問の規制ブリーフィング。13問を1回にまとめると、13回の個別呼び出しに比べてコストは 11.5〜12.2倍、速度は 9.6〜10.0倍改善し、答えは変わらなかった(公式ドキュメント内で記載箇所により数値が異なる)。

コーディングエージェントほど「1回1問」に陥りやすい公式ドキュメントによれば、人よりもコーディングエージェントの方がこの癖に陥りがち。追加の質問もトークンは消費するので、実際の予算・コスト・エンドツーエンドのレイテンシは計測すること。

Pattern 2 · reliability, safety

確信度ゲート付きルーティング

confidence を第2の判断軸として使います。答えは「何を」、confidence は「実行してよいか」。

intent.choice ↓ / confidence →0.6 未満0.6 〜 0.850.85 超
check_balance
低リスク
人に回す残高を表示(最悪でも残高が読み上げられるだけ)
approve_transfer
高リスク
人に回すユーザーに確認してから送金を承認
other人に回す
音声バンキングの例。行(何をするか)と列(どれだけ確かか)の2軸で行動が決まります。操作できる図とコードは Confidence のページに。

Pattern 3 · cost, reliability, speed

コンポジット・スコアリング

複数の基準で項目をランク付けしたいとき、判断を独立した次元に分けて別々に採点し、コード側で持つ重みで合成します。例は、エンジニア職の履歴書スクリーニングです。

step 1 · 次元ごとに独立して採点(各 0〜4 の5レベル)
python_depth記載なし → 言及のみ → プロジェクトで使用 → 主要言語 → アーキテクチャ・性能まで深い専門性
team_leadership経験なし → 非公式なメンター → 小チームを主導 → 部下を持つ管理職 → 複数チーム・組織を管理
system_design設計業務なし → 議論に参加 → 部品を設計 → 重要システムの設計を所有 → 複数領域で大規模設計
generalist単一領域のみ → 狭い範囲で多少 → 数領域を経験 → 領域を頻繁に横断 → 未知の領域で成果を出した実績
4問は1リクエストで並列評価。各スコアを 4 で割って 0〜1 に正規化してから合成する。
step 2 · 重みで合成(動かせます)
0.40
0.10
0.40
0.10

    プリセットの重みは公式パターンの値。候補者4名のスコアは説明用の架空データです。右の数値は重みを合計1に正規化したもの。Jev を呼び直さずに、順位だけが変わる点に注目してください。

    scoring.py公式パターンより
    py      = response.answers["python_depth"].score / 4
    lead    = response.answers["team_leadership"].score / 4
    arch    = response.answers["system_design"].score / 4
    general = response.answers["generalist"].score / 4
    
    # Senior IC
    ic_score = (0.40 * py) + (0.10 * lead) + (0.40 * arch) + (0.10 * general)
    
    # Engineering Manager
    em_score = (0.15 * py) + (0.40 * lead) + (0.20 * arch) + (0.25 * general)

    得られるのは順位だけではありません。最終スコアがどう計算されたかが完全に見えることが本質です。上位の候補者が期待と違えば、プロンプトを書き直すのではなく重みを調整します。

    重みやしきい値、表示フィルタを変えても、証拠(state)と質問の意味が変わらない限り、推論をやり直す必要はありません。一度採点した生の判断は再利用できるデータです。

    Pattern 4 · cost, speed

    インテント・ルーティング

    すべてのリクエストに同じ種類のハンドラは要りません。Jev を手前に置く高速・安価な分類器として使い、高価なリソースは本当に必要なリクエストにだけ使います。

    in顧客メッセージ
    1 callintent(Choice)
    + complexity(Score)
    order_status → 決定的なコードDB を引くだけ。LLM は関与しない
    product_question → 製品スペシャリスト LLM製品知識の文脈を載せた LLM
    return_exchange → 返品スペシャリスト LLM返品規程の文脈を載せた LLM
    complaint → 複雑さで分岐complexity.score ≤ 1 かつ確信あり → 苦情対応 LLM / それ以外 → 人
    intent.confidence < 0.5 → 人のエージェント分類に十分な確信がないなら、最初から人へ
    routing.py公式パターンより
    def route_ticket(ticket_id, response):
        intent = response.answers["intent"]
        complexity = response.answers["complexity"]
    
        if intent.confidence < 0.5:
            return route_to_human_agent(ticket_id)
    
        if intent.choice == "order_status":
            handle_order_status(ticket_id)
        elif intent.choice == "product_question":
            handle_with_llm(ticket_id, PRODUCT_SPECIALIST)
        elif intent.choice == "return_exchange":
            handle_with_llm(ticket_id, RETURNS_SPECIALIST)
        elif intent.choice == "complaint":
            low_confidence = complexity.confidence < 0.5
            # complexity.score が高いほど「エスカレーションが必要」側
            if complexity.score > 1 or low_confidence:
                route_to_human_agent(ticket_id)
            else:
                handle_with_llm(ticket_id, COMPLAINT_RESOLUTION)

    その他の型:分類だけで終わらせない

    公式のエージェント用スキルは、アーキテクチャを考えるとき「分類器」以上の発想を持つよう勧めています。

    生成せず、選択する

    候補の値やテキスト範囲は正規表現などコードで見つけ、Jev には「意図された1つ」を選ばせ、コードがそれをコピー・正規化する。原文どおりの値が得られる。

    ルーティングして、引数も埋める

    リクエストからハンドラを選ぶだけでなく、その型付き引数も、分岐ごとの質問を先に投機的に聞いて埋める(関数呼び出しクックブック)。

    証拠を探し、判定する

    候補を検索で集め、クエリへの関連度を比べ、有用な文脈を選ぶ。再ランクや階層分類。

    検証して、エスカレーションする

    特定の主張やフィールドを、その証拠と突き合わせて点検。不確か・不合格のものは人か推論モデルへ(mini → verify → reasoning のカスケード)。

    判断を再利用可能なデータに

    次元ごとに一度採点しておき、重み・しきい値・ランキング・ビューはコードや UI 側で変える。ラベルがあれば古典的 ML の特徴量に。

    変化する状態に応答する

    目標と観測はコードが保持し、新しい判断が次の一手を導く。推論された状態と観測された事実は区別し、結果を適用する前に鮮度を確認する。