01-设计篇-我用前端那一套手艺造了一个AI-Native工具

我用前端那一套手艺,造了一个 AI-Native 工具------比你想象的离谱得多

副标题:基于 Next.js 16 + Vercel AI SDK 6 + Anthropic / DeepSeek / Cursor / SiliconFlow 四路模型,我们做了一个叫"轻辔"的 Agent Harness 基线。这是第一篇:聊聊"为什么前端来主导设计,反而比后端主导更对"。

一、先把项目摊开

这一系列三篇文章会反复拿一个真实项目当解剖样本------内部代号叫轻辔 (技术仓库名 ai-native)。我先把它的"配置单"摆出来,免得后面讲架构的时候像在画 PPT:

维度 选型
框架 Next.js 16(App Router + Turbopack dev)
UI React 19 + Ant Design 6 + Tailwind v4
模型 SDK Vercel AI SDK 6 (ai / @ai-sdk/anthropic / @ai-sdk/deepseek / @ai-sdk/openai)
Agent SDK @cursor/sdk(子代理与 agent 流)
MCP @modelcontextprotocol/sdk(Pixso 等外部工具)
渲染 react-markdown + remark-gfm + rehype-raw/sanitize + mermaid
动态 UI @json-render/core + @json-render/react(Schema 驱动)
本地状态 dexie(IndexedDB) + 自建外置 Vault(加密文件)
校验 zod 4(env / 入参全管控)
测试 vitest + 自家 pnpm harness:eval
包管理 pnpm 10(强制,禁用 npm)
Node ≥ 18.18

它不是一个简单的"ChatGPT 套壳",定位写在 package.json 里那行:

轻辔:轻巧助手型 Agent Harness 可交付基线(Next.js + Vercel AI SDK + 任务分流 + Skills + harness:eval)。

这个项目有意思的点是:它绝大部分代码是纯前端味道的------TypeScript、Next.js、React、Vercel AI SDK,没有 Python、没有 LangChain、没有独立的 Agent 服务。整套 Harness(我们自己写的)、整套 Skills 协议、整套对话状态管理,跑在一份 Next.js 工程里。后端的"模型调用"只是一个被薄薄包了一层的 fetch。

而它跑得起来。

这一篇,我就想从设计层面说清楚一件事:为什么"前端口味"的工程哲学,对 AI-Native 工具是降维优势

二、起因:那条让我清醒的产品反馈

第一版上线的那个晚上,组里产品发了一条消息:

"你们做的这个东西像 Postman,不像 ChatGPT。"

那一版我们干了什么?后端写了 streaming 接口、前端做了一个 Markdown 渲染器、加了一个聊天框、套了个 SSE。技术上挑不出毛病------但用户进来三秒就关了。

我把产品的话翻译成前端术语:我们做的是一个"调用模型的 UI",不是一个"AI 用起来的产品"。我们把 AI 当后端写,把对话框当业务表单写,把模型当一个偶尔会卡住的接口写。

那一周整套东西推倒重来,"轻辔"这个项目就是从那时候诞生的。它的第一个设计原则,我后来写在了仓库 CLAUDE.md 第一条:

定位:轻巧助手型 Harness 基线(Chat UI + lib/harness 编排 + 多后端/Skills 执行)。不是 IDE / AIGC 平台。

短短一句话,但下面三个观念翻转,都是从这里推出来的。

三、观念翻转一:对话框不是 input,它是一个"运行时入口"

刚开始做 AI 产品的人最容易踩的坑,是把那个聊天输入框当 <textarea> 设计。

但你试着这样想:用户敲下"帮我把这段代码重构一下,顺便检查有没有性能问题",这一句话里藏着多少东西?

  • 一个意图(改代码)
  • 一个附带任务(性能检查)
  • 一个隐含上下文(他指的是哪段代码)
  • 一个预期输出(diff 还是建议)
  • 一个风险等级(改代码 ≠ 闲聊)

如果你把这玩意当 onChange 处理,你就废了。

正确姿势是:把对话框当作一个状态机的入口,它的对面不是 API,而是一整套调度。前端要做的事不是把字符串发出去,而是在用户回车之前,先把这一轮的"路由"算清楚。

在轻辔里,这个动作有个具体函数,叫 buildHarnessPlan(userText),落在 src/lib/harness/plan-core.ts。它返回的对象长这样:

