作为两个口味极为挑剔的幼儿的爸爸——一个总是想在晚餐时吃零食,另一个拒绝任何不是米色的食物——我把家里的晚餐时间视为每晚一集的《Chopped:幼儿版》。再加上早睡时间和避免情绪崩溃(他们的和我的)的需要,这顿饭与其说是追求美食理想,不如说是为了生存。目标是什么?让每个人都吃饱、开心,最好还不会弄得满身是食物。
膳食规划是终极的平衡艺术:管理挑剔的偏好、饮食需求和时间的限制,同时还要尽量避免做三顿不同的饭。
这就是我转向 AI 的原因。
多智能体系统,其中每个智能体都有特定的任务,比如管理偏好或优化购物清单,是应对这种混乱的完美方式。但就像对付两个幼儿一样,协调是关键,没有好的计划,事情很快就会失控。
于是事件驱动架构登场了。
这就像一个运转良好的厨房中精心编排的混乱:智能体保持同步,适应变化(比如意外的零食突袭),并从灾难中优雅恢复(比如烤焦了大蒜面包)。借助 Apache Kafka ® 、Apache Flink ® 、LangChain 和 Claude 等工具,我构建了一个 AI 膳食规划器,消除了晚餐时间的压力,让我在那些宝贵的晚间时光中有了奋力一搏的机会。
在本文中,我将分享我是如何做到的——也许甚至能说服你让 AI 来应对你的晚餐难题。毕竟,如果它能搞定我的孩子们,它就能搞定任何事情。
注意:如果你只想看代码,请跳转到我的 GitHub 仓库 这里 。
从本质上讲,智能体就像一个小型决策机器人:它可以分析信息,根据所处环境进行推理,并采取行动。
智能体架构(灵感来自 https://arxiv.org/pdf/2304.03442 )
智能体令人兴奋之处在于它们能够超越静态的、预先编程的任务。它们可以适应新的输入,从过去的经验中学习,并处理动态问题——本质上就是为重复性或复杂任务承担繁重的工作。无论是自动化你的工作日,还是优化食品杂货采购,智能体都能将被动响应的技术转变为主动解决问题的工具。
当你开始组合智能体时,事情才真正变得有趣起来。你可以组合多个智能体,让每个智能体专注于不同的任务,以解决更大的问题。这就是多智能体系统。
不再是单个什么都懂一点的通才,而是拥有一支协同工作的专家团队:一个负责整理食谱,另一个计算营养,还有一个与家人的日历同步。这是规模化的协作,能够解锁单个智能体无法独自处理的、过于庞大或过于复杂的问题的解决方案。
当然,协调这支团队本身就是一项挑战。如果没有一个可靠的系统,你将面临职责重叠、更新遗漏或智能体之间目标不一致的问题。管理这些问题正是事件驱动设计大放异彩之处。
在事件驱动架构中,智能体不依赖僵化的指令。它们响应实时事件,例如日程变更或食品储藏更新。事件作为一种共享的动态语言,让智能体保持同步、快速响应,并在不拖垮整个系统的情况下处理故障。
这种方法将多智能体系统从理论概念转变为实用工具。但这对一个忙碌家庭的膳食规划意味着什么呢?让我们现在来探讨一下。
在我家,晚餐是每晚的难题:两个挑食的孩子,我和妻子想要吃得更健康,而且几乎没有足够的时间在睡前把一切搞定。
例如,我儿子喜欢鱼,但我女儿对鱼过敏,所以每顿有鱼的饭都需要为她准备替代食物。与此同时,他们俩都喜欢芝士通心粉,但我和妻子希望膳食中蛋白质、碳水化合物和脂肪的搭配更加均衡。再加上忙碌生活的种种限制,每周手动规划膳食就变成了一件令人倍感压力的事情。
为了解决这个问题,我设计了一个多智能体系统——一个由 AI 专家组成的团队,每位专家专注于特定任务。其工作方式如下:
儿童餐食规划智能体:该智能体专注于创建营养丰富、适合儿童且我的孩子真正会吃的餐食。
成人餐食规划智能体:该智能体是规划情侣餐食的专家,擅长高蛋白、低升糖、控制碳水但仍然美味的餐食。
共享偏好智能体:将儿童和成人餐食计划合并为一份统一的菜单,同时考虑过敏因素和共享食材。
格式化输出智能体:该智能体接收最终确定的餐食计划,添加购物清单,并将其重新格式化为 JSON 格式的结构化购物清单。
通过将工作拆分为专业化的智能体,系统提高了准确性和效率。每个智能体在其领域内就像一位专业厨师,而事件驱动架构使一切保持同步,无需显式依赖。
该 Web 应用是标准的三层架构,前端使用 Next.js 构建,应用数据库使用 MongoDB。它被有意保持简单,不包含任何直接的 AI 逻辑或对 Kafka 的了解。其主要作用是让用户配置餐食规划设置,例如孩子的喜好和厌恶,并提交新餐食计划请求。
当新的餐食计划请求被提交时,它会以“processing”状态写入 MongoDB。这会触发多智能体工作流,为这一周生成完整的餐食计划。流程完成后,状态会更新,用户可以点击单个餐食计划来展开用户界面(UI)。这会显示这一周的完整详情,包括餐食明细和自动生成的购物清单。
要有效协调多智能体系统,你需要一种共享语言——一种让智能体交换信息、解释命令并协作的结构化方式。
在事件驱动架构中,事件就是这种共享语言,作为结构化更新,使智能体保持一致、响应迅速并能适应变化。可以把它想象成系统的群聊,智能体在其中广播更新、共享上下文,并在保持同步的同时独立执行任务。
Kafka 为这种通信提供了骨干。
智能体从 Kafka 主题中生产和消费事件,这使它们能够实时响应变化,例如新的偏好,甚至新的智能体。Apache Flink 增加了处理这些流的能力,支持即时进行复杂推理和转换。如果你不熟悉 Flink,它是一个开源流处理框架,专为实时处理大量数据而构建,非常适合高吞吐、低延迟的应用。Flink 非常适合 AI 应用。
借助 Confluent 的平台简化该架构的管理和扩展,你可以构建一个可靠、事件驱动的多智能体工作流,优雅地处理现实世界的复杂性。
下图展示了我在 Confluent Cloud 中如何使用这些工具,以实现智能体之间无缝且可扩展的协作。
其工作原理如下。
当数据写入 MongoDB 时,Confluent Cloud 中的源连接器会在 Kafka 主题“Meal Plan Requests”中创建一个事件。该事件启动多智能体工作流。
儿童和成人膳食规划智能体并行运行,生成各自的计划并产生事件,这些事件由 Flink 连接(如下所示)到主题“Joined Preferences”中。
CREATE TABLE `default`.`dev-us-east-1-cluster`.`meal-planner.output.joined-preferences` ( `request_id` STRING, `child_preference` STRING, `adult_preference` STRING ) WITH ( 'value.format' = 'json-registry' ); INSERT INTO `meal-planner.output.joined-preferences` SELECT c.`request_id`, c.content as child_preference, a.content as adult_preference FROM `meal-planner.output.child-preferences` c JOIN `meal-planner.output.adult-preferences` a ON c.`request_id` = a.`request_id`;
当新事件写入 Joined Preferences 时,共享膳食计划智能体被触发,它将输入整合为统一的家庭膳食计划。最终事件写入另一个 Kafka 主题,激活格式输出智能体,将结果结构化为用户友好的格式,完成工作流。
此设置高度可扩展,并解耦了智能体交互,使得添加新智能体或下游消费者变得容易,而不会影响性能或需要更改现有智能体。
让我们深入探讨各个智能体的角色。
儿童和成人膳食规划智能体遵循 React 设计模式。这种设计模式将推理和行动结合到一个循环中,使智能体能够根据对任务的当前理解迭代地做出决策和行动。
推理:智能体分析情况,考虑上下文,并使用其内部知识或外部输入确定下一个最佳行动。
行动:智能体执行所选行动,这可能涉及与环境交互、查询数据或生成输出。
反馈循环:行动的结果提供新信息,智能体将其纳入下一轮迭代的推理中。
这种模式非常适合需要适应性的动态任务,因为它允许智能体持续改进其方法并实时适应新进展。
我使用了 LangGraph 对此的内置支持。儿童和成人版本的代码非常相似,但系统提示指定了不同的行为和专业知识集。
儿童膳食规划系统提示词:
SYSTEM_PROMPT = """You are an expert at designing nutritious meals that toddlers love. You will be prompted to generate a weekly dinner plan. You'll have access to meal preferences. Use these as inspiration to come up with meals but you don't have to explicitly use these items. You'll have access to recent meals. Factor these in so you aren't repetitive. You must take into account any hard requirements about meals. Bias towards meals that can be made in less than 30 minutes. Keep meal preparation simple. There is no human in the loop, so don't prompt for additional input. """
成人膳食规划系统提示词:
SYSTEM_PROMPT = """You are an expert at designing high protein, low glycemic, low carb dinners for couples. You will be prompted to generate a weekly dinner plan. You'll have access to recent meals. Factor these in so you aren't repetitive. Bias towards meals that can be made in less than 30 minutes. Keep meal preparation simple. There is no human in the loop, so don't prompt for additional input. """
两个智能体都使用工具。儿童膳食规划器尤其使用了多个工具来考虑膳食规划器配置的设置。例如,下面的代码展示了三个工具,分别用于获取儿童膳食偏好、硬性要求和近期膳食。
@tool
def get_kid_preferences():
"""Use this to get the likes and dislikes for the kids preferences."""
# Connect to the MongoDB instance
client = MongoClient(os.getenv("MONGODB_URI")) # Replace with your MongoDB URI
# Access the database and collection
db = client['meal_planner'] # Database name
collection = db['meal_preferences'] # Collection name
projection = {"likes": 1, "dislikes": 1, "_id": 0}
result = collection.find_one({}, projection)
return result
@tool
def get_hard_requirements():
"""Use this to get the hard requirements for recommending a meal. These must be enforced."""
# Connect to the MongoDB instance
client = MongoClient(os.getenv("MONGODB_URI")) # Replace with your MongoDB URI
# Access the database and collection
db = client['meal_planner'] # Database name
collection = db['meal_preferences'] # Collection name
projection = {"hardRequirements": 1, "_id": 0}
result = collection.find_one({}, projection)
return result
@tool
def get_recent_meals():
"""Use this to get recent meals."""
# Connect to the MongoDB instance
client = MongoClient(os.getenv("MONGODB_URI")) # Replace with your MongoDB URI\
# Access the database and collection
db = client['meal_planner']
collection = db['weekly_meal_plans']
# Query to get the last two entries
recent_meals = list(collection.find().sort([("$natural", -1)]).limit(2))
return recent_meals为了合并儿童和成人膳食规划智能体的输出,我使用了反思(Reflection)设计模式,这是一个使生成式 AI 智能体能够迭代评估和改进其输出的框架。该模式通过三个关键步骤运作:
生成(Generate):智能体根据其输入或任务生成初始输出。
反思(Reflect):智能体批判性地评估输出,将其与任务要求或质量标准进行比较。此步骤可能涉及自我评估或借助另一个智能体进行审查。
修订(Revise):基于反思结果,智能体优化其输出,产生更准确或更完善的结果。
该模式对于单次处理可能无法胜任的复杂任务尤为有效。通过嵌入反思机制,智能体能够自主提升其表现,确保更高的可靠性和质量,而无需外部干预。
在共享膳食计划智能体中,我创建了两个定制提示词来指导生成和反思过程:
生成内容提示词:generate_content_prompt = ChatPromptTemplate.from_messages( [ ( "system", "You are a meal planning assistant for families." "Your job is to combine the recommended meal plan for the children and the adults into a singular meal plan that works for the family." "Aim to minimize creating multiple dishes. Each meal should be able to work for both the adults and kids." "Make sure you include the same number of meals in the combined plan as in the original plans." "Output should contain the name of the meal, any modification or version for the children, any modification or version for the adults, core ingredients, prep time, and basic recipe." "If the user provides critique, respond with a revised version of your previous attempts.", ), MessagesPlaceholder(variable_name="messages"), ] )
反思提示词:reflection_prompt = ChatPromptTemplate.from_messages( [ ( "system", "You are a family meal planning expert grading the quality of the recommended meals on taste, variety, and nutritional value." "Generate critique and recommendations for the user's submission." "Provide detailed recommendations, including requests for greater variety, tastier meals, or higher nutritional value.", ), MessagesPlaceholder(variable_name="messages"), ] )
使用 LangGraph,我创建了一个连接生成器节点和反思节点的工作流图。条件边确保如果反思步骤识别出需要改进的地方,流程会进行迭代。这个迭代工作流动态地优化合并后的膳食计划,直到达到预期标准,从而有效地平衡儿童和成人的需求。
builder = StateGraph(State)
builder.add_node("generate", generation_node)
builder.add_node("reflect", reflection_node)
builder.add_edge(START, "generate")
def should_continue(state: State):
if len(state["messages"]) > MAX_ITERATIONS:
return END
return "reflect"
builder.add_conditional_edges("generate", should_continue)
builder.add_edge("reflect", "generate")
memory = MemorySaver()
graph = builder.compile(checkpointer=memory)工作流的最后一步是为合并后的膳食计划生成一份全面的购物清单,并将输出格式化为结构化、用户友好的响应。为此,我再次应用了 React 设计模式,为智能体配备了高度详细的指令,以确保精确的输入解析和输出生成。该智能体拥有整个工作流中最详尽、最详细的系统提示,如下所示。
You are a system that processes meal plans and reformats them into a structured JSON format.
The input you receive contains an unstructured meal plan for a specific week in the year.
Your task is to extract key details and output a JSON payload with the following structure:
{
"summary":,
"groceryList":,
"meals": [
{
"title":,
"coreIngredients": [],
"kidsVersion":,
"adultVersion":,
"recipe":},
...
]
}
Instructions:
1. For the summary, create a short summary for the meals of the week including the meal names.
2. For the groceryList, analyze the existing meals and create a list of grocery items required. Format the grocery list as HTML as shown in the example.
3. For each meal in the input:
- Extract the meal title and assign it to title.
- Identify the core ingredients and list them in coreIngredients.
- Extract the description for the kids' version and assign it to kidsVersion.
- Extract the description for the adult version and assign it to adultVersion.
- Extract the basic recipe and assign it to recipe.
4. Ensure all extracted data is clean, properly formatted, and follows the given JSON structure.
Example Input:
1. Cheesy Broccoli Chicken Pasta Bake
- Name: Family Pasta Bake
- Kids Version: Regular cheesy pasta with extra cheese
- Adult Version: Herb-seasoned with added protein (ground chicken)
- Core Ingredients: Pasta, broccoli, cheese, chicken, herbs
- Prep Time: 35 minutes
- Modifications:
* For kids: Smaller pieces, milder seasoning
* For adults: More herbs, potential addition of red pepper flakes
- Recipe: Instructions:
Preheat Oven:
Preheat your oven to 375°F (190°C).
Cook Pasta and Broccoli:
Bring a large pot of salted water to a boil. Cook the pasta according to package instructions, adding the broccoli to the pot for the last 3 minutes of cooking.
Drain and set aside.
Prepare the Chicken:
Heat a large skillet over medium heat. Add a drizzle of olive oil and cook the ground chicken until browned and cooked through, breaking it into small pieces as it cooks.
Season with salt, pepper, and the mixed dried herbs.
Assemble the Pasta Bake:
In a large mixing bowl, combine the cooked pasta, broccoli, chicken, and 1 1/2 cups of shredded cheese. Mix until evenly combined.
If making the adult version, add Parmesan and red pepper flakes at this stage.
Divide for Kids and Adults (Optional):
If making separate versions, split the mixture into two baking dishes.
For the kids' version, sprinkle extra shredded cheese on top.
For the adults' version, sprinkle a mix of Parmesan and shredded cheese on top.
Bake:
Place the baking dish(es) in the preheated oven. Bake for 20–25 minutes, or until the top is golden and bubbly.
Serve:
Let the pasta bake cool for a few minutes before serving. Garnish the adult version with fresh herbs if desired.
2. Teriyaki Protein Plate
- Name: Family Teriyaki Plate
- Kids Version: Mild teriyaki chicken, udon noodles
- Adult Version: Spicy teriyaki option, added vegetables
- Core Ingredients: Chicken, udon noodles, teriyaki sauce, vegetables
- Prep Time: 25 minutes
- Modifications:
* For kids: Less spicy, cut into smaller pieces
* For adults: Add chili oil, extra vegetables
- Recipe: Instructions:
Prep Ingredients:
Wash and chop the vegetables into bite-sized pieces.
Cut the chicken into small chunks.
Cook the Udon Noodles:
Bring a pot of water to a boil. Cook the udon noodles according to package instructions.
Drain, rinse with cool water, and set aside.
Prepare the Chicken:
Heat 1 tbsp of vegetable oil in a large skillet over medium heat. Add the chicken pieces and cook until golden brown and fully cooked, about 6–8 minutes.
Remove half of the chicken for the kids' version.
Make Kids' Teriyaki Chicken:
In a small pan, combine the mild teriyaki sauce with the reserved chicken and warm over low heat. Remove from heat once coated.
Make Adults' Spicy Teriyaki Chicken:
In the original skillet, push the remaining chicken to one side and add an additional 1 tbsp of oil.
Add the extra vegetables and stir-fry for 2–3 minutes until slightly tender.
Drizzle chili oil over the chicken and vegetables, then pour in the teriyaki sauce. Stir until everything is evenly coated.
Assemble the Plates:
For the kids' version, place udon noodles on a plate, top with mild teriyaki chicken, and garnish with a few plain veggies if desired.
For the adults' version, layer udon noodles, spicy teriyaki chicken, and sautéed vegetables. Garnish with additional chili oil for extra heat.
Serve:
Divide the plates for kids and adults, ensuring everyone gets the flavor and spice level they prefer.
Example Output:
{
"summary": "Family pasta bake and teriyaki protein plate",
"grocyerList": "Proteins:Ground chicken (1 lb for pasta bake, 1 lb for teriyaki plate)Pasta and Noodles:Pasta (e.g., penne or fusilli, 12 oz)Udon noodles (10 oz)Vegetables:Broccoli (2 cups)Mixed vegetables (e.g., bell peppers, broccoli, carrots, zucchini, snap peas, 3 cups total)Cheese:Shredded cheese (cheddar or mozzarella, 2 cups + 1/2 cup extra for kids)Parmesan cheese (1/4 cup)Sauces and Seasonings:Teriyaki sauce (1/2 cup mild for kids, regular or spicy for adults)Chili oil (1 tsp or to taste)Mixed dried herbs (e.g., oregano, basil, thyme, 1 tsp)Red pepper flakes (optional, 1/2 tsp)Salt and pepperOther Pantry Items:Olive oil (2 tbsp)Vegetable oil (2 tbsp)",
"meals": [
{
"title": "Family Pasta Bake",
"coreIngredients": ["pasta", "broccoli", "cheese", "chicken", "herbs"],
"kidsVersion": "Regular cheesy pasta with extra cheese",
"adultVersion": "Herb-seasoned with added protein (ground chicken)"
"recipe": "Instructions:
Preheat Oven:
Preheat your oven to 375°F (190°C).
Cook Pasta and Broccoli:
Bring a large pot of salted water to a boil. Cook the pasta according to package instructions, adding the broccoli to the pot for the last 3 minutes of cooking.
Drain and set aside.
Prepare the Chicken:
Heat a large skillet over medium heat. Add a drizzle of olive oil and cook the ground chicken until browned and cooked through, breaking it into small pieces as it cooks.
Season with salt, pepper, and the mixed dried herbs.
Assemble the Pasta Bake:
In a large mixing bowl, combine the cooked pasta, broccoli, chicken, and 1 1/2 cups of shredded cheese. Mix until evenly combined.
If making the adult version, add Parmesan and red pepper flakes at this stage.
Divide for Kids and Adults (Optional):
If making separate versions, split the mixture into two baking dishes.
For the kids' version, sprinkle extra shredded cheese on top.
For the adults' version, sprinkle a mix of Parmesan and shredded cheese on top.
Bake:
Place the baking dish(es) in the preheated oven. Bake for 20–25 minutes, or until the top is golden and bubbly.
Serve:
Let the pasta bake cool for a few minutes before serving. Garnish the adult version with fresh herbs if desired."
},
{
"title": "Family Teriyaki Plate",
"coreIngredients": ["chicken", "udon noodles", "teriyaki sauce", "vegetables"],
"kidsVersion": "Mild teriyaki chicken, udon noodles",
"adultVersion": "Spicy teriyaki option, added vegetables"
"recipe": "Instructions:
Prep Ingredients:
Wash and chop the vegetables into bite-sized pieces.
Cut the chicken into small chunks.
Cook the Udon Noodles:
Bring a pot of water to a boil. Cook the udon noodles according to package instructions.
Drain, rinse with cool water, and set aside.
Prepare the Chicken:
Heat 1 tbsp of vegetable oil in a large skillet over medium heat. Add the chicken pieces and cook until golden brown and fully cooked, about 6–8 minutes.
Remove half of the chicken for the kids' version.
Make Kids' Teriyaki Chicken:
In a small pan, combine the mild teriyaki sauce with the reserved chicken and warm over low heat. Remove from heat once coated.
Make Adults' Spicy Teriyaki Chicken:
In the original skillet, push the remaining chicken to one side and add an additional 1 tbsp of oil.
Add the extra vegetables and stir-fry for 2–3 minutes until slightly tender.
Drizzle chili oil over the chicken and vegetables, then pour in the teriyaki sauce. Stir until everything is evenly coated.
Assemble the Plates:
For the kids' version, place udon noodles on a plate, top with mild teriyaki chicken, and garnish with a few plain veggies if desired.
For the adults' version, layer udon noodles, spicy teriyaki chicken, and sautéed vegetables. Garnish with additional chili oil for extra heat.
Serve:
Divide the plates for kids and adults, ensuring everyone gets the flavor and spice level they prefer."
}
]
}
Follow this pattern strictly. If any part of the input is ambiguous or missing,
make a best guess based on context and include placeholder values like null where appropriate.
Respond with the JSON only. Absolutely nothing else.一旦结构化响应生成,它会被写回 Kafka 主题。一个专用服务消费这些消息,并更新 MongoDB 中对应的每周膳食计划请求。通过这最后一次更新,UI 获得了完整完成的膳食计划和购物清单的访问权限。
智能体服务和 Web 应用程序目前位于同一个仓库中,但为了更接近生产环境的实现,它们可以轻松地分离到不同的仓库中。同样,虽然在这个设置中所有智能体都是单个项目的一部分,但它们可以解耦并作为独立的无服务器函数部署。这种方法将允许每个智能体自主运行,增强可扩展性、灵活性和故障隔离。
由于我采用了事件驱动的方法,我可以在未来扩展或更改智能体系统。例如,我可能想要包含一个营养分析智能体,它使用外部工具根据膳食计划计算卡路里或宏量营养素。或者我可能想要将人工反馈纳入膳食计划中,允许用户对餐食进行点赞或点踩,以随着时间的推移教会 AI 了解我们的偏好。
恭喜——你已经挺过了 AI 膳食规划的旋风、挑食孩子的发脾气,以及事件驱动的多智能体系统。我家可能偶尔仍然像“Chopped:幼儿版”,但有了 Kafka、Flink 和一组 AI 智能体,晚餐时间少了一点混乱,多了一点享受。
这里真正的收获是事件驱动的多智能体系统如何能够简化复杂的现实世界挑战。
这些智能体不仅能工作,还能协作、适应并无缝扩展——这一切都归功于共享的事件“语言”。这就像拥有一支厨房团队,他们能够沟通并毫不间断地完成任务。
这种架构不仅仅适用于膳食规划。它还可以应用于任何领域,从自动化工作中的工作流到解决日常问题。例如,我使用类似的系统构建了一个研究智能体并生成 LinkedIn 帖子。无论你是在自动化自己的任务还是构建更大的东西,配方都是一样的:扎实的设计、多智能体协作和事件驱动的魔力。
现在,请原谅我,另一个房间里有一个安静得可疑的幼儿……
转载声明:本文为转载整理,原文版权归原作者及原站点所有。
原文来源:Confluent Blog
原文作者:Sean Falconer;如有侵权,请联系管理员删除。