@entrypoint / @task:関数型 API
役割
@entrypoint と @task は LangGraph 関数型 API (FunctionalAPI) の2つの基礎ブロックで、libs/langgraph/langgraph/func/__init__.py で定義されます。@entrypoint は通常の Python 関数(同期・非同期どちらも)を Pregel インスタンスに包みます。戻り値はコンパイル済みのグラフで、そのまま .invoke / .astream できます(entrypoint.__call__:516)。@task は関数を _TaskFunction に包み(_TaskFunction:59)、呼び出すと SyncAsyncFuture を返します。これは @entrypoint または StateGraph のノード内からしか呼べません。
これは StateGraph の上に位置する別の上位入口で、StateGraph の構築や add_node / add_edge / compile は不要で、関数を書くだけで済みます。ただし裏側は相変わらず Pregel です。entrypoint.__call__ は最終的に return Pregel(...) し、entrypoint 関数を単一ノードグラフに包みます(func.__name__ が唯一のノード名で、トリガーは START)。ノード内で entrypoint 関数本体を実行し、関数が返るときに戻り値を END チャネルに書き込みます(Pregel construction:572)。
設計動機
- ボイラープレートを1層減らす:
StateGraphは状態スキーマ、ノード関数、エッジ、条件エッジの明示的な宣言を要求します。@entrypointなら関数を1つ書くだけで、関数本体の task 呼び出しシーケンスがそのままグラフ構造になります。シンプルなワークフロー(数ステップの呼び出し、いくつかのツールを繋ぐ程度)なら、関数型 API のほうがはるかに軽く書けます。 - Pregel の全能力を保持:関数型 API は機能削減版ではなく、裏側は
Pregelです。なのでcheckpointer、store、cache、retry_policy、context_schema、interrupt()、Command(resume=...)はすべて使えます。entrypoint 関数内でinterrupt()を呼んでも、StateGraphノード内でinterrupt()を呼ぶのと同じ機構を通ります。 entrypoint.finalで戻り値と永続化値を分離:対話型ワークフローでは「今回は呼び出し元に X を返すが、checkpoint に保存して次回使うのは Y とする」(previousパラメータが前回の save を読む)というニーズがよくあります。entrypoint.final(value=X, save=Y)(entrypoint.final:477)はこの2つを明示的に分けます。dictの取り決めで代用しません。previousパラメータモデル:entrypoint 関数のシグネチャには*, previous: Any = Noneを付けられます。同一thread_idで次回呼ぶと、前回 save した値が渡されます。状態スキーマや reducer を自前で宣言せずに「呼び出し間で保持される記憶変数」相当になります。- タスク呼び出し即ちグラフのエッジ:entrypoint 関数本体内で
task_a(...)が future を返し、.result()/awaitで結果を取る。この呼び出し関係が自然に1本のエッジになります。Pregel はこれを PUSH タスクとしてTASKSチャネルに積み、次ステップのprepare_next_tasksが自然にスケジュールします。add_edgeは不要です。
主要ファイル
_TaskFunction:59—@taskの装飾結果。func/retry_policy/cache_policy/timeoutを保持。_TaskFunction.__call__:86— task 呼び出し時に_call_with_optionsに進み、config からCONFIG_KEY_CALLコールバックを取り出して、タスクを現在の runner に渡す。task decorator:110—@taskデコレータ入口。@task/@task(retry_policy=...)の2形式をサポート。entrypoint class:262—@entrypointデコレータクラス。finalはクラスにぶら下がるネストされた dataclass。entrypoint.__init__:437—checkpointer/store/cache/retry_policy/timeout/context_schemaを受け取りselfに格納。entrypoint.final:477—final(value=R, save=S)データクラス。呼び出し元にはvalueを返し、checkpoint にはsaveを保存。entrypoint.__call__:516— 関数をPregelインスタンスに変換。変換ロジックの本体はここ。Pregel construction:572—Pregel(nodes={func.__name__: PregelNode(bound=bound, triggers=[START], ...)})を構築。channels はSTART/END/PREVIOUSの3つのLastValue。get_runnable_for_entrypoint:175— entrypoint 関数をRunnableCallableに包む。同期版はrun_in_executor経由。get_runnable_for_task:200— task 関数をRunnableSeq(run, ChannelWrite([RETURN]))に包む。task の戻り値をRETURNチャネルに書き込む。
データフロー
@entrypoint が関数を Pregel に変換する中核は、3つのチャネルだけを持つ極小グラフの構築です:
graph: Pregel[Any, ContextT, Any, Any] = Pregel(
nodes={
func.__name__: PregelNode(
bound=bound,
triggers=[START],
channels=START,
timeout=self.timeout,
writers=[
ChannelWrite(
[
ChannelWriteEntry(END, mapper=_pluck_return_value),
ChannelWriteEntry(PREVIOUS, mapper=_pluck_save_value),
]
)
],
)
},
channels={
START: EphemeralValue(input_type),
END: LastValue(output_type, END),
PREVIOUS: LastValue(save_type, PREVIOUS),
},
input_channels=START,
output_channels=END,
stream_channels=END,
stream_mode=stream_mode,
stream_eager=True,
checkpointer=self.checkpointer,
store=self.store,
cache=self.cache,
# ...
)START は EphemeralValue(毎ステップ開始時にリセット)、END と PREVIOUS は LastValue(最新のみ保持)。entrypoint 関数の戻り値は2つの mapper で分岐します。_pluck_return_value は entrypoint.final.value(または裸の値)を END に書き込み、呼び出し元に渡します。_pluck_save_value は entrypoint.final.save(または裸の値)を PREVIOUS チャネルに書き込み、次回呼び出し時の previous にします(pluck mappers:547)。stream_mode="updates" は関数型 API のデフォルト値(values ではない)で、これは entrypoint 内部に複数の task があり得るため、updates のほうが中間の進捗をよく表すからです。
task の呼び出しチェーンは次のとおりです。task_fn(arg) → _TaskFunction.__call__ → _call_with_options(_call_with_options:276) → config[CONF][CONFIG_KEY_CALL] から impl コールバックを取り出す → impl(func, args, kwargs, retry_policy=..., callbacks=..., timeout=...) が SyncAsyncFuture を返す。この impl は PregelRunner.tick / atick がタスクをスケジュールするときに partial(...) で注入したものです(CONFIG_KEY_CALL inject:211)。つまり task 自身は実行せず、関数と引数を現在の runner に渡し、runner がいつ並行実行するかを決め、完了後は future 経由で呼び出し元に結果を返します。
境界と失敗
- ジェネレータはサポート外:entrypoint 関数は
def gen/async def gen(yield付き)にできません。__call__の冒頭で明示的にraise NotImplementedErrorします(no generators:525)。ストリーム出力にはyieldではなくStreamWriter(stream_mode="custom")を使います。 - task は外部から呼べない:
@entrypoint/StateGraphノード外でtask_fn(...)を呼ぶと失敗します。get_config()[CONF][CONFIG_KEY_CALL]が存在しないからです。task は現在の runner コンテキストに依存します。 - 同期 task に timeout は設定不可:
task(timeout=...)は非同期関数にしか効きません。同期関数の場合はsync_timeout_unsupported(sync timeout unsupported:237)。同期 task は executor スレッドで動き、Python はプロセス内で安全にキャンセルできません。 previousのデフォルトは None:初回呼び出しではpreviousはNoneで、entrypoint 関数自身が「前回値なし」の分岐を処理する必要があります。entrypoint.finalを使わない場合、戻り値はENDとPREVIOUS両方に書き込まれ、次回のpreviousは前回の戻り値そのものになります。finalは正しくパラメータ化必須:戻り値型注釈-> entrypoint.final[int, str]は2つの型パラメータを両方与える必要があります。1つだけ、または省略すると__call__でTypeError(final param check:562)。- checkpointer に依存:
entrypoint(checkpointer=...)で checkpointer を設定しないと、interrupt()とpreviousの呼び出し間記憶はどちらも使えません。前者は RuntimeError を投げ、後者は毎回Noneになります。
まとめ
関数型 API は StateGraph の糖衣構文で、裏側は相変わらず Pregel の単一ノードグラフ + TASKS チャネル + runner が注入する CONFIG_KEY_CALL です。entrypoint 関数本体の task(...) 呼び出しシーケンスが暗黙のグラフ構造になります。並行処理の詳細は /func/concurrency を、中断と再開は /interrupt/interrupt を参照してください。
公式資料:LangGraph 文档 · README