ts 复制代码
plan.taskFlow.mode      // "none" | "gccd" | "chain"
plan.capability.kind    // "image" | "video" | "coding" | "research" | "local_workspace" | "general"
plan.capability.skillId // 比如 "generate_video"
plan.subagent.suggested // 是否建议召唤子代理
plan.subagent.capability// "research" | "coding" | "review" ...

注意------这个函数不调任何模型 。它就是一份 TypeScript 纯函数算出来的"调度表",80% 是关键词正则,比如 task-flow.ts 里那个 MUTATION_RE:

ts 复制代码
const MUTATION_RE =
  /(?:改代码|写代码|修改|修复|重构|实现|集成|迁移|部署|排查|调试|...|fix(?:\s|$)|bug|deploy|typecheck|lint\b)/i;

前端工程师做表单校验、做路由匹配、做请求合并那套手艺,原封不动就能干这个。规则能搞定的别花钱让模型搞------这是一种朴素到不能再朴素的工程审美,但 AI 圈很多人忘了。

我们项目的三种"任务流"分别对应:

mode 何时 行为
none 闲聊、极短确认 不注入任何框架,直接走模型
gccd 方案、只读分析、生图/生视频 注入 Goal/Context/Constraints/Done-when 简报
chain 改代码、修 bug、/chain 注入五关卡执行链(理解→计划→执行→验证→交付)

这是一个完全前端化的设计 ------它本质上是在做意图分类,而前端是写状态机最熟的人。

四、观念翻转二:流式不是炫技,是"感知带宽"

新人做流式输出最容易出现的灾难,是把流式当 loading 用

模型边吐字边显示,但用户不知道自己在等什么、能不能取消、还要多久、出问题了没有。这跟 2010 年的"加载中..."转圈圈没区别------只不过现在转的是字。

前端工程师手里有一整套"感知带宽"工具,但很多人在做 AI 产品时忘了用。轻辔里把这些用了个遍:

骨架屏的思路 :Harness 算出 mode === "gccd" 时,前端先渲染四块空骨架------目标 / 上下文 / 约束 / 完成标准。模型流式生成的内容填进这四块。用户从第一帧就知道这一轮的形状。

乐观更新的思路 :流式 metadata 里带了 taskFlowModeharnessCapabilitysubagentSuggested,这些字段在模型还没说完整一句话之前就到了前端,我们用它点亮顶部的 "Harness 条"------告诉用户"本轮要走 chain、能力是 coding、可能调用 explore 子代理"。

取消的思路 :Esc 即时 abort,这是从 IDE 借来的肌肉记忆。AbortController 不是后端的事,是前端的本职。

错误边界的思路 :流到一半断了不能让 UI 变成残骸。React 18 的 ErrorBoundary,加上 useChatRunStreamReconnect 这个 hook(实际代码就在 src/features/chat/client/use-chat-run-stream-reconnect.ts),会自动检测后台 run 是否仍可 replay,SSE 断了自动 resume,用户感知不到中间故障。

实际效果:同样的模型、同样的 prompt、同样的延迟,只是把"模型还没说话的那 800ms"塞了一个简报骨架,留存率(我们内部叫"等下去率")提升了三倍多。

模型那 800ms 是物理事实改不掉,但用户对那 800ms 的感知,只能由前端来改

五、观念翻转三:UI 是动态生成的,但骨头是静态的

"动态 UI"是 AI 产品里最迷人也最危险的概念------模型说生成表单就生成,模型说画图表就画。听着科幻,做起来九成翻车。

翻车的根因:模型输出是不稳定的,但 UI 必须是稳定的 。模型今天给你 {type: "form"},明天给你 {kind: "FORM"},后天给你一段 HTML。一旦字段名漂移,前端渲染层就崩了。

