TT Lab
はじめる
学ぶ 学習パス コース

AIエージェント — モデルではなくグラフ

高額返金が承認待ちにすら現れなかった

TT Labで続きを見る

目標

取り消せない作業の前でグラフを止め、人が値を直すか拒否したあとで続きを動かします。止める方法を2つとも作ってみて、2つの違いを、自分で数えた数値で確認します。

なぜ重要なのか

LangGraphで、中断は保存の上に築かれています。止まるとは、「次のステップを今は実行せず、残しておいた状態から、あとで続きを行う」という意味なので、残しておく場所(チェックポインター)がなければ、続きを行うこともできません。ところが、チェックポインターなしで中断点だけを渡しても、エラーが出ません。グラフは黙って止まり、半分だけの状態を返します。このラボで、それをまず自分の目で見ます。 止まったあとに人が行うことは、承認だけではありません。金額を減らすことも、拒否することもあります。update_stateで直した値は、そのキーのリデューサーを通って入るので、上書きするキーと連結するキーで、動作が変わります。 止める方法は2つあります。interrupt_beforeは、ノードに入る前に止まり、ノードの中のinterrupt()は、呼んだその場所で止まります。後者は、再開するときにそのノードを最初からもう一度動かします。前に置いた副作用が2回起きるという意味です。このラボで、何回入ってくるかを自分で数えます。 採点ツールは、書かれた説明を信用しません。書かれたモジュールを実際に読み込んで、任意の金額と理由で動かしてみて、止まった場所と続きを動かした結果を、採点ツールが別に計算した値と突き合わせます。

ステップ

  1. /root/work/aghitl/approve.pyにAUTO_LIMIT = 100000・RISKY・needs_approval(amount, reason)・State・3つのノード(intake・auto_settle・settle)・triage・build_graph()を作成してください。まだ止まりません。
  2. build_forgotten()とrun_forgotten(amount, reason)を追加してください。チェックポインターなしでinterrupt_before=["settle"]だけを渡したグラフを動かして、何が起きるかを書きます。
  3. SAVER = MemorySaver()・build_pausing()・thread(name)・start_case(amount, reason, name)を追加して、本当に止まるようにしてください。start_caseは{"next": ["settle"], "outcome": "", "paid": -1}を返します。
  4. resume_case(name)を追加して、invoke(None, config)で続きを動かすようにしてください。
  5. resume_case(name, amount=..., decision=...)で、人が金額を直すか拒否したあとで、続きを動かすようにしてください。拒否なら、支払いはまったく行われません。
  6. confirmノードとbuild_asking()・ask_and_answer(amount, reason, answer, name)を追加してください。ノードの中でinterrupt()で止まり、Command(resume=답)(プレースホルダーは答えです)で続きを動かします。ノードに何回入ってきたかをSIDE_EFFECTSで数えます。
  7. handle(...)を追加して、1件を最後まで処理し、要求値と実行値、承認の有無を1行で残すようにしてください。
  8. 記録を残してください(保存先: /root/work/aghitl/approve_report.json、/root/work/aghitl/approve_report.md)。

参考

人が見るべき作業を分ける

/root/work/aghitl/approve.pyにAUTO_LIMIT = 100000・RISKY・needs_approval・State・3つのノード・triage・build_graph()を作成してください。少額で単純な理由ならauto_settle、そうでなければsettleに進みます。まだ止まりません。

基準をneeds_approvalの1か所に書いておけば、方針を変えることが、その関数を直すことになります。triageはブランチ名(auto・hold)を返し、どのノードへ行くかは、パスマップが決めます。ノード名は、状態のキーと違う名前にしてください。

チェックポインターを忘れると黙って消える

build_forgotten()とrun_forgotten(amount, reason)を追加してください。チェックポインターなしでinterrupt_before=["settle"]だけを渡したグラフを動かし、例外が出たか・どこまで進んだか・続きを動かせるかを、{"raised": ..., "trace": [...], "outcome": ..., "resumable": ...}として書いてください。

