Skip to content

Provider 抽象与 9 家内置注册

源码版本v0.73.1

@mariozechner/pi-ai 对 provider 的定义只有三个字段:apistreamstreamSimpleregister-builtins.tsregisterApiProvider 把 9 家厂商的实现注册进表里。注册时不直接 import 具体 provider 模块,而是用 createLazyStream / createLazySimpleStream 包一层 dynamic import——首次调用才加载 SDK,模块体积和启动时间都不被拖累。

职责

  1. 定义 Provider 抽象:ApiProvider 接口要求 api 标识符 + stream / streamSimple 两个函数,见 packages/ai/src/api-registry.ts:23-27
  2. 懒加载包装:createLazyStream / createLazySimpleStream 返回一个同步 stream 函数,内部 import() 具体模块后才 forwardStream,见 packages/ai/src/providers/register-builtins.ts:159-178
  3. 9 家注册:registerBuiltInApiProviders 逐家 registerApiProvider,见 packages/ai/src/providers/register-builtins.ts:342-396
  4. 模块加载即注册:文件结尾顶层 registerBuiltInApiProviders() 调用,只要 stream.ts import 了它就触发,见 packages/ai/src/providers/register-builtins.ts:403-403
  5. 9 种 Api 类型:KnownApi 联合类型枚举 9 个字面量,见 packages/ai/src/types.ts:6-17

设计动机

为什么所有 provider 都懒加载?因为有些 SDK 重——Bedrock 拖 AWS SDK 整个 @aws-sdk/client-bedrock-runtime,OpenAI Responses 拉一堆依赖。如果顶层静态 import,即便用户只用 Anthropic,也得加载所有 SDK。createLazyStream 把首次调用变成 dynamic import,启动只解析 register-builtins.ts 本身这几十行,真正用哪家才加载哪家。

为什么 registerBuiltInApiProviders 在文件末尾顶层调用而不是导出后由调用方决定?因为 stream.ts 顶部就 import "./providers/register-builtins.js",这是副作用 import——只要有人 import 了 @mariozechner/pi-aistream,注册就发生了。调用方零配置就能用。resetApiProviders 给测试场景重置用。

关键文件

懒加载包装的核心,外层 stream 立刻返回,加载失败也走 push 转 error 事件:

typescript
// packages/ai/src/providers/register-builtins.ts:159-178
function createLazyStream<TApi extends Api, TOptions extends StreamOptions, TSimpleOptions extends SimpleStreamOptions>(
	loadModule: () => Promise<LazyProviderModule<TApi, TOptions, TSimpleOptions>>,
): StreamFunction<TApi, TOptions> {
	return (model, context, options) => {
		const outer = new AssistantMessageEventStream();
		loadModule()
			.then((module) => {
				const inner = module.stream(model, context, options);
				forwardStream(outer, inner);
			})
			.catch((error) => {
				const message = createLazyLoadErrorMessage(model, error);
				outer.push({ type: "error", reason: "error", error: message });
				outer.end(message);
			});
		return outer;
	};
}

9 家注册就是逐个 registerApiProvider,每家一个 api 字面量:

typescript
// packages/ai/src/providers/register-builtins.ts:343-347
	registerApiProvider({
		api: "anthropic-messages",
		stream: streamAnthropic,
		streamSimple: streamSimpleAnthropic,
	});

数据流

provider 注册与首次调用两段:

边界与失败

小结

Provider 抽象 = api + stream + streamSimple 三件套,9 家内置全部走 createLazyStream 懒加载。门面派发逻辑看 stream/complete 门面,注册表数据结构看 Provider 注册表,具体某家的 SSE 解析看 Anthropic SSE 实现