ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Activepieces AI Providers 深度指南:多 Key 管理、托管计费与模型目录的源码级解析

Activepieces AI Providers 深度指南:多 Key 管理、托管计费与模型目录的源码级解析 Activepieces AI Providers 深度指南多 Key 管理、托管计费与模型目录的源码级解析【免费下载链接】activepiecesAI Agents MCPs AI Workflow Automation • (~400 MCP servers for AI agents) • AI Automation / AI Agent with MCPs • AI Workflows AI Agents • MCPs for AI Agents项目地址: https://gitcode.com/GitHub_Trending/ac/activepiecesActivepieces 的 AI Providers 模块让平台管理员为一套或任意多套 LLM 后端统一配置密钥供流程中的 AI Pieces 与 Agent 使用它还自动托管一个基于 OpenRouter 的 Activepieces Provider其信用余额与自动充值由 Autumn 计费系统计量。阅读本文后你将掌握 AIProvider 的数据模型与全部 REST 接口、托管 Provider 的信用门禁与自愈机制、模型目录的发布链路与缓存层级以及新增一个 AI Provider 必须改动的六个位置和最容易踩中的工程陷阱。一、AI Providers 是什么平台级 LLM 密钥管理AI Providers 是 Activepieces 中面向平台管理员platform admin的密钥管理能力管理员在平台级别配置一个或多个 LLM 后端流程里的 AI Pieces、Agent 与 Chat 能力通过统一的解析机制按项目取用对应的密钥。该特性为EE/Cloud 专属在 CECommunity Edition中不注册入口模块未挂载。后台实现在 packages/server/api/src/app/ai/共享 zod schema 位于 packages/core/shared/src/lib/management/ai-providers/index.ts。1.1 核心实体 AIProviderAIProvider 是平台作用域platform-scoped的数据行由 ai-provider-entity.ts 定义的ai_provider表承载关键字段如下字段类型说明displayNamestring密钥显示名同一 provider 下不可重复assertDisplayNameIsFree校验platformIdstring所属平台与provider组成普通联合索引非唯一——2026-08 多 Key 改造后一个平台可持有多把同 provider 的密钥providerAIProviderName 枚举见下文 16 个取值authEncryptedObject凭据落库时经 AES-256 加密encryptUtils.encryptObject每次写入使用随机 IVconfigJSON供应商特有配置如 Azure 的resourceName、Bedrock 的regionenabledForChatboolean是否作为 Chat 后备 ProvidermodelScopemodelIds[]all|selected模型级白名单selected时仅放行modelIds中的模型projectScopeprojectIds[]all|selected|except项目级作用域projectIds带 GIN 索引status系列string密钥健康状态active等与原因、时间戳、乐观锁版本1.2 支持的 Provider16 个枚举值AIProviderName枚举定义在 packages/core/utils/src/lib/permission.ts共 16 个取值10 个一等公民openai、anthropic、googleGemini Developer API、azure、openrouter、bedrock、mistral、cloudflare-gateway、customOpenAI 兼容可接 Ollama/LM Studio 等、activepieces由系统自动托管、底层走 OpenRouter。6 个 OpenAI 兼容厂商xai、deepseek、zai、qwen、minimax、moonshot——它们不各自建文件而是共享一个openAiCompatibleVendor({ name, provider })策略工厂openai-compatible-vendor.ts默认 Base URL 集中在OPENAI_COMPATIBLE_VENDOR_BASE_URLS且支持按密钥覆盖baseUrl其中四家分设中国与国际端点。二、REST API 与请求安全模型控制器 ai-provider-controller.ts 以/v1/ai-providers为前缀注册模块入口aiProviderModule在 packages/server/api/src/app/app.ts 中紧跟在aiProviderService(app.log).setup()之后挂载路由权限行为GET /USER / ENGINEproject 级QUERY列出项目可见的 Provider每 provider 去重为一条会触发托管行自动创建GET /configsplatformAdminOnly管理员视角列出全部配置含敏感字段之外的所有字段GET /configs/:id/modelsplatformAdminOnly返回指定配置的未过滤模型列表管理员据此挑选白名单GET /:provider/configengine only返回解密后的 auth/config含modelScope/modelIdsACTIVEPIECES 走此路由时先过信用门禁GET /:provider/modelsUSER / ENGINEproject 级返回模型列表带 24h 缓存应用该密钥的modelScope白名单POST /platformAdminOnly创建先校验凭据再落库POST /:id/recheckplatformAdminOnly触发密钥健康复查POST /:idplatformAdminOnly更新改enabledForChat时事务内先全局关掉其他行的 chat 开关DELETE /:idplatformAdminOnly删除托管行删除后会在下次 list 时自愈重建2.1 引擎侧的逐次调用与信用门禁引擎engine在执行每一次AI action 时都会调用GET /v1/ai-providers/{provider}/config获取凭据不做 per-run 缓存由引擎令牌鉴权。对托管 ACTIVEPIECES Provider该路由同时也是信用门禁控制器在返回配置前调用assertCreditsAndAppSumoNotExceededplatform/billing-provider.ts当 Autumn 信用余额或 AppSumo 余额被冻结时抛出QUOTA_EXCEEDED。注意该检查在每次 AI 调用前触发但用量只在运行结束后才计量上报因此进行中的消费对门禁不可见——这正是决策 000016-managed-ai-metering-moves-to-centralized-worker-execution.md 记录的场景。三、托管 ACTIVEPIECES Provider 与 Autumn 信用体系3.1 自动开通与 OpenRouter 密钥铸造当平台启用了aiCreditsEnabled等价于设置了OPENROUTER_PROVISION_KEY环境变量见 flag.service.ts时GET /v1/ai-providers或listConfigs都会先经过listVisibleRows内部调用ensureManagedProviderRow自动补建 ACTIVEPIECES 托管行。当引擎请求该 Provider 的配置时getConfigOrThrow→decryptRowAuth发现托管行没有apiKey便调用enrichWithKeysIfNeeded()通过 openrouter-api.ts 的openRouterApi.createKey()向 OpenRouter 铸造一把新密钥并加密保存。没有系统定时任务密钥的续期与充值由 Autumn 驱动autoTopUps而非 Stripe。3.2 信用费率与消费护栏费率1000 credits $1 USDOpenRouter 按 API key 计量用量用量数据缓存 180s。硬性消费护栏新铸造的托管密钥带$500/月的支出上限MANAGED_OPENROUTER_KEY_MONTHLY_LIMIT_USD 500、limit_reset: monthly见 ai-provider-service.ts 与enrichWithKeysIfNeeded的调用——这是独立于 Autumn 信用余额的失控成本天花板。2026-07 变更之前铸造的旧密钥携带的是 $1000/月的额度来自一次性的 OpenRouter 回填。没有月度信用重置任务、没有直接的 Stripe 开票唯一月度机制就是密钥自身的limit_reset。模型列表缓存modelsCache存在内存中由cron.schedule(0 0 * * *)每天零点清空setup()中注册。3.3 托管行的单例约束与自愈托管 ACTIVEPIECES 行的单例由数据库而非代码保证代码层面create()会直接拒绝aiProvider.activepiecesIsManaged但这只覆盖管理员路由行还会在每次 list 时被listVisibleRows自动补建两个并发GET /v1/ai-providers可能同时漏过existsBy检查因此实体上保留了部分唯一索引idx_ai_provider_platform_id_managed对(platformId)唯一、WHERE provider activepieces插入语句带ON CONFLICT DO NOTHINGorIgnore()竞争失败方成为 no-op 而非 500。相比分布式锁这个方案对任何不拿锁的写入者同样成立见 ai-provider-entity.ts 与ensureManagedProviderRow。对托管行的更新被提前返回拦截只允许把enabledForChat置为 true删除则被允许但会在下次 list 时自愈重建。四、Provider 可见性隐藏语义isActivepiecesAiProviderHidden在以下任一条件成立时隐藏托管 Providerai-provider-service.tsaiCreditsEnabled关闭即未设置OPENROUTER_PROVISION_KEY——典型自托管形态shouldHideActivepiecesAiProvider返回 true仅由plan.embeddingEnabled决定。隐藏意味着处处视为不存在listProviders()省略该行getChatProvider()/getChatProviderName()返回 nullfindAvailableChatProviderRow的过滤逻辑。这能防止一个过期的enabledForChat例如来自 0.82.1 迁移的残留把 Chat 钉死在一个没有充值路径、必然 402 的 Provider 上GIT-1620。五、模型目录Model Catalog从 CDN 到下拉框的完整链路5.1 生成与发布每个模型在目录中携带元数据上下文窗口、最大输出、发布日期、每百万 token 的输入/输出价格、是否支持工具调用/推理/视觉数据源自 models.devMIT 许可由npm run sync-model-catalog生成再由每周一次的工作流发布到https://cdn.activepieces.com/ai/model-catalog.json——仓库不提交任何目录内容也没有任何进程 import 它。5.2 唯一访问器与缓存策略modelCatalog.lookup({ provider, modelId })是唯一的访问入口packages/server/utils/src/model-catalog.ts其行为异步首次调用拉取对象成功后缓存24hCATALOG_TTL_MS并发去重多个并发调用者共享同一个 in-flight Promise失败退避拉取失败后 5 分钟内不再重试FAILURE_BACKOFF_MSCDN 故障不会拖慢 models 接口别名映射activepieces在查询时别名到openrouterCATALOG_SOURCE_PROVIDERBedrock 的 inference-profile id 会剥离us./eu./apac./global.前缀再查保留:N版本后缀。元数据只做一次富化在fetchModels的modelsCache重映射阶段统一挂载因此 web 端的模型选择器、AI piece 的下拉框和ap_list_ai_models拿到的都是同一份富化结果。5.3 价格三位小数舍入生成器把价格统一舍入到三位小数既消除浮点噪声0.049999999999999996也因为少数 OpenRouter 模型如deepseek/deepseek-v4-flash带连续浮动的五位小数价格。三位小数低于 UI 渲染精度且不丢失任何真实价格集合中最便宜的是 0.01。5.4 目录相关的四个关键认知目录变更到达下拉框最长约 2 天——CDN 边缘max-age36001h→ 服务端内存目录24h→modelsCache零点 cron 清空三级串联。重启 API 可跳过两个进程内层。我重新发布了但 UI 还是旧价格是预期行为而非 bug。边缘 TTL 故意用 1h 而非publish-embed-sdk.yml的 604800那条路径带版本戳而目录是原地覆写稳定 key一周的边缘缓存会把过期价格钉住一周。发布是对单个 S3 key 的覆写只发生在周一 cron 或手动workflow_dispatch从不随 merge/deploy/release 触发无版本、无历史last write wins。判断线上目录新旧只能靠curl -s https://cdn.activepieces.com/ai/model-catalog.json | jq .generatedAt。无法访问 CDN 永久且静默地没有模型元数据离线安装、出站白名单网络、CDN 故障都会静默回退到裸{ id, name }行且没有任何提示——所有元数据字段都是可选的不会抛错。AP_MODEL_CATALOG_URL指向自建镜像地址是唯一解法。目录对象必须先于读取它的代码发布仓库不内置副本在workflow_dispatch把目录送上 CDN 之前所有安装含本地开发都看不到任何元数据。六、多 Key 与作用域解析确定性的路由规则6.1 同一 Provider 多把密钥的排序当一个项目对某 Provider 有多把合格密钥时resolveEligibleRow按确定性规则挑选ai-provider-service.ts先按projectScope特异性selectedexceptall平局时取created最新的一把。不存在 priority/default 字段见决策 providers-redesign-before-routing.md。该排序只是回退step 或 agent 也可以直接钉死某把密钥决策 000030-a-step-may-pin-an-ai-provider-key-and-omitting-one-can-only-narrow.md此时resolveRowForScope先校验该密钥对调用方项目合格再直接返回它。ACTIVEPIECES 永远是单例create()拒绝。6.2 ProviderScope 是必填参数所有解析器都要求显式传入scope: { type: project, projectId } | { type: platform }不存在没有项目的默认值决策 000027-ai-provider-resolution-takes-a-required-scope-and-splits-reads-by-trust-level.md。getConfigOrThrow/getChatProvider/getChatProviderName/listModels全部如此。早期版本把projectId设为可选、缺失即视为所有密钥合格结果 agent piece/knowledge-base 工具处理器、chat 模型选择器、configId模型查询各自静默绕过了项目作用域改为必填后遗漏变成了编译错误{ type: platform }成为一个可审查的声明而非意外。全平台范围合法的消费者只有三个tool-search 向量化器、chat 记忆抽取、托管 ACTIVEPIECES 单例。6.3 最后一个 fail-open 位置在 scope 构造器里Chat 先解析回合所在的项目selectRunProject该项目可空——用户若已看不到任何项目会得到null。早期实现把null转成{ type: platform }等于把平台全部密钥交给该回合。现在agentHelpers.runScopeOrThrow直接拒绝该回合只想要 provider 名称的 analytics/billing 路径则自己接收可空 projectIdresolveChatProviderName对无 provider 的会话报告无 provider。经验法则喂给 scope 构造器的可空 id 才是要找的形状而不是缺失的参数。6.4 按信任级别拆分读路径运行时/项目读GET /v1/ai-providers?projectId去重为每 provider 一条仅{provider, name, enabledForChat}与GET /v1/ai-providers/:provider/models?projectId均securityAccess.project([USER, ENGINE], undefined, QUERY)——ENGINE 自报项目USER 必须指明自己所属的项目且两者都应用解析密钥的modelScope白名单。管理员读GET /v1/ai-providers/configs与/configs/:id/modelsplatformAdminOnly精确定位某一行并返回未过滤模型列表。规则永不拓宽项目路由去接受 config id永不把projectIds/modelIds交给项目调用方——那是其他项目的标识符。该规则由守护测试钉死它断言项目条目的精确键集合含keys数组内每项的键集合给项目侧响应加字段就是故意让ai-provider.test.ts失败直到有人声明该字段可安全暴露。七、Chat 模型解析与对话资费7.1 三级模型解析resolveModelIdForProvideree/agent/agent-helpers.ts按以下顺序回答该用哪个模型密钥自身的config.models数组MANUAL_MODEL_PROVIDERS场景Vertex/Custom/Cloudflare Gateway管理员手输模型 id。注意空目录 ≠ 缺失目录——只配了图像模型的密钥若被当作没有目录处理会回退到精选列表、解析出一个它根本不提供的 Gemini id。管理员列举的目录是密钥的全部事实所以空目录现在会拒绝该回合并给出提示。静态ALLOWED_CHAT_MODELS_BY_PROVIDER精选表openai/anthropic/google/vertex/activepieces 等有条目。以上都没有时返回剥离厂商前缀的原始 tier id——这正是早期 Vertex 密钥曾解析出claude-sonnet-4-6并交给 Gemini 客户端的根因。密钥自身的modelScope/modelIds白名单最后应用到候选列表上解析结果被白名单清空时拒绝回合而不是返回被禁用的模型。GetProviderConfigResponse同时携带这两个字段在getChatProvider、getConfigOrThrow、enrichWithKeysIfNeeded三处构造点填充。7.2 Chat 层级与信用权重ACTIVEPIECES_CHAT_TIERS定义在 packages/core/piece-types/src/lib/ai-providers.ts是 Activepieces 专属概念只对 ACTIVEPIECES 与 OPENROUTER 两个 chat provider 有意义tier id显示标签OpenRouter 形态模型 id原生模型 idthinkingBudgetcreditWeightfastFastanthropic/claude-haiku-4.5claude-haiku-4-55,0002smartExpertanthropic/claude-sonnet-4.6claude-sonnet-4-610,00010premiumHeavyanthropic/claude-opus-4.8claude-opus-4-720,00020托管 Chat 每回合信用成本 tier.creditWeight billableToolCallsBYOK 把权重折叠为CHAT_BYOK_CREDIT_WEIGHT1与 tier 无关——所以永远不要向 BYOK 平台展示 tier 权重。这两个常量与CHAT_CREDITS_PER_TOOL_CALL一起放在activepieces/shared保证计费数字与模型选择器展示的数字同源。任何信用成本展示面都必须从ACTIVEPIECES_CHAT_TIERS渲染禁止本地副本——billing 的 Credits FAQ 对话框曾硬编码自己的 2/10/20 表在 tier 重新命名后漂移成展示 Fast/Smart/Premium 对应真实的 Fast/Expert/Heavy。conversation.modelName要么是 tier id 要么是真实模型 id无判别器的自由字符串旧 tier id 在供应商有对应模型时解析为等价模型否则落到该供应商的第一个精选模型保证切换 Provider 后旧会话继续可用。注意premium映射到 opus 4.8而 anthropic 原生列表并不携带它所以遗留的premium在 anthropic 上会落到 Sonnet。7.3 Chat Provider 选择第一个enabledForChat行胜出findAvailableChatProviderRow的三个分支都归结为第一个enabledForChat行胜出不是优先 ACTIVEPIECES当托管 Provider 可见时函数直接返回chatProviders[0]所以一个[openai, activepieces]都开启 chat 的平台解析结果是openai。客户端镜像为aiProviderQueries.useChatProvider()providers.find((p) p.enabledForChat)——读取已解析的 chat provider 一律走它不要内联重推规则。另外去重项目条目上的enabledForChat必须是该 provider所有密钥的 OR绝不能读排名第一密钥的标记排序与 chat 选择回答的是不同问题读rows[0].enabledForChat会让客户端在 chat 密钥不是排序赢家时报未配置 provider而服务端照常服务。两侧都依赖无序的findBy()没有ORDER BY多个 provider 同时 chat-enabled 时第一个并不保证稳定。八、新增一个 AI Provider六处接线与两大模型工厂8.1 六处改动清单新增供应商必须触碰AIProviderName枚举packages/core/utils/src/lib/permission.tsauth/config schema 及两个 unionAIProviderConfig、AIProviderAuthConfig与ProviderConfigUnionpackages/core/shared/src/lib/management/ai-providers/index.ts——全部位于 per-provider 区域高于文件底部的通用 request/response schema策略文件并在 providers/index.ts 的aiProvidersmap 中注册模型工厂 switch见 8.2web 端名称/Logo/markdownpackages/web/src/features/agents/ai-providers.ts翻译 key。凭据表单几乎不用改除apiKey之外的字段Azure 的resourceName、Bedrock 的 region只声明在一个文件——PROVIDER_CREDENTIAL_FIELDS.../setup/ai/providers-tab/provider-credentials.ts未登记的任何 provider 回退到DEFAULT_CREDENTIAL_FIELDS单个apiKey所以纯 API-key 厂商零 UI 工作。该文件在多 Key 改造中取代了已删除的universal-pieces/upsert-provider-config-form.tsx——基于旧分支的 provider 在 merge 后会静默丢失自定义字段git 对 delete-vs-modify 采取删除无冲突标记provider 在管理 UI 中直接不可配置。没有/models端点的厂商还要加入同文件的MANUAL_MODEL_PROVIDERSCUSTOM、CLOUDFLARE_GATEWAY让管理员手输模型 id。8.2 两个模型工厂必须同时接线createLanguageModelpackages/core/ai-providers/src/lib/create-language-model.ts的 switch 以const exhaustiveCheck: never provider收尾漏掉 provider 是编译错误而 AI piece 内部刻意复制的buildLanguageModelpackages/pieces/community/ai/.../common/ai-sdk.ts因 AI SDK 代际差异保留在 ai6以default: throw new Error(...)收尾——只接入第一个工厂的 provider 能通过类型检查、正常发布、在管理 UI 里连接成功然后在流程步骤实际执行时抛出Provider name is not supported。两个工厂都必须接。且注意同文件还有第三个 switchbuildNativeImageModel它的遗漏更隐蔽createAIModel里if (imageModel) return imageModel否则回落到语言模型——缺失的 provider 会用语言模型应答图像请求而不是报错。NO_IMAGE_GENERATION_PROVIDERS负责把这种回退变成明确的does not support image models提示因此新 provider 在真正接通图像能力之前必须留在该集合里。8.3 与代际相关的隐藏风险仓库同时运行三套ai-sdk代际activepieces/ai-providers在 ai7google 4.0.29、openai-compatible 3.0.18、provider-utils 5.0.16AI piece 在 ai6google 3.0.65、openai-compatible 2.0.42、provider-utils 4.0.24更老代码在 ai5。供应商 SDK 因而需要每个工厂各一个版本如ai-sdk/google-vertexai7 用 5.0.36、ai6 用 4.0.113对两者都取最新会静默混入LanguageModelV3/V4并在引擎边界失败。对照curl -s https://registry.npmjs.org/ai-sdk/pkg的 dependencies 与目标包的钉版匹配或直接看ls node_modules/.bun/ | grep pkg。覆盖fetch会静默关闭密钥健康上报...observed本身就是observedProviderFetch包装的 fetch同一 options 对象里后写的fetch会替换它spread 顺序决定无任何报错。需要自管fetch的路径Responses 头剥离、Cloudflare gateway 删除Authorization必须把observedProviderFetch(options.onOutcome)作为委托调用且在调用时解析委托而非构造模型时捕获。RecordAIProviderName, ...全量 map 只有两个AI_PROVIDER_CAPABILITIES与aiProvidersPROVIDER_CREDENTIAL_FIELDS、PROVIDER_EMBEDDING_MODELS、ALLOWED_CHAT_MODELS_BY_PROVIDER、PROVIDER_MAX_CONTEXT_TOKENS、DEFAULT_EMBEDDING_MODELS、WEB_SEARCH_MODE_BY_PROVIDER都是PartialRecord…静默退化到默认值。grep -rn RecordAIProviderName | grep -v Partial就是全部编译期安全网。九、运维与排障要点Gotchas 精选凭据校验失败对管理员几乎不透露原因仅 Cloudflare Gateway 例外validateProviderCredentials把上游消息挡在includeHttpErrorInMessage之后该开关只对CLOUDFLARE_GATEWAY为 true其余一律返回裸的Failed to validate credentials for name完整原因记在日志中[aiProviderService#validateProviderCredentials]。已确认案例全新 xAI 团队未购点数时GET /v1/models返回403 permission-deniedUI 却渲染成凭据校验失败把管理员引向重新生成本没问题的密钥。推论能保存成功 ≠ 密钥可用只有真实生成才能端到端证明。Azure 模型列表钉在已退役的 contenteditable="false">【免费下载链接】activepiecesAI Agents MCPs AI Workflow Automation • (~400 MCP servers for AI agents) • AI Automation / AI Agent with MCPs • AI Workflows AI Agents • MCPs for AI Agents项目地址: https://gitcode.com/GitHub_Trending/ac/activepieces创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进