跳到內容

語音 session 預留 (substrate)

是 substrate,不是 adapter。 D41 session 預留是 LiveKit 與 Pipecat 語音 adapter 都必須使用的共用 budget 生命週期。這一頁不主張我們有 LiveKit 或 Pipecat 的 adapter 覆蓋率;它只是把這些 adapter 所相依的預留 contract 寫清楚。

即時語音 agent 會把一個 session 一直開著,而花費會跨 STT、LLM 回合、tool 呼叫、串流 回應、TTS 輸出一路累積上去。用 request 範圍的 reserve 沒辦法乾淨地描述這種生命週期。 D41 補上一個 session 範圍的 ledger hold,這樣語音 adapter 就可以在付費 session 開始前 先 reserve 一個額度信封,session 進行中把正值的串流 delta commit 進去,等 session 結束 或到期時再把剩下的額度 release 掉。

voice room connect
-> ReserveSession # hold session budget before paid provider dispatch
-> CommitSessionDelta # positive idempotent streaming deltas
-> CommitSessionDelta # reconnect replays by streaming_commit_id
-> ReleaseSession # release uncommitted remainder
-> SessionExpire # TTL safety net for crashed sessions

| 路徑 | 狀態 | |---|---| | Session 開始 | ReserveSession 會在語音 adapter 接上付費 model 流量之前,先建立一筆 ledger hold。 | | 串流花費 | CommitSessionDelta 把正值且 idempotent 的 delta 套用到這筆 hold 上。 | | 重連 / replay | 一模一樣的 replay 會回傳原本的 outcome;同一把 key 但 payload 不同就會 conflict。 | | 超量 | commit delta 超過 hold 的額度會被 deny,而且不會去動到已 commit 或已 release 的餘額。 | | 結束 / crash | ReleaseSession 把剩餘額度 release 掉;如果 adapter 不見了,TTL 到期也會把剩餘額度 release 掉。 |

這筆 hold 不是信用額度。每一次 commit 都會減少 hold 的剩餘額度,同時增加已 commit 的 花費。release 或到期只會把還沒 commit 的剩餘額度結算回可用 budget。

這套 substrate 釘在現有的 sidecar adapter service 裡:

rpc ReserveSession(ReserveSessionRequest) returns (ReserveSessionOutcome)
rpc CommitSessionDelta(CommitSessionDeltaRequest) returns (CommitSessionDeltaOutcome)
rpc ReleaseSession(ReleaseSessionRequest) returns (ReleaseSessionOutcome)

ReserveSessionRequest 要帶上現行 SpendGuard substrate 在 reserve 時的那組標準 tuple:tenant_idbudget_idwindow_instance_idunitpricingsession_idrouteestimated_amount_atomicttl_seconds,以及 idempotency_key

凡是用到這套 substrate 的新 adapter,從第一天起就要把 unitIdwindowInstanceId 跟那組 pricing freeze tuple 串進去。ledger commit 會把 tuple 漂移的情況擋下來。

目前的收尾狀態:sidecar 那幾個 UDS method 是 compile-safe、fail-closed 的 UNIMPLEMENTED stub。本機的 D41 demo 直接驗證 ledger 的 session 生命週期跟 canonical audit 路徑。在 LiveKit 或 Pipecat adapter 真的去呼叫這些 session RPC 之前,必須先把 sidecar 到 ledger 的橋接 slice COV_D41S_06_sidecar_session_bridge 做完上線。

Session 生命週期事件會以簽章過的 audit decision/outcome 配對寫入,並透過既有的 audit 路徑做 canonicalize。事件家族如下:

| 事件 | 何時送出 | |---|---| | spendguard.audit.session.reserve | session hold 被接受。 | | spendguard.audit.session.commit_delta | 串流 delta 被 commit。 | | spendguard.audit.session.release | 剩餘額度被 release。 | | spendguard.audit.session.expired | TTL sweep 把剩餘額度 release 掉。 | | spendguard.audit.session.denied | session reserve 或超量被 deny。 |

Reserve DENY 會把完整的 typed outcome 回給 caller,連同當下的可用額度一起;而簽章過的 audit outcome 則保留具決定性的拒絕事實,這些事實在 replay 時是穩定不變的。

Terminal window
make -C deploy/demo demo-down
make -C deploy/demo demo-up DEMO_MODE=session_reservation
make -C deploy/demo demo-verify-session-reservation

成功時 runner 會印出這一行:

[demo] session_reservation ALL 7 steps PASS

這個 demo 用的是真的 Postgres ledger 跟 canonical 資料庫。它涵蓋了一個主要的 reserve、 兩次正值 commit、idempotent replay、conflict 偵測、超量 deny、release、一個真實的 budget 不足 reserve DENY(含 replay/conflict 檢查),以及第二筆會走 TTL 到期的預留。 hard gate 會斷言剛好有八組簽章過的 decision/outcome 配對,範圍鎖在那三個 demo session reservation ID 上。

LiveKit 跟 Pipecat 的 adapter 開發,應該去參考鎖定好的 D41 substrate 設計跟 sidecar 橋接 spec,不要自己另外發明一套本機的語音生命週期。我們預期 adapter 程式碼要做的是:把 框架的 session/room ID 轉成 session_id、在付費 provider 工作開始前先 reserve、用穩定的 streaming_commit_id 值去 commit 正值的串流 delta,並在 session 結束時 release,至於 crash 復原就靠 TTL 兜底。

在 sidecar 橋接 demo 證明 UDS 路徑可行之前,runtime adapter 的工作都先卡住:

Terminal window
make -C deploy/demo demo-up DEMO_MODE=session_bridge
make -C deploy/demo demo-verify-session-bridge