OpenWork 与 LiteLLM 集成:零依赖 per-member 虚拟密钥对账方案实战
OpenWork 与 LiteLLM 集成零依赖 per-member 虚拟密钥对账方案实战【免费下载链接】openworkThe open-source alternative to Claude Cowork (powered by opencode)项目地址: https://gitcode.com/GitHub_Trending/ope/openwork本文基于 OpenWork 仓库中examples/litellm-per-member-keys示例展开。该示例是一个零依赖的 Node.js provisioner对账器用于将 OpenWork Cloud 的 per-member LLM 凭据绑定与 LiteLLM 虚拟密钥virtual key体系进行双向同步解决一个上游密钥被多成员共享的安全问题。读完本文你将掌握如何配置该 provisioner 的环境变量、理解reconcile与offboard两条命令背后的完整调用链与 fail-closed 校验逻辑以及如何用一条命令拉起完整的本地验证环境桌面客户端 隔离的 Den 组织 数据库版 LiteLLM 网关。背景为什么需要 per-member 凭据绑定在组织场景中管理员往往只持有一个上游 LLM 网关如 LiteLLM的接入密钥。若把这个共享密钥直接配给所有成员将无法审计谁在用、也无法单独吊销某个人的访问权。OpenWork 的解决方案是per-member 凭据绑定为组织中的一个托管 LLM Provider 声明credentialMode: per_member由外部网关例如 LiteLLM为每个成员签发独立的虚拟密钥OpenWork 侧只保存成员与外部凭据 ID 的绑定关系。这一点在 OpenWork 的官方 API 契约文档 per-member-llm-credentials.mdx 中有明确说明shared仅保存一个 Provider 凭据所有被授权成员通过 connect 路由获得同一份凭据per_member为调用方组织成员解析各自独立的绑定Provider 可以在绑定存在之前就完成授权。需要特别强调的是LiteLLM 的密钥铸造与元数据发现能力并未内建于 Den。官方文档明确指出LiteLLM provisioning and metadata discovery are not built into Den因此组织需要自行运行一个 provisioner把它对接在 Den 的通用 Provider/凭据绑定 API 与 LiteLLM 管理 API 之间——这正是本文主角examples/litellm-per-member-keys/provision.mjs的定位示例级对账器而非 Den 的原生 LiteLLM 集成。环境变量配置在仓库根目录examples/litellm-per-member-keys下provision.mjs通过configFromEnv()源码 provision.mjs从环境读取全部配置。下表逐项说明含义环境变量含义源码中的处理OPENWORK_DEN_API_URLDen API 基础地址经cleanBaseUrl去除尾部斜杠provision.mjsOPENWORK_DEN_TOKEN组织 owner 或 admin 的 Bearer Token作为authorization头发送OPENWORK_ORG_ID组织 ID作为x-openwork-org-id请求头发送provision.mjsOPENWORK_LLM_PROVIDER_IDper-member LLM Provider 的 ID用于 URL 路径编码encodeURIComponentLITELLM_BASE_URLLiteLLM 基础地址带或不带/v1均可通过liteLlmAdminBaseUrl剥离末尾的/v1provision.mjsLITELLM_MASTER_KEYLiteLLM master key用于所有 LiteLLM 管理接口的 Bearer 认证LITELLM_MODELS逗号分隔的模型 ID 列表经去重、trim 后作为每个虚拟密钥的授权模型集合provision.mjs配置完成后即可触发对账node provision.mjs reconcile该脚本是零依赖的只用 Node.js 内建能力fetch、node:url通过// ts-check加 JSDoc 类型注释获得静态检查没有任何npm install步骤。reconcile 的两阶段流程reconcile命令内部调用reconcileMemberKeys()provision.mjs分两个阶段执行先同步模型元数据再铸造缺失的成员密钥。阶段一Provider 模型元数据同步syncProviderModelMetadata()provision.mjs的执行顺序调用GET {denApiUrl}/v1/llm-providers?scopemanageable获取可管理的 Provider 列表并校验目标 Provider必须存在providerId was not foundsource必须为customcredentialMode必须为per_member对应源码 manageableProvider。使用 master key 调用 LiteLLMGET /model_group/info拉取模型组元数据。对LITELLM_MODELS中的每个模型要求精确的model_group精确匹配且max_input_tokens、max_output_tokens必须是有限的正数对应 liteLlmModelMetadata 中的requirePositiveNumber。若 LiteLLM 漏掉了某个请求的模型、或缺少任一限额在创建任何成员密钥之前就 fail closed失败即中止——示例从不猜测 token 限额也不回退到通用值。将 LiteLLM 提供的能力事实映射到 Den 的模型字段LiteLLM 元数据字段Den 模型字段说明max_input_tokenslimit.context/limit.input上下文与输入 token 上限max_output_tokenslimit.output输出 token 上限supports_function_callingtool_call函数调用能力supports_reasoningreasoning推理能力supports_visionattachment视觉/附件能力supports_response_schemastructured_output结构化输出能力supported_openai_params含temperaturetemperature温度参数支持该映射逻辑集中在 synchronizedModelConfig只有 LiteLLM 明确给出布尔事实时才写入对应字段typeof metadata.facts.supports_function_calling boolean绝不臆造。构造当前配置与期望配置两份快照后用canonicalJson对对象键递归排序provision.mjs做规范化比较仅当确实发生变化时才向 Den 发送PATCH /v1/llm-providers/:id否则返回{ action: unchanged }避免无意义写入。这个全量替换式 PATCH有一个关键安全细节请求体中不包含apiKey/apiKeys字段因此 Den 会保留已落库的只写凭据write-only stored credential不会在元数据同步时被覆盖或泄露。阶段二铸造缺失的成员密钥元数据同步完成后脚本调用GET /v1/llm-providers/:id/member-credentials列出所有被授权成员绑定的凭据状态对每个state missing的成员生成 key aliasopenwork-${orgMembershipId}并携带metadata.openwork_org_membership_id调用 LiteLLMPOST /key/generate铸造虚拟密钥要求响应中必须同时包含key明文密钥与token_idexternalCredentialId 中强制校验缺失则拒绝落库调用 DenPUT /v1/llm-providers/:id/member-credentials/:orgMembershipId写入{ apiKey, externalCredentialId, externalPrincipalId? }。值得注意LiteLLM v1.97 返回的token_id可以不保留明文密钥即可寻址该虚拟密钥。示例将token_id存入 Den 的externalCredentialId之后管理员列表接口可以把该标识安全地回传给 provisioner——这正是 offboard 阶段不需要明文密钥的前提。最终输出摘要中只包含元数据动作updated/unchanged与安全的模型限额、以及新铸造凭据的externalCredentialId绝不打印成员密钥本身。provision.mjs中多处通过redact()provision.mjs在错误信息里对denToken、liteLlmMasterKey、成员apiKey等敏感串做[REDACTED]脱敏且所有请求统一走 30 秒超时的requestJsonprovision.mjs错误文本截断为 1000 字符。Den 侧 API 契约速览为读懂 provisioner 的每一步这里补上 Den 侧契约详见 per-member-llm-credentials.mdx成员视角只能管理自己的只写凭据PUT /v1/llm-providers/:id/my-credential写入自己的凭据body 只能是{ apiKey: ... }或{ apiKeys: { ENV_NAME: ... } }二者之一DELETE /v1/llm-providers/:id/my-credential删除自己的凭据GET /v1/llm-providers/:id/connect获取 Provider 配置与解析后的凭据。即使绑定缺失也返回 HTTP 200凭据字段为 null并附带memberCredential.state。管理员/Provisioner 视角中央对账GET /v1/llm-providers/:id/member-credentials列出每个被授权成员绑定的状态、版本与外部标识永不返回凭据明文PUT /v1/llm-providers/:id/member-credentials/:orgMembershipId写入某成员的凭据body 支持apiKey/apiKeys可选externalPrincipalId、externalCredentialId、expectedVersionPOST /v1/llm-providers/:id/member-credentials/:orgMembershipId/block将既有绑定标记为 blocked。其中memberCredential.state取值包括missing、active、blocked、stale、error。两个并发相关的语义expectedVersion用于多 provisioner worker 场景版本不匹配时返回 HTTP 409{ error: version_conflict }被 block 的绑定由管理员持有成员无法覆盖或删除成员侧写/删请求返回 HTTP 409{ error: credential_blocked }管理员PUT是显式的解封与替换路径。offboard必须先吊销上游再标记本地撤下某成员时顺序规则是先 block 上游 LiteLLM 密钥再标记 Den 绑定为 blocked。原因在于若上游吊销失败应让 Den 绑定保持 active使失败可见、可重试而不是制造一个本地已封禁的假象。node provision.mjs offboard member_...offboardMember()provision.mjs执行的顺序从GET /v1/llm-providers/:id/member-credentials读取该成员的externalCredentialId即 LiteLLM 的token_id若不存在则直接报错拒绝执行调用 LiteLLMPOST /key/blockbody 为{ key: credentialId }并确认成功——LiteLLM v1.97 接受其生成的token_id因此 offboard 阶段不需要成员密钥的明文调用 DenPOST /v1/llm-providers/:id/member-credentials/:orgMembershipId/block标记本地绑定验证成员 connect 请求返回 HTTP 200、凭据为 null 且memberCredential.state: blocked。这一顺序也被 per-member-llm-credentials.mdx 文档作为官方推荐流程列出。一键拉起完整本地验证环境README 提供了一个可运行的整体世界用于手工演练完整闭环桌面客户端 隔离 Den 组织 数据库版 LiteLLM 网关且 Provider 与成员密钥已预先对账完成。从仓库根目录执行pnpm world up ./worlds/litellm-per-member.ts启动后会打印Den 的 URL、LiteLLM URL、同步后的模型限额、Den Provider 记录 ID、以及桌面客户端的 CDP URL。测试期间保持运行结束后按 Ctrl-C 拆除整个环境world 的 teardown 会自动清理。前置条件Docker、本地 MySQL、本地 Redis。该 world 的网关使用确定性的本地 OpenAI 兼容 witnesswitness不会读取也不需要OPENAI_API_KEY。从源码看这个 world 的实现位于 worlds/litellm-per-member.ts它启动一个数据库版 LiteLLMdatabase: true对应 evals/packages/env/src/litellm.ts 中以ghcr.io/berriai/litellm:v1.97.0镜像拉起网关与postgres:16-alpine的流程创建名为LiteLLM Per-Member World的组织、管理员与成员 Alice用liteLlmPerMemberProviderevals/packages/env/src/litellm-provider.ts完成 Provider 创建 元数据对账并以管理员身份登录桌面客户端、将模型指向{provider}/{model}最后输出 Alice 的 per-member 虚拟密钥、master key、upstream key 等凭证供手工验证。这里有一个值得玩味的工程细节witness 网关会校验虚拟密钥的指纹。在 litellm.ts 的 witness 中每个上游请求都记录了tokenId对携带的 Bearer token 做 SHA-256只有与upstreamTokenId指纹匹配的请求才会返回 200——这意味着任何未走 Den 下发、非法铸造的密钥都会在网关层直接被拒整条链路可观测、可审计。这为验证每成员一密钥是否真正生效提供了最直接的证据。从源码看对账器的设计要点保守的 fail-closed 哲学从requirePositiveNumber、requireString到模型必须精确匹配 model_group 且限额必须是有限正数任何元数据缺口都会让对账在铸造密钥前整体中止绝不回退猜测值最小写、保现状Den PATCH 仅在规范化 JSON 比较后确有变化才发出且全量替换时省略apiKey/apiKeys保留 Provider 配置、模型名与未知字段、当前成员/团队授权不变只更新 token 限额与能力字段密钥只在最短窗口内出现明文apiKey仅在铸造响应与写入 Den 的请求之间停留随后只以token_idexternalCredentialId继续流转错误信息全程脱敏与 Den 核心解耦/model_group/info调用与字段映射被刻意实现为 LiteLLM 专属的示例逻辑而 Den 的 Provider PATCH 与 per-member 凭据 API 只接收通用模型配置不硬编码任何 LiteLLM 行为从而保持厂商中立。延伸阅读API 契约与成员/管理员双视角流程per-member-llm-credentials.mdx示例完整实现provision.mjs本地 world 编排worlds/litellm-per-member.ts测试环境封装witness 网关与 Provider 对账evals/packages/env/src/litellm.ts、evals/packages/env/src/litellm-provider.ts该集成在 OpenWork 的 eval 体系中作为可执行证明存在evals/packages/env/src/litellm-provider.ts会直接import示例模块并调用reconcileMemberKeys验证其返回的元数据动作与模型限额符合预期同时该世界也登记在evals/specs/shared-world-engine.test.ts的 world 清单中可纳入自动化回归。【免费下载链接】openworkThe open-source alternative to Claude Cowork (powered by opencode)项目地址: https://gitcode.com/GitHub_Trending/ope/openwork创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联
返回资讯列表 →