轻辔的解法,前端工程师一看就懂------Schema-Driven UI 。我们用了 @json-render/core@json-render/react 这一对儿,然后在它之上定义了一份叫 FieldSpec 的协议,文件落在 src/features/ai-design/**

模型的活儿是按协议产出 JSON,带 dependsOnoptionsUrlmfe 这些字段;前端拿到 JSON 走一套固定的 catalog 渲染。创意层 (模型能自由组合字段、校验、联动)和渲染层(组件是闭集,样式是设计审过的)解耦得干干净净。

这是不是和 Low-Code 一模一样?是的。Low-Code 的全部经验,在 AI-Native 时代会再火一次。区别只是:Low-Code 时代写 Schema 的是产品经理,AI-Native 时代写 Schema 的是模型。但渲染层是同一套。

如果某个交互复杂到 catalog 表达不下来,我们走 MFE 兜底:模型给一个 MFE id + 参数,前端拉一个独立打包的微应用嵌进来。module federation / qiankun / wujie 你练过的都用得上。

六、设计原则:克制 > 炫技

做了一年多,我个人最大的感受是:

AI 产品的设计上限,不在于你给它接了多少能力,而在于你拒绝接了多少能力。

这条原则在轻辔的代码里有几个具体落点:

反面 1:默认开十个工具

最早我们想着"反正模型自己会选",把生图、生视频、读文件、查网页、跑代码都挂上去。结果模型每一轮都要看一遍工具说明,token 蹭蹭涨,响应变慢,还经常调用了用户根本没要的工具。

后来改成按意图懒加载 ------src/lib/chat/capability-routing.ts 这个文件就是干这事的。聊闲天一个工具都不挂,聊到"画"才挂 generate_image,聊到"视频"才挂 generate_video,聊到"代码"才挂 host_workspace

这是前端 code-splitting 的思路,落在了 system prompt 这个新的"包"上。

反面 2:全局轮询

为了让 UI 实时反映后端状态,我们曾经在前端挂了一个 5 秒一次的全局心跳。生产环境跑了一周,有人投诉笔记本风扇起飞。

现在的规则,全部写在了 docs/轻量与简洁原则.md------客户端生产态零常驻轮询 ,只在 busy 状态、断连状态触发探活;dev 自我修复也仅 NODE_ENV=development 才生效。document.visibilitychange 一切走,该停就停。

反面 3:把所有上下文都喂给模型

"context 越多越准"------错了。喂太多反而稀释信号,而且烧钱。轻辔的会话上下文管理走两个原则:

  1. 历史消息走 dexie(IndexedDB) + 加密 Vault,前端本地有完整副本
  2. 喂给模型的,是经过任务路由筛过的子集------闲聊不带代码上下文,改 bug 不带前几轮闲聊

这跟前端做请求最小化、props drilling 治理是同一种洁癖。

七、写在第一篇的最后

如果你是前端工程师,正在考虑入场 AI 产品,我想说三件事:

第一,你不需要先变成算法工程师。轻辔整个项目里没有一行算法代码,我们只是在调 Anthropic、DeepSeek 这些公司的 API。AI-Native 工具的胜负手八成不在模型,在体验。

第二,把你十年前学的 Web 工程基本功翻出来。请求合并、code splitting、Schema 驱动、错误边界、乐观更新、状态机、可访问性------这些"老掉牙"的东西在 AI-Native 里全是新刚需。

第三,先做减法,再做加法 。轻辔的整个 pnpm lint 流水线里挂了一个特殊检查叫 check:lightweight------专门拦截"又往项目里塞重型依赖"的提交。这套自虐式的 lint,让我们一年里没有一次"功能变多但产品变重"的退化。

下一篇,我会把架构层完整摊开:三层是怎么分的、lib/harness 长什么样、Skills 协议怎么写、Vault 为什么不上云、写保护怎么挡住模型的失误。

相关推荐
大圣编程39 分钟前
Python中continue语句的用法是什么?
开发语言·前端·python
yuhaiqiang40 分钟前
随手 vibecoding 的浏览器插件已经 6000 多次下载,聊聊他的产品设计
前端·后端·面试
之歆1 小时前
Vue商品详情与放大镜组件
前端·javascript·vue.js
再吃一根胡萝卜2 小时前
如何把小米 MiMo 接入 CodeBuddy,打造私有 Agent
前端
负责的蛋挞3 小时前
异步HttpModule的实现方式
java·服务器·前端
丹宇码农6 小时前
把 HLS 字幕玩出花:zwPlayer 如何让 M3U8 视频支持全文搜索、翻译与码率自适应
前端·javascript·音视频·hls·视频播放器
2501_943782356 小时前
【共创季稿事节】猜数字游戏:二分法思维与交互式反馈
前端·游戏·microsoft·harmonyos·鸿蒙·鸿蒙系统
GV191rLvq6 小时前
基于Socket实现的最简单的Web服务器【ASP.NET原理分析】
服务器·前端·asp.net
吠品6 小时前
LangChain 里 tool_call_id 为空?一次 MCP 工具集成的排查记录
前端