For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending .md to the page URL.
主导航
2026年2月4日 Apps SDK

构建 ChatGPT 应用的 15 条经验

以及我们如何将这些经验融入一项 Codex 技能,帮助您以 10 倍的速度构建 ChatGPT 应用。

作者: Nikolay Rodionov (Co-founder, Alpic)

构建 ChatGPT 应用的 15 条经验

Alpic,我们相信,下一代产品和服务将围绕 以 AI 为核心的体验构建。在这样的界面中,用户与模型协作,而不是按照传统的、预先设定的 UI 工作流操作。

OpenAI 发布 Apps SDK 后,我们立即开始用它开发。在三个月内,我们开发了 24 个 ChatGPT 应用,既供内部使用,也服务于 旅游、零售和 SaaS 等 B2B 和 B2C 领域的客户。

我们很早就发现, 构建 ChatGPT 应用与构建传统的网页或移动应用有着根本区别。在网页开发中行之有效的模式,例如即时获取数据、由 UI 驱动状态、让用户显式配置等,在智能体环境中往往会失效,甚至损害体验。

本文提炼了我们在开发实际使用的 ChatGPT 应用时积累的 15 条最重要的经验 ,并介绍我们如何将这些经验融入面向社区的开源框架 Skybridge 和一项 Codex 技能,帮助开发者显著加快应用的构思、构建、测试和发布。

三体问题

在传统网页应用中,情况很简单:只有 用户UI 两方。而在 ChatGPT 应用中,系统引入了第三方: 模型

为 ChatGPT 开发应用时,最棘手的问题之一就是管理信息在这三方之间的流动。如果用户点击了您小组件中的“选择”按钮,UI 会发生可见的变化,但作为对话大脑的模型却对此毫不知情,除非您明确将这些上下文传递给它。如果用户接着说: “请详细介绍一下这款商品。” 模型根本不知道用户正在看什么。

我们将这种情况称为 上下文不对称 :每一方只掌握系统的部分信息,没有任何一方了解全貌。构建出色的 ChatGPT 应用,并不在于让一切始终同步,而在于决定应该共享 哪些 信息、 何时 共享,以及 需要了解这些信息。能否解决这个问题,决定了应用是生硬难用,还是能提供流畅的智能体体验。

1. 并非所有上下文都应共享

我们最初的直觉是“把所有信息共享给所有地方就好”。结果,这成了我们最早犯下的错误之一。

在实际开发中,ChatGPT App 的不同部分往往需要看到同一状态的不同信息,而且这种差异是 有意设计的 。这是为什么?

  • 出于性能考虑: UI 小组件需要的数据往往远多于模型所需。例如,旅游预订应用可能需要图片、不同价格方案和预加载的选项。将这些数据全部发送给模型,会增加 Token 用量、延迟和理解信息时的干扰。
  • 出于逻辑需要: 某些信息必须在设计上保持不对称。在我们最早开发的一款应用,也就是推理游戏 Murder in the Valleys 中,模型需要知道凶手是谁,才能正确扮演角色,而 UI 和用户都不能知道。在 Time’s Up 这类游戏中,情况恰好相反:UI 向用户展示待猜词,但模型必须对此毫不知情。

我们学到的不是“始终同步所有信息”,而是: 明确决定谁需要知道什么。我们通过不同的 工具输出 字段,将这一原则落实下来:

字段用途可见对象
structuredContent供小组件和模型使用的带类型数据小组件和模型均可见(通过 toolOutput 和 callTool 函数)
_meta响应元数据仅小组件可见,对模型隐藏

例如,在 Time’s Up 游戏中,我们通过 _meta 字段将待猜词仅传给小组件,让模型根据用户的提示来猜词。

2. 懒加载不太适合 AI 应用

由于有网页开发背景,我们习惯采用懒加载:用户点击时才获取数据,按需加载详情,并尽量减少首次加载的数据量。

但在 ChatGPT 中,思路恰好相反:工具调用会带来延迟,安全沙盒和模型推理往往会使一次调用耗时数秒。

在实践中,我们学会了尽可能提前加载数据:在首次工具响应中发送尽量多的数据,并通过 window.openai.toolOutput 将数据填充到小组件中。这种做法几乎总能带来更快、更灵敏的体验。

