基于EdgeOne Makers Agents与Next.js构建智能菜谱AI助手实践
1. 项目缘起:当烹饪APP遇到AI Agent
最近在迭代一个自己维护的烹饪类Side Project,功能挺常规的,就是菜谱浏览、收藏和购物清单。但做着做着,总觉得少了点“灵魂”。用户跟着菜谱做,成功了固然好,可要是家里缺了某样调料,或者想根据口味调整咸淡,甚至想把中式红烧肉改成适合健身的版本,现有的静态菜谱就无能为力了。用户只能去别的平台搜索,或者凭经验摸索,体验是割裂的。
我一直关注着AI Agent的发展,觉得它不该只是个高级聊天机器人。真正的智能,应该能理解上下文,主动规划步骤,并执行具体任务。直到我深入体验了EdgeOne的Makers Agents平台,一个想法冒了出来:为什么不给我的APP“雇佣”一位24小时在线的AI主厨呢?这位主厨不止能回答“料酒是什么”这种问题,更能理解“我家没有蚝油,用什么代替?”、“这份糖醋排骨的糖量能减半吗?会影响口感吗?”这类动态需求,并直接给出修改后的、可执行的菜谱方案。
这个项目,就是基于Next.js前端框架,利用EdgeOne Makers Agents构建的后端AI能力,打造的一个能深度理解用户意图、并动态“改写”菜谱的智能烹饪助手。它让菜谱从冰冷的文本,变成了一个可以对话、可以协商、可以个性化定制的智能体。
2. 技术选型与架构设计:为什么是它们?
2.1 核心平台:EdgeOne Makers Agents
选择EdgeOne Makers Agents作为AI能力的基座,是经过一番对比的。市面上提供AI Agent开发能力的平台不少,但Makers Agents有几个点直接命中我的需求:
第一,开箱即用的Agent编排能力。我不需要从零开始用LangChain或AutoGen去搭框架,处理复杂的工具调用、记忆流和规划逻辑。Makers Agents提供了可视化的编排界面,我可以像搭积木一样,把“理解用户查询”、“检索知识库(菜谱)”、“调用大模型分析”、“生成修改建议”这几个模块连接起来。这极大降低了开发门槛,让我能聚焦在烹饪领域的业务逻辑本身。
第二,原生集成与低延迟。我的APP主体部署在Vercel(与Next.js绝配),而EdgeOne本身与Vercel同属一个生态,且在边缘网络上有深度优化。这意味着AI Agent的服务可以部署在离用户更近的边缘节点,当用户提出“修改菜谱”的请求时,Agent的思考与响应速度会非常快,避免了因网络延迟带来的对话卡顿感,这对交互体验至关重要。
第三,可控的成本与权限管理。作为一个个人项目,成本必须可控。Makers Agents的按需调用计费模式很清晰,并且它允许我精细地控制Agent能访问哪些数据(比如只访问我的菜谱数据库)、能调用哪些工具(比如只能进行菜谱文本修改,不能访问外部API),安全性更有保障。
2.2 前端框架:Next.js 14 (App Router)
Next.js几乎是现代React应用的首选,尤其是其App Router模式,为这个项目带来了巨大便利。
服务端组件(Server Components)的天然优势。菜谱详情页、用户历史记录这些静态或个性化内容,我可以用服务端组件直接渲染,速度快且利于SEO。而需要与AI主厨交互的聊天界面,则使用客户端组件,实现动态交互。这种混合渲染模式让架构非常清晰高效。
API Routes的便捷性。在app/api/目录下创建路由来处理与EdgeOne Agents后端的通信变得极其简单。我只需要一个/api/chat的POST接口,前端将用户消息和上下文传过来,我在这里调用EdgeOne的Agent API,再将流式或非流式的响应返回给前端。Next.js帮我处理了路由、解析等琐事。
对流式响应(Streaming)的出色支持。AI主厨的“思考”过程如果一下子全返回,用户需要等待较长时间。更好的体验是让回答一个字一个字“流”出来。Next.js 14的App Router可以很方便地使用Response对象和TextStream来实现服务端到客户端的流式传输,这正是实时对话体验所需要的。
2.3 AI Agent设计思路:从聊天到“执行”
这是本项目的核心。我的目标不是做一个“菜谱问答机”,而是一个能执行“修改菜谱”指令的Agent。它的工作流我设计如下:
- 意图识别与槽位填充:用户说“我想做红烧肉但不吃肥肉”。Agent需要识别出核心意图是“修改菜谱”,并提取关键槽位:
目标菜谱=红烧肉,约束条件=去除肥肉。 - 知识检索与上下文构建:根据
目标菜谱,从我的数据库里检索出标准的红烧肉菜谱(包括食材、步骤、技巧)。将原始菜谱和用户约束一起作为新的上下文。 - 任务规划与工具调用:Agent内部规划:要满足“去肥肉”,需要调整“选材”步骤(改用瘦肉部位)和“烹饪”步骤(调整炖煮时间以防柴)。它需要调用我预设的“菜谱修改工具”,这个工具本质上是一套提示词(Prompt),指导大模型进行专业修改。
- 执行与呈现:大模型基于工具调用,输出修改后的完整菜谱。Agent将此结果结构化(如分成“调整要点”、“新食材清单”、“新步骤”),返回给前端。
这个流程的关键在于,Agent的“大脑”被植入了专业的烹饪知识(通过高质量的提示词和示例),使其修改建议不是天马行空,而是合乎烹饪逻辑的。
注意:Agent的能力边界必须在一开始就定义清楚。例如,我明确禁止它生成全新的、未经验证的菜谱(有食品安全风险),它的核心权限仅限于在现有权威菜谱基础上进行符合常识的替换、增减量、合并步骤等操作。
3. 核心实现细节拆解
3.1 EdgeOne Makers Agents 的编排实战
在Makers Agents的控制台,我创建了一个名为“AI主厨”的Agent。其编排逻辑如下图所示(用文字描述):
节点1:输入解析器。接收来自Next.js后端的用户查询。这里我配置了一个预处理提示词,让模型先判断用户意图是否是“修改菜谱”或“烹饪建议”。如果是简单问答(如“牛排几分熟好吃?”),会走一个快速问答分支;如果是复杂修改,则进入主流程。
节点2:菜谱检索器。这是一个自定义工具节点。我编写了一个函数,根据解析出的菜谱名称,去查询我的PostgreSQL数据库。这里有个技巧:除了精确匹配,我还使用了向量搜索。在录入菜谱时,我用OpenAI的嵌入模型(text-embedding-3-small)为每道菜的标题、主要食材和风味特点生成了向量,存储在Supabase的pgvector扩展中。当用户说“我想做那个甜甜的、用鸡肉和花生做的菜”这种模糊描述时,Agent能通过语义相似度找到“宫保鸡丁”,检索准确率大幅提升。
节点3:菜谱修改引擎(核心工具)。这是我花费精力最多的部分。这不是一个简单的代码函数,而是一个精心设计的、具有链式思考(Chain-of-Thought)能力的提示词模板。它被定义为一个“工具”,接收两个参数:original_recipe(原始菜谱JSON)和user_request(用户修改请求)。提示词会要求模型按以下步骤工作:
1. 分析用户请求对菜谱的哪一部分(食材、调料、步骤、技巧、设备)造成了影响。 2. 评估修改的可行性及对最终风味、口感可能产生的影响。 3. 分点列出具体的修改建议,并解释原因。 4. 输出修改后的完整菜谱,并特别标注出改动处。例如,对于“去除肥肉”的请求,模型可能会输出:“调整要点:1. 主料改用猪梅花肉或去皮五花肉瘦肉部分,以平衡口感与油脂。2. 煸炒阶段减少出油时间,避免瘦肉变干。3. 炖煮时增加约15分钟,并使用更小的火力,使瘦肉更酥烂。新食材清单:猪梅花肉500克……(后续略)”
节点4:结果格式化与安全过滤。修改引擎输出的内容还需要经过一层处理。这里我设置了一个“安全与格式检查”节点,它会检查输出中是否包含不安全的烹饪建议(如“生吃豆角”),并将模型输出的文本格式化为前端易于解析的JSON结构,比如{“summary”: “调整要点”, “ingredients”: […], “steps”: […], “tips”: “…”}。
3.2 Next.js 前端与Agent的通信桥梁
前端界面是一个典型的聊天界面,但重点在于与后端的高效、流畅通信。
API路由设计 (app/api/chat/route.ts):
import { NextRequest, NextResponse } from 'next/server'; import { EdgeOneAgentClient } from '@/lib/edgeone'; // 封装的EdgeOne SDK客户端 export async function POST(request: NextRequest) { try { const { messages, recipeContext } = await request.json(); // 获取消息历史和当前菜谱上下文 const userMessage = messages[messages.length - 1].content; // 构建Agent执行所需的输入,包含对话历史和关键的菜谱上下文 const agentInput = { query: userMessage, context: `当前用户正在查看的菜谱信息:${JSON.stringify(recipeContext)}。历史对话:${JSON.stringify(messages.slice(-5))}` // 限制历史长度 }; // 调用EdgeOne Makers Agents的API,启用流式响应 const stream = await EdgeOneAgentClient.executeStreaming('your-agent-id', agentInput); // 将EdgeOne返回的流,转换为Next.js可用的ReadableStream const encoder = new TextEncoder(); const readableStream = new ReadableStream({ async start(controller) { for await (const chunk of stream) { const content = chunk.choices[0]?.delta?.content; if (content) { controller.enqueue(encoder.encode(content)); } } controller.close(); }, }); // 返回流式响应 return new Response(readableStream, { headers: { 'Content-Type': 'text/plain; charset=utf-8', 'Cache-Control': 'no-cache', }, }); } catch (error) { console.error('Chat API error:', error); return NextResponse.json({ error: 'AI主厨暂时离线,请稍后再试' }, { status: 500 }); } }前端流式渲染 (app/components/chat-ui.tsx): 前端使用useState管理消息列表,在用户发送消息时,调用上述API,并通过fetch处理流式响应。
const [input, setInput] = useState(''); const [messages, setMessages] = useState<ChatMessage[]>([]); const [isLoading, setIsLoading] = useState(false); const handleSubmit = async () => { if (!input.trim()) return; const userMessage: ChatMessage = { role: 'user', content: input }; setMessages(prev => [...prev, userMessage]); setInput(''); setIsLoading(true); // 添加一个空的助手消息,用于接收流式内容 setMessages(prev => [...prev, { role: 'assistant', content: '' }]); try { const response = await fetch('/api/chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ messages: [...messages, userMessage], recipeContext: currentRecipe // 当前页面菜谱数据 }), }); const reader = response.body?.getReader(); const decoder = new TextDecoder(); if (!reader) return; while (true) { const { done, value } = await reader.read(); if (done) break; const chunk = decoder.decode(value); // 将流式内容逐步追加到最后一条助手消息中 setMessages(prev => { const lastMsg = prev[prev.length - 1]; const otherMsgs = prev.slice(0, -1); return [...otherMsgs, { ...lastMsg, content: lastMsg.content + chunk }]; }); } } catch (error) { console.error('Streaming error:', error); setMessages(prev => [...prev, { role: 'assistant', content: '抱歉,我好像出了点小状况。' }]); } finally { setIsLoading(false); } };3.3 提示词工程:让AI成为“专业主厨”
Agent的智能程度,90%取决于提示词的质量。我的“菜谱修改引擎”提示词,是一个多轮对话的模板:
你是一位经验丰富、精通中西餐的顶级主厨。你的任务是帮助用户根据他们的个性化需求,调整一份既有的经典菜谱。 ## 背景信息 这是原始菜谱: {original_recipe} 这是用户的需求: {user_request} ## 你的任务 1. **分析影响**:用户的请求主要希望改变菜谱的哪个方面?(食材、调味、步骤复杂度、烹饪时间、适用人群等) 2. **评估可行性**:这个改变在烹饪原理上是否可行?可能会带来哪些正面或负面的风味/口感变化? 3. **制定修改方案**:给出具体的、可操作的修改点。对于食材替换,请给出1-2种常见替代品。对于增减量,请给出建议比例。 4. **输出新菜谱**:生成一份完整的、修改后的菜谱。请用以下JSON格式回复,不要有任何额外的markdown标记: { "analysis": "简要的分析与可行性评估", "modification_points": ["要点1: ...", "要点2: ..."], "modified_recipe": { "name": "新菜谱名称(可稍作调整)", "ingredients": ["用量 食材1", "用量 食材2..."], "steps": ["步骤1...", "步骤2..."], "chef_tips": "给你的额外烹饪建议" } } ## 重要原则 - **安全第一**:绝对不要建议生食易中毒的食材(如豆角、木薯、某些蘑菇)。 - **忠于原味**:修改应尽量保持菜系的原始风味精髓。 - **清晰具体**:用量使用“克”、“毫升”、“茶匙”等标准单位,避免“适量”、“少许”。这个提示词明确了角色、任务、输出格式和边界原则,让大模型的输出稳定、可靠、有用。
4. 踩坑实录与性能优化
4.1 遇到的典型问题与解决方案
问题一:Agent“幻觉”导致离谱建议。早期测试中,用户问“做蛋糕没有烤箱怎么办?”,Agent有时会建议“用微波炉高火加热10分钟”,这显然会导致灾难。或者建议用“酸奶”代替“鸡蛋”,完全无视食材功能性的不同。
解决方案:
- 强化提示词中的约束:在提示词中明确加入“安全第一”和“烹饪原理可行性”章节,并列举负面例子。
- 引入知识库校验:在Agent流程中,增加一个“常识校验”节点。当修改引擎输出建议后,这个节点会调用一个更轻量级的模型(如GPT-3.5-Turbo),基于一个烹饪常识库(我整理的QA对)进行快速校验,如果发现高风险或明显错误建议,则触发重新生成或直接返回“此修改存在风险,建议您...”。
- 设置用户确认环节:对于涉及重大改变(如主要蛋白质替换)或非主流烹饪方法的建议,前端会在展示时高亮提示:“此为AI建议,请谨慎尝试,并确保食材熟透”。
问题二:流式响应在复杂思考时卡顿。当用户请求复杂时,Agent内部思考(Chain-of-Thought)时间变长,导致前端等待十几秒才看到第一个字,体验很差。
解决方案:
- 分阶段流式输出:我修改了Agent的编排,让“分析影响”和“评估可行性”这两个初步思考结论先流式输出。比如先快速打出:“好的,您希望减少糖分。这会影响菜品的色泽和焦糖化风味,但用代糖或水果天然甜味可以部分弥补。” 让用户立刻感知到AI已理解。然后再慢慢输出具体的修改方案和完整菜谱。
- 前端优化等待体验:在等待时,前端不仅显示加载动画,还会随机显示一些烹饪小贴士(如“热锅冷油不易粘锅”),转移用户注意力,降低等待焦虑感。
问题三:上下文长度与成本控制。每次对话都携带完整的菜谱文本和长对话历史,导致Token消耗很快,成本上升。
解决方案:
- 智能上下文窗口:不是所有历史都有用。我实现了一个简单的历史摘要机制。当对话轮次超过5轮,我会调用大模型将之前的对话总结成一段简短的摘要(如“用户希望将川菜口味调淡,并已讨论了减少辣椒和花椒的方案”),然后用摘要替代原始长历史,作为新的上下文输入。
- 菜谱向量化检索:如前所述,只传递菜谱的关键特征向量和ID,而不是全文,在Agent需要细节时再通过ID实时查询数据库,大幅减少了无效Token。
4.2 性能与体验优化点
1. 预加载Agent:在用户进入某个菜谱详情页时,前端就在后台静默初始化一个与该菜谱关联的Agent会话。这样当用户点击“咨询AI主厨”按钮时,几乎可以立刻开始对话,消除了冷启动延迟。
2. 结构化结果渲染:前端解析Agent返回的JSON,不是简单以文本形式展示。modification_points会渲染成高亮的要点列表,modified_recipe的食材和步骤会重新渲染成漂亮的UI组件,并且“修改处”有视觉对比(如划掉原内容,新增内容标绿),让用户一目了然。
3. 操作快捷入口:在AI主厨给出的新菜谱下方,提供“使用此菜谱创建新菜单”、“将修改加入购物车”等按钮,将AI的建议无缝转化为用户的下一步操作,形成体验闭环。
5. 效果评估与未来展望
上线测试一段时间后,我从用户反馈和数据中观察到一些有趣的现象:
用户活跃度提升:集成AI主厨的菜谱页面,平均停留时长提升了近3倍。用户不仅看,更开始“问”和“改”。
高频修改场景:排名前三的修改需求是:1.食材替代(家里没有/不喜欢某样东西);2.口味调整(减盐、减糖、加辣);3.工具简化(没有烤箱/空气炸锅怎么办)。这验证了“动态菜谱”需求的真实性。
信任建立需要过程:初期用户对AI建议将信将疑,但随着多次提供合理建议(例如建议用菠萝汁代替糖来增加酸甜味),用户信任度逐渐建立。在界面明确标注“AI建议,仅供参考”反而增加了可信度。
我个人最大的体会是:AI Agent的价值不在于替代人,而在于放大人的能力。它把用户从一个被动的菜谱“跟随者”,变成了一个主动的“共创者”。技术实现上,EdgeOne Makers Agents提供的编排能力让复杂AI逻辑的开发变得像搭乐高,而Next.js的现代前端架构则让交互体验流畅自然。两者的结合,让我这个小型独立开发者,也能在有限资源下做出具有“智能感”的产品。
这个项目还有很多可以深挖的方向,比如让AI主厨根据用户冰箱里的现有食材自动推荐菜谱并生成采购清单,或者根据用户的历史健康数据(如需要低钠饮食)自动过滤和调整菜谱。AI与垂直场景的结合,永远有新的故事可讲。
