2026/9/18 9:15:16

自制AI汽水机:用语言调制抽象口味的8个月实践

自制AI汽水机:用语言调制抽象口味的8个月实践 跟朋友聊这台机器的时候我一般会先问他一个问题你试过对着一台机器说“来一杯下午四点半、刚下过雨的院子里那种味道”然后它真的给你端出一杯东西吗这台我折腾了整整8个月的自制AI汽水机核心就干这一件事——用语言调制饮料。不是照着固定菜单按键选择而是往“抽象口味”那个方向调你说得出来它就敢给你兑。所谓“中配”是我给自己这套方案的定义。它没上工业级的计量泵也没有定制的不锈钢管路整机物料成本压缩在四千多块用的都是普通玩家能买到、能替换、能看懂原理的零件。但它能完成一件挺“不普通”的事基于大语言模型理解你的描述再通过一套封闭的配方规则引擎生成一杯可以喝、可以复刻、配方可追溯的汽水。适合对AI落地、硬件DIY、食品工程感兴趣的玩家看也适合那些对“口味能不能被数字化”这个问题有好奇心的人。这篇文章我不打算只展示“成果”更想把8个月里踩过的坑、想通的原理、被现实教育过的瞬间都倒出来。毕竟一台能说人话的饮料机难点从来不在“喷饮料”而在“听懂人话之后怎么别把事情搞砸”。1. 从一句“要一杯雨后的味道”说起项目动机与最终形态1.1 为什么做的是“抽象口味”而不是单纯的好喝市面上现成的汽水机逻辑都很统一机器里预置几个按键可乐味、橙子味、苏打水按下哪颗出哪杯。这是“按钮到配方”的硬映射配方是固定的口味是标准化工业品。我做这台AI汽水机的时候给自己定了一个完全不同的目标不要固定配方表把“口味”本身变成一种可组合、可描述、可寻址的参数空间。用户说“想要一杯像夏天傍晚雷雨后的院子”这不是一个标准配方而是一种情绪、场景、记忆混合在一起的感觉。传统商用设备做不到因为它的控制面板上没有“雨后院子”这个按钮。这其实是“抽象口味”的真正含义不是玄学而是把味觉从“配方表”里解放出来让用户在自然语言层面描述感受由机器负责翻译成具体的原料组合。它调出来的未必是你记忆里那个味道但它会给你一个“能引发类似联想”的版本。这种体验固定菜单做不出来。1.2 “中配”的定义在哪里花钱在哪里省钱很多朋友看到“自制AI汽水机”的第一反应是这玩意儿得烧多少钱我的方案总共花在物料上的钱大约4300元核心部件清单和选型逻辑如下表部件选型大约花费为什么是这个主控板ESP3260元负责泵阀实时控制GPIO多Wi-Fi方便便宜可靠大脑树莓派4B350元跑LLM推理和业务逻辑算力刚好够用泵组6个蠕动泵900元食品级硅胶管液体不接触泵体防回流碳酸系统CO₂气瓶碳化罐600元持续供气碳酸强度可调比家用苏打水机改装灵活管路与接头食品级硅胶管快插250元铂金硫化硅胶耐折、易更换阀/杯/结构件电磁阀混合杯亚克力800元混合杯排液用电磁阀控制结构件上花的钱不超过整机20%风味原料库食品级浓缩液/基底液/辅料1300元全部正规渠道食品级原料支持后期补充中配的关键不是“能用就行”而是“关键件可靠、非关键件省钱”。泵、管路、碳酸系统这些跟液体直接打交道的部分我全部选食品级而外壳、支架这类不影响食品安全的地方能3D打印就打印能亚克力就亚克力。省钱的思路是把钱花在“入口的东西”和“容易坏、影响体验的东西”上其他位置怎么便宜怎么来。1.3 用户从开口到拿到杯子的完整链路这台机器从用户说话到出品完整链路大概是这样的用户对着触摸屏/手机输入自然语言描述比如“一杯有点苦但回甘的像雨天木头台阶的气泡水”树莓派上的LLM服务把这句描述翻译成结构化风味画像甜度、酸度、苦度、木质调、烟熏调等维度的数值规则引擎对风味画像做映射在封闭原料库中挑出基底液、浓缩液组合和工艺参数配方通过串口JSON下发到ESP32ESP32按顺序控制蠕动泵和电磁阀逐个把基底、浓缩液、调味液送入混合杯碳酸水系统同步打气混合杯搅拌/摇晃后经电磁阀排入杯中屏幕上显示配方成分和强度档位。全程大约15到25秒其中LLM推理占2到5秒云端API泵送占8到12秒混合打气占5秒左右。这个延迟体验下来是可以接受的它不像自动咖啡机那样追求“按下即出”因为用户本身也处于“等待一杯抽象口味被翻译出来”的期待状态。2. 整机架构泵、管路、碳酸系统与控制大脑2.1 液体配送为什么选了蠕动泵饮料机的核心执行机构是“把液体按量送到杯子里”。我一开始面临三个候选方案蠕动泵、隔膜泵、步进电机注射泵。方案优点缺点我的结论蠕动泵液体只接触软管防回流易换管成本低流量会漂移精度受管径和转速影响选了它用定时定期校准解决精度隔膜泵流量较大适合大量输送液体经过泵腔清洗麻烦脉动明显不适合浓缩液小剂量场景步进注射泵精度很高适合微量结构复杂、管路多、单路成本高备选方案后续做精调时再上蠕动泵的原理看着很简单电机带动滚轮挤压弹性软管液体就被“挤”着往前走。它的优势是流体全程在软管内不接触任何金属或泵腔结构所以换一根管就等于换了一个泵腔卫生维护成本极低。缺点是流量不绝对稳定管子的弹性、电机的转速、液体的粘度都会影响单位时间流量。我处理精度问题的方法是把“泵送时间”当作控制量每两天做一次称重校准生成一张“泵送时间→实际毫升数”的对照表。对浓缩液这种小剂量原料控制在1到3毫升量级对基底液和碳酸水控制在20到60毫升量级。这个精度放在饮料场景里完全够用毕竟人的舌头对口味差异的感知远没有对温度和气感的敏感度那么高。2.2 碳酸水系统自建气瓶碳化罐而不是改装家用苏打水机汽水没有气泡是没灵魂的。做碳酸水有两个路线一个是拆一台家用苏打水机把出水管接到我的泵组上另一个是自建“CO₂气瓶碳化罐”。我最后选了自建方案原因有三个。第一家用苏打水机本质上是“手动按钮固定碳化压力”我想让“气泡强度”成为配方里的一个可调维度而不是只有“有气/没气”两种状态。第二家用机通常一次性打满一瓶而我需要的是连续供气能在一分钟内给多杯出水。第三从长期使用成本看CO₂气瓶补充一次能打几百升综合成本远低于用家用机的气弹或小气瓶。碳化罐的工程参数我调试后的稳定区间是水温0到4℃罐压0.3到0.4MPa即3到4个大气压碳化时间30到60秒。温度越低CO₂溶解度越高压力越高气体越容易融入液体。想要“气泡强度5档”的区分本质上就是在这三个参数里组合。低温是关键我单独加了一路半导体制冷模块维持碳化罐温度实测下来罐压稳定、水温够低的时候出品的碳酸水气感明显优于常温高压的版本。2.3 控制大脑ESP32树莓派的分工整个系统的控制中枢没有用一台设备全包而是把实时控制和业务逻辑分开ESP32管泵阀时序树莓派管LLM推理和配方决策。为什么这么拆因为GPT这类LLM推理是一个“不确定耗时”的操作云端API调用可能出现2秒到5秒的抖动本地模型推理也只能说“大概几秒”而泵阀控制要求严格时序先泵送什么、再泵送什么、每路多少毫秒、什么时候开阀排液一旦中断就可能搞出一杯分层奇怪或者混合不均的东西。让一个不稳定的大模型直接去控制硬件时序是灾难性的设计。所以控制层和数据层之间只跑JSON协议。树莓派算完配方发给ESP32一个类似这样的指令先开CO₂电磁阀碳化然后依次泵送基底36ml、浓缩液A 2ml、浓缩液B 1ml、甜味液1档搅拌3秒开排液阀。ESP32收到这个数组后按固定时序执行完全不受上层推理速度影响。这套分层设计是我在整个项目里做得最正确的一个决定。3. 最硬核的一环把一句话变成一杯饮料3.1 LLM只做翻译不做决策最开始做“语言调制”的时候我的想法特别天真让LLM直接输出配方比如“柠檬汁30ml、糖浆10ml、薄荷叶2片”。结果很快翻车。第一模型自创了我不存在的原料“雨前龙井浓缩”这种东西它都敢写我的原料库里根本没有第二同样一句描述温度稍微一调、上下文稍微一变它给的配方每次都不一样第三它完全不了解这台机器的出液能力有时候给一个总量120ml的配方但我的系统更适合做150ml标准杯。让LLM做配方决策本质上是让一个从没碰过这台机器的人远程指挥泵阀出错是必然的。后来我把架构改成LLM只做“语义翻译”配方决策交给确定性算法。LLM的职责是吃进自然语言输出一个结构化的“风味画像”甜度、酸度、苦度、咸度、鲜度各0到1的浮点数风味倾向果香、花香、草本、木质、烟熏、矿物等各0到1的浮点数关键词列表从原料库关联词中选取整体强度档位1到5。规则引擎拿到这份画像后再根据“原料库的风味向量”去匹配具体组合。机器里有什么、每种料能用多少、哪些组合不好喝全部由规则层说了算。LLM负责把抽象的“雨后院子”翻译成可计算的“泥土调0.7、草本调0.5、微苦0.3、低甜”但具体怎么兑是规则引擎的地盘。3.2 输出契约提示词与JSON Schema为了让LLM稳定输出我设计了一套严格的输出契约。系统提示词的核心思想是告诉它“你是风味翻译官不是调酒师。你不知道这台机器有什么原料你只负责把用户的话翻译成风味画像。”我用的提示词结构大概长这样节选你是“风味翻译器”。用户会给出对一杯饮料的任意自然语言描述。 你的任务把这段描述翻译为风味画像而不是直接提供配方。 你必须且只能输出如下JSON结构 { sweetness: 0.0-1.0, sourness: 0.0-1.0, bitterness: 0.0-1.0, saltiness: 0.0-1.0, umami: 0.0-1.0, notes: { fruity: 0.0-1.0, floral: 0.0-1.0, herbal: 0.0-1.0, woody: 0.0-1.0, smoky: 0.0-1.0, mineral: 0.0-1.0 }, keywords: [不超过5个简短关键词], strength: 1-5 } 注意不要输出配方不要输出原料名不要提到你了解的饮料品牌。如果描述太难翻译选最接近的通用风味维度。然后我在后面跟一组few-shot示例至少给5个“描述→风味画像”的配对案例。比如“夏日午后切开的西瓜加了一点海盐”→甜度0.75、酸度0.2、咸度0.35、果香0.9、矿物0.3。Few-shot的作用是把“描述到向量”的映射方式演示给模型看而不是让它自由发挥。3.3 校验、重试和可复现性输出契约设好了但LLM总有抽风的时候。我在解析层做了严格的校验核心代码如下import json def parse_flavor_profile(raw: str): try: data json.loads(raw) except json.JSONDecodeError: return None required [sweetness, sourness, bitterness, saltiness, umami, notes, keywords, strength] if not all(k in data for k in required): return None for k in [sweetness,sourness,bitterness,saltiness,umami]: if not 0.0 float(data[k]) 1.0: return None for k in [fruity,floral,herbal,woody,smoky,mineral]: if k not in data.get(notes, {}): return None if len(data.get(keywords, [])) 5: return None return data任何一步校验失败脚本会把错误信息反馈给LLM让它重新生成一次最多重试两次。连续三次失败系统就放弃LLM返回一个“经典口味”默认画像确保用户不会面对一台转圈圈的机器。可复现性方面我把模型温度设到0.2同时固定了系统提示词。这样同一个描述在不同时间输入得到的画像基本一致不会出现“昨天调的雨后院子”和“今天调的雨后院子”完全是两杯饮料的情况。稳定在“抽象口味”里反而比自由更重要——用户复刻一杯喜欢的口味时机器得给得出同样的东西。3.4 实测一个抽象口味是怎么被“翻译”出来的我给朋友做测试时有个人输入了一句“我想要一杯像周二下午三点窗外开始下雨空气中有一点潮湿的泥土味但又不是很阴沉的那种。”LLM翻译出来的画像大概是甜度0.5、酸度0.2、苦度0.35、咸度0.1、鲜度0.1草本0.6、木质0.5、矿物0.4、花香0.2关键词“泥土、潮湿、草本、微苦、清新”强度档位3。规则引擎拿到这份画像后在原料库里找匹配基底选了海盐气泡基底提供矿物感和轻度咸味浓缩液挑了焙茶浓缩液提供木质微苦和黄瓜薄荷浓缩液提供草本清新甜度给到中低档酸度给到低档。出品后的实际口感说实话不能说是“好喝”但那种“雨后、泥土、微苦、清新”的画面感确实能通过味觉被联想到。这就是抽象口味的核心体验不追求所有人都爱喝追求“描述和结果之间有可感知的对应关系”。4. “50万种口味”是怎么算出来的4.1 配方空间的数学不是海量原料而是参数组合很多人听到“50万种口味”第一反应是“你仓库里得放50万瓶原浆吧”实际上完全不是。50万不是靠原料数量堆出来的而是靠参数组合算出来的可寻址空间。我的口味空间由五层参数构成基底液8种可乐型基底、姜汁气泡、青柠苏打、西柚基底、接骨木花、海盐气泡、椰子水、无味气泡基底风味浓缩液12种从0到3种自由组合组合数为C(12,0)C(12,1)C(12,2)C(12,3)11266220299种甜度5档、酸度5档、气泡强度5档芳香浓度4档装饰/喷雾层可选“不加”或任选4种之一共5种选项。按全组合计算是8×299×5×5×5×4×5接近600万。但我并没有把全部组合开放出来因为其中很多组合在味觉上是灾难。我在风味引擎里用约束规则筛选后对外开放了约52万组“喝了不会想骂人”的组合。所以50万这个数字是产品定义里“可控可用空间”的下限不是理论数学的上限。4.2 能喝的边界约束规则怎么过滤“暗黑组合”如果只按组合数量来做那这台机器可以轻松宣称“几千万种口味”但出来的东西大概率让人反胃。我在规则引擎里内置了好几类硬性约束苦度阈值苦度超过0.7时甜度不得低于0.4否则会被判定为“过苦”酸度平衡酸度超过0.7时必须有甜度0.3以上的原料参与避免纯酸尖刺高冲突组合某些原料本身味觉冲突严重比如“烟熏浓缩液椰子水基底高酸档”规则引擎直接封禁这类组合总量约束无论怎么组合成品总量必须落在150±10ml范围内基底液占主要比例浓缩液总量不得超过8ml。这些约束是“能喝”的底线。它们的作用不是限制创造力而是让“抽象”保持在人类舌头能接受的范围内。你可以让机器做一杯“烟熏木头微甜海盐气泡”但你不能让机器做一杯“苦到极致的酸味烟熏浓浆”。规则层像一个口味编辑把LLM的想象力从“无限”压到“可用”。另外每一杯成品在屏幕上都会显示完整的原料成分和用量。这也是我坚持规则层做决策的原因之一配方必须可追溯。用户喝到的每一杯“抽象口味”都知道自己到底喝进去了哪几种食品原料、各多少克。这一点是做食物相关DIY的底线。4.3 口味代码让每一杯抽象口味都可以被记录、分享、复刻50万种口味如果只是“调过一次就没了”那这个数字就只是个话题。为了让空间真正可用我给每个配方生成一个“口味指纹”一串由原料ID、档位参数、工艺参数组成的短代码。打个比方一杯“雨后院子”被翻译并生成配方后会得到一个类似“SB-06-03-02-01-01-004”的代码其中SB-06代表基底ID后面的分段分别代表浓缩液组合ID、甜度档、酸度档、气泡档、芳香档、装饰层选项。用户可以在机器上记住这串代码也可以扫码保存到手机里。下次想喝同样的味道只需要输入这串代码或者把之前的描述原封不动再说一遍机器就会复刻出一模一样的成品。这个设计让“50万种抽象口味”变成真正可寻址的空间而不是“每次随口发挥完就蒸发”的随机生成器。我自己平时用的时候也养成了记录口味代码的习惯。每调出一杯特别有意思的抽象口味我就把代码保存在备忘录里标签写上当时的描述语境。现在已经攒了三十多杯“值得复喝”的抽象口味有些名字我自己都忘了当时为什么这么调但代码一输进去味道又回来了这种感觉挺奇妙的。5. 8个月的时间线每个阶段都在解决什么问题5.1 第1到2个月先搞出一杯合格的碳酸基底项目启动的第一个月我没有碰AI也没有碰任何智能模块目标只有一个用最简单的手动方式做出一杯“气感合格、温度合适、没有奇怪杂味”的碳酸水。这个阶段的核心成果是自建碳化罐方案跑通。我踩的最深的一个坑是温度一开始用常温自来水直接碳化无论压力打多高气感都又粗又散喝起来有很强的刺激感。后来查到碳酸化工艺的关键参数才知道低温下CO₂的溶解度和稳泡性远好于常温。于是加装半导体制冷模块把碳化罐水温降到1到4℃之后出品的碳酸水才开始有“汽水该有的质感”。另一个问题是水里混入空气会导致泡沫不可控。解决方案是在碳化罐的进气口加了一个单向阀确保CO₂只进不出同时每次碳化前先排气1到2秒把罐内残留空气赶走。这两个细节做完之后碳酸基底的稳定性才真正达标。5.2 第3到5个月泵、协议与“让AI帮我写代码”碳酸水稳定之后我开始搭建泵送系统。这个阶段最大的麻烦来自泵管不同浓缩液的粘度差异很大糖浆类原料流动性差用同一套泵送时间参数出液量误差可能超过20%。我花了两周时间给每种原料做“粘度标定”记录不同泵送时间下的实际出液量再在配方协议里为每种原料单独维护一个标定表。也是在第三到第五个月我开始大量使用AI辅助编程来控制ESP32和树莓派的通信。比如我需要写一个“依次泵送多路原料并在每步之间校验状态”的时序控制脚本放在以前至少得翻两天文档现在直接把需求描述给AI生成初版后我再根据硬件手册修参数一晚上就能跑通一版。AI编程在这个阶段的定位是帮我快速验证想法、生成脚手架代码而最终的电路时序、泵阀互锁逻辑我还是会自己读一遍、理清状态流转再上机。这个工作习惯后面帮我避免了好几次“看起来能跑、实际上会把糖浆泵进气管”的事故。同时这个阶段我定下了JSON通信协议。树莓派和ESP32之间所有指令都是结构化数据不传自然语言。这个协议一直沿用到最后没改过算是早期做得最稳的一个决策。5.3 第6到7个月LLM接入与30人盲测第六个月开始正式接LLM。我先用云端API做了第一个“你说我调”的demo版本体验是能跑通但很粗糙模型经常输出格式错误配方的随机性也高。之后用了将近两周时间打磨提示词和输出契约就是前面第三章节里写的那套提示词结构核心思路从“让AI出配方”改为“让AI翻译画像”。第七个月我做了一次30人的小型盲测。盲测规则很简单每个人输入一句抽象描述比如“失恋的第二天早上”“大学图书馆旧书的味道”“夏天海边的风”然后把机器调出来的饮料端给他喝让他打分能喝出画面感、完全对不上、还是难以下咽。结果比我预期好也比预期差。好的方面是大约四成的人说“确实能联想到描述的画面”有两个人甚至说“这个味道让我想起了某段记忆”。差的方面是还有两成人明确表示“难喝”大多数是苦度和烟熏类描述处理得太重。这次盲测让我意识到抽象口味不是“越准越好”还要照顾“可饮性”。于是我在第七月末给规则引擎加了一批苦度、烟熏阈值约束也就是前面提到的“能喝的边界”。5.4 第8个月外壳、安全、清洁与收尾最后一个月做的全是“看起来不酷、但决定能不能长期用”的活。我给机器装了一块亚克力前面板把泵组、碳化罐、管路都收进了半开放式的机身里既方便维修散热也避免小孩伸手碰到液体管路。触摸屏装在中部偏上的位置交互界面简洁到只有输入框、出品按钮、配方代码显示区。安全方面做了三件事第一所有接触液体的管路都是食品级铂金硫化硅胶浓缩液和基底液全部选用正规渠道的食品级原料每瓶标注开封日期和保质期第二机器每次出品后自动执行一次清水冲洗循环每周更换一次泵管和过滤芯第三系统设置了“锁定模式”没有管理员密码无法修改原料库和白名单避免有人误操作把非食品级液体接进管路。清洁这一步我吃了不少苦头。一开始用食品级浓缩液测试隔了一周没清理管路内壁就出现了一层薄薄的颜色附着物。后来养成习惯每周换管、每次出品后冲洗10秒、碳酸罐每个月拆开彻底清洗一次。机器的稳定出品一半靠设计一半靠维护。6. 踩坑实录与这台机器的下一步6.1 油性香精挂壁一个让所有饮料看起来像放坏的细节第一次调制“坚果/焦糖”类口味时我在原料库里加了一款油性香精。当时没想太多结果出品后饮料表面浮着一层类似油膜的痕迹喝起来也确实有“水油分离”的颗粒感非常像过期饮品。排查之后才意识到问题油性香精不溶于水更不溶于碳酸水泵送时还会残留在管路内壁。换用水溶性香精和“水溶性乳化剂预处理”之后这个问题才彻底解决。做饮料DIY的玩家买香精调味液一定要先确认是否水溶性泵送系统里最怕的就是油类物质附着导致的交叉污染。6.2 蠕动泵流量漂移校准比精度更重要蠕动泵用久了硅胶管会因为反复被滚轮挤压而逐渐变形松弛单位时间流量会发生变化。这个漂移不是线性的可能前两周好好的第三周突然每秒钟少出20%的液体。我一开始以为泵坏了实测之后发现是管子疲劳。解决办法很土但有效每周拆下泵管让它休息恢复弹性同时用电子秤做一次校准更新每路泵的“泵送毫秒与毫升数对照表”。把“每周校准”写进使用流程之后配方的可复现性明显提升。后来我还在每个配方代码里记录了“校准版本号”如果某杯复刻出来味道不对先看校准版本是不是太久没更新多数情况下问题都出在这里。6.3 食品安全是底线也是“AI自由发挥”的笼子做这台机器的全过程中我给自己立过一条硬规矩所有进嘴的东西必须是正规渠道采购的食品级原料每一杯出品必须有成分展示。这条规矩其实也在间接制约AI。因为原料库封闭LLM再能“发挥”它也只能从已经存在的食品原料里挑选组合。它不能自创原料不能偷偷加它没见过的成分不能突破配方火山车间的安全约束。我一直觉得AI真正好用的地方不是“无边界地自由”而是“在一个明确的安全笼子里自由发挥”。这台汽水机就是这种理念的一个缩影。如果你也想复制一台请一定把这条放在最高优先级食品级管路、食品级原料、可追溯配方、定期维护清洁。就算有AI参与它也只是一个翻译官和创意引擎代替不了食品安全工程的基础常识。6.4 还能往哪玩本地模型、口味社交、更细颗粒的味觉档位机器做完之后我并没有把它的能力锁死。后续最想做的升级有三件事。第一把云端LLM替换成本地小模型。现在树莓派4B跑1.5B参数的量化模型虽然速度慢了几秒、风味词汇理解能力也弱一些但胜在隐私和无网络依赖。如果能换到NPU比较强的开发板本地化的空间还会更大。第二把口味代码做成社交分享功能。用户在手机上保存一个“口味指纹”之后可以把它发送给另一台汽水机让异地朋友复刻同一杯。目前我已经在局域网环境验证过“发送代码→远程出品”的流程但还没有做成公网可用的产品形态。第三加入更细颗粒的味觉档位。现在的甜酸气泡都是5档理论上够用但实际体验中人和人之间的甜感阈值差异很大同样一档甜有人觉得刚好有人觉得寡淡。下一版计划把基础味觉档位从5档扩展到9档同时增加“粘度/口感”维度让抽象描述里的“厚重”“轻薄”“沙口感”也能被映射到实际参数里。如果让我重新做一次我会需要的核心设计依然不变LLM只翻译、规则层做决策、配方必须可追溯。这个设计本身限制了不少算法层面的“创造力”但也正因为有这个笼子AI才真的敢在一杯饮料里自由发挥。毕竟能让人喝下去的东西才能算数。