当然,如果小组件可以安全地从公开 API 端点获取数据,而且不需要与模型共享信息,您随时可以在小组件内使用传统的 XHR 调用。但大多数时候,您会希望模型能够自主调用工具,让整个体验保持对话式交互。

3. 模型需要了解界面状态

当用户与小组件交互(例如在列表中选择某款商品),然后在聊天中提问时,就会出现一个不易察觉却很关键的问题:如果模型不知道用户指的是 UI 的哪一部分,就无法正确回答。

为此,我们使用了 window.openai.setWidgetState(state)。它允许您存储特定的状态数据,并在用户下一次与模型交互时将这些数据加入模型的上下文。

随着应用日益复杂,我们发现,为了让模型跟上用户的导航操作,我们在很多地方都添加了 setWidgetState。于是,我们决定引入一种描述 UI 上下文的声明式方法:不再在每次交互时以命令式方式向模型更新信息,而是直接为组件附加 data-llm 属性:

<div
  data-llm={
    selectedTab === "details"
      ? "User is viewing product details"
      : "User is viewing reviews"
  }
>

为了让这套机制在后台运行,我们构建了一个 Vite 插件,用来提取这些属性并自动更新 widgetState。对模型来说,它只需在合适的时机接收相关的 UI 上下文,开发者无需再手动同步每一次交互。

我们创建了一个开源框架,用于向社区分享这些经验。您可以在其中找到这个 Vite 插件,以及本文介绍的许多其他技巧。

4. 不同的交互需要不同的 API

ChatGPT 应用在小组件、服务器和模型之间有多条交互路径。这些路径不能互相替代:每条路径都用于支持不同类型的交互。

构建 ChatGPT 应用的一条关键经验是:明确这些通信路径,并有意识地决定由哪种机制负责体验的哪一部分。

将这些路径画成图,大致如下:

小组件、服务器和模型之间不同交互的示意图

这些经验奠定了 ChatGPT App 的基础:如何共享上下文、如何让模型了解界面状态,以及不同的交互如何在系统中传递。下一节将在此基础上,重点讨论这些机制对 UI 设计的影响。

为 AI 重新设计 UI

ChatGPT 应用是一个全新的环境,因此我们很快学会了放下对 UI 的固有观念,充分运用新能力。本节介绍为了打造有效的应用,我们需要学习哪些界面设计理念,又需要放下哪些旧观念。

5. UI 必须适应多种显示模式及其限制

ChatGPT 应用并不局限于一种布局。根据调用方式和时机的不同,同一个小组件可以采用三种不同的显示模式。

应用可以 内嵌 在对话中,以 画中画(PiP) 形式悬浮在对话上方,或在需要更多空间时以 全屏 模式显示。画中画和全屏模式能够呈现更丰富的界面,但也会引入不受小组件控制的 UI 叠加层。要避免内容被裁切并优化交互,就必须考虑不同设备的安全区域,例如移动端始终显示的关闭按钮所占的区域。

随着经验积累,我们总结出了各显示模式的特点和适用场景:

显示效果适用场景
内嵌默认显示模式。小组件保留在对话历史记录中。适合快速交互
全屏小组件占据整个屏幕,聊天栏位于底部。适合复杂且需要较大空间的小组件(例如地图)
画中画大小与内嵌模式相同,但小组件会始终悬浮在对话上方适合生成后仍与后续对话相关的小组件

6. 在嵌入式环境中,UI 一致性很重要

早期,我们曾拿不准 ChatGPT App 的视觉设计应该有多大的自由度。作为用户接触的新界面,它既需要在我们自己的应用之间保持一致,也需要与周围的 ChatGPT 生态协调,让用户感到熟悉。与独立产品不同,小组件嵌入在现有界面中,任何视觉上的不一致都会立刻显现。

幸运的是,OpenAI Apps SDK UI Kit 为我们提供了明确的设计基准。

它基于 Tailwind CSS 构建,提供符合 ChatGPT 设计系统的现成组件、图标和设计 Token。使用这套工具包让我们能够快速开发,同时确保小组件具有原生体验,并与周围界面在视觉上保持一致,即使构建自定义组件时也是如此,例如用于集成 Mapbox 的组件。

7. 优先用自然语言筛选

