本文介绍了为什么React适用于大语言模型(LLM)的后端工作流程,并推荐了GenSX框架作为构建这些工作流程的最佳选择。文章阐述了现有框架的问题,如过度抽象、静态图形定义、全局状态管理等,以及这些问题对AI开发的影响。文章还介绍了GenSX框架的特点和优势,如采用React风格的编程模型、清晰的数据流、可复用的组件等。最后,文章呼吁Node.js开发者加入AI开发的行列,并分享了GenSX框架的GitHub项目链接。
现有框架存在的问题,如过度抽象、静态图形定义、全局状态管理等,使得AI开发困难。而React风格的编程模型提供了清晰的数据流和可复用的组件,使得AI工作流更加易于构建和管理。
GenSX框架采用React风格的编程模型,提供了清晰的数据流和可复用的组件。它解决了现有框架存在的问题,如过度抽象和全局状态管理。同时,它还提供了一系列LLM相关的工具包,使得AI开发更加便捷。
未来,Node.js开发者将成为AI工具的最大用户群体,同时也是催生最大的AI生态系统的重要力量。他们可以通过使用GenSX等框架,参与到AI开发的行列中。
前言
介绍了为什么 React 适用于大语言模型(LLM)的后端工作流程,并推荐了 GenSX 框架作为构建这些工作流程的最佳选择。今日前端早读课文章由 @Evan Boyle 分享,@飘飘翻译。
译文从这开始~~
出人意料的是,React 竟然是 LLM 后端工作流、智能体和持久性的最佳编程模型。
如今构建 LLM 应用仍然很糟糕
如果你曾尝试用大型语言模型构建一个超出简单单轮对话界面的应用,你一定感受过其中的痛苦。目前的生态系统一团糟:
1、一切都是以 Python 为先。 JavaScript 和 TypeScript 已经在前端和后端占据主导地位,但当涉及到 AI 和智能体的开发时,主流框架依然停留在一个过度抽象、依赖全局状态的 Python 生态中。几乎找不到 JavaScript 或 TypeScript 的身影,也没有声明式、可复用组件的概念。
2、当前的工作流抽象方式是错误的。 流行的框架迫使开发者使用静态的图形定义(static graph definitions),这种定义缺乏灵活性,难以理解。我在白板前浪费了太多时间,试图弄明白自己写的代码在做什么。
3、全局状态管理简直是噩梦。 许多后端开发者构建了一些极其复杂的 “鲁布・戈德堡(Rube Goldberg)” 式机器,必须将所有状态在整个工作流中不断传递。然而,这种方式完全不适用于 AI 智能体开发,因为你需要灵活调整和快速实验,但全局状态让这一切变得困难重重。
那么,如果构建智能体的过程就像写一个 React 组件一样简单,会怎么样?
interface BlogWriterProps{
prompt: string;
}
exportconst WriteBlog = gsx.StreamComponent<BlogWriterProps>(
"WriteBlog",
({ prompt })=>{
return(
<OpenAIProvider apiKey={process.env.OPENAI_API_KEY}>
<Research prompt={prompt}>
{(research)=>(
<WriteDraft prompt={prompt} research={research.flat()}>
{(draft)=><EditDraft draft={draft} stream={true}/>}
</WriteDraft>
)}
</Research>
</OpenAIProvider>
);
},
);
上周,我们开源了 GenSX,这是一个用于构建 AI 智能体和工作流的框架,采用类似 React 组件的开发方式。但与 React 不同,GenSX 是一个基于 Node.js 的后端框架,专为 生产级 AI 应用 设计,具备 单向数据流,且完全没有 “重新渲染”(re-rendering)的概念。
“无框架” 运动的兴起
“无框架”(No Framework)运动正在迅速升温,越来越多的开发者正在放弃现有的框架。在 Hacker News 和 Reddit 上,有无数帖子都在讨论当前 LLM 框架的种种问题,数量之多,简直铺天盖地。
这些框架 确实能让你快速入门,但却难以扩展。它们通过 过度抽象 一些本不该被抽象的东西,把开发者逼进了死角。这种做法就像是我们 完全忽视了过去 30 年在工作流引擎方面的经验教训,尤其是在 开发体验(ergonomics) 方面的积累。
我最近看到某个框架的示例代码,看上去简洁又优雅:
new Agent(..., new Memory(), new RAG());
任何在这个领域花费过大量时间构建产品的人都能看出,这种做法充满了过度抽象的味道。这就好比调用
new DatabaseSchema()
却不传递任何参数,这毫无意义。从根本上讲,AI 领域的许多问题都与具体的使用场景和应用特定的数据模型紧密相连。华而不实的抽象可能会让你在第一天感觉良好,但到了第十天,你可能已经开始抓狂了。
这些框架简直糟透了!但如果因此就全盘否定它们,那就忽略了一个事实:为昨日的互联网设计的基础设施几乎与我们今日的需求背道而驰。
如今的 AI 工作负载已经彻底打破了过去的所有假设。P99 请求延迟已经不是 500 毫秒了。对于最简单的单轮对话,你可能要等上几秒才能从 LLM(大语言模型)那里拿到第一个 token。而当你开始处理文档、并行调用 API 时,时间往往要花上几分钟甚至更久…… 更不用说那些能在后台运行数小时的 AI 代理(Agent)。这些过去极为小众的需求,如今却变成了家常便饭。
如今,全世界的工程师都在接触 AI,不知不觉间,他们已经变成了数据工程师和工作流工程师。但对于大多数全栈工程师来说,他们不想背负过去工具(如 Airflow 或其他重量级的持久化执行引擎)带来的负担。他们想要的是一种能直接解决这些问题,并且像写普通应用代码一样简单的编程模型。这,才是真正值得解决的问题。
从实践中学习
GenSX 其实是一次意外的转型。最初,我和团队花了九个月时间在开发 Cortex Click,一个用于自动化开发者营销工作流的工具,并且吸引了许多付费用户。我们把几十个 AI 代理工作流投入生产,其中不少都非常复杂,运行时长超过 5 分钟,并且涉及数千次 LLM 调用。
我们当时使用了一款热门的 AI 框架,但在使用过程中,我们对以下几点越来越感到不满:
-
基于 DSL(领域特定语言)的图构建方式极难理解 —— 代码本身毫无可读性,每次调试时我都得拿出白板才能理清楚逻辑。
-
-
静态图的特性让工作流难以灵活调整,实验和优化变得非常麻烦。
-
整个框架感觉像 Python 的移植版本,而不是一个真正符合 Node.js 生态的原生解决方案。
这些可不是纯粹的学术问题。我们的工作流涉及多个编辑阶段,比如去除营销术语、添加吸引人的开头、优化文风、代码检查和验证等。但当我们不断叠加新步骤时,之前的优化成果往往会被意外回滚,甚至产生冲突。
最终,我们面临了一个优化难题:在一个包含 N 个步骤的工作流中,哪种执行顺序能最大化评估指标?然而,由于框架采用静态图结构,并且全局状态交错复杂,每次尝试新的排列都要花上 10 多分钟来修改大量样板代码 —— 这根本无法持续下去。而且,该框架对 “长时间运行” 场景也没有提供任何帮助。
最后,我愤怒地放弃了这个框架,开始寻找其他替代方案。然而,令我失望的是,市面上的所有 AI 框架都存在同样的核心问题和设计缺陷!
于是,在感恩节期间,本来只是一次临时的尝试,想要解决自己的问题,结果却意外催生了 GenSX。
React 作为工作流引擎
我知道,这听起来很反常。谁会想到类似 React 的模型能在后端发挥作用,而且还能保持良好的可维护性?但事实证明,我错了。前端世界几乎完全依赖这种模式,而一旦你深入其中,就会发现自己已经离不开它了。它能直接切入状态管理的复杂性,提供一种专注于最终结果的编程模型。
同样的思路也适用于 LLM(大语言模型)工作流。
封装与模块化
React 让开发者能够复用组件,解耦 “做什么” 和 “怎么做”,并且可以优雅地封装共享上下文,比如主题、日志、跟踪等。
在现代前端开发中,数据获取通常与 UI 层分离 —— 这就是 “前端 for 后端”(Frontend for Backend) 的理念。这种拆分让一个团队专注于高效查询数据,而另一个团队专注于如何展示数据。
如果把这个模式应用到工作流中,就能通过清晰定义的契约(contract)实现职责分离。每个组件(或工作流步骤)都封装自己的逻辑,只暴露结构化的输入和输出。
如果你用过 React,那么在 GenSX 里写组件的体验会让你感到熟悉:
interface WriteDraftProps{
research: string[];
prompt: string;
}
const WriteDraft = gsx.Component<WriteDraftProps, string>(
"WriteDraft",
({ prompt, research })=>{
const systemMessage =`You're an expert technical writer.
Use the information when responding to users: ${research}`;
return(
<ChatCompletion
model="gpt-4o-mini"
temperature={0}
messages={[
{
role:"system",
content: systemMessage,
},
{
role:"user",
content:`Write a blog post about ${prompt}`,
},
]}
/>
);
},
);
输入数据,输出数据。相比在整个管道中散布全局状态,这种方式更加可控。同时,组件和工作流步骤都是默认可复用的,可以在代码库中随时调用,并且易于独立测试。
组合优于抽象
在 GenSX 里,你可以构建可复用的组件,并像拼积木一样组合它们。React 风格的子函数模式使数据依赖关系变得清晰,同时让整个数据管道一目了然。
考虑这个 GenSX 组件,它接收一份黑客新闻故事列表,并为每一条生成由语言模型生成的摘要和情感分析:
const AnalyzeHNPosts = gsx.Component<AnalyzeHNPostsProps, AnalyzeHNPostsOutput>(
"AnalyzeHNPosts",
({ stories })=>{
return{
analyses: stories.map((story)=>({
summary:<SummarizePost story={story}/>,
commentAnalysis:<AnalyzeComments comments={story.comments}/>,
})),
};
},
);
这段代码从上到下清晰易读,混合了声明式组件和普通的 JavaScript 循环。几行代码就可以执行数千次 LLM 调用,而框架会自动并行化执行、处理重试,并在合适的地方进行跟踪和缓存。
这种模式符合前端对抽象的理解 —— 将组件组合在一起,并通过上下文(context)共享依赖。它允许团队在恰当的地方进行抽象,既不过度封装,也不会暴露不必要的复杂性。
现在,让我们看看现有框架是怎么做的(一个简单的线性数据流):
const graph =newGraph()
.addNode("hnCollector", collectHNStories)
.addNode("analyzeHNPosts", analyzePosts)
.addNode("trendAnalyzer", analyzeTrends)
.addNode("pgEditor", editAsPG)
.addNode("pgTweetWriter", writeTweet);
graph
.addEdge(START,"hnCollector")
.addEdge("hnCollector","analyzeHNPosts")
.addEdge("analyzeHNPosts","trendAnalyzer")
.addEdge("trendAnalyzer","pgEditor")
.addEdge("pgEditor","pgTweetWriter")
.addEdge("pgTweetWriter",END);
你能一眼看出这段代码的逻辑吗?对我来说,需要五分钟和一块白板。即便到了那一步,代码里还藏着一堆需要追踪的全局状态。想要调整流程或添加一个新的步骤?请准备好花 20 分钟重构全局状态的定义和传递方式。
兼具声明式与动态性的完美融合
如果你是 Python 开发者,可能还记得 TensorFlow 和 PyTorch 之争。TensorFlow 早期占据市场主导地位,但它的静态计算图(DAG)模式限制了灵活性。PyTorch 则允许开发者动态构建 DAG,让代码更具表现力,最终推翻了 TensorFlow 的统治,甚至迫使 TensorFlow 2 采用了类似 PyTorch 的动态计算图。