Provider 抽象与 9 家内置注册
@mariozechner/pi-ai 对 provider 的定义只有三个字段:api、stream、streamSimple。register-builtins.ts 用 registerApiProvider 把 9 家厂商的实现注册进表里。注册时不直接 import 具体 provider 模块,而是用 createLazyStream / createLazySimpleStream 包一层 dynamic import——首次调用才加载 SDK,模块体积和启动时间都不被拖累。
职责
- 定义 Provider 抽象:
ApiProvider接口要求api标识符 +stream/streamSimple两个函数,见packages/ai/src/api-registry.ts:23-27。 - 懒加载包装:
createLazyStream/createLazySimpleStream返回一个同步 stream 函数,内部import()具体模块后才forwardStream,见packages/ai/src/providers/register-builtins.ts:159-178。 - 9 家注册:
registerBuiltInApiProviders逐家registerApiProvider,见packages/ai/src/providers/register-builtins.ts:342-396。 - 模块加载即注册:文件结尾顶层
registerBuiltInApiProviders()调用,只要stream.tsimport 了它就触发,见packages/ai/src/providers/register-builtins.ts:403-403。 - 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-ai 的 stream,注册就发生了。调用方零配置就能用。resetApiProviders 给测试场景重置用。
关键文件
packages/ai/src/types.ts:6-17—KnownApi9 个字面量 +Api允许扩展字符串。packages/ai/src/api-registry.ts:23-27—ApiProvider接口,三件套。packages/ai/src/providers/register-builtins.ts:159-178—createLazyStream:外层 stream + 动态加载 + forwardStream + 错误 push。packages/ai/src/providers/register-builtins.ts:180-220—createLazySimpleStream,simple 版本同模式。packages/ai/src/providers/register-builtins.ts:323-340— 9 家 lazy 函数 + Bedrock 私有 lazy。packages/ai/src/providers/register-builtins.ts:342-396—registerBuiltInApiProviders主体。packages/ai/src/providers/register-builtins.ts:398-403—resetApiProviders和顶层注册。packages/ai/src/types.ts:155-159—StreamFunction签名,返回AssistantMessageEventStream。
懒加载包装的核心,外层 stream 立刻返回,加载失败也走 push 转 error 事件:
// 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 字面量:
// packages/ai/src/providers/register-builtins.ts:343-347
registerApiProvider({
api: "anthropic-messages",
stream: streamAnthropic,
streamSimple: streamSimpleAnthropic,
});数据流
provider 注册与首次调用两段:
边界与失败
- 懒加载失败:
loadModule().catch把 error 包成{ type: "error" }事件 push 进 outer stream,调用方for await能收到,见packages/ai/src/providers/register-builtins.ts:170-174。 - 9 家清单:
anthropic-messages、openai-completions、mistral-conversations、openai-responses、azure-openai-responses、openai-codex-responses、google-generative-ai、google-vertex、bedrock-converse-stream,见packages/ai/src/providers/register-builtins.ts:343-395。 - Bedrock 单独处理:由于 AWS SDK 重量,
streamBedrockLazy/streamSimpleBedrockLazy没 export,只内部用,见packages/ai/src/providers/register-builtins.ts:339-340。 - 扩展注册:第三方扩展可以调
registerApiProvider注册自己的api字符串,Api类型用string & {}兜底,见packages/ai/src/types.ts:17-17。
小结
Provider 抽象 = api + stream + streamSimple 三件套,9 家内置全部走 createLazyStream 懒加载。门面派发逻辑看 stream/complete 门面,注册表数据结构看 Provider 注册表,具体某家的 SSE 解析看 Anthropic SSE 实现。