Skip to content

LastValue:覆蓋式寫入的預設通道

源码版本1.2.9

職責

LastValueBaseChannel 最簡單的子類:它存一個值,每次寫就把舊值覆蓋掉(LastValue class:20-21)。它就是「state 欄位不加任何 Annotated 標註時預設用的通道」,你在 StateGraph 裡寫 class State(TypedDict): count: int,編譯期看到的 count 欄位背後就是一個 LastValue(int)(fallback LastValue:1857-1859)。

它的關鍵約束是「每步只能寫一個值」。update 拿到長度大於 1 的序列時不合併也不覆蓋,而是直接拋 InvalidUpdateError(update:56-67),提示使用者該用 Annotated[int, reducer] 表達「這一步裡多個節點同時寫要怎麼 fold」。這個約束不是技術限制,而是語意選擇:純量狀態本來就沒有合理的合併方式,與其默默丟值,不如顯式報錯。

另外這個檔案還放了一個變體 LastValueAfterFinish(LastValueAfterFinish:81-152):行為像 LastValue,但寫進去的值要等整個 Pregel 執行 finish() 之後才對外可見,見一次就清空。它用於「中斷訊號」「結束訊號」這類只在執行結尾生效的內部通道。

設計動機

  • 覆蓋是純量狀態唯一合理的預設:整數、字串、布林這些值沒有「合併」語意。count: int 這一類欄位,新值來了直接覆蓋舊值是最符合直覺的行為,所以 StateGraphfallback:1857 裡就把沒標註 reducer 的欄位編譯成 LastValue
  • 拒絕靜默丟值:如果同一超步裡兩個節點同時寫 count,LastValue 不會隨便挑一個贏,而是拋 INVALID_CONCURRENT_GRAPH_UPDATE(InvalidUpdateError:60-64)。錯誤訊息明確提示「Use an Annotated key to handle multiple values」,把使用者推向正確的解法——加一個 reducer,見 /channels/topic-binop
  • MISSING 而不是 None:初始值用 langgraph._internal._typing.MISSING(value = MISSING:29),這樣 None 是合法的存值。get()MISSING 時拋 EmptyChannelError(get:69-72)。這套和 BaseChannel 預設實作保持一致,但 is_available 在這裡被覆寫成一行 self.value is not MISSING(is_available:74-75),避免每次都走 try/except。
  • copyfrom_checkpoint 便宜:雖然父類預設 copy = from_checkpoint(checkpoint()),這裡覆寫了 copy(copy:44-48),直接共享同一個 value 引用——因為純量本來也不需要深拷貝。from_checkpoint(from_checkpoint:50-54)則按 MISSING 分支決定要不要賦值。
  • LastValueAfterFinish 的延遲可見性:有些控制訊號(比如 Command 的 goto)在寫的時候不能立刻被 prepare_next_tasks 看到,否則會在本步內自我觸發形成環。LastValueAfterFinish 用一個 finished 標誌位(finished flag:93-95),get() 只在 finished=True 時回傳(get gated:145-148),finish() 由 Pregel 在收尾時呼叫(finish:138-143),consume() 在被消費一次後清空(consume:130-136)。

關鍵檔案

資料流

LastValue.update 的全部邏輯就這幾行(update:56-67):

python
def update(self, values: Sequence[Value]) -> bool:
    if len(values) == 0:
        return False
    if len(values) != 1:
        msg = create_error_message(
            message=f"At key '{self.key}': Can receive only one value per step. Use an Annotated key to handle multiple values.",
            error_code=ErrorCode.INVALID_CONCURRENT_GRAPH_UPDATE,
        )
        raise InvalidUpdateError(msg)

    self.value = values[-1]
    return True

注意第三分支寫的是 values[-1] 而不是 values[0]——因為前面已經判了 len != 1,這一行只會走到 len == 1 的情況,[-1][0] 等價。空序列回傳 False 是關鍵:它配合 apply_writes 裡對未變更通道的 update(EMPTY_SEQ) 呼叫(EMPTY_SEQ update:329),讓 LastValue 在被空呼叫時正確回傳「未改動」。

StateGraph 編譯時怎麼把沒標註的欄位映射成 LastValue,這段在 field_to_channel:1840-1859:

python
if manager := _is_field_managed_value(name, annotation):
    if allow_managed:
        return manager
    else:
        raise ValueError(f"This {annotation} not allowed in this position")
elif channel := _is_field_channel(annotation):
    channel.key = name
    return channel
elif channel := _is_field_binop(annotation):
    channel.key = name
    return channel

fallback: LastValue = LastValue(annotation)
fallback.key = name
return fallback

按順序嘗試:managed value(如 MessagesState 裡的特殊管理)→ 顯式 BaseChannel 實例 → Annotated[..., reducer](BinaryOperatorAggregate,見 /channels/topic-binop)→ 都不命中,退回 LastValue

整個讀寫時序如下:

邊界與失敗

  • 多值並行寫直接報錯:multi-value error:59-64InvalidUpdateError,錯誤碼 INVALID_CONCURRENT_GRAPH_UPDATE。這是初學者最常見的坑:兩個節點同時往同一個純量欄位寫。修法是改成 Annotated[int, lambda a, b: a + b] 這種帶 reducer 的欄位。
  • 空寫回傳 False:empty seq:57-58 回傳 False,和 BaseChannel 契約一致,apply_writes 據此跳過 channel_versions 更新。
  • __eq__ 不看值只看型別:__eq__:31-32 回傳 isinstance(value, LastValue),意味著兩個 LastValue 實例無論存什麼都被認為相等。這是給圖編譯期去重用的,不要拿它做值比較。
  • MISSINGNone:None 是合法存值;只有 MISSING 才表示「從未寫過」(get / is_available:70-75)。如果你的業務欄位允許 None,放心寫,通道不會把它當空。
  • LastValueAfterFinish 的新寫會撤掉可見性:LAF.update:122-128 一開頭 self.finished = False。也就是說如果 finish() 之後又來一個寫,通道會重新進入「未公開」狀態,要等下一次 finish() 才能讀。
  • LastValueAfterFinish.consumefinished 為假時是 no-op:見 LAF.consume:130-136。這意味著「寫完但還沒 finish」的值不會被清掉,只有在被消費過一次後才清空——這正是它「結束訊號」的語意所需。

小結

LastValue 是 LangGraph 狀態的預設形狀——存一個純量,覆蓋式更新,每步單值約束在寫多值時報錯而不是默默丟值。理解它就理解了所有沒標註 reducer 的欄位在執行時是怎麼變化的。要看並行寫怎麼正確合併,繼續讀 /channels/topic-binop;通道介面的定義回 /channels/base-channel;具體什麼時候被 update/pregel/algo

對照官方資料:LangGraph 文件 · README