呱呱聚合
← 返回基因库
大模型应用

在 Caffeine 应用里接入…

当构建需要调用 OpenAI(ChatGPT、GPT-4o、LLM、聊天机器人)的 Caffeine 应用时,用 openai-client mops 包在 canister 侧安全持有 API key 并完成调用。

被复用0 次
复用有效—
置信度55%
沉淀时间2026-09-23
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
来自 rose.feng 的小呱沉淀
GDI C 38/100

进化评分(GDI)

综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」

内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 59/100 ×0.15

适用场景

在 Caffeine 应用里接入 Ope特定

解决思路

  1. 用 mops 工具添加依赖:`mops add openai-client@0.3.0`(要求 Mops ≥ 2.13),它会同时更新 mops.toml 和 mops.lock;最低版本为 openai-client ≥ 0.3.0。
  2. 先选定密钥归属变体:默认用「每用户密钥」(每个登录用户粘贴自己的 key,各自付费);当规格明确由运营方付费时用「管理员密钥」;仅当规格明确说明完全没有登录时用「完全匿名」。
  3. 在 Config 上必须设置 `is_replicated = ?false`:原因有三——安全(复制式 outcall 会在每个子网节点上暴露 Bearer)、计费(复制式会产生 N 倍调用与约 13 倍 cycles)、确定性(LLM 采样结果无法在共识中一致)。0.3.0 的 defaultConfig 已默认该值,但仍建议在调用处显式写出。
  4. 密钥只存在 canister 内,绝不返回给前端:前端只能通过 `isMyOpenAIConfigured : async Bool`(每用户)或 `isOpenAIConfigured : async Bool`(管理员/匿名)得知是否已配置,绝不生成任何返回 `?Text`/`Text` 的 getter(如 getApiKey、getMyOpenAIApiKey)。
  5. 每用户变体:用 `Map<Principal, Text>` 以 caller 为键存储密钥,暴露 `setMyOpenAIApiKey(key)`、`isMyOpenAIConfigured`、可选 `clearMyOpenAIApiKey`,全部以 `not caller.isAnonymous()` 为门槛;绝不用前端传来的 userId 作为键。
  6. 管理员密钥变体:用单个 `var openAIApiKey : ?Text`(无 getter),暴露管理员专属 `setOpenAIApiKey(key)`(用 extension-authorization 的 `#admin` 角色校验)与无鉴权的 `isOpenAIConfigured` 查询;每次调用都重新构造 Config,不要缓存。
  7. 完全匿名变体:与管理员变体结构相同,但去掉 AccessControl 导入与 `#admin` 校验,`setOpenAIApiKey` 无鉴权、任何访客可覆盖;同样不提供 getter、不记录日志。
  8. 每用户与管理员变体必须先接入 extension-authorization:它提供 Internet Identity 登录流程(useInternetIdentity、登录/登出按钮、useActor 接线)与后端 caller/角色基础设施;完全匿名变体不需要它。
  9. 在 `lib/openai.mo` 中写 Config 构造器:`{ defaultConfig with auth = ?#bearer key; is_replicated = ?false }`,并用 `ChatApi.createChatCompletion` 完成聊天往返。
  10. 构造请求时用 `CreateChatCompletionRequest.JSON.init({ messages; model })`,让所有可选字段默认为 null,再用记录更新语法叠加可选值(如 `{ ... with temperature = ?0.7 }`);不要手写 JSON,也不要手列所有 null 可选字段。
  11. 用户消息内容用变体 `#string(text)`(纯文本)或 `#array([...])`(多模态);`ModelIdsShared = Text` 是字符串别名,直接传 `"gpt-4o-mini"` 等模型 id。
  12. 处理响应时先检查 `resp.choices.size() > 0`,再对 `resp.choices[0].message.content`(类型是 `?Text`)做 switch,拒绝、工具调用或纯音频响应会使其为 null。
  13. 调用形式二选一:函数形式 `ChatApi.createChatCompletion(config, req) : async* T`(调用处用 `await*`),或套件形式 `let api = ChatApi(config); api.createChatCompletion(req) : async T`(用 `await`);同一 shared 方法内不要混用。
  14. 只使用可用的操作:ChatApi.createChatCompletion、存储补全管理(get/list/getMessages/update/delete)、ImagesApi.createImage、ModelsApi 的 list/retrieve/delete、FilesApi 的 list/retrieve/delete。
  15. 不要路由到不可用的操作:EmbeddingsApi.createEmbedding、ModerationsApi.createModeration、CompletionsApi.createCompletion、ChatApi 的 tool_choice/function_call、所有文件上传(FilesApi.createFile、ImagesApi.createImageEdit/createImageVariation、AudioApi.createTranscription/createTranslation)、非 JSON 响应(AudioApi.createSpeech、FilesApi.downloadFile);遇到这些需求应说明 0.3.0 不支持并在 caffeinelabs/skills-internal 提 issue,不要用 ic.http_request 绕过。
  16. 按需调整 cycles 与响应大小:默认 `cycles = 30_000_000_000`;长补全(max_completion_tokens > 2000)设 `cycles = 100_000_000_000`;图像生成设 `max_response_bytes = ?2_000_000` 且 `cycles = 100_000_000_000`。
  17. 不要开启流式:`stream = ?true` 不可用,IC 的 http_request 只能原子返回完整响应体,保持 `stream = null`。
  18. 对 HTTP 429 做退避重试;注意 OpenAI 按 key 的每分钟/每天限流。
  19. 前端必须提供粘贴密钥的设置页(所有变体都需要),否则每次聊天都会 trap;每用户与管理员变体还需登录流程,完全匿名变体不需要。
  20. 前端聊天 UI 只调用后端 chat 端点,不引入任何客户端 OpenAI SDK,不在 localStorage/IndexedDB/cookie 中保存密钥。
  21. 每用户变体前端:密码输入框绑定 `setMyOpenAIApiKey`,状态指示器绑定 `isMyOpenAIConfigured()`,可选「清除我的密钥」按钮绑定 `clearMyOpenAIApiKey()`,并在未配置时显示引导到 /settings/openai 的空状态。
  22. 管理员变体前端:管理员专属设置页绑定 `setOpenAIApiKey`,用 extension-authorization 的 `isCallerAdmin` 隐藏入口并在路由守卫中拦截非管理员,聊天页在未配置时显示「请让管理员设置 OpenAI API key」。
  23. 匿名变体前端:无鉴权门、无 useInternetIdentity、无登录按钮,任何访客可访问设置页,未配置时显示「粘贴 OpenAI API key 以开始使用」。
  24. 升级时决定密钥的保留/轮换/丢弃:默认保留整个 Map;若要轮换则整体丢弃,绝不部分丢弃。

适用边界 · 注意事项