传统仪表盘依赖布满复选框和范围滑块的侧边栏。在智能体 UI 中,这种设计往往是一种倒退。当用户可以直接用自然语言表达意图,例如“预算低于 200 美元、阳光充足的欧洲目的地”时,强迫他们操作多个 UI 控件只会增加使用阻力。他们应该只需说出需求。

因此,我们决定让大多数应用采用“无筛选控件”的设计。我们不再提供带有筛选和排序选项的侧边栏,而是向模型提供工具参数的 值列表(LOV)

这样,模型就能直接以用户的消息作为输入,无需“猜测”有哪些可用选项。换句话说,它可以将自然语言直接映射为符合后端 API 要求的参数。如果用户说“晴天”,模型就知道应使用 weather="sunny" 调用工具。

8. 文件可以带来更丰富的交互

随着我们构建的应用越来越复杂,一条经验逐渐清晰:不应将文件视为次要输入。在 ChatGPT 应用中,文件可以带来新的交互方式。交互不必从表单或筛选器开始,也可以从用户已有的内容开始。

例如,在电商应用中,用户可以在聊天中上传一张商品照片,让模型识别它,然后直接在小组件中继续匹配或发现商品。

要实现这一点,需要让系统的模型端和 UI 端都能处理文件。在模型端,工具可以通过 openai/fileParams 直接使用用户在聊天中上传的文件,让模型能够基于图像或用户提供的其他素材进行推理。在 UI 端,小组件也可以使用 window.openai.uploadFilewindow.openai.getFileDownloadUrl 直接处理文件,从而在 UI 流程中请求用户上传文件,或生成可供用户下载和重复使用的文件。

走向生产环境

接下来,当应用走出本地开发阶段,安全、配置和工具方面就会出现另一组需要考虑的问题。这正是第三组经验所要讨论的内容。

9. CSP 成了新的 CORS 难题

出于安全考虑,OpenAI 在双层嵌套的 iframe 中渲染应用。内容安全策略(CSP)是 iframe 隔离的原生机制,这种设置会严格执行这些策略,因此常常出现经典的“本地运行正常,到了生产环境就出问题”的情况。

在传统 Web 开发中,宽松的策略或许也能应付过去,但 Apps SDK 要求您进行精确配置。

这意味着,您需要在应用清单中仔细声明每种交互允许使用哪些域名:

字段用途示例常见错误
connectDomainsAPI 和 XHR 请求https://api.weather.com忘记区分预发布环境和生产环境的 API。
resourceDomains图像、字体、脚本https://cdn.jsdelivr.net使用 delivr.net 之类的通用 CDN,却未将其加入允许列表
frameDomains嵌入 iframehttps://www.youtube.com嵌入 YouTube 视频或 Mapbox 实例,却未将其加入允许列表。
redirectDomains打开时不显示警告的外部链接https://app.alpic.ai遗漏结账或 OAuth 回调域名。

从一开始就重视 CSP 配置,为我们省下了后续大量的生产环境调试工作。

10. 小组件的标志虽小,影响却很大

除了 CSP,还有少量小组件级别的设置,决定了小组件、模型和宿主环境之间如何分配控制权。这些标志很容易被忽略,却定义了导航、工具访问和发布方面的关键边界。

宿主与导航边界

  • 提交应用时必须设置 widgetDomain 。它定义了全屏模式下“在 <App> 中打开”按钮默认指向的位置,同时也参与来源允许列表的配置,因为小组件是在 <widgetDomain>.web-sandbox.oaiusercontent.com 下渲染的。我们使用 setOpenInAppUrl 根据上下文将用户引导到合适的路径。

模型与工具边界

  • 工具注解 必须遵循发布指南。readOnlydestructiveHintopenWorldHint 等标志是必填项,并会在提交时接受验证。
  • 工具可见性 很重要:不应允许模型调用的工具,必须明确标记为私有。

小组件执行边界

  • widgetAccessible 控制小组件能否使用 callTool 自行调用工具。

单独来看,这些设置都不起眼,但它们共同决定了应用发布后能否正常运行。

为快速迭代优化开发流程

Apps SDK 正在快速发展,我们也很高兴能在这一过程中不断构建应用。为了让开发流程更顺畅、更高效,我们决定开发自己的开源框架,并与社区分享。以下经验可以帮助您避开我们初期遇到的一些开发体验问题。

11. 快速迭代需要热重载