自分で動かして、見たとおりに書いてください。予想と違うはずです。その驚きが、このステップのすべてです。保存されたものがないので、invoke(None, ...)で続きを動かすこともできません。これがなぜ危険かは、ログにエラーが1つもないことにあります。

本当に止める

SAVER = MemorySaver()・build_pausing()・thread(name)・start_case(amount, reason, name)を追加してください。承認が必要な案件はsettleの前で止まり、start_caseは{"next": ["settle"], "outcome": "", "paid": -1}を返します。

3つが一緒にある必要があります。チェックポインター、thread_id、中断点です。get_state(config).nextが空でないことが、そのまま「まだ終わっていない」という意味です。少額の案件は止まらずにそのまま終わる必要があるので、中断点を置いたノードを通らないブランチがあることが重要です。

残しておいた場所から続きを動かす

resume_case(name)を追加して、invoke(None, config)で続きを動かすようにしてください。答えは{"outcome": "settled", "paid": 금액, "trace": [...]}です(プレースホルダーは金額です)。

入力の場所にNoneを渡すのが、「新しい入力はない。保存された場所から続きを実行する」という意味です。元の入力をもう一度入れると、最初からもう一度動き、そうすると足跡が2倍になるので、すぐわかります。同じthread_idを使って初めて、つながります。

承認者が金額を直すか拒否する

resume_case(name, amount=..., decision=...)が、update_stateで状態を先に直してから、続きを動かすようにしてください。decision="reject"なら、outcomeはrejectedで、paidは0です。直した痕跡は、traceにhumanとして残します。

update_stateが入れる値も、そのキーのリデューサーを通ります。amountのように上書きするキーは上書きしますが、traceのように連結するキーは積み上がります。そのため、痕跡を1行残すのにちょうどよいです。拒否も結果である必要があります。承認者が「ただ押さない」ことで拒否するようにしておくと、その案件は永遠に待ち行列に残ります。

ノードの中で止まると、そのノードは2回動く

confirmノードとbuild_asking()・ask_and_answer(amount, reason, answer, name)を追加してください。interrupt()で止まり、Command(resume=답)(プレースホルダーは答えです)で続きを動かし、SIDE_EFFECTSで、ノードに何回入ってきたかを数えます。

interrupt()が返す値が、そのまま人が出した答えです。再開すると、そのノードが最初からもう一度動くので、interrupt()の前に置いたことは2回起きます。自分で数えて確認してください。get_state(config).tasksの中のinterruptsに、止まるときに渡した値が入っています。

要求した値と出た値を一緒に残す

handle(amount, reason, name, amount_override=None, decision=None)を追加して、1件を最後まで処理し、{"approval": ..., "outcome": ..., "paid": ..., "requested": ..., "changed_by_human": ...}を返すようにしてください。

承認記録の核心は、人の名前ではなく変わった値です。要求された金額と実際に出た金額が違うなら、その事実が記録にあって初めて、あとで説明できます。承認が不要な案件は、not_requiredと書いて、止まりません。

何を見たかを記録する

/root/work/aghitl/approve_report.jsonにauto_limit・forgotten・small・large・trimmed・rejected・confirm_enteredを、/root/work/aghitl/approve_report.mdに## 어디서 멈추기로 했나 ## 체크포인터를 빠뜨리면 ## 사람이 고친 값은 어디로 들어가나 ## 노드 밖에서 멈추기와 노드 안에서 멈추기の4つの節を書いてください(4つの見出しは、順に韓国語で「どこで止めることにしたか」「チェックポインターを忘れると」「人が直した値はどこへ入るのか」「ノードの外で止まることとノードの中で止まること」を意味します)。

forgottenはrun_forgottenの答えをそのまま、small・large・trimmed・rejectedは、handleの答えからapproval・outcome・paidの3つのキーだけを入れます。confirm_enteredは{"before": 정수, "after": 정수}(プレースホルダーは整数です)で、ask_and_answerが数えた値です。でっち上げず、動かして得てください。