解开 LLM 意大利面条:从命令式取数到声明式同步绑定
解开 LLM 意大利面条从命令式取数到声明式同步绑定【免费下载链接】electricThe agent platform built on sync.项目地址: https://gitcode.com/GitHub_Trending/el/electricLLM 正在大规模生成代码而这些生成的代码正以命令式方式获取数据最终演变成难以维护的意大利面条。本文基于 Electric 的这篇官方博文展开剖析命令式取数为何必然走向失控并给出解药让 LLM 声明组件所需的数据依赖把网络请求与数据传输交给同步引擎sync engine托管。读完你会理解声明式数据绑定GraphQL fragments、Zero、PGlite live query、Electric syncShapeToTable的运作原理并掌握用 llms.txt AGENTS.md 让 LLM 生成 Electric 同步代码的实操路径以及仓库中 linearlite 等真实示例的源码佐证。LLM 生成代码为什么必然走向意大利面条博文的核心论断非常直接LLM 在生成代码而生成的代码在命令式地获取数据这必然导向一团意大利面条。以 Bolt 和 Lovable 这类用 Supabase 生成应用的平台为例LLM 生成的典型取数代码长这样const fetchTodos async () { try { setLoading(true) const { data, error } await supabase .from(todos) .select(*) .order(created_at, { ascending: false }) if (error) throw error setTodos(data || []) } catch (error) { console.error(Error fetching todos:, error) } finally { setLoading(false) } }这段代码的问题在于它是**命令式imperative**的一个内联在 React 组件里的函数在组件挂载时被调用一次。你看到问题了吗生成的组件越多发出的请求就越多更多 loading spinner、更多的请求瀑布waterfall、更多的失败模式当组件没有在数据更新时重新拉取就会产生越来越多的陈旧数据stale data。从源码层面看这种模式在仓库中也有对应体现。例如 linearlite 示例的写入路径 中开发者需要手动用pg.live.query监控本地表里synced false的行再用互斥锁包裹写回操作并自行处理sent_to_server标记与modified时间戳的并发冲突——这恰恰说明当数据获取、同步、冲突处理都要由开发者或 LLM逐行命令式地写出来时代码复杂度会随组件数量线性甚至超线性膨胀。解药声明式数据依赖 同步引擎那么解决方案是什么博文给出的答案是声明式数据依赖declarative data dependencies 同步引擎sync engine。让网络请求和真正的数据获取委托给一个为你优化数据传输与放置data placement的系统。具体落地路径分两步从逻辑数据模型logical data model开始LLM 完全理解数据模型让它生成和演进 schema 毫无压力不要让它写取数代码改为告诉它使用同步引擎生成**声明组件需要什么数据**的代码而不是如何获取数据的代码。这个思路其实并非新事物博文明确指出它一直是状态传输的终局endgame for state transfer是 Karpathy Software 2.0 和 Rich Hickey Simple Made Easy 的关键要素。只是在 LLM 大规模写代码的今天它的重要性被放大了。三种声明式方案对比GraphQL fragments / Zero / PGlite ElectricGraphQLfragment 聚合顶层拉取GraphQL 通过 fragment 声明组件的数据需求再在顶层做一次聚合拉取export const TodoFragment graphql fragment TodoFragment on Todo { id text complete createdAt updatedAt relationship user { id name } } 组件通过TodoFragment声明自己需要id、text、complete等字段以及关联的user信息取数与组合由 GraphQL 运行时统一处理。Zero与 Supabase 几乎一样的代码底层委托给同步引擎Zero 的写法与前面 Supabase 示例几乎一模一样但关键在于它把取数真正委托给了底层同步引擎function TodoList() { const z useZeroSchema, Mutators() let todoQuery z.query.todo .related(user) .limit(100) const [todos, todosDetail] useQuery(todoQuery)表面上是同一套.query、.related、.limit的链式 API用户心智负担几乎为零但数据实际由同步引擎负责传输与维护。本地优先Valtio / PGlite 本地存储 Electric 同步引擎博文指出一般意义上的本地优先local-first系统采用**本地存储如 Valtio 状态库或 PGlite 嵌入式数据库 同步引擎如 Electric**的组合。核心是syncShapeToTable——把服务端的 shape表数据的子集同步到本地表const shape await pg.electric.syncShapeToTable({ shape: { url: http://localhost:3000/v1/shape, params: { table: todo, }, }, table: todo, primaryKey: [id], })这段代码在仓库中有着完整的真实对应物。在 linearlite 示例的 sync.ts 中真实项目正是这样把issue表同步到 PGliteconst issuesSync await pg.sync.syncShapeToTable({ shape: { url: issueUrl.toString(), params: { table: issue, source_id: ELECTRIC_SOURCE_ID, }, }, table: issue, primaryKey: [id], shapeKey: issues, commitGranularity: up-to-date, useCopy: true, onInitialSync: async () { issueShapeInitialSyncDone true await pg.exec(ALTER TABLE issue ENABLE TRIGGER ALL) doPostInitialSync() }, })可以看到相对于博文里的最小示例真实源码还补充了关键工程细节url通过new URL(...)构造并支持追加secret认证参数sync.tsshapeKey用于区分多个同步的 shapelinearlite 同时同步issue与comment两个表各自使用独立的 shapeKeycommitGranularity: up-to-date控制提交粒度useCopy: true走批量复制加速初始同步onInitialSync回调里重新启用数据库触发器确保同步进来的数据不会被本地触发器重复处理。syncShapeToTable同样出现在 write-patterns 示例的 db.ts 中你可以把该函数视为本地优先应用接入 Electric 的标准入口。组件直接面向本地存储编程数据同步到本地后组件就可以直接面向本地存储编程。以 PGlite 为例用 **live SQL 查询useLiveQuery**声明组件需要的数据function TodoList() { const todos useLiveQuery(SELECT * FROM todos;, []) }注意此时useLiveQuery声明的是数据需求而不是取数动作。当本地数据库数据变化时live query 会自动驱动组件重渲染——没有手动refetch没有 stale data。仓库中的 linearlite 看板页面是这一模式的完整实证。IssueBoard.tsx 中组件对每个状态的列分别调用useLiveQuery把 live query 结果直接作为 UI 数据源拖拽改变 issue 状态时只需一条pg.sqlUPDATEIssueBoard.tsxlive query 会自动刷新界面甚至利用movedIssues状态处理 live query 更新与拖拽之间的瞬时竞态。整个渲染管线里看不到任何手写 fetch。云端数据库 同步引擎LLM 不需要知道如何取数这套方案与 Supabase 或 Neon 这类云端数据库托管完美配合云端Supabase / Neon 承担数据库托管本地PGlite / Valtio 作为嵌入式本地存储中间Electric 同步引擎在后台管理网络请求与真正的数据获取。于是 LLM完全不需要知道数据是怎么取来的——它更不该写那些在渲染管线各个角落、各个阶段乱发 fetch 请求的代码。声明式绑定declarative bindings把数据从哪里来、怎么更新这件脏活彻底从生成代码中剥离出去。从仓库角度看website/docs/agents 目录下的 index.md、quickstart.md、walkthrough.md 等文档正是围绕让 LLM 生成基于 Electric 的同步代码这一目标的完整配套指南而 website/blog/posts/2025-04-09-building-ai-apps-on-sync.md 这篇姊妹博文则进一步阐述了AI 应用构建在同步之上的观点。实操让 LLM 用 Electric 生成代码博文给出的落地建议非常务实把 llms.txt 加入你的项目上下文。llms.txt是面向 LLM 的索引文件让模型在生成代码前就能读到 Electric 的同步能力与用法直接告诉你的 LLM 使用 Electric——用一句清晰的指令替代冗长的取数逻辑描述进一步参考 AGENTS.md 文档 与《Building AI apps? You need sync》一文后者是AI 应用构建在同步之上的完整论述。一句话总结本博文的核心主张告诉你的 LLM停止编写命令式取数的代码转而使用同步引擎如 Electric的声明式数据绑定。小结对比维度命令式取数Supabase 风格声明式绑定Electric/PGlite 风格代码形态组件内内联 async 函数挂载时手动调用useLiveQuery/syncShapeToTable声明数据需求数据更新需手动 refetch易产生 stale data本地数据变更自动驱动重渲染请求数量随组件数量线性膨胀形成瀑布由同步引擎统一优化传输与放置LLM 心智负担需生成取数、loading、错误处理全链路只需声明组件需要什么数据失败模式每个请求都是新失败点同步引擎统一处理重连、重试与一致性核心结论清晰且可执行让 LLM 面向数据需求编程而不是面向数据获取编程。前者把网络与同步的复杂度收敛到同步引擎这一层后者则会在渲染管线的每个角落埋下 fetch 请求最终长成一团无法理清的意大利面条。【免费下载链接】electricThe agent platform built on sync.项目地址: https://gitcode.com/GitHub_Trending/el/electric创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考