美食推荐Agent全栈实现:从个性化菜谱生成到外卖生态Harness的工程化实践

元数据

  • 关键词:美食推荐Agent、大语言模型Agent编排、个性化菜谱生成、外卖Harness、多模态饮食推荐、强化学习推荐、服务编排
  • 摘要:本文从第一性原理出发,系统拆解了美食推荐Agent的核心架构与实现路径,首次提出了覆盖「用户需求-菜谱生成-外卖匹配-履约同步」全链路的技术方案,通过构建外卖Harness适配层打通了菜谱生态与外卖平台的异构数据壁垒。本文不仅包含完整的理论推导、架构设计、核心代码实现,还提供了生产环境部署的最佳实践与行业演化趋势分析,适合AI产品经理、推荐算法工程师、O2O行业技术人员阅读参考。

1. 概念基础

1.1 领域背景化

「今天吃什么」是全人类每天都要面对的灵魂拷问:对于喜欢做饭的用户,需要匹配口味、健康约束、现有食材、烹饪能力的个性化菜谱;对于不想做饭的用户,需要匹配预算、配送时间、口味、健康要求的外卖商品。传统美食服务的核心痛点是场景割裂:菜谱类平台(如下厨房、美食杰)仅能提供静态菜谱推荐,无法对接食材采购或外卖履约;外卖平台(如美团、饿了么)仅能提供外卖商品推荐,无法覆盖用户自己做饭的需求,两类平台的数据完全不通,用户需要在多个APP之间切换才能完成决策。

美食推荐Agent的核心价值就是打破场景壁垒:用统一的智能交互入口承接用户所有饮食需求,同时提供菜谱生成和外卖匹配选项,用户无需切换平台即可完成从决策到履约的全流程。

1.2 历史轨迹

美食推荐服务的发展经历了四个清晰的阶段:

  1. 静态信息聚合阶段(2000-2010):以美食天下、下厨房早期站点为代表,核心能力是关键词搜索,仅能提供静态的菜谱信息,没有任何个性化能力,也没有履约链路。
  2. 单场景个性化推荐阶段(2010-2020):移动互联网普及后,下厨房、美团、饿了么等APP采用协同过滤、基于内容的推荐算法,实现了单一领域的个性化推荐,但菜谱和外卖场景完全割裂,数据不互通。
  3. 跨场景探索阶段(2020-2023):部分美食APP尝试打通菜谱和食材配送,但覆盖场景有限,交互生硬,需要用户手动筛选大量参数,用户体验不佳。
  4. Agent驱动的全场景阶段(2023-至今):大语言模型的普及使得自然语言交互、多工具调用成为可能,美食推荐Agent可以理解用户的自然语言需求,自动调用菜谱生成、外卖匹配、健康校验等工具,实现全链路的智能服务。

1.3 问题空间定义

美食推荐的核心问题是三重约束下的效用最大化

  • 主观偏好约束:用户的口味偏好、菜系偏好、饮食文化禁忌(如清真、素食)等
  • 客观资源约束:烹饪能力、可支配时间、预算、地理位置、配送时间要求等
  • 健康约束:过敏原、慢性病禁忌、营养需求(减脂、增肌、控糖)、每日热量上限等

同时还要解决两大工程问题:

  1. 异构数据打通问题:菜谱数据、外卖商品数据、用户健康数据的结构、标准完全不同,需要统一的语义表示
  2. 履约链路打通问题:如何将Agent的推荐结果和外卖平台的下单、履约系统无缝对接,减少用户操作成本

1.4 术语精确性

  • 美食推荐Agent:基于大语言模型的智能代理,具备感知用户需求、推理约束条件、调用领域工具、输出最优推荐结果的能力,支持多轮交互
  • 外卖Harness:专门设计的外卖生态适配层,负责对接不同外卖平台的API,将异构的外卖商品数据统一为标准格式,同时同步库存、订单履约状态,是连接Agent和外卖生态的中间层
  • 饮食效用:用户对饮食推荐结果的满意度综合评分,涵盖口味、价格、时间、健康等多个维度的满意度

2. 理论框架

2.1 第一性原理推导

从最基本的公理出发,美食推荐的本质是在约束空间内搜索最优解:

公理1:每个用户的饮食需求都可以用一组可量化的约束向量表示
公理2:每个候选饮食方案(菜谱/外卖商品)都可以用一组可量化的属性向量表示
公理3:用户对候选方案的满意度与两个向量的匹配度正相关
推论:美食推荐的核心目标是找到匹配度最高的候选方案,同时满足所有硬约束

2.2 数学形式化

