【深度思考】为什么 React 出人意料地成为 LLM 工作流程的最佳模型
@zs.duan
【深度思考】为什么 React 出人意料地成为 LLM 工作流程的最佳模型
阅读量:51
2025-04-25 15:39:05

前言

介绍了为什么 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 组件一样简单,会怎么样?

interfaceBlogWriterProps{
   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) 方面的积累。

我最近看到某个框架的示例代码,看上去简洁又优雅:

newAgent(...,newMemory(),newRAG());

任何在这个领域花费过大量时间构建产品的人都能看出,这种做法充满了过度抽象的味道。这就好比调用 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 里写组件的体验会让你感到熟悉:

interfaceWriteDraftProps{
   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 的动态计算图。

目前的 AI 代理框架大多沿袭了 TensorFlow 早期的静态模型,而我一直期待更接近 PyTorch 方式的解决方案。

React 的模型正好能提供 最佳的平衡。JSX 远不止用于定义 DOM,它可以表达树状结构,并且可以在运行时动态渲染元素,这实际上让你拥有了一个可以 动态构建 DAG 的引擎:

const AgentWorkflow = gsx.Component(
   "AgentWorkflow",
   <AgentStep>
     {(result)=>
       result.needsMoreWork ?(
         // 递归调用,形成 AgentWorkflow -> AgentStep -> AgentWorkflow -> … 的循环
         <AgentWorkflow />
       ):(
         result
       )
     }
   </AgentStep>,
);

这种方式可以优雅地表达非确定性的 AI 代理模式,比如循环和反思(reflection)。声明式代码可读性极佳,同时仍然具备动态灵活性。

JavaScript 依然是未来

目前 LLM 生态系统中最让我失望的一点,是 缺乏一个强大的开源社区。虽然有很多框架,但很少有可以直接 npm install 的、高质量的 LLM 组件。唯一的 “社区” 似乎就是 Reddit 上不断涌现的 Prompt Hack 讨论帖。

这太可惜了。

造成这种现状的原因有很多,其中一个就是现有框架过于臃肿,依赖全局状态,不利于模块化和共享。而 JavaScript 及其 npm 生态与 Python 及 pip 形成了鲜明对比

看看 React 生态,有上百个 Star 破千的组件库,甚至专门做按钮的包都能火起来!这个社区 乐于构建和分享。

过去,传统机器学习都是用 Python 完成的。但 LLM 让 AI 开发变得更加开放,构建出色的 AI 产品 不再依赖 PhD 级别的数学功底,更多取决于工程能力和产品思维。

未来几年,Node.js 开发者将成为 AI 工具的最大用户群体,同时也会催生最大的 AI 生态系统。

尝试 GenSX,加入构建者行列!

在使用现有工具后,我能理解为什么有人提倡 “无框架” 开发。但事实上,我们都在寻找 “正确的框架”—— 既要足够灵活,避免过度抽象,又要足够强大,以适应未来六个月 LLM 领域的飞速发展。

GenSX 提供了一种熟悉但全新的方式来构建 AI 代理。它是 开源的,采用 Apache 2.0 许可证。

当前,它提供了一种干净且可扩展的编程模型,并配备了一系列 LLM 相关的工具包。而在不久的将来,你的所有组件和工作流都将默认具备持久化能力,并能无缝映射到可持久化存储中。而且,这种基于纯函数的组件模式,天生适合缓存和状态管理。

如果你对 Node.js、框架设计和基础设施构建 感兴趣,欢迎加入我们的 Discord 讨论!

GenSX 诞生于对 JavaScript 生态的热爱,以及多年来在生产环境中构建 AI 代理的痛苦经验。

📌 查看 GitHub 项目,立即开始使用!Happy building! 🚀

GenSX GitHub:https://github.com/gensx-inc/gensx

评论:

还没有人评论 快来占位置吧