stream/complete:LLM 呼叫的門面入口
stream.ts 是 @mariozechner/pi-ai 對外的最薄一層——四個函式 stream / complete / streamSimple / completeSimple,加上一個內部 resolveApiProvider。它不做 SSE 解析、不建構請求體、不管重試,只負責把 (model, context, options) 三元組按 model.api 路由到對應 provider。可以把它理解成「呼叫方與 provider 實作之間的派發所」:呼叫方只看到統一的回傳型別 AssistantMessageEventStream,看不到底下是 Anthropic SSE 還是 OpenAI Responses 還是 Bedrock Converse。
職責
這一層做四件事:
- 派發:
stream按model.api找到註冊表裡的 provider,轉交(model, context, options)。見packages/ai/src/stream.ts:25-32。 - 同步回傳流:無論底層 provider 是不是非同步初始化(懶載入),
stream都同步回傳一個AssistantMessageEventStream,事件晚到時再 push。見packages/ai/src/stream.ts:30-31。 - 簡化版入口:
streamSimple/completeSimple接收SimpleStreamOptions(只暴露reasoning、thinkingBudgets等 UI 常用欄位),由 provider 自己把 simple options 翻譯成完整 options。見packages/ai/src/stream.ts:43-50。 - 缺 provider 報錯:
resolveApiProvider在沒註冊時拋No API provider registered for api: <api>,不在呼叫點靜默失敗。見packages/ai/src/stream.ts:17-23。
設計動機
為什麼要有這麼薄的一層?因為呼叫方關心的兩件事——「拿哪個模型調」和「串流還是拿最終結果」——跟具體 provider 解耦。stream.ts 把這兩個維度收成四個函式,下游 pi-coding-agent 的 AgentSession、CLI 工具、擴充都用同一套入口,不必直接 import 具体 provider 模組。這樣 provider 實作可以懶載入、可以替換、可以由擴充註冊新的 api,呼叫方程式碼完全不變。
complete 不另寫一套非串流邏輯,而是復用 stream 再 s.result()——串流基礎設施與一次性回傳共用一條管線,provider 只需實作串流版本。
關鍵檔案
packages/ai/src/stream.ts:1-3— 頂部import "./providers/register-builtins.js"觸發 9 家內建 provider 自註冊;getApiProvider從註冊表取。packages/ai/src/stream.ts:17-23—resolveApiProvider,缺 provider 時拋錯。packages/ai/src/stream.ts:25-32—stream全選項版。packages/ai/src/stream.ts:34-41—complete等價於stream(...).result()。packages/ai/src/stream.ts:43-50—streamSimple走provider.streamSimple。packages/ai/src/stream.ts:52-59—completeSimple等價於streamSimple(...).result()。
stream 的全部實作就是 resolve + 轉交,沒有任何業務邏輯:
// packages/ai/src/stream.ts:25-32
export function stream<TApi extends Api>(
model: Model<TApi>,
context: Context,
options?: ProviderStreamOptions,
): AssistantMessageEventStream {
const provider = resolveApiProvider(model.api);
return provider.stream(model, context, options as StreamOptions);
}complete 復用 stream 再取 result(),不重複派發邏輯:
// packages/ai/src/stream.ts:34-41
export async function complete<TApi extends Api>(
model: Model<TApi>,
context: Context,
options?: ProviderStreamOptions,
): Promise<AssistantMessage> {
const s = stream(model, context, options);
return s.result();
}資料流
stream(model, ctx, opts) 的派發鏈:
邊界與失敗
- Provider 缺失:
resolveApiProvider直接拋錯,不回傳空流,見packages/ai/src/stream.ts:19-21。擴充動態註冊的 provider 沒註冊時,呼叫方第一時間知道。 - 懶載入 provider:具體 provider 模組用
createLazyStream包過,stream同步回傳 outer stream,模組載入完後再forwardStream到 inner,見packages/ai/src/providers/register-builtins.ts:159-178。stream本身不感知懶載入。 - 型別斷言:
options as StreamOptions把ProviderStreamOptions(含擴充自訂欄位)強轉到 provider 期望的型別,provider 內部按需讀欄位。 options可選:三個函式都允許options?缺省,provider 用預設值兜底。
小結
stream.ts 是 60 行的派發門面,把 (model, context, options) 路由到註冊表裡的 provider。stream / complete 是全選項版,streamSimple / completeSimple 是 UI 友好子集版。註冊表本身的結構看 Provider 註冊表,9 家內建 provider 的註冊看 Provider 抽象與內建,最終回傳的 AssistantMessageEventStream 看 EventStream 非同步迭代器。