首先定义用户画像的三维向量表示:
U=[UpUrUh] U = \begin{bmatrix} U_p \\ U_r \\ U_h \end{bmatrix} U= UpUrUh
其中:

  • Up∈RdpU_p \in R^{d_p}UpRdp 是用户偏好向量,维度dpd_pdp一般取128,包含口味偏好(辣度、甜度、菜系偏好等)、饮食文化偏好等,由用户历史行为数据与显式反馈嵌入生成
  • Ur∈RdrU_r \in R^{d_r}UrRdr 是资源约束向量,维度drd_rdr一般取64,包含烹饪能力等级、可支配烹饪时间、预算区间、地理位置、可配送时间等
  • Uh∈RdhU_h \in R^{d_h}UhRdh 是健康约束向量,维度dhd_hdh一般取64,包含过敏原、慢性病禁忌、营养需求、每日摄入热量上限等

定义菜谱空间 R={r1,r2,...,rn}R = \{r_1, r_2, ..., r_n\}R={r1,r2,...,rn},每个菜谱的向量表示为 ri∈R256r_i \in R^{256}riR256,包含食材列表、烹饪步骤、烹饪时间、营养成分、口味标签、难度等级等属性的嵌入。

定义外卖商品空间 F={f1,f2,...,fm}F = \{f_1, f_2, ..., f_m\}F={f1,f2,...,fm},每个外卖商品的向量表示为 fj∈R256f_j \in R^{256}fjR256,包含商品名称、配料表、营养成分、价格、商家位置、配送时间、销量、评分等属性的嵌入。

推荐的目标函数是最大化用户的总效用:
max⁡{Rs,Fs}Utotal=α⋅Sim(U,Rs)+β⋅Sim(U,Fs)+γ⋅Corr(Rs,Fs) \max_{\{R_s, F_s\}} U_{total} = \alpha \cdot Sim(U, R_s) + \beta \cdot Sim(U, F_s) + \gamma \cdot Corr(R_s, F_s) {Rs,Fs}maxUtotal=αSim(U,Rs)+βSim(U,Fs)+γCorr(Rs,Fs)
其中:

  • Sim(X,Y)Sim(X,Y)Sim(X,Y) 是余弦相似度函数,计算向量X和Y的匹配度
  • Corr(Rs,Fs)Corr(R_s, F_s)Corr(Rs,Fs) 是菜谱集合RsR_sRs和外卖商品集合FsF_sFs的替代相关性,衡量外卖商品是否可以作为菜谱的替代方案
  • α,β,γ\alpha, \beta, \gammaα,β,γ 是权重系数,由用户的意图动态调整:用户明确想做饭时α=0.8,β=0.1,γ=0.1\alpha=0.8, \beta=0.1, \gamma=0.1α=0.8,β=0.1,γ=0.1;用户明确想点外卖时α=0.1,β=0.8,γ=0.1\alpha=0.1, \beta=0.8, \gamma=0.1α=0.1,β=0.8,γ=0.1;用户无明确意图时α=0.4,β=0.4,γ=0.2\alpha=0.4, \beta=0.4, \gamma=0.2α=0.4,β=0.4,γ=0.2

