Skip to content

stream/complete:LLM 呼叫的門面入口

源码版本v0.73.1

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。

職責

這一層做四件事:

  1. 派發:streammodel.api 找到註冊表裡的 provider,轉交 (model, context, options)。見 packages/ai/src/stream.ts:25-32
  2. 同步回傳流:無論底層 provider 是不是非同步初始化(懶載入),stream 都同步回傳一個 AssistantMessageEventStream,事件晚到時再 push。見 packages/ai/src/stream.ts:30-31
  3. 簡化版入口:streamSimple / completeSimple 接收 SimpleStreamOptions(只暴露 reasoningthinkingBudgets 等 UI 常用欄位),由 provider 自己把 simple options 翻譯成完整 options。見 packages/ai/src/stream.ts:43-50
  4. 缺 provider 報錯:resolveApiProvider 在沒註冊時拋 No API provider registered for api: <api>,不在呼叫點靜默失敗。見 packages/ai/src/stream.ts:17-23

設計動機

為什麼要有這麼薄的一層?因為呼叫方關心的兩件事——「拿哪個模型調」和「串流還是拿最終結果」——跟具體 provider 解耦。stream.ts 把這兩個維度收成四個函式,下游 pi-coding-agentAgentSession、CLI 工具、擴充都用同一套入口,不必直接 import 具体 provider 模組。這樣 provider 實作可以懶載入、可以替換、可以由擴充註冊新的 api,呼叫方程式碼完全不變。

complete 不另寫一套非串流邏輯,而是復用 streams.result()——串流基礎設施與一次性回傳共用一條管線,provider 只需實作串流版本。

關鍵檔案

stream 的全部實作就是 resolve + 轉交,沒有任何業務邏輯:

typescript
// 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(),不重複派發邏輯:

typescript
// 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-178stream 本身不感知懶載入。
  • 型別斷言:options as StreamOptionsProviderStreamOptions(含擴充自訂欄位)強轉到 provider 期望的型別,provider 內部按需讀欄位。
  • options 可選:三個函式都允許 options? 缺省,provider 用預設值兜底。

小結

stream.ts 是 60 行的派發門面,把 (model, context, options) 路由到註冊表裡的 provider。stream / complete 是全選項版,streamSimple / completeSimple 是 UI 友好子集版。註冊表本身的結構看 Provider 註冊表,9 家內建 provider 的註冊看 Provider 抽象與內建,最終回傳的 AssistantMessageEventStreamEventStream 非同步迭代器