迭代速度是我们最早着手解决的问题之一。资源缓存的 TTL 较长,加上资源通过 JSON-RPC 转发,使得标准模块热重载功能(例如 Vite 或 Next.js 中的实现)无法直接用于 ChatGPT 应用。

在花了大量时间了解 Vite 的内部机制后,我们构建了一个 Vite 插件,让小组件能够直接在 ChatGPT 中实时重载。该插件会拦截发往 MCP 服务器的资源请求,并将实时更新注入 ChatGPT 的 iframe。在 IDE 中做出的修改能立即反映在 ChatGPT 中,大幅缩短了我们的反馈周期。

展示热重载效果的 GIF 动图

12. 并非所有测试都需要在 ChatGPT 中进行

在 ChatGPT 中测试是黄金标准,但在最初几轮迭代中,本地模拟器可以帮助您更快地推进开发,尤其是在修改工具定义、需要在开发者模式下重新加载应用时。

为了加快早期迭代,我们构建了一个轻量级本地模拟器,用于模拟 ChatGPT 宿主环境,并配备调试工具和应用专用日志。这样,我们就能以毫秒级速度迭代 React 状态和布局,将真实 ChatGPT 环境中的测试留给模型交互和边界情况的验证。

13. 移动端测试需要专门支持

移动端测试带来了另一个挑战:在 ChatGPT 中测试需要通过隧道连接本地服务器,但 Vite 默认使用 localhost,导致其他设备无法访问同一个 URL。

为了解决这个问题,我们扩展了 Vite 插件,使其支持隧道端口上的域名转发,从而让我们能够在 iOS 和 Android 设备上进行测试,并将移动端验证纳入日常工作流程。

14. 熟悉的抽象(如 React 钩子)能加快前端开发

Apps SDK 提供了强大的能力,但主要通过底层 JavaScript API 暴露。作为长期使用 React 的开发者,我们希望能以更贴近已掌握概念的方式使用这些能力。

因此,我们引入了一些适合 React 的抽象,例如 useCallTooluseWidgetStateuseLocale 等钩子,以及用于复杂数据流的更高级状态管理方案,例如基于 Zustand 构建的 createStore。重新引入熟悉的前端模式,减少了样板代码,也让小组件开发更贴近现代 Web 工作流。

将经验转化为 Codex 技能

15. 将经验转化为可复用的工具

随着这些模式在多个应用中反复出现,我们逐渐意识到,反复摸索同样的经验正在拖慢开发速度。为了加快 ChatGPT App 开发,让开发过程更可预测,我们决定将这些经验直接融入工具中,既供自己使用,也分享给社区。

由此,我们开展了两项相辅相成的工作:

  1. Skybridge 框架 这个开源 React 框架将本文介绍的许多模式封装成可复用的构建模块,包括我们开发的钩子(useCallTooluseToolInfo)、开发工具(HMR 和本地模拟器),以及 data-llm 属性。
  2. chatgpt-apps-builder Codex 技能 我们在该框架的基础上构建了一项专用的 Codex 技能,为应用的整个生命周期提供支持:
    • 创意构思: 集思广益,探索如何让应用具备智能体能力,而不只是移植现有的网页应用。
    • 代码生成: 同时编写 React 前端和 MCP 服务器后端,并预先配置所有适用的 UX 和 UI 模式。
    • 本地测试: 启动开发服务器,将本地应用连接到 ChatGPT,通过热重载实时迭代。
    • 质量保证与发布: 按照 OpenAI 的提交指南进行系统化检查,包括 CSP 验证、安全区域相关考量和生产环境测试。
    • 应用部署: 协助完成应用上线和迭代所需的最后步骤。

要安装并使用该技能,只需运行以下命令:

npx skills add alpic-ai/skybridge

结语

构建 ChatGPT 应用,需要重新思考上下文如何流转、界面如何运作,以及用户与模型如何协作。本文中的许多经验,都源于熟悉的网页开发模式与智能体系统实际情况之间的差异。

我们分享这些经验,并将其融入开源框架和 Codex 技能,希望帮助团队减少在相同问题上反复摸索的时间,将更多时间用于探索这种新交互模式带来的可能性。最吸引人的 ChatGPT 应用,不会只是现有产品的简单移植,而是围绕这种以 AI 为先的新体验精心设计而成。