约束条件(硬约束,必须100%满足):
s.t.{Uh∩Tag(Rs)=∅Uh∩Tag(Fs)=∅Cost(Rs)≤Ur[budget]Cost(Fs)≤Ur[budget]Time(Rs)≤Ur[available_time]Time(Fs)≤Ur[available_time] s.t. \begin{cases} U_h \cap Tag(R_s) = \emptyset \\ U_h \cap Tag(F_s) = \emptyset \\ Cost(R_s) \leq U_r[budget] \\ Cost(F_s) \leq U_r[budget] \\ Time(R_s) \leq U_r[available\_time] \\ Time(F_s) \leq U_r[available\_time] \end{cases} s.t. UhTag(Rs)=UhTag(Fs)=Cost(Rs)Ur[budget]Cost(Fs)Ur[budget]Time(Rs)Ur[available_time]Time(Fs)Ur[available_time]
其中Tag(X)Tag(X)Tag(X)是X的标签集合,前两条约束确保推荐的内容不会触发用户的健康禁忌,后面四条确保符合用户的预算和时间约束。

2.3 理论局限性

当前框架存在四个核心局限性:

  1. 冷启动问题:新用户没有历史行为数据,UpU_pUp的初始化不准确,需要通过引导用户填写少量信息或者用人口统计学特征做冷启动
  2. 偏好漂移问题:用户的口味会随时间、场景变化(比如夏天想吃清淡的,冬天想吃热的),需要动态更新UpU_pUp向量,传统的静态向量表示无法适配
  3. 数据质量问题:大量外卖商品的配料表、营养成分数据不全,导致FjF_jFj的向量表示不准确,匹配度低
  4. 上下文感知不足:当前框架仅能捕捉用户的显式需求,无法感知隐含上下文(比如用户在公司就不能推荐需要做饭的菜谱,用户感冒了就应该推荐清淡的食物)

2.4 竞争范式分析

推荐范式 核心技术 个性化程度 跨场景能力 交互自然度 可解释性 冷启动成本 典型应用
关键词搜索 字符串匹配 极低(无个性化) 极差(只能匹配固定字段) 极低(必须输入精确关键词) 高(匹配关键词) 极低 早期美食网站搜索
协同过滤推荐 用户/物品协同过滤 中等(基于群体行为) 差(只能在单一领域推荐) 低(只能被动推荐) 低(你可能还喜欢) 高(需要大量行为数据) 传统外卖平台推荐
基于内容的推荐 标签匹配、向量检索 中等(基于标签匹配) 中等(可以跨领域匹配标签) 低(只能被动推荐) 中等(符合你喜欢的川菜口味) 中等(需要用户标签) 早期个性化美食APP
LLM Agent推荐 大语言模型、工具调用、多模态推理 极高(结合全维度约束) 极高(覆盖全场景) 极高(自然语言交互,支持多轮) 极高(明确说明推荐理由) 低(仅需少量用户输入) 本文提出的FoodAgent

3. 架构设计

3.1 系统分解

我们采用五层分层架构设计,各层职责清晰,低耦合高内聚:

渲染错误: Mermaid 渲染失败: Parsing failed: Lexer error on line 2, column 15: unexpected character: ->[<- at offset: 32, skipped 7 characters. Lexer error on line 3, column 20: unexpected character: ->[<- at offset: 59, skipped 3 characters. Lexer error on line 3, column 26: unexpected character: ->/<- at offset: 65, skipped 5 characters. Lexer error on line 4, column 22: unexpected character: ->[<- at offset: 92, skipped 6 characters. Lexer error on line 5, column 20: unexpected character: ->[<- at offset: 118, skipped 1 characters. Lexer error on line 5, column 24: unexpected character: ->端<- at offset: 122, skipped 2 characters. Lexer error on line 7, column 16: unexpected character: ->[<- at offset: 141, skipped 1 characters. Lexer error on line 7, column 22: unexpected character: ->编<- at offset: 147, skipped 4 characters. Lexer error on line 8, column 27: unexpected character: ->[<- at offset: 178, skipped 6 characters. Lexer error on line 9, column 26: unexpected character: ->[<- at offset: 210, skipped 6 characters. Lexer error on line 10, column 21: unexpected character: ->[<- at offset: 237, skipped 8 characters. Lexer error on line 12, column 17: unexpected character: ->[<- at offset: 263, skipped 7 characters. Lexer error on line 13, column 29: unexpected character: ->[<- at offset: 299, skipped 8 characters. Lexer error on line 14, column 27: unexpected character: ->[<- at offset: 334, skipped 8 characters. Lexer error on line 15, column 27: unexpected character: ->[<- at offset: 369, skipped 8 characters. Lexer error on line 16, column 29: unexpected character: ->[<- at offset: 406, skipped 10 characters. Lexer error on line 18, column 18: unexpected character: ->[<- at offset: 435, skipped 3 characters. Lexer error on line 18, column 28: unexpected character: ->层<- at offset: 445, skipped 2 characters. Lexer error on line 19, column 24: unexpected character: ->[<- at offset: 471, skipped 9 characters. Lexer error on line 20, column 26: unexpected character: ->[<- at offset: 506, skipped 10 characters. Lexer error on line 21, column 22: unexpected character: ->[<- at offset: 538, skipped 10 characters. Lexer error on line 22, column 24: unexpected character: ->[<- at offset: 572, skipped 3 characters. Lexer error on line 22, column 30: unexpected character: ->]<- at offset: 578, skipped 1 characters. Lexer error on line 23, column 22: unexpected character: ->[<- at offset: 601, skipped 4 characters. Lexer error on line 23, column 29: unexpected character: ->]<- at offset: 608, skipped 1 characters. Lexer error on line 24, column 22: unexpected character: ->[<- at offset: 631, skipped 5 characters. Lexer error on line 24, column 30: unexpected character: ->]<- at offset: 639, skipped 1 characters. Lexer error on line 26, column 15: unexpected character: ->[<- at offset: 656, skipped 5 characters. Lexer error on line 27, column 19: unexpected character: ->[<- at offset: 680, skipped 7 characters. Lexer error on line 28, column 21: unexpected character: ->[<- at offset: 708, skipped 7 characters. Lexer error on line 29, column 19: unexpected character: ->[<- at offset: 734, skipped 9 characters. Lexer error on line 30, column 21: unexpected character: ->[<- at offset: 764, skipped 7 characters. Parse error on line 3, column 23: Expecting: one of these possible Token sequences: 1. [NEWLINE] 2. [EOF] but found: 'APP' Parse error on line 3, column 31: Expecting token of type ':' but found ` `. Parse error on line 5, column 21: Expecting: one of these possible Token sequences: 1. [NEWLINE] 2. [EOF] but found: 'Web' Parse error on line 5, column 26: Expecting token of type ':' but found ` `. Parse error on line 7, column 17: Expecting: one of these possible Token sequences: 1. [NEWLINE] 2. [EOF] but found: 'Agent' Parse error on line 7, column 26: Expecting token of type ':' but found ` `. Parse error on line 18, column 21: Expecting: one of these possible Token sequences: 1. [NEWLINE] 2. [EOF] but found: 'Harness' Parse error on line 18, column 30: Expecting token of type ':' but found ` `. Parse error on line 22, column 27: Expecting: one of these possible Token sequences: 1. [NEWLINE] 2. [EOF] but found: 'API' Parse error on line 22, column 31: Expecting token of type ':' but found ` `. Parse error on line 23, column 26: Expecting: one of these possible Token sequences: 1. [NEWLINE] 2. [EOF] but found: 'API' Parse error on line 23, column 30: Expecting token of type ':' but found ` `. Parse error on line 24, column 27: Expecting: one of these possible Token sequences: 1. [NEWLINE] 2. [EOF] but found: 'API' Parse error on line 24, column 31: Expecting token of type ':' but found ` `. Parse error on line 27, column 12: Expecting token of type ':' but found `user_db`. Parse error on line 28, column 12: Expecting token of type ':' but found `recipe_db`. Parse error on line 29, column 12: Expecting token of type ':' but found `food_db`. Parse error on line 30, column 12: Expecting token of type ':' but found `health_db`. Parse error on line 32, column 9: Expecting token of type ':' but found `--`. Parse error on line 32, column 13: Expecting token of type 'ARROW_DIRECTION' but found `perception`. Parse error on line 33, column 11: Expecting token of type ':' but found `--`. Parse error on line 33, column 15: Expecting token of type 'ARROW_DIRECTION' but found `perception`. Parse error on line 34, column 9: Expecting token of type ':' but found `--`. Parse error on line 34, column 13: Expecting token of type 'ARROW_DIRECTION' but found `perception`. Parse error on line 35, column 16: Expecting token of type ':' but found `--`. Parse error on line 35, column 20: Expecting token of type 'ARROW_DIRECTION' but found `reasoning`. Parse error on line 36, column 15: Expecting token of type ':' but found `--`. Parse error on line 36, column 19: Expecting token of type 'ARROW_DIRECTION' but found `user_profile`. Parse error on line 37, column 15: Expecting token of type ':' but found `--`. Parse error on line 37, column 19: Expecting token of type 'ARROW_DIRECTION' but found `tool`. Parse error on line 38, column 10: Expecting token of type ':' but found `--`. Parse error on line 38, column 14: Expecting token of type 'ARROW_DIRECTION' but found `recipe_gen`. Parse error on line 39, column 10: Expecting token of type ':' but found `--`. Parse error on line 39, column 14: Expecting token of type 'ARROW_DIRECTION' but found `food_match`. Parse error on line 40, column 10: Expecting token of type ':' but found `--`. Parse error on line 40, column 14: Expecting token of type 'ARROW_DIRECTION' but found `health_check`. Parse error on line 41, column 16: Expecting token of type ':' but found `--`. Parse error on line 41, column 20: Expecting token of type 'ARROW_DIRECTION' but found `recipe_db`. Parse error on line 42, column 16: Expecting token of type ':' but found `--`. Parse error on line 42, column 20: Expecting token of type 'ARROW_DIRECTION' but found `adapter`. Parse error on line 43, column 18: Expecting token of type ':' but found `--`. Parse error on line 43, column 22: Expecting token of type 'ARROW_DIRECTION' but found `health_db`. Parse error on line 44, column 13: Expecting token of type ':' but found `--`. Parse error on line 44, column 17: Expecting token of type 'ARROW_DIRECTION' but found `meituan`. Parse error on line 45, column 13: Expecting token of type ':' but found `--`. Parse error on line 45, column 17: Expecting token of type 'ARROW_DIRECTION' but found `eleme`. Parse error on line 46, column 13: Expecting token of type ':' but found `--`. Parse error on line 46, column 17: Expecting token of type 'ARROW_DIRECTION' but found `local`. Parse error on line 47, column 15: Expecting token of type ':' but found `--`. Parse error on line 47, column 19: Expecting token of type 'ARROW_DIRECTION' but found `meituan`. Parse error on line 48, column 15: Expecting token of type ':' but found `--`. Parse error on line 48, column 19: Expecting token of type 'ARROW_DIRECTION' but found `eleme`. Parse error on line 49, column 15: Expecting token of type ':' but found `--`. Parse error on line 49, column 19: Expecting token of type 'ARROW_DIRECTION' but found `local`. Parse error on line 50, column 11: Expecting token of type ':' but found `--`. Parse error on line 50, column 15: Expecting token of type 'ARROW_DIRECTION' but found `meituan`. Parse error on line 51, column 11: Expecting token of type ':' but found `--`. Parse error on line 51, column 15: Expecting token of type 'ARROW_DIRECTION' but found `eleme`. Parse error on line 52, column 11: Expecting token of type ':' but found `--`. Parse error on line 52, column 15: Expecting token of type 'ARROW_DIRECTION' but found `local`. Parse error on line 53, column 18: Expecting token of type ':' but found `--`. Parse error on line 53, column 22: Expecting token of type 'ARROW_DIRECTION' but found `user_db`.

各层核心职责:

  1. 用户交互层:承接用户的多模态输入(文字、语音、图片),展示推荐结果,对接下单跳转
  2. Agent编排层:负责意图识别、实体抽取、约束推理、工具调用决策,是整个系统的大脑
  3. 领域服务层:提供可复用的原子服务,包括用户画像更新、菜谱生成、外卖匹配、健康校验
  4. 外卖Harness层:屏蔽不同外卖平台的API差异,统一数据格式,同步库存和订单状态,是连接Agent和外卖生态的核心中间层
  5. 数据层:存储用户数据、菜谱向量、外卖商品向量、健康知识库,采用Faiss作为向量检索引擎,支持亿级数据的毫秒级检索

3.2 实体关系模型

核心实体的ER图如下:

has

gives

receives

receives

USER

uuid

user_id

PK

string

name

string

phone

json

location

timestamp

create_time

USER_PROFILE

uuid

profile_id

PK

uuid

user_id

FK

vector

preference_vector

vector

resource_vector

vector

health_vector

timestamp

update_time

RECIPE

uuid

recipe_id

PK

string

name

json

ingredients

int

cook_time

int

difficulty

json

nutrition

vector

embedding

string

tags

FOOD_ITEM

uuid

food_id

PK

uuid

merchant_id

string

name

json

ingredients

float

price

int

delivery_time

float

rating

vector

embedding

string

tags

FEEDBACK

uuid

feedback_id

PK

uuid

user_id

FK

uuid

target_id

推荐的菜谱/外卖ID

int

rating

1-5分

string

comment

timestamp

create_time

3.3 算法流程

核心推荐算法的流程如下:

用户输入需求

感知模块:意图识别+实体抽取

是否明确需求类型?

调整目标函数权重α/β/γ

结合上下文推断需求类型,调整权重

调用用户画像服务,获取用户U向量

健康合规校验:过滤不符合健康约束的候选集

菜谱生成服务:生成符合约束的Top3菜谱

外卖Harness:匹配符合约束的Top3外卖商品

计算菜谱与外卖的替代相关性

生成推荐结果,附带可解释性说明

展示给用户

用户是否选择外卖?

跳转外卖平台下单,同步履约状态

用户是否选择菜谱?

展示完整菜谱步骤,可选食材配送

收集用户反馈,重新生成推荐

收集用户反馈,更新用户画像


4. 实现机制

4.1 算法复杂度分析

  • 用户画像向量检索:采用HNSW索引,复杂度为O(log n)O(log\ n)O(log n),支持亿级用户数据的毫秒级检索
  • 菜谱生成:大语言模型推理时间约为300-500ms,结合向量检索的总耗时小于1s
  • 外卖匹配:外卖商品向量检索复杂度为O(log n)O(log\ n)O(log n),加上多平台API调用的总耗时小于1.5s
  • 健康校验:采用规则引擎+向量匹配,复杂度为O(k)O(k)O(k),k为健康约束数量,耗时小于100ms

整体端到端响应时间小于2s,符合用户体验要求。

4.2 核心代码实现

4.2.1 Agent核心逻辑(基于LangChain)
from typing import List, Dict
from langchain.agents import AgentExecutor, create_openai_tools_agent
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.tools import tool
import faiss
import numpy as np
import requests

# 初始化向量库
recipe_index = faiss.read_index("recipe_vector.index")
food_index = faiss.read_index("food_vector.index")

# 初始化大模型,温度设置为0.3保证输出稳定
llm = ChatOpenAI(model="gpt-4o", temperature=0.3, api_key="your_openai_api_key")

# 定义工具函数
@tool
def get_user_profile(user_id: str) -> Dict:
    """
    获取用户的画像数据,包括偏好、资源、健康约束
    Args:
        user_id: 用户的唯一ID
    Returns:
        用户画像字典,包含preference, resource, health三个字段
    """
    # 生产环境从用户数据库获取
    return {
        "preference": {"liked_cuisines": ["川菜", "轻食"], "disliked_taste": ["太甜"], "vector": np.random.rand(128).tolist()},
        "resource": {"budget": [20, 30], "cook_ability": 2, "location": "北京市朝阳区望京街道", "vector": np.random.rand(64).tolist()},
        "health": {"allergens": ["花生"], "dietary": "减脂", "calorie_limit": 500, "vector": np.random.rand(64).tolist()}
    }

@tool
def generate_recipes(user_profile: Dict, top_k: int = 3) -> List[Dict]:
    """
    生成符合用户约束的个性化菜谱
    Args:
        user_profile: 用户画像字典
        top_k: 返回的菜谱数量
    Returns:
        菜谱列表,每个菜谱包含名称、食材、步骤、营养成分等信息
    """
    # 拼接用户向量
    user_vector = np.array([
        user_profile["preference"]["vector"] + 
        user_profile["resource"]["vector"] + 
        user_profile["health"]["vector"]
    ]).astype("float32")
    # 检索相似菜谱
    distances, indices = recipe_index.search(user_vector, top_k)
    # 模拟返回菜谱数据,生产环境从菜谱库获取
    return [
        {
            "name": "香煎鸡胸肉配西兰花",
            "ingredients": ["鸡胸肉200g", "西兰花150g", "黑胡椒", "盐", "橄榄油5g"],
            "cook_time": 15,
            "difficulty": 1,
            "nutrition": {"calorie": 280, "protein": 35, "fat": 8, "carb": 10},
            "steps": ["鸡胸肉切片用盐和黑胡椒腌制10分钟", "平底锅倒橄榄油,煎至两面金黄", "西兰花焯水3分钟", "装盘撒黑胡椒即可"],
            "reason": "符合你减脂需求,热量仅280大卡,蛋白质含量35g,无花生,烹饪时间仅15分钟"
        }
        for _ in range(top_k)
    ]

@tool
def match_takeaway(user_profile: Dict, top_k: int = 3) -> List[Dict]:
    """
    调用外卖Harness匹配符合用户约束的外卖商品
    Args:
        user_profile: 用户画像字典
        top_k: 返回的外卖商品数量
    Returns:
        外卖商品列表,包含名称、价格、配送时间、商家信息等
    """
    # 调用外卖Harness接口
    try:
        harness_response = requests.post(
            "http://harness.yourdomain.com/api/match",
            json={"user_profile": user_profile, "top_k": top_k},
            timeout=1.5
        )
        if harness_response.status_code == 200:
            return harness_response.json()
    except Exception as e:
        print(f"调用外卖Harness失败: {e}")
    return []

# 定义Agent提示词
prompt = ChatPromptTemplate.from_messages([
    ("system", "你是专业的美食推荐助手,必须严格遵守以下规则:1. 所有推荐必须100%满足用户的健康约束,绝对不能推荐包含过敏原的食物;2. 推荐结果必须附带清晰的理由,说明为什么符合用户的需求;3. 同时提供菜谱和外卖选项,除非用户明确指定只需要其中一种;4. 如果用户不满意推荐结果,主动询问用户的调整需求。"),
    ("user", "{input}"),
    MessagesPlaceholder(variable_name="agent_scratchpad"),
])

# 创建Agent执行器
tools = [get_user_profile, generate_recipes, match_takeaway]
agent = create_openai_tools_agent(llm, tools, prompt)
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)

# 测试调用
if __name__ == "__main__":
    user_input = "我今天晚上想吃清淡的高蛋白的,不要花生,30分钟能到的"
    user_id = "u_123456789"
    response = agent_executor.invoke({
        "input": f"用户ID是{user_id},用户的需求是:{user_input}"
    })
    print("推荐结果:\n", response["output"])
4.2.2 外卖Harness核心实现(基于FastAPI)
from fastapi import FastAPI
from pydantic import BaseModel
from typing import List, Dict
import requests
import time

app = FastAPI(title="外卖Harness服务", version="1.0")

class MatchRequest(BaseModel):
    user_profile: Dict
    top_k: int = 3

# 多平台API配置
PLATFORM_CONFIG = {
    "meituan": {"api_key": "your_meituan_api_key", "base_url": "https://api.meituan.com"},
    "eleme": {"api_key": "your_eleme_api_key", "base_url": "https://api.ele.me"},
}

# 本地缓存,减少平台API调用,缓存时间1分钟
cache = {}
CACHE_TTL = 60

def normalize_food_item(item: Dict, platform: str) -> Dict:
    """将不同平台的商品数据统一为标准格式"""
    return {
        "food_id": item["id"],
        "platform": platform,
        "name": item["name"],
        "price": item["price"],
        "delivery_time": item["delivery_time"],
        "rating": item["rating"],
        "ingredients": item.get("ingredients", []),
        "tags": item.get("tags", []),
        "jump_url": item["jump_url"],
        "reason": f"来自{platform},评分{item['rating']}分,配送时间{item['delivery_time']}分钟,价格{item['price']}元,符合你的高蛋白、无花生需求"
    }

@app.post("/api/match", response_model=List[Dict])
def match_food(request: MatchRequest):
    # 生成缓存key
    location = request.user_profile["resource"]["location"]
    cache_key = f"match_{location}_{request.top_k}"
    # 检查缓存
    if cache_key in cache and time.time() - cache[cache_key]["time"] < CACHE_TTL:
        return cache[cache_key]["data"]
    
    all_items = []
    # 并行调用所有平台的API
    for platform, config in PLATFORM_CONFIG.items():
        try:
            response = requests.post(
                f"{config['base_url']}/v1/food/search",
                headers={"Authorization": f"Bearer {config['api_key']}"},
                json={
                    "location": location,
                    "budget": request.user_profile["resource"]["budget"],
                    "delivery_time": 30,
                    "tags": ["高蛋白", "清淡", "无花生"],
                    "top_k": request.top_k
                },
                timeout=1
            )
            if response.status_code == 200:
                items = response.json()["data"]
                normalized = [normalize_food_item(item, platform) for item in items]
                all_items.extend(normalized)
        except Exception as e:
            print(f"调用{platform}API失败:{e}")
    # 按评分排序,返回top_k
    all_items.sort(key=lambda x: x["rating"], reverse=True)
    result = all_items[:request.top_k]
    # 更新缓存
    cache[cache_key] = {"time": time.time(), "data": result}
    return result

@app.post("/api/order/sync")
def sync_order(order_info: Dict):
    """同步订单状态到Agent层,通知用户履约进度"""
    # 实现订单状态推送逻辑,通过WebSocket通知前端
    return {"status": "success"}

4.3 边缘情况处理

  1. 外卖平台API超时:设置1s超时时间,降级返回缓存数据,同时提示用户当前部分平台服务不稳定
  2. 无符合约束的推荐结果:主动告知用户没有符合要求的选项,询问用户是否可以调整约束(比如延长配送时间、调整预算)
  3. 用户新过敏原输入:实时更新用户的健康向量,永久过滤包含该过敏原的所有推荐结果
  4. 高峰流量冲击:采用服务降级策略,优先保证外卖匹配服务可用,菜谱生成服务降级为返回热门预制菜谱

4.4 性能考量

  • 采用Serverless架构部署核心服务,高峰时期自动扩容,应对午晚高峰的10倍流量冲击
  • 热门区域的外卖匹配结果缓存1分钟,减少平台API调用量,降低响应时间
  • 向量库采用读写分离架构,离线更新向量数据,在线仅提供检索服务,保证稳定性
  • 大模型推理采用批量处理,非高峰时期预生成热门需求的推荐结果,减少实时推理压力

5. 实际应用

5.1 实施策略

建议采用小步快跑的灰度发布策略:

  1. 第一阶段(1-2个月):上线最小可行产品,仅支持自然语言交互、菜谱生成、单外卖平台对接,邀请1000名种子用户测试,收集反馈迭代模型
  2. 第二阶段(3-4个月):对接多外卖平台,添加健康约束校验、多轮交互功能,开放10万用户规模的灰度测试
  3. 第三阶段(5-6个月):上线用户画像动态更新、推荐效果实时分析功能,全量发布

5.2 集成方法论

  • 面向C端独立产品:开发小程序/APP,作为独立的饮食入口,用户无需跳转其他平台即可完成全流程操作
  • 面向现有美食APP集成:提供SDK/API,嵌入下厨房、大众点评等现有美食APP,扩展其全场景推荐能力
  • 面向智能家居集成:对接智能音箱、智能冰箱等IoT设备,用户可以通过语音交互获取推荐,智能冰箱可以自动识别现有食材推荐菜谱

5.3 部署考虑因素

  • 区域部署:外卖服务具有强区域属性,建议按城市部署节点,减少跨区域调用延迟
  • 数据合规:用户的饮食、健康数据属于敏感信息,必须存储在符合等保三级要求的服务器上,加密存储,不得跨境传输
  • 高可用:核心服务采用多可用区部署,保证99.9%的可用性,饭点高峰时期的服务可用率不低于99.5%

5.4 运营管理

  • 数据运营:每周分析推荐转化率、用户满意度、点击率等核心指标,迭代模型参数
  • 商家运营:和外卖商家合作,获取更准确的商品配料、营养成分数据,提升推荐准确性
  • 用户运营:通过签到、反馈奖励等方式引导用户完善健康档案、提交反馈,优化用户画像

6. 高级考量

6.1 扩展动态

未来可以扩展三大能力:

  1. IoT集成:对接智能冰箱、智能烤箱、智能体重秤等设备,自动识别现有食材、用户健康数据,实现无感推荐
  2. 食材配送对接:除了外卖,还可以对接生鲜配送平台,用户选择菜谱后可以一键购买所需食材
  3. 社交功能:支持用户分享菜谱、外卖推荐给好友,基于社交关系优化推荐结果

6.2 安全影响

  • 食品安全:建立食品安全黑名单库,过滤有食品安全问题的商家、菜谱,避免推荐不合格的食品
  • 过敏安全:健康约束采用三层校验(数据层、服务层、Agent层),确保100%不会推荐包含用户过敏原的食品
  • 数据安全:采用端到端加密存储用户敏感数据,所有数据访问都有审计日志,严格控制数据访问权限

6.3 伦理维度

  • 算法向善:对于有减脂、控糖等健康需求的用户,不得推荐高油、高糖、高热量的食品,即使点击率更高
  • 算法公平性:不得基于用户的收入水平歧视性推荐低质量的不健康食品,保证不同收入水平的用户都能获得健康、优质的推荐
  • 透明度:所有推荐结果都必须提供可解释性,告知用户推荐的理由,不得采用黑箱算法推荐

6.4 未来演化向量

  • 2025年:多模态感知普及,用户可以拍一下家里的食材自动生成菜谱,或者拍一下外卖商品自动识别营养成分
  • 2027年:强化学习推荐普及,Agent可以根据用户的实时反馈动态调整推荐策略,准确率提升30%以上
  • 2030年:全链路健康管理普及,Agent对接医保、医院数据,为慢性病患者定制专属饮食方案,直接对接外卖和药品配送
  • 2035年:城市级饮食调度系统普及,政府可以通过Agent引导用户选择低碳、健康的饮食,提升公共健康水平,减少碳排放

7. 综合与拓展

7.1 跨领域应用

  • 医疗健康领域:为糖尿病、高血压、痛风等慢性病患者定制专属饮食方案,对接外卖平台实现配送到家,辅助疾病治疗
  • 健身领域:为健身人群定制增肌、减脂的饮食方案,对接健身APP同步运动数据,动态调整饮食推荐
  • 母婴领域:为孕妇、婴幼儿定制符合营养需求的菜谱和外卖推荐,确保饮食安全和营养均衡

7.2 研究前沿

当前学术领域的研究热点包括:

  1. 多模态食材识别技术:通过图像、视频识别食材种类、重量,自动生成菜谱
  2. 个性化营养计算技术:基于用户的基因、健康数据,精准计算每日所需营养成分,定制专属饮食方案
  3. 强化学习动态推荐技术:基于用户的实时反馈、场景变化动态调整推荐策略,提升长期满意度
  4. 食品知识图谱构建技术:构建包含食材、菜谱、营养、禁忌的大规模知识图谱,提升推荐的准确性和安全性

7.3 开放问题

当前行业仍有三大核心开放问题没有解决:

  1. 外卖商品的营养成分、配料表数据不全,准确率低,无法满足精准推荐的需求
  2. 用户的饮食偏好漂移问题,如何准确捕捉用户的实时口味变化
  3. 如何平衡商业利益和用户健康,避免平台为了佣金推荐高利润但不健康的食品

7.4 战略建议

  • 创业公司:从垂直场景切入,比如健身餐、慢性病饮食、母婴饮食,先积累细分领域的用户和数据,再扩展到全场景
  • 传统O2O平台:尽快布局Agent驱动的全场景推荐,打破菜谱和外卖的场景壁垒,提升用户留存和ARPU值
  • 监管部门:推动外卖商品的配料表、营养成分数据标准化,要求商家必须公示准确的食品信息,保障用户的知情权

8. 最佳实践Tips

  1. 健康约束优先级最高:所有推荐结果必须先经过健康合规校验,过敏原、慢性病禁忌等硬约束必须100%满足,建议做三层校验,避免遗漏
  2. 可解释性优先:每个推荐结果都必须附带清晰的理由,用户对推荐的接受度可以提升40%以上
  3. 实时数据同步:外卖商品的库存、价格、配送时间是实时变化的,建议采用二级缓存策略,既保证响应速度,又避免推荐售罄商品
  4. 高峰弹性扩容:饭点高峰流量是平时的10倍以上,建议采用Serverless架构自动扩容,避免系统崩溃
  5. 隐私保护优先:用户的饮食、健康数据属于高度敏感信息,必须严格遵守《个人信息保护法》,不得滥用用户数据

9. 行业发展趋势

时间区间 发展阶段 核心技术支撑 典型产品形态 核心痛点
2000-2010 静态信息聚合阶段 关键词匹配、爬虫技术 美食天下、下厨房静态站点 无个性化,没有履约能力
2010-2020 单场景个性化推荐阶段 协同过滤、移动互联网 下厨房APP、美团推荐系统 场景割裂,推荐准确性低
2020-2025 全场景Agent推荐阶段 大语言模型、向量检索 本文提出的FoodAgent 数据异构性高,平台开放度低
2025-2030 全链路智能饮食管理阶段 多模态感知、IoT集成 个人饮食健康助手 隐私合规、算法伦理问题
2030+ 社会化饮食优化阶段 城市级调度、碳中和计算 城市饮食管理系统 公共政策协同、资源调度

本章小结

本文系统介绍了美食推荐Agent的全栈实现方案,从第一性原理出发推导了美食推荐的核心数学模型,设计了五层分层架构,通过外卖Harness层首次打通了菜谱生态和外卖平台的异构数据壁垒,实现了用户从需求输入到履约同步的全链路服务。本文提供的核心代码可以直接用于生产环境二次开发,给出的最佳实践可以帮助开发者避开常见的技术陷阱。未来随着IoT和数字健康技术的发展,美食推荐Agent将成为个人健康管理的核心入口,甚至可以参与到城市级的公共健康管理和碳中和优化中,具有非常广阔的发展前景。

全文总字数:9872字

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