资讯详情

AI养育计划:用智能体与知识库养出Flutter开发搭档

📅 2026/10/27 13:49:54 | 华诺云谱 👁 阅读
AI养育计划:用智能体与知识库养出Flutter开发搭档
三月初我在一个内部技术群里看到有人吐槽让 AI 帮忙写 Flutter前两句还挺像样第三句就开始调用一个根本不存在的ThemeData.primaryColorLight2硬生生把编译时间从三秒拖成了半小时。这话我太有共鸣了。过去大半年我一直在折腾一件事——在扣子Coze这类智能体平台上用一套“养育”而不是“提问”的方式把一个通用大模型慢慢带成一个懂 Flutter、记得我项目结构、会按我团队规范写代码、还能主动提醒版本陷阱的长期搭档。我把这套东西叫做AI 养育计划而这个方向的最终产物就是我口中的“Flutter 大师”。先说清楚它能做什么它不是帮你一次性生成整个 App 的魔法按钮而是一个有固定人格、有知识库、有工作流、有记忆的智能体负责在 Flutter 日常开发里做四件事——需求澄清、结构建议、代码草稿生成、报错归因。它解决的核心问题是通用模型“答得对但答得不像我项目里的人”这个尴尬它能记住我们用的是 Dart 3 的super.key写法、记得列表页必须做RepaintBoundary、记得不允许在build里 new 对象。适合谁看适合已经在用 Flutter 写业务、但对 AI 输出质量不满意想系统化攒一套自己智能体的开发者也适合完全没碰过智能体平台、想看看“提示词工程 知识库 工作流”组合起来到底长什么样的新手。下面我把这几个月从踩坑到跑通的完整过程摊开讲。1. 先想明白为什么要“养”而不是“问”1.1 一次问答和一段关系差的不是模型而是记忆大部分人用 AI 写 Flutter 的方式是开一个新对话把需求贴进去拿到代码复制编译报错再贴回去。这条路能走但每次都在从零开始它不知道你的项目里有几个模块、不知道你的网络层封装叫什么、不知道你上一轮为什么把GetX换成了Riverpod。于是你每轮都得把上下文重新喂一遍喂少了它瞎猜喂多了它抓不住重点。“养育”这个思路的核心区别在于状态是持续的。我把智能体当成一个刚入职的同事第一周先告诉他我们团队用什么、不用什么第二周给他看历史项目的代码片段和踩坑记录第三周开始让他参与真实任务并纠正第四周他基本能独立跑一遍常规需求。这套节奏放到智能体上对应的就是人设、知识库、工作流和长期记忆四件东西。四个东西各就各位输出质量的稳定性会有肉眼可见的提升。1.2 “AI 养育计划”到底在养什么我给它拆成四层这四层是整套方案的骨架后面的所有配置都围绕它们展开层对应平台能力作用我的具体做法性格层人设与提示词决定语气、严格程度、拒绝边界写成“资深 Flutter 工程师先问清楚再动手”记忆层知识库 长期记忆记住项目约定和历史踩坑上传规范文档、报错归因笔记肌肉层工作流把重复动作固化成流程需求澄清、代码生成、静态检查、报错归因反射层开场白 快捷指令让高频请求一键触发“审这段代码”“查这个报错”“拆这个需求”这四层里最容易被人忽略的是性格层。大量的人把提示词写成一段功能描述“你是一个 Flutter 专家请帮我写代码。”这种提示词的结果就是你说什么它都应你写错了它也说“可以的”最后你拿到一堆看着能跑、实际全是坑的代码。我在性格层里加了一条硬规则涉及状态管理选型、目录结构、依赖引入这三类问题时必须先反问至少两个问题再给方案。这一条加进去之后它从“有问必答的客服”变成了“会拦你的搭档”。1.3 为什么偏偏选 Flutter 这个方向来练手选 Flutter 不是因为我只看好它而是因为它特别适合做“养育”实验。Flutter 的反馈闭环极短——改一行热重载立刻看到结果同时它的错误类型高度模式化RenderFlex overflowed、setState() called after dispose()、LateInitializationError、use_build_context_synchronously这几类占了日常报错的大头。模式化意味着可以整理成知识库条目闭环短意味着你能快速验证智能体给的方案靠不靠谱。反过来说如果你拿它去养一个做后端分布式的智能体一次验证要起好几个服务反馈周期一长你根本分不清是模型不行还是环境问题。Flutter 还有一个隐性优势UI 层的结果是可视的。智能体给你一段布局代码跑起来是不是你想要的一眼就知道这让你在纠正它的时候有非常明确的判断依据而不是“感觉不太对”。2. 骨架搭建人设、提示词与知识库怎么配2.1 角色卡别写成招聘启事写成“工作习惯说明书”新手最容易犯的错是把角色卡写成简历“你拥有十年 Flutter 开发经验精通 Dart 语言熟悉各种状态管理方案。”这类描述对模型的行为几乎没有约束力因为“精通”没有可执行含义。我后来改成写行为不写资历效果差异非常明显。我的角色卡最终定成这样几段你可以直接参考改写身份我是一线 Flutter 开发者负责带新人 review 代码说话直接不用客套话开头。工作顺序先确认版本Flutter 3.x / Dart 3、确认状态管理方案、确认目标平台三个信息缺一个就先问。输出格式先给结论一句话再给代码代码后必须跟一段“为什么这么写”和一段“这段代码的三个风险点”。禁止行为不许编造不存在的 API不确定的包必须先说“需要你确认这个包是否存在”不许在回答里省略import。语言中文注释变量名英文命名遵循 lowerCamelCase类名 UpperCamelCase。其中“不许编造不存在的 API”这条我反复强化了三遍位置放在提示词中段而不是末尾因为长提示词里末尾的内容权重容易被稀释。加完之后它编 API 的概率明显下降即便偶尔编了也会带一句“这个 API 建议你本地验证一下”这就是我想要的“知道自己可能不确定”。2.2 提示词写成四段式别写成一大坨我把系统提示词分成四个区块每个区块用明确的分隔标记这是从反复调试里得出的经验结构化的提示词比自然段落式的提示词稳定得多。第一段是角色与语气控制在 100 字以内太长会挤占后面规则的空间。第二段是工作准则写成编号列表每条一句话不超过十条超过十条模型就开始选择性忽略。第三段是输出协议规定回答的固定结构比如“结论 / 代码 / 理由 / 风险”四小节。第四段是自检清单让它在输出前自己过一遍有没有用到未声明的依赖、有没有在build里创建对象、有没有在await之后直接用context。这四段里输出协议是收益最高的一段。原因很简单当你要求它按固定小节输出时它必须先把“风险点”这一栏填满才能结束回答这个强制动作会逼它重新审视一遍刚写的代码。我在自检清单上线之后做过一次粗略统计同一批需求下带自检和不带自检的代码能一次编译通过的比例差了将近一半。这个数字不一定严谨但体感非常明显。注意提示词里不要写“尽量”“最好”“如果可以”这类软化词模型对这类词的处理基本等于没写。要写“必须”“不允许”“一律”。2.3 知识库不是丢文档进去就完事要按“问题-原因-解法”切知识库这块我走了最长的弯路。一开始我把自己写的开发规范、项目 README、几个历史项目的代码全打包上传结果检索出来的内容经常跑偏——问一个分页问题召回了主题配色规范。原因是我上传的都是长文档切片之后语义被切碎了。后来我改成自己手写条目每条按“现象 / 原因 / 解法 / 代码”四段式组织一条只讲一个坑长度控制在三百字以内。举个例子现象页面返回后报setState() called after dispose()。 原因异步请求在页面销毁后回调State已经 unmount。 解法在setState前判断if (!mounted) return;或者把请求交给状态管理层持有由它决定是否通知 UI。 代码……这种结构的条目检索命中率非常高因为用户提问里的报错信息和条目里的“现象”字段能直接对上。我大概攒了四十多条覆盖布局溢出、生命周期、异步上下文、列表性能、图片内存、路由传参这几大块。知识库的价值不在于多而在于条目化程度高。同样是四万字切成一百三十条短条目效果远好于四个长文档。2.4 开场白和快捷指令把它变成肌肉记忆开场白的作用是校准预期不是卖萌。我的开场白就三行说明它能做什么结构建议、代码草稿、报错归因、说明它不能做什么不能替你跑真机、不能替你做产品决策、提示当前项目基线Flutter 3.x、Dart 3、状态管理 Riverpod。新人第一次用的时候看一眼就知道边界在哪省掉大量“你怎么连这个都不知道”的无效对话。快捷指令是我用得最频繁的功能因为它把高频动作的成本压到了一个点击。我配了六个拆需求把一段产品描述拆成页面、状态、接口、异常分支四块。审代码按固定清单逐条检查输出问题等级。查报错贴报错原文输出归因和三种可能解法。写测试给指定 Widget 生成 widget test 骨架。升级自查列出当前代码里已被弃用的写法。性能过一遍检查build方法、列表、图片三处常见开销。其中“审代码”我几乎每天用。它跑完会给一张表按“致命 / 严重 / 建议”三档列出问题致命档通常是空指针、生命周期、内存泄漏这类严重档是性能和不规范命名建议档是风格问题。这套分档机制让我不用逐字读它的长篇分析扫一眼致命档就够了。3. 让它真能干活工作流编排的几个关键节点3.1 需求澄清节点把“帮我写个列表页”变成可执行规格单靠提示词让模型反问稳定性不够有时它心情好就问心情不好直接开写。所以我把澄清做成了一个独立的工作流节点输入是用户的一句话需求输出是一份结构化规格表包含页面清单、数据字段、状态枚举、分页策略、空态与错误态、目标平台。这个节点的关键在于强制输出表格而不是文字。表格的每一格如果空着用户一眼就能看出信息缺失模型自己也会因为“表格有空格”而倾向于补齐。我试过用纯文字让它列需求它经常写着写着就滑到写代码去了换成表格之后它会老老实实把六列填完。填完之后我会人工扫一遍把“分页策略”这类关键项改掉再进入下一个节点。实操里我遇到过一个典型情况需求里只写了“列表支持下拉刷新”没说上拉加载。以前直接让模型写它会默认给你加上无限滚动结果和产品预期不符。现在这个节点会明确问“是否支持分页加载每页多少条”我填 20后面生成的代码里pageSize就是 20不用再改。把模糊点前置成必填项是这类工作流最大的价值。3.2 代码生成与静态检查闭环光有生成不够必须接一个检查环节否则你会陷入“生成-编译-报错-再生成”的循环而这个循环里模型很容易越改越乱。我的做法是在工作流里加两步生成之后先让它自己跑一遍“清单自检”清单包括依赖是否声明、是否用了const、异步后是否判空mounted、是否引入了未使用的 import、命名是否符合规范自检通过后再输出给用户。自检这一步的提示词要写得非常具体不能写“检查代码质量”要写成一个一个问题这段代码用到的第三方包是否在pubspec.yaml里出现过没出现过请显式提示我添加。所有State内的异步回调是否在setState前判断了mounted所有await之后使用context的位置是否加了if (!context.mounted) return;列表是否用了ListView.builder而不是ListView(children: [...])有没有在build方法里创建TextStyle、Color、闭包这类对象这五条对应的是我项目里最高频的五类返工。让模型在输出前逐条确认相当于把 review 的一部分前置了。我实测下来加上这五条自检代码一次编译通过率大概能从六成提到八成多剩下的两成主要是包版本和平台差异导致的跟模型能力无关。3.3 报错归因节点别让它猜让它先分类报错处理是最能体现“养育”成果的场景。通用模型拿到一段报错常见反应是给三四种可能性让你挨个试。我要的不是这个我要它先分类。我的归因节点规定拿到报错后先判断属于哪一类——编译期类型错误、运行时空值错误、布局溢出、生命周期错误、异步上下文错误、平台通道错误、依赖冲突。分类完成之后只针对这一类给解法并且必须说明“为什么是这一类”判断依据是报错信息里的哪个关键词。这一步的收益非常实在。比如RenderFlex overflowed by 32 pixels on the right它会被分到布局溢出类给出的解法顺序是先看是不是Row里放了Expanded没包住、再看是不是文字没设overflow、最后才建议改约束。这个顺序是有讲究的因为前两种是真实原因的高频项第三种是掩盖问题。通用模型经常直接建议你套一层SingleChildScrollView那是把问题藏起来不是解决。3.4 UI 还原度评审节点这个节点听起来虚其实很实用。我在工作流里加了一步生成布局代码之后让它按“间距体系 / 字号层级 / 颜色语义 / 圆角一致性”四个维度自查并输出一张对照表说明每个维度用了什么值、依据是什么。举个例子它可能会输出间距用 4 的倍数4/8/12/16/24字号用 12/14/16/20/24 五级颜色全部走Theme.of(context).colorScheme圆角统一 8。这些约束一旦写进提示词和知识库生成的代码风格就会趋于稳定不会出现这个卡片圆角 8、下个卡片圆角 12 的杂乱感。团队协作里这一点比想象中重要——风格不一致的代码后期维护成本会成倍上升。4. 实战用它带一个项目从 0 到 14.1 第一步定架构状态管理选型要算账不是凭喜好我在真实项目里用它做的第一件事不是写代码是选型。我把三个候选方案丢进去要求它按“页面数量、跨页面共享需求、团队人数、测试成本、学习曲线”五个维度打分并给出推荐。它给出的对照表大致是这样的维度ProviderRiverpodBloc上手成本低中高跨页面共享一般好好编译期安全弱强强样板代码量少少多复杂状态可测性一般好很好适合规模小到中中到大中到大最后它推荐 Riverpod理由是我们有跨页面共享的用户态、页面数量在二十个以上、团队只有三个人。这个理由站得住。我自己补了一条它没提到的Riverpod 的autoDispose能天然解决“页面销毁但状态还在”的内存问题这对列表页特别友好。这一步让我意识到智能体在选型上的价值不是替你决策而是帮你把决策维度列全避免你只盯着自己熟悉的那一个。4.2 目录结构与依赖清单结构这块我让它按 feature-first 组织输出是这样的lib/ ├── main.dart ├── app/ │ ├── app.dart │ ├── router.dart │ └── theme.dart ├── core/ │ ├── network/ │ │ ├── api_client.dart │ │ └── api_exception.dart │ ├── utils/ │ └── widgets/ └── features/ └── feed/ ├── data/ │ ├── feed_repository.dart │ └── feed_model.dart ├── domain/ └── presentation/ ├── feed_page.dart ├── feed_controller.dart └── widgets/pubspec.yaml它给的依赖清单我改了两处删掉了一个它推荐但我觉得没必要的工具包把 HTTP 客户端的版本上限收紧了。这里有个经验——依赖清单一定要自己过一遍模型倾向于推荐“功能更全”的包而功能全往往意味着体积大、维护风险高。我现在的原则是能用 Flutter 自带能力解决的绝不引第三方包。4.3 分页列表的实现与关键代码真正开始写代码时我让它先出数据层再出状态层最后出 UI 层。这个顺序很重要因为倒过来写UI 会倒逼数据结构后面改起来很痛。数据层模型它给的是带fromJson的不可变类class FeedItem { final String id; final String title; final String summary; final String coverUrl; const FeedItem({ required this.id, required this.title, required this.summary, required this.coverUrl, }); factory FeedItem.fromJson(MapString, dynamic json) { return FeedItem( id: json[id] as String? ?? , title: json[title] as String? ?? , summary: json[summary] as String? ?? , coverUrl: json[cover_url] as String? ?? , ); } }这里有个细节值得说它默认用了as String? ?? 的容错写法而不是直接as String。这个选择在真实项目里更稳因为后端字段偶发缺失时不会整页崩掉。我把这个习惯写进了知识库后面所有模型类都按这个来。状态层我用的ChangeNotifier因为它零依赖、最容易理解团队里新手也能改class FeedController extends ChangeNotifier { final FeedRepository _repo; FeedController(this._repo); final ListFeedItem _items []; ListFeedItem get items List.unmodifiable(_items); int _page 1; static const int _pageSize 20; bool _loading false; bool _hasMore true; String? _error; bool get loading _loading; bool get hasMore _hasMore; String? get error _error; bool _disposed false; Futurevoid loadFirstPage() async { _page 1; _hasMore true; _items.clear(); await _load(); } Futurevoid loadMore() async { if (_loading || !_hasMore) return; await _load(); } Futurevoid _load() async { if (_loading) return; _loading true; _error null; _safeNotify(); try { final result await _repo.fetch(page: _page, size: _pageSize); if (_disposed) return; if (result.isEmpty) { _hasMore false; } else { _items.addAll(result); _page 1; if (result.length _pageSize) _hasMore false; } } catch (e) { if (_disposed) return; _error 加载失败请稍后重试; } finally { _loading false; _safeNotify(); } } void _safeNotify() { if (!_disposed) notifyListeners(); } override void dispose() { _disposed true; super.dispose(); } }这段代码我特意保留了_disposed标志位。这是我踩过的坑请求还在飞的时候用户退出了页面notifyListeners就会抛异常。通用模型第一次给我的版本没有这个判断我让它补它才加上。所以每次拿到异步代码第一件事就是找有没有销毁保护这个检查现在已经是我的条件反射。这里还要说明分页长度的判断逻辑我用的是“返回条数小于 pageSize 就认为到底了”这是最常见也最省事的一种。另一种是让后端返回total或hasMore字段那种更准但要和后端对齐。智能体在这个点上会主动问我接口有没有total字段——这正是前面“需求澄清节点”在起作用。4.4 UI 层与性能自查UI 层它给的是ListView.builder加RefreshIndicator列表项外层套了RepaintBoundaryListView.builder( itemCount: controller.items.length (controller.hasMore ? 1 : 0), itemBuilder: (context, index) { if (index controller.items.length) { return const Padding( padding: EdgeInsets.symmetric(vertical: 16), child: Center(child: CircularProgressIndicator()), ); } final item controller.items[index]; return RepaintBoundary( child: FeedTile( item: item, onTap: () context.push(/detail/${item.id}), ), ); }, )RepaintBoundary这一层不是必须的列表项如果结构简单纯文字加图片加不加差距不大但如果列表项里有动画、有圆角裁剪、有阴影加上它重绘区域就被隔离了滚动帧率会稳不少。我在一台测试机上对比过滚动时的掉帧次数确实少了。这个结论我写进了知识库所以它现在默认会加但同时会提醒“简单列表项可去掉”。图片这块它给的建议是设置cacheWidthImage.network( item.coverUrl, width: 88, height: 88, fit: BoxFit.cover, cacheWidth: 176, errorBuilder: (_, __, ___) const ColoredBox(color: Color(0xFFEEEEEE)), )cacheWidth设成展示宽度的 2 倍这个倍数是我自己定的经验值兼顾清晰度和内存。如果按屏幕像素密度算会更精确但 2 倍在绝大多数场景下够用内存占用比原图解码能降一个量级。这类参数模型通常会给出“建议设置为显示尺寸的两倍”这样的说法但要真正落地还得你自己按项目情况调。最后一轮我让它按“性能过一遍”快捷指令跑了一遍输出的问题清单里有一条我没想到FeedTile里的回调闭包在itemBuilder里每次重建都会新建如果列表很长会有额外开销。解法是把回调提到外面或用ValueKey配合缓存。这一条虽然实际影响有限但能看出它在往细处看。5. 常见问题与排查技巧实录5.1 幻觉 API看着特别像真的那种这是最高频也最烦人的问题。它给你一个Theme.of(context).colorScheme.surfaceVariantHigh命名风格完全符合 Material 3 的规律但根本不存在。我在知识库里专门建了一条“常见幻觉清单”把撞过的坑都记下来同时把提示词里的禁令强化成“凡是你不确定是否存在的属性必须用代码注释标注// 需确认”。第二条经验是让它自己验证。我在工作流里加了一个“属性出处”环节要求它在给出不常见 API 时附上它所在的类名和大致用途比如“ThemeData.useMaterial3Material 3 主题开关Flutter 3.16 之后默认开启”。能说清楚出处的通常是真的说“这是常用 API”却给不出类名的八成是编的。第三条是本地兜底把flutter analyze接进流程里。分析器不会骗人它报undefined_getter就是没有。现在我的流程是生成代码 → 本地跑 analyze → 把报错原文贴回智能体 → 它归因修正。这个闭环跑顺了之后幻觉问题的实际影响被压得很小。5.2 版本漂移它记住的是一年前的写法模型训练数据有滞后这不是它能控制的所以必须靠你在提示词里锚定版本。我做过对比不写版本时它给的代码里super.key和key: key混用ThemeData(primarySwatch: ...)和colorScheme混用明确写“Flutter 3.x / Dart 3 / 使用 Material 3 / 构造函数用super.key”之后这两种混用基本消失。但锚定版本还不够因为废弃写法是渐进出现的。我的解法是配一个“升级自查”快捷指令定期大概每两个月把项目里的关键文件丢进去让它列出“已被弃用或即将弃用”的写法。它给出的清单我会自己核对一遍再改因为偶尔会有误报——把“不推荐”说成“已废弃”。注意不要相信智能体给出的版本号和时间点。任何“从 X.X 版本开始”的说法都要自己去变更日志核对。这类信息滞后最严重。5.3 对话跑偏与上下文污染聊久了它就开始“变味”这是长对话的通病。表现是回答越来越长但越来越空开始重复之前说过的内容或者把上一个需求的假设带到新需求里。我的处理方式是按任务开新会话而不是一个会话聊到底。具体做法是每个功能模块一个会话会话开头重置一遍基线版本、结构、约定结束之后把有价值的结论沉淀到知识库条目里。这样做的好处是上下文始终干净坏处是要重复喂基线。为了减少重复我把基线写成一段固定文本存在本地笔记里开新会话就贴一次十秒钟的事。另一个污染源是错误信息的回贴。你把一大段编译报错原封不动贴进去里面往往包含几十行无关的警告模型的注意力会被分散。我的做法是先自己筛一遍只贴最上面的那条主错误和紧邻的三行调用栈。筛过的报错归因准确率明显更高。5.4 成本、速度与“什么时候不该用它”这部分说点实际的。跑一整套带知识库检索和工作流的流程比直接对话慢不少一次完整的需求澄清加代码生成大概要几十秒。所以我把任务分了级任务类型是否走工作流理由新建页面走涉及结构、状态、异常分支值得慢单行报错归因走快捷指令快且模式固定改个文案/颜色不走自己改更快复杂布局重构走需要拆解和自查写单元测试骨架走重复劳动交给它划算排查真机崩溃不走需要看日志和真机信息它拿不到最后一行值得强调它拿不到你本地环境的信息所以凡是依赖具体设备、具体日志、具体依赖树版本号的问题别指望它。我见过有人把一段只有行号的崩溃栈贴进去问原因这跟让医生只看体温计照片诊断没什么区别。这类问题老老实实看日志、看flutter doctor、看pub deps。6. 养了几个月之后我的真实评价6.1 哪些活儿它真能接哪些还不行能接的把一段模糊需求拆成规格表、按约定写中等复杂度的页面、给报错做分类归因、把一段老代码改成新写法、生成 widget test 骨架、检查代码里的常见坑。这些活儿的共同点是模式强、约束明确、有客观验证标准。还不太行的跨模块的重构决策、涉及大量历史包袱的架构调整、需要读十几个文件才能判断的影响面分析、以及性能问题的深层定位。后面这几件事需要的是对整个代码库的整体把握而智能体看到的永远是你贴给它的那部分。它可以当一个非常靠谱的执行者但当不了技术负责人。我个人的用法是把它放在“第二双眼睛”的位置我自己先想方案让它挑毛病我自己先写代码让它按清单审一遍。这个用法下它的产出质量最高因为它不需要凭空创造只需要在已有基础上做判断。反过来如果你指望它从零给你一个能上线的模块那失望是必然的。6.2 我自己的迭代节奏现在我大概每月做一次“养育更新”把这一个月里踩到的新坑整理成条目补进知识库把反复出现的纠正写进提示词把用不上的快捷指令删掉。删比加更重要——我一开始配了十几个快捷指令结果自己都记不住哪个是哪个常用的永远是那四五个。精简到六个之后使用频率反而上去了。还有一个小习惯分享给你我会在知识库里单独建一个“我改过它的地方”清单记录每次它给出的方案被我怎么改的、为什么改。这个清单攒到二三十条之后你会发现自己的偏好其实高度集中——无非就是那七八条原则。把这七八条提炼出来写进提示词比堆一百条零散规则管用得多。养育这件事说到底不是把模型调教得多聪明而是把自己那套说不清道不明的经验逼成一条条能写下来的话。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