1. 从 HC 2026 现场看 OpenTiny NEXT 到底想解决什么问题
今年 HC 大会我全程跟了下来,OpenTiny 展台前前后后去了三趟。倒不是说展台布置得多花哨,而是他们这次抛出来的 OpenTiny NEXT 确实踩到了很多前端团队正在头疼的那个点上——Web 应用怎么真正接上 AI 能力,而不是在页面上挂一个聊天框就完事。
先说清楚 OpenTiny NEXT 是什么。它是 OpenTiny 团队在 HC 2026 上正式对外发布的一套面向 Web 应用的智能化开发套件,核心由三块组成:一套前端 AI SDK、一套 GenUI 生成式界面引擎、以及一套把 AI 能力注入现有 Web 工程的适配层。说白了,它想做的事情是让一个已经跑了三五年的中后台系统,不用推倒重来,就能把 AI 对话、智能表单、动态界面生成这些能力长进去。
这件事为什么值得单独拿出来讲?因为过去一年我接触过的前端团队,十个里有八个都在做同一件事:老板说我们要加 AI。然后团队花两周接了个大模型 API,在右下角放了个悬浮球,点开是个对话框。上线之后发现没人用,因为用户要的是"帮我把这批数据按季度汇总成图表",而不是"你好,我是智能助手,请问有什么可以帮您"。
OpenTiny NEXT 的思路不太一样。它把 AI 能力拆成了几个可组合的原子能力,通过 SDK 的形式暴露给前端工程,让开发者可以在具体的业务组件里按需调用。比如表格组件可以调用它的智能列配置能力,表单组件可以调用它的自然语言转表单能力,页面容器可以调用它的 GenUI 动态渲染能力。这个拆法我觉得是对的,因为 AI 在 Web 应用里的价值从来不是"有一个对话框",而是"让已有的交互变聪明"。
适合谁来参考这篇内容?三类人。第一类是前端架构师,正在评估怎么把 AI 能力体系化地引入现有工程;第二类是中后台业务的前端负责人,被老板的 AI 需求追着跑但不想做面子工程;第三类是对 GenUI、前端 SDK 设计感兴趣的开发者,想看看大厂在这块是怎么抽象接口的。如果你只是想知道怎么调一个对话 API,那这篇可能不太对你的胃口。
2. OpenTiny NEXT 的整体设计与方案选型拆解
2.1 为什么是 SDK 而不是平台
这是我在现场问的第一个问题。市面上做 AI 加 Web 的方案,主流有两种路线:一种是做一个 AI 中台,业务方接入中台,中台统一管理模型、提示词、会话;另一种就是 OpenTiny NEXT 选的这条,做 SDK,把能力下沉到前端工程里。
两种路线没有绝对优劣,但适用场景差别很大。中台路线适合大集团、多业务线、需要统一管控的场景,缺点是重,接入周期长,前端团队想调个参数得走工单。SDK 路线适合单一业务团队快速落地,前端自己就能控制调用时机、参数、降级策略,缺点是能力治理要靠团队自觉。
OpenTiny NEXT 选 SDK 路线,我理解背后的判断是:AI 在 Web 应用里的调用点太分散了。一个页面里可能有十几个地方需要 AI 能力,有的在表格列头,有的在表单校验,有的在搜索框。如果每个调用点都走中台,网络往返和治理成本都受不了。SDK 直接跑在浏览器里,调用链路短,响应快,而且可以跟组件生命周期绑定。
提示:SDK 路线的一个隐含前提是前端团队要有一定的工程能力,能自己管理 API Key 的代理、能做请求降级、能处理流式响应的异常。如果团队里没人碰过这类事情,建议先从中台路线起步。
2.2 GenUI 为什么放在核心位置
GenUI 这个词这两年出现频率很高,但各家理解不一样。有的把它理解成"AI 生成页面代码",有的理解成"AI 生成 JSON Schema 然后渲染"。OpenTiny NEXT 的 GenUI 属于后者,而且做得更细一层——它不是生成整个页面,而是生成页面里的某个区块。
这个选择很关键。生成整个页面听起来酷,但实际落地时问题一堆:生成的代码风格跟现有工程不一致、状态管理接不上、路由对不上、样式冲突。生成区块就务实多了,区块的边界清晰,输入输出可控,跟现有工程的耦合面小。
我现场看了一个 demo:一个订单管理页面,用户输入"把最近三个月退款率超过 5% 的商品列出来,按退款率倒序",GenUI 引擎解析后,动态生成了一个带筛选条件、排序规则、数据源配置的表格区块,直接嵌在原有页面里。原有页面的头部、侧边栏、其他区块都没动。这个粒度我觉得是能落地的粒度。
2.3 前端 AI SDK 的接口抽象思路
OpenTiny NEXT 的 SDK 接口设计有几个细节值得说。它没有把所有能力塞进一个大对象里,而是按能力域拆成了多个子模块:ai.chat、ai.form、ai.table、ai.genui、ai.agent。每个子模块有自己的配置项和生命周期钩子。
另一个细节是它区分了"同步能力"和"流式能力"。像自然语言转表单这种,输入一句话,输出一个表单配置,属于同步能力,一次请求一次响应。像对话、Agent 执行这种,属于流式能力,需要处理 SSE 或 WebSocket 的增量数据。SDK 对这两类能力用了不同的接口形态,同步能力返回 Promise,流式能力返回一个可迭代的流对象。
这个区分看起来是小事,但实际用起来差别很大。我见过太多 SDK 把流式能力包装成 Promise,结果开发者拿到的是一个"等全部生成完才 resolve"的假流式,用户体验跟同步没区别。OpenTiny NEXT 在这块没有偷懒。
2.4 与现有 Web 工程的集成方式
集成方式上,OpenTiny NEXT 提供了三种粒度。最轻的是 CDN 引入,适合老项目快速试水;中间的是 npm 包按需引入,适合有构建体系的工程;最重的是工程级集成,会往项目里注入配置文件、代理层、类型定义。
我建议大多数团队从第二种开始。CDN 引入虽然快,但类型提示没有,调试体验差,而且不好做 Tree Shaking。工程级集成虽然完整,但会改动项目结构,老项目接入时容易跟现有构建流程打架。npm 按需引入是个平衡点,改动能控制在一个文件里。
3. 核心能力拆解与实操要点
3.1 前端 AI SDK 的初始化与配置
SDK 初始化这块,我踩过的坑主要在两个地方:一是 API Key 的存放位置,二是请求代理的配置。
API Key 绝对不能放在前端代码里,这是底线。OpenTiny NEXT 的文档里也强调了这一点,推荐的做法是在自己的后端搭一个代理层,前端 SDK 请求代理层,代理层再去请求模型服务。代理层要做的事情包括:鉴权、限流、日志、以及把模型服务的响应格式转换成 SDK 期望的格式。
代理层的配置我一般会写成一个独立的配置文件,大概长这样:
// ai-proxy.config.js export default { endpoint: '/api/ai/proxy', timeout: 30000, retry: { maxAttempts: 2, backoff: 'exponential' }, stream: { enabled: true, heartbeat: 15000 }, fallback: { onError: 'silent', message: '智能服务暂时不可用' } };timeout设 30 秒是因为大模型生成有时候确实慢,设太短会频繁超时。retry设 2 次是因为网络抖动导致的失败重试一次基本能恢复,重试太多次反而会放大服务端压力。heartbeat是流式连接的保活间隔,15 秒是个经验值,太短浪费请求,太长容易被中间层断开。
SDK 初始化代码本身不复杂:
import { createAI } from '@opentiny/next-sdk'; const ai = createAI({ proxy: '/api/ai/proxy', modules: ['chat', 'form', 'table', 'genui'], locale: 'zh-CN', debug: process.env.NODE_ENV === 'development' });modules这个配置项我建议按需声明,不要全开。全开的话打包体积会大不少,而且有些模块初始化时会注册全局事件监听,用不到的话是浪费。
3.2 GenUI 区块生成的参数与边界控制
GenUI 是 OpenTiny NEXT 里最需要小心使用的能力。它强大,但也最容易失控。核心是要把生成的边界控制好。
我一般会从三个维度设边界:数据维度、交互维度、样式维度。
数据维度控制的是 GenUI 能访问哪些数据源。不能让它随便查库,要给它一个白名单。比如:
const genuiConfig = { dataSources: [ { id: 'orders', schema: orderSchema, fields: ['id', 'amount', 'status', 'createdAt'] }, { id: 'refunds', schema: refundSchema, fields: ['orderId', 'rate', 'reason'] } ], maxRows: 1000, timeout: 5000 };maxRows设 1000 是防止生成的查询把数据库拖垮。timeout设 5 秒是防止慢查询阻塞页面。
交互维度控制的是生成的区块能做什么。默认情况下,GenUI 生成的区块应该是只读的,不能直接触发写操作。如果业务确实需要写操作,要显式声明:
const genuiConfig = { interactions: { read: true, write: false, export: true, drilldown: true } };样式维度控制的是生成的区块跟现有页面的视觉一致性。OpenTiny NEXT 支持传入一个主题 token 对象,GenUI 生成时会参考这些 token:
const themeTokens = { primaryColor: '#1677ff', borderRadius: '6px', fontSize: '14px', spacing: '16px' };不传 themeTokens 的话,生成的区块会用默认样式,跟现有页面大概率不搭。传了之后,一致性会好很多,但也不是 100%,复杂组件还是需要人工微调。
3.3 自然语言转表单的实现细节
自然语言转表单是我觉得最实用的能力之一。中后台系统里表单太多了,每个表单都要前端手写,费时费力。用 AI 生成表单配置,前端只需要渲染,能省不少事。
实现上,SDK 会把自然语言描述转成一个表单 Schema,前端拿到 Schema 后用现有的表单渲染器渲染。Schema 的格式我贴一个实际的例子:
{ "fields": [ { "name": "productName", "label": "商品名称", "type": "string", "required": true, "maxLength": 100 }, { "name": "refundRate", "label": "退款率", "type": "number", "min": 0, "max": 100, "precision": 2, "suffix": "%" }, { "name": "refundReason", "label": "退款原因", "type": "enum", "options": ["质量问题", "尺寸不符", "物流损坏", "其他"], "multiple": true } ], "layout": "vertical", "labelWidth": "120px" }这个 Schema 的字段类型是有限的,SDK 文档里列了支持的类型:string、number、boolean、date、enum、array、object。超出这个范围的需求,AI 会尽量往最近似的类型上靠,但可能不准。所以我的经验是,用这个能力生成表单初稿,然后人工过一遍,比从零手写快,但别指望完全不用改。
注意:自然语言转表单的准确率跟描述的详细程度强相关。你写"做一个商品表单",生成的结果会很泛。你写"做一个商品表单,包含商品名称(必填,最多100字)、退款率(0到100的百分比,保留两位小数)、退款原因(多选,选项为质量问题、尺寸不符、物流损坏、其他)",生成的结果基本能直接用。
3.4 智能表格的列配置与数据映射
智能表格能力解决的是另一个高频痛点:表格列太多,用户找不到想要的列;或者不同角色的用户需要看不同的列。
OpenTiny NEXT 的做法是让用户用自然语言描述需求,SDK 生成列配置。列配置包括:显示哪些列、列的排序、列的宽度、列的格式化方式。
const tableConfig = await ai.table.generateColumns({ dataSource: 'orders', userIntent: '我只关心订单金额、状态和下单时间,金额要显示成带千分位的', role: 'finance' });生成的结果大概是这样:
{ "columns": [ { "field": "amount", "title": "订单金额", "width": 140, "formatter": "thousands" }, { "field": "status", "title": "状态", "width": 100, "formatter": "statusTag" }, { "field": "createdAt", "title": "下单时间", "width": 180, "formatter": "datetime" } ] }这里有个细节:formatter字段引用的是前端已经注册的格式化函数。SDK 不会生成格式化逻辑本身,只会引用。这样做的好处是格式化逻辑可控,不会出现 AI 生成的格式化代码有 bug 的情况。坏处是前端需要预先注册一批 formatter,没注册的 formatter 会降级成默认显示。
我一般会注册这么一批 formatter:thousands(千分位)、percent(百分比)、datetime(日期时间)、date(日期)、statusTag(状态标签)、currency(货币)、fileSize(文件大小)。基本覆盖中后台的常见场景。
4. 完整实操流程与关键环节实现
4.1 从零搭建一个带 AI 能力的订单管理页面
我拿一个实际做过的项目来拆。需求是:一个订单管理页面,支持自然语言筛选、智能列配置、以及一个 AI 助手侧边栏。
第一步是工程准备。我用的是 Vite + Vue 3,先装依赖:
npm install @opentiny/next-sdk @opentiny/vue第二步是配置代理层。我在 Vite 的 dev server 里加了一个中间件,生产环境用的是 Nginx 转发。代理层的核心逻辑是:接收前端请求,加上 API Key,转发给模型服务,把响应转成 SSE 格式返回。
// vite.config.js export default defineConfig({ server: { proxy: { '/api/ai/proxy': { target: 'http://localhost:3001', changeOrigin: true } } } });第三步是初始化 SDK。我在 main.js 里做全局初始化,把实例挂到 app.config.globalProperties 上,方便各组件调用。
import { createApp } from 'vue'; import { createAI } from '@opentiny/next-sdk'; import App from './App.vue'; const app = createApp(App); const ai = createAI({ proxy: '/api/ai/proxy', modules: ['chat', 'table', 'genui'], locale: 'zh-CN' }); app.config.globalProperties.$ai = ai; app.mount('#app');第四步是实现自然语言筛选。我在页面顶部放了一个搜索框,用户输入自然语言,回车后调用 SDK 的解析能力,把自然语言转成筛选条件,然后刷新表格。
async function handleNaturalSearch(input) { const filters = await this.$ai.table.parseFilter({ dataSource: 'orders', input, schema: orderSchema }); this.filters = filters; this.loadData(); }parseFilter返回的是一个结构化的筛选条件对象,格式跟大多数表格组件的 filter 配置兼容。如果返回的条件里有表格组件不支持的运算符,需要在前端做一次映射。
第五步是实现智能列配置。我在表格右上角放了一个"列设置"按钮,点开后除了常规的勾选列表,还有一个输入框,用户可以用自然语言描述想要的列。
async function handleColumnIntent(intent) { const config = await this.$ai.table.generateColumns({ dataSource: 'orders', userIntent: intent }); this.columns = config.columns.map(col => ({ ...col, formatter: this.formatters[col.formatter] || this.formatters.default })); }第六步是实现 AI 助手侧边栏。这个用的是 chat 模块,支持流式响应。
async function sendMessage(text) { const stream = await this.$ai.chat.stream({ messages: this.messages, context: { page: 'order-management', currentFilters: this.filters, visibleColumns: this.columns.map(c => c.field) } }); for await (const chunk of stream) { this.currentReply += chunk.content; } }context这个参数很关键。把当前页面的状态传给 AI,AI 的回答才能跟当前场景相关。不传 context 的话,AI 只能给通用回答,价值大打折扣。
4.2 参数计算与性能调优
SDK 用起来之后,性能是个绕不开的话题。我实测下来,几个参数对性能影响最大。
第一个是流式响应的 chunk 大小。SDK 默认的 chunk 大小是 20 个字符左右,这个值偏小,导致渲染频率过高。我调到 50 之后,渲染帧率明显改善,用户感知的流畅度反而更好,因为人眼对 50 字符的增量感知跟 20 字符差不多,但渲染压力小了一半。
第二个是 GenUI 的缓存策略。GenUI 生成一个区块大概需要 1 到 3 秒,如果每次进页面都重新生成,体验很差。我的做法是把生成结果按"用户意图 + 数据源 schema 哈希"做缓存,缓存有效期设 24 小时。同一个用户重复问同样的问题,直接命中缓存。
const cacheKey = `${userId}:${hash(intent)}:${hash(schemaVersion)}`; const cached = await cache.get(cacheKey); if (cached) return cached; const result = await ai.genui.generate({ intent, schema }); await cache.set(cacheKey, result, 86400);第三个是并发控制。一个页面里可能有多个 AI 调用点,如果同时发起,会打爆代理层。我加了一个简单的并发队列,最多同时 3 个请求,超出的排队。
class AIQueue { constructor(concurrency = 3) { this.concurrency = concurrency; this.running = 0; this.queue = []; } async add(task) { if (this.running >= this.concurrency) { await new Promise(resolve => this.queue.push(resolve)); } this.running++; try { return await task(); } finally { this.running--; const next = this.queue.shift(); if (next) next(); } } }4.3 降级策略与异常处理
AI 能力再强,也有不可用的时候。模型服务挂了、网络断了、代理层限流了,这些都会发生。降级策略做不好,AI 能力就是系统的脆弱点。
我的降级策略分三层。第一层是超时降级,请求超过 8 秒没响应,直接返回降级结果,同时后台继续等待,等到了再更新 UI。第二层是错误降级,请求失败时,如果是筛选类能力,降级成传统筛选表单;如果是列配置类能力,降级成默认列配置;如果是对话类能力,降级成提示文案。第三层是熔断降级,连续失败 5 次后,熔断 60 秒,期间所有请求直接走降级,不再尝试。
const circuitBreaker = { failures: 0, threshold: 5, cooldown: 60000, lastFailure: 0, canTry() { if (this.failures < this.threshold) return true; if (Date.now() - this.lastFailure > this.cooldown) { this.failures = 0; return true; } return false; }, recordFailure() { this.failures++; this.lastFailure = Date.now(); } };提示:降级策略一定要在接入 AI 能力的第一天就做,不要等出问题了再补。我见过一个项目,AI 能力上线后模型服务挂了半天,整个订单页面白屏,因为筛选逻辑强依赖 AI 返回。这种事故完全可以通过降级避免。
5. 常见问题与排查技巧实录
5.1 SDK 初始化失败与模块加载问题
问题现象:控制台报failed to load plugins,或者某个模块的方法调用时报 undefined。
排查思路:先看modules配置里有没有声明这个模块。OpenTiny NEXT 的模块是按需加载的,没声明就不会注册。再看 SDK 版本跟模块版本是否匹配,有时候 SDK 升级了但某个模块没跟着升,会出现接口不兼容。
我遇到过一次,ai.genui一直 undefined,查了半天发现是modules数组里写的是'genUI',大小写错了。SDK 对模块名是大小写敏感的,文档里写的是小写genui。这种低级错误排查起来最费时间,建议配置项都从文档里复制,别手敲。
另一个常见问题是代理层返回的格式不对。SDK 期望的响应格式是特定的,如果代理层直接把模型服务的原始响应透传,SDK 解析会失败。代理层需要做一次格式转换,把模型服务的响应转成 SDK 期望的格式。这个转换逻辑 SDK 文档里有示例,照着改就行。
5.2 流式响应中断与重连
问题现象:对话进行到一半,突然停了,没有报错,也没有后续内容。
排查思路:先看网络面板,确认是连接断了还是服务端没发数据。如果是连接断了,看断开的时机,如果是固定 30 秒或 60 秒断开,大概率是中间层(Nginx、网关)的超时配置。Nginx 默认的proxy_read_timeout是 60 秒,流式响应超过 60 秒没数据就会被断开。
解决办法是在 Nginx 配置里调大这个值,同时开启proxy_buffering off,让数据实时透传:
location /api/ai/proxy { proxy_pass http://backend; proxy_read_timeout 300s; proxy_buffering off; proxy_cache off; chunked_transfer_encoding on; }如果是服务端没发数据,看是不是模型服务在长文本生成时卡住了。这种情况需要在代理层加心跳,每隔 15 秒发一个空 chunk,保持连接活跃。
重连逻辑我一般这样写:检测到流中断后,记录已接收的内容,重新发起请求时把已接收的内容作为上下文传过去,让模型接着生成。这样用户感知上就是"卡了一下又继续了",而不是"从头再来"。
5.3 GenUI 生成结果不符合预期
问题现象:GenUI 生成的区块跟用户想要的差很远,或者生成的区块渲染报错。
排查思路:先看用户输入的意图描述是否足够具体。GenUI 对模糊描述的处理能力有限,"给我做个报表"这种输入,生成结果基本靠猜。要引导用户把需求说清楚,或者在 UI 上给一些示例输入。
再看数据源 schema 是否完整。GenUI 生成时会参考 schema 里的字段名、类型、注释。如果 schema 里字段没有注释,GenUI 只能靠字段名猜含义,准确率会下降。我一般会在 schema 里给每个字段加description,这个投入产出比很高。
const orderSchema = { fields: [ { name: 'amount', type: 'number', description: '订单金额,单位元' }, { name: 'status', type: 'enum', description: '订单状态', options: ['待付款', '已付款', '已发货', '已完成', '已取消'] }, { name: 'createdAt', type: 'date', description: '下单时间' } ] };如果生成的区块渲染报错,先看控制台的错误信息。常见的是生成的配置里引用了不存在的 formatter 或组件。这种情况在前端做一次校验,把无效引用替换成默认值。
5.4 常见问题速查表
| 问题现象 | 可能原因 | 排查动作 | 解决方案 |
|---|---|---|---|
| SDK 初始化报错 | 模块名拼写错误 | 检查 modules 配置 | 从文档复制模块名 |
| 某模块方法 undefined | 模块未声明 | 检查 modules 数组 | 添加对应模块 |
| 流式响应 60 秒断开 | 中间层超时 | 查看网络面板断开时机 | 调大 proxy_read_timeout |
| 流式响应无数据 | 服务端卡住 | 检查模型服务日志 | 代理层加心跳 |
| GenUI 结果偏差大 | 意图描述模糊 | 检查用户输入 | 引导具体描述或给示例 |
| GenUI 渲染报错 | 引用了无效 formatter | 查看控制台错误 | 前端校验并替换默认值 |
| 请求频繁超时 | 模型服务慢 | 查看请求耗时分布 | 调大 timeout 或加缓存 |
| 页面卡顿 | 流式渲染频率过高 | 查看渲染帧率 | 调大 chunk 大小 |
5.5 几个我踩过的坑
第一个坑是 API Key 泄露。早期图省事,把 API Key 写在了前端环境变量里,结果打包后能在 bundle 里搜到。虽然后来紧急换了 Key 并加了代理层,但这件事给我提了个醒:任何跟前端沾边的密钥,都要假设它会泄露。代理层不是可选项,是必选项。
第二个坑是没做请求去重。用户快速点击搜索按钮,会发起多个相同的 AI 请求,浪费额度还拖慢响应。后来加了一个请求指纹,相同指纹的请求在 1 秒内只发一次,后续请求复用第一个的结果。
第三个坑是忽略了移动端。桌面端跑得好好的,移动端一测,流式响应在弱网下频繁中断,GenUI 生成的区块在小屏上布局错乱。后来针对移动端单独调了 chunk 大小和 GenUI 的布局约束,才勉强能用。如果你的项目有移动端,建议一开始就把移动端纳入测试范围。
第四个坑是没做用量监控。AI 调用是有成本的,不监控的话,月底账单出来才知道超了。我后来在代理层加了一个简单的计数器,按用户、按接口、按天统计调用次数和 token 消耗,超阈值告警。这个投入不大,但能避免很多麻烦。
6. 我对 OpenTiny NEXT 这套东西的实际判断
用下来这几个月,我的整体判断是:OpenTiny NEXT 在"Web 应用接 AI"这件事上,方向是对的,抽象层次也是对的。它没有去追那些听起来很酷但落不了地的概念,而是老老实实把 SDK 接口设计好,把 GenUI 的边界控制好,把降级和异常处理考虑进去。这些东西不性感,但真正做工程的人知道,这些才是决定一个方案能不能上生产的关键。
当然也有我觉得可以更好的地方。比如 GenUI 的缓存策略目前需要业务方自己实现,SDK 层面如果能提供一套默认的缓存机制,接入成本会更低。再比如流式响应的重连逻辑,目前也是业务方自己写,如果 SDK 能封装一层,开发者会省不少事。还有文档,有些接口的参数说明不够详细,我是靠读源码才搞明白的。
不过这些都是细节,不影响整体判断。如果你正在评估怎么给现有 Web 应用加 AI 能力,OpenTiny NEXT 值得花两天时间做个 POC。我的建议是,先从一个具体的、边界清晰的能力开始试,比如自然语言转表单,跑通了再往其他能力扩展。不要一上来就全量接入,那样出了问题不好定位。
最后分享一个小技巧:接入 AI 能力之前,先把你现有系统的组件库、数据 schema、格式化函数整理一遍。这些东西整理得越清楚,AI 能力接入后效果越好。AI 不是凭空变出能力,它是基于你已有的东西做组合和生成。你已有的东西越规整,它组合出来的结果就越靠谱。这个准备工作看起来跟 AI 无关,但实际上是整个接入过程中最影响最终效果的一步。