一 需求分析
AI私厨是一个基于LangChain和多模态AI的食谱推荐应用。用户可以拍摄自家冰箱或厨房的食物照片,AI会自动识别图片中的食材,根据食材搜索相关食谱推荐给用户。
1.1 功能特性
- 📸 图片识别 - 上传食材图片,自动识别其中的食材
- 🔍 智能搜索 - 根据识别的食材搜索相关食谱
- 🍽️ 智能排序 - 按推荐度、难度、营养价值对食谱进行排序
- 💡 创意建议 - 当找不到合适食谱时,提供创意搭配建议
- 💬 对话交互 - 聊天式界面,支持图片上传 + 文本对话
1.2 预期效果
基本的聊天界面窗口,支持图片上传+文本对话:

上传图片,根据图片识别食材:

根据食材搜索食谱,并智能排序:

1.3 实现思路分析
定义一个基础Agent,核心就是模型(Model)和工具(Tool),我们从这两点入手:
- 模型:由于要实现多模态输入,所以必须用多模态模型,比如qwen3.5-plus
- 工具:需要联网搜索食谱,所以要用到web搜索工具,推荐Tavily
- 记忆:短期记忆我们暂时使用Sqlite来实现
剩下就是提示词了,这里我给大家准备了一份测试过的提示词:
system_prompt = """
你是一名私人厨师。收到用户提供的食材照片或清单后,请按以下流程操作:
1.识别和评估食材:若用户提供照片,首先辨识所有可见食材。基于食材的外观状态,评估其新鲜度与可用量,整理出一份"当前可用食材清单"。
2.智能食谱检索:优先调用 web_search 工具,以"可用食材清单"为核心关键词,查找可行菜谱。
3.多维度评估与排序:从营养价值和制作难度两个维度对检索到的候选食谱进行量化打分,并根据得分排序,制作简单且营养丰富的排名靠前。
4.结构化方案输出:把排序后的食谱整理为一份结构清晰的建议报告,要包含食谱信息、得分、推荐理由、食谱的参考图片,帮助用户快速做出决策。
请严格按照流程,优先调用 web_search 工具搜索食谱,搜索不到的情况下才能自己发挥。
"""
接下来,就是组合这些东西,开发你的智能体了,赶紧动手试试吧!
二 功能模拟(jupyter)
我们先在jupyter将Agent的流程跑通。
2.1 配置
确保你的.env中有以下配置:
python
# .env
# 阿里百炼
DASHSCOPE_API_KEY=sk-913a82aa121f412aa9a8c8c7b22f7792
DASHSCOPE_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
# web_search引擎
TAVILY_API_KEY=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
2.2 依赖
参考下面的依赖:
python
[project]
name = "food-recipe-recommender"
version = "0.1.0"
description = "AI-powered recipe recommender based on uploaded food images"
readme = "README.md"
requires-python = ">=3.10"
dependencies = [
"langchain>=0.3.0",
"langchain-community>=0.3.0",
"langchain-openai>=0.2.0",
"langchain-core>=0.3.0",
"streamlit>=1.40.0",
"pillow>=11.0.0",
"python-dotenv>=1.0.0",
"tavily-python>=0.5.0",
"langchain-tavily>=0.1.0",
"fastapi>=0.109.0",
"uvicorn>=0.27.0",
"python-multipart>=0.0.6",
"alibabacloud-oss-v2>=1.2.4",
"langgraph-checkpoint-sqlite>=3.0.3",
]
[tool.uv]
dev-dependencies = [
"pytest>=8.0.0",
"black>=24.0.0",
]
2.3 加载配置
首先,我们要加载环境变量:
python
# 加载环境变量
from dotenv import load_dotenv
load_dotenv()
2.4 定义工具
然后,我们要定义工具:
python
from langchain_tavily import TavilySearch
# web搜索工具,使用tavily作为web搜索工具
web_search = TavilySearch(
max_results=5,
topic="general",
)
2.5 初始化模型
接着,初始化多模态模型:
python
from langchain.chat_models import init_chat_model
import os
model = init_chat_model(
model="qwen3.5-plus", # 模型名称,这里选择qwen3.5-plus,这是一个多模态模型
model_provider="openai",
base_url=os.getenv("DASHSCOPE_BASE_URL"),
api_key=os.getenv("DASHSCOPE_API_KEY")
)
2.6 记忆管理
然后,我们定义记忆管理的checkpointer:
python
from langgraph.checkpoint.sqlite import SqliteSaver
import sqlite3
# 连接sqlite
connection = sqlite3.connect("resources/personal_chief.db", check_same_thread=False)
# 初始化checkpointer
checkpointer = SqliteSaver(connection)
# 自动建表
checkpointer.setup()
2.7 初始化Agent
接下来,就是智能体了:
python
from langchain.agents import create_agent
system_prompt = """
你是一名私人厨师。收到用户提供的食材照片或清单后,请按以下流程操作:
1.识别和评估食材:若用户提供照片,首先辨识所有可见食材。基于食材的外观状态,评估其新鲜度与可用量,整理出一份"当前可用食材清单"。
2.智能食谱检索:优先调用 web_search 工具,以"可用食材清单"为核心关键词,查找可行菜谱。
3.多维度评估与排序:从营养价值和制作难度两个维度对检索到的候选食谱进行量化打分,并根据得分排序,制作简单且营养丰富的排名靠前。
4.结构化方案输出:把排序后的食谱整理为一份结构清晰的建议报告,要包含食谱信息、得分、推荐理由、食谱的参考图片,帮助用户快速做出决策。
请严格按照流程,优先调用 web_search 工具搜索食谱,搜索不到的情况下才能自己发挥。
"""
agent = create_agent(
model=model,
tools=[web_search],
system_prompt=system_prompt,
checkpointer=checkpointer
)
2.8 测试
我们在网上找到一张冰箱食物图片:

测试一下:
python
from langchain.messages import HumanMessage
# 准备多模态消息,图片是网络上的冰箱食物图片
multimodal_message = HumanMessage(
content=[
{"type": "image",
"url": "https://img.freepik.com/free-photo/arrangement-different-foods-organized-fridge_23-2149099882.jpg"},
{"type": "text", "text": "帮我看看这些食材能做些什么?"}
])
config = {"configurable": {"thread_id": "6"}}
response = agent.invoke({"messages": [multimodal_message]}, config)
打印:
python
# 友好打印
for message in response['messages']:
message.pretty_print()
结果:
python
================================ Human Message =================================
[{'type': 'image', 'url': 'https://img.freepik.com/free-photo/arrangement-different-foods-organized-fridge_23-2149099882.jpg'}, {'type': 'text', 'text': '帮我看看这些食材能做些什么?'}]
================================== Ai Message ==================================
这些食材非常丰富,包括了多种蔬菜、菌菇、鱼类和肉类,非常适合制作健康美味的餐食。我将根据这些食材,为您搜索可用的食谱,并提供创意搭配建议。同时,我会对每个食谱的营养价值和制作难度进行评分,并综合排序,为您提供最佳选择。
首先,我将分析这些食材,然后进行网络搜索,寻找适合的食谱。
Tool Calls:
tavily_search (call_5f30eac22b4b4f8c8b7927)
Call ID: call_5f30eac22b4b4f8c8b7927
Args:
query: 食谱 用蘑菇 西红柿 三文鱼 鸡胸肉 西兰花 生菜 红椒 胡萝卜 花椰菜
================================= Tool Message =================================
Name: tavily_search
{"query": "食谱 用蘑菇 西红柿 三文鱼 鸡胸肉 西兰花 生菜 红椒 胡萝卜 花椰菜", "follow_up_questions": null, "answer": null, "images": [], "results": [{"url": "https://www.facebook.com/groups/455719458197800/posts/%E9%B8%A1%E6%B1%A4%E9%A3%9F%E8%B0%B1-%E5%8E%9F%E6%96%99200%E5%85%8B%E9%B8%A1%E8%82%89%E8%A5%BF%E5%85%B0%E8%8A%B1%E8%8F%9C%E8%8A%B1%E8%83%A1%E8%90%9D%E5%8D%9C%E5%9C%9F%E8%B1%86%E6%89%80%E6%9C%89%E6%9D%90%E6%96%99%E9%83%BD%E6%A0%B9%E6%8D%AE%E5%8F%A3%E5%91%B3%E5%88%87%E7%89%87%E7%A3%A8%E7%A2%8E%E7%9A%84%E9%A6%99%E6%96%992%E7%93%A3%E5%A4%A7%E8%92%9C%E5%85%B6%E4%BB%96%E9%A6%99%E6%96%991%E6%B1%A4%E5%8C%99%E8%98%91%E8%8F%87%E6%B1%A4%E7%B2%891%E8%8C%B6%E5%8C%99%E7%9B%90%E5%B0%91%E8%AE%B8%E8%83%A1%E6%A4%92%E7%B2%89%E7%83%B9%E9%A5%AA%E6%96%B9%E6%B3%95%E5%B0%86%E9%B8%A1%E8%83%B8%E8%82%89%E7%9F%AD%E6%9A%82%E7%85%AE%E4%B8%80%E4%B8%8B%E7%84%B6%E5%90%8E%E5%8F%96%E5%87%BA/2224317858004609/", "title": "鸡汤食谱🌺 原料: 200克鸡肉西兰花菜花胡萝卜土豆所有材料 ...", "content": "磨碎的香料: 2瓣大蒜 其他香料: 1汤匙蘑菇汤粉 1茶匙盐 少许胡椒粉 烹饪方法: 将鸡胸肉短暂煮一下,然后取出并沥干。 将植物油加热,然后爆香蒜头,然后加水,", "score": 0.6437107, "raw_content": null}, {"url": "https://www.instagram.com/p/DRzSXVLjRjI/", "title": "西兰花、鸡胸肉、蒜、姜", "content": "做法超级简单: 1. 鸡胸肉切块,加入适量生抽、料酒、黑胡椒、盐、生粉,抓匀腌制15 分钟。 2. 西兰花切小朵,蒜切末,姜切片。 3. 锅中烧开水,加少许盐和油,放", "score": 0.5015387, "raw_content": null}, {"url": "https://cookpad.com/tw/%E6%90%9C%E5%B0%8B/%E8%A5%BF%E8%98%AD%E8%8A%B1%E4%B8%89%E6%96%87%E9%AD%9A", "title": "西蘭花三文魚料理與家常食譜", "content": "香焗紅椒三文魚炒飯 ... 三文魚一件 • 紅椒兩棵 • 西蘭花粒、蒜蓉、薑蓉、芝士碎各適量。 ... 油二湯匙、鹽2茶匙、蠔油一茶匙。", "score": 0.40387306, "raw_content": null}, {"url": "https://www.reddit.com/r/EatCheapAndHealthy/comments/136ppq6/im_marinating_some_chicken_breasts_this_week_for/?tl=zh-hans", "title": "这周我腌了一些鸡胸肉准备晚餐......我能搭配什么吃呢? ...", "content": "我把我的蔬菜(芦笋、西兰花、西葫芦、辣椒、蘑菇,我想大部分都可以)切好,拌上油、柠檬、大蒜、盐/胡椒,然后放进烤箱里,400度烤10-15分钟。简直是改变游戏", "score": 0.39333898, "raw_content": null}, {"url": "https://fsnep.ucdavis.edu/sites/g/files/dgvnsk2286/files/inline-files/02.%20Lesson%204%20Handout%201%20ChickenBroccoliAndRiceCasserole_Recipe_SCH.pdf", "title": "鸡肉西兰花饭", "content": "食谱来自从 Making Every Dollar Count curriculum 由 University of California Cooperative Extension California's CalFresh Healthy Living, with funding from the United States Department of Agriculture's Supplemental Nutrition Assistance Program -- USDA SNAP, produced this material. These institutions are equal opportunity providers and employers. For important nutrition information, visit www.CalFreshHealthyLiving.org. \\01.1 CURRICULA\\CURRICULA\\Curricula Print files\\Adult Curricula\\PSSC\\PSSC 2017-Current\\Lesson 4 Simplified Chinese Rev: 04/20 鸡肉西兰花饭 • 4 人分量 • 材料: • 1 磅去骨、去皮 鸡胸 • 1 勺油 • 1 青椒或甜椒 • 1 罐 (10.75 盎司) 奶油蘑菇汤、或蘑菇鸡肉 汤、或芹菜汤 • 1 杯未熟的即食糙米 • 1 杯水 • 1 包(10-12盎司)已切好的西兰花 • ½ 杯干酪粉(起司粉、芝士粉) • 适量盐及黑胡椒粉以作调味 做法: 1. 把青椒切成小块,约1/2 英寸厚。 3. 把鸡肉切成小块,约1/2 英寸厚。 5. 在锅中加入生鸡。用中火煮几分钟,直到煮熟为 止,或到中间不再呈粉红色的程度。 7. 在锅中加入青椒,洋葱,汤,米和水。 8. 从炉台移开。加上奶酪。加入盐和胡椒调味。 营养成分*: 每份含量: 总卡路里-- 360; 总脂肪-- 11 克; 饱和脂肪-- 4 克; 钠-- 690 毫克; 碳水化合物-- 31 克; 膳食纤维-- 5 克; 糖-- 4 克; 蛋白质-- 35 克; 维生素 A-- 30%; 维生素 C-- 120%; 钙-- 15%; 铁-- 15%. *以下分析使用了低钠奶油蘑 菇汤及切达干酪(切达起司、 车打芝士) 材料: • 菜板 • 菜刀 • 平底锅 • 锅铲 • 开罐器 • 量杯 • 奶酪刨丝器.", "score": 0.36283427, "raw_content": null}], "response_time": 1.0, "request_id": "18ed28fb-aa5d-4d28-8c7d-05fc5db4cf69"}
================================== Ai Message ==================================
根据您提供的食材,我为您找到了一些可用的食谱和创意搭配建议。以下是综合排序后的推荐:
### 1. **三文鱼西兰花烤盘料理**
- **食材**: 三文鱼、西兰花、红椒、蘑菇、胡萝卜
- **营养价值**: 高蛋白、富含Omega-3脂肪酸、维生素C和膳食纤维。
- **制作难度**: 低(只需腌制和烤制)
- **评分**: 营养价值 9/10,制作难度 3/10
- **食谱说明**: 将三文鱼用盐、黑胡椒和柠檬汁腌制15分钟,西兰花、红椒和蘑菇切块,与胡萝卜一起拌入橄榄油、蒜末和少许盐。将所有食材铺在烤盘上,放入预热至200°C的烤箱中烤15-20分钟,直到三文鱼熟透,蔬菜变软。
### 2. **鸡胸肉西兰花炒饭**
- **食材**: 鸡胸肉、西兰花、蘑菇、红椒、胡萝卜
- **营养价值**: 高蛋白、低脂肪,富含维生素和矿物质。
- **制作难度**: 中等(需要炒制和调味)
- **评分**: 营养价值 8/10,制作难度 5/10
- **食谱说明**: 鸡胸肉切丁,用生抽、料酒、黑胡椒腌制15分钟。西兰花切小朵,红椒和胡萝卜切丁,蘑菇切片。热锅加油,先炒鸡胸肉至变色,再加入蔬菜翻炒,最后加入米饭和少许酱油调味,炒匀即可。
### 3. **创意搭配:蘑菇鸡肉蔬菜炖锅**
- **食材**: 鸡胸肉、蘑菇、西兰花、红椒、胡萝卜、生菜
- **营养价值**: 营养均衡,富含植物纤维和优质蛋白。
- **制作难度**: 中等(需要炖煮)
- **评分**: 营养价值 8/10,制作难度 6/10
- **食谱说明**: 鸡胸肉切块,用盐、胡椒和蒜粉腌制。锅中加少许油,先炒香蒜末,再加入鸡肉翻炒至变色。加入切好的蘑菇、红椒、胡萝卜和西兰花,倒入适量高汤或水,小火炖煮15分钟。最后加入生菜,稍微翻炒即可。
### 4. **三文鱼蘑菇意面**
- **食材**: 三文鱼、蘑菇、西兰花、红椒、胡萝卜
- **营养价值**: 富含蛋白质和健康脂肪,适合健身人群。
- **制作难度**: 中等(需要煮意面和调味)
- **评分**: 营养价值 8/10,制作难度 5/10
- **食谱说明**: 意面煮熟备用。三文鱼切块,用盐、黑胡椒和柠檬汁腌制。蘑菇切片,红椒和胡萝卜切丁。热锅加油,先炒香蒜末,再加入蘑菇和蔬菜翻炒,加入少许奶油或牛奶,煮至浓稠。最后加入三文鱼和煮好的意面,拌匀即可。
### 5. **创意搭配:蔬菜鸡肉卷**
- **食材**: 鸡胸肉、西兰花、红椒、蘑菇、胡萝卜、生菜
- **营养价值**: 低热量、高纤维,适合减肥人群。
- **制作难度**: 中等(需要卷制和烤制)
- **评分**: 营养价值 7/10,制作难度 6/10
- **食谱说明**: 鸡胸肉切薄片,用盐、胡椒和酱油腌制。西兰花、红椒、蘑菇和胡萝卜切丝,与生菜一起铺在鸡胸肉上,卷成卷状,用牙签固定。放入烤箱200°C烤15分钟,直到鸡肉熟透,蔬菜变软。
这些食谱不仅充分利用了您现有的食材,还兼顾了营养均衡和制作的便捷性。希望您能享受这些美味的料理!如果有任何其他需求,随时告诉我。
继续询问:
python
response = agent.invoke(
{"messages": [HumanMessage(content="我喜欢第1道菜,可以说的更详细点吗?")]},
config
)
# 友好打印
response['messages'][-1].pretty_print()
结果:
python
================================== Ai Message ==================================
当然可以!既然您喜欢第1道菜------**三文鱼西兰花烤盘料理**,我会为您提供更详细的步骤、技巧和小贴士,确保您能轻松做出美味又健康的餐食!
---
## 🍽️ **三文鱼西兰花烤盘料理(详细版)**
### 📌 **食材清单**(根据您冰箱里的食材调整)
- 三文鱼:200-250克(约1-2块,根据个人喜好)
- 西兰花:1小颗(约200克,切小朵)
- 红椒:1个(切块或切条)
- 蘑菇:约100克(切片,如香菇、口蘑等)
- 胡萝卜:1根(切片或切丁,约100克)
- 橄榄油:2汤匙
- 蒜末:2瓣(可选,增加风味)
- 柠檬汁:1汤匙(可选,去腥提鲜)
- 盐:适量(约1/2茶匙)
- 黑胡椒:适量(约1/4茶匙,现磨更香)
- 新鲜香草(如迷迭香、百里香):少许(可选,提升香气)
---
## 🍳 **详细制作步骤**
### **1. 准备食材**
- **三文鱼**:用厨房纸吸干表面水分,用盐、黑胡椒和柠檬汁腌制15分钟(如果时间允许,可腌制30分钟,风味更佳)。
- **西兰花**:掰成小朵,用淡盐水浸泡5分钟,再冲洗干净,沥干水分。
- **红椒、胡萝卜、蘑菇**:分别切成均匀大小的块或片,确保烤制时受热均匀。
- **蒜末**:提前准备好,用于调味。
### **2. 调味蔬菜**
- 在一个大碗中,将西兰花、红椒、胡萝卜和蘑菇放入,加入1汤匙橄榄油、少许盐、黑胡椒和蒜末,搅拌均匀,让蔬菜充分吸收调味料。
### **3. 组装烤盘**
- 在烤盘上铺一层烘焙纸或刷一层薄薄的橄榄油,防止粘连。
- 将调味好的蔬菜均匀铺在烤盘底部。
- 将腌制好的三文鱼放在蔬菜上,尽量让三文鱼平铺,不要重叠。
- 如果喜欢,可以在三文鱼表面撒上少许新鲜香草(如迷迭香或百里香),增加香气。
### **4. 烘烤**
- **预热烤箱**:将烤箱预热至200°C(392°F)。
- **烤制时间**:将烤盘放入烤箱中层,烤15-20分钟。
- 如果喜欢三文鱼外焦里嫩,可以烤15分钟。
- 如果喜欢更熟透的口感,可以烤20分钟。
- **检查熟度**:用叉子轻轻戳三文鱼,如果鱼肉轻松散开,说明已经熟透。
### **5. 出炉与享用**
- 烤好后,从烤箱取出,稍微静置2-3分钟,让三文鱼的汁水重新分布。
- 可以搭配一小碗柠檬汁或蜂蜜芥末酱,增加风味。
- 搭配米饭、藜麦或全麦面包,营养更均衡!
---
## 📝 **小贴士**
1. **三文鱼的处理**:如果三文鱼是冷冻的,提前解冻,避免烤制时外焦内生。
2. **蔬菜的切法**:蔬菜切得越均匀,烤制时受热越均匀,口感更好。
3. **调味调整**:如果喜欢更浓郁的风味,可以在烤制前撒上少许芝士碎(如帕玛森芝士),烤制时融化,增加香气。
4. **搭配建议**:这道菜本身营养丰富,可以搭配一杯清爽的白葡萄酒或柠檬水,提升用餐体验。
---
## 📊 **营养与健康价值**
- **三文鱼**:富含Omega-3脂肪酸,有助于降低心血管疾病风险,同时提供优质蛋白。
- **西兰花**:富含维生素C、膳食纤维和抗氧化物质,有助于增强免疫力。
- **红椒和胡萝卜**:富含β-胡萝卜素和维生素A,对眼睛和皮肤健康有益。
- **蘑菇**:低热量、高纤维,富含维生素D和抗氧化成分。
- **整体搭配**:这道菜低脂、高蛋白、富含膳食纤维,适合健身、减脂或日常健康饮食。
---
## 🎯 **为什么推荐这道菜?**
- **制作简单**:只需腌制和烤制,无需复杂技巧。
- **营养均衡**:蛋白质、蔬菜、健康脂肪一应俱全。
- **适合家庭**:可以一次烤制多份,方便分餐或保存。
- **健康低卡**:橄榄油和香料调味,避免过多油脂和盐分。
---
如果您有任何特殊需求(比如素食、无麸质、低钠等),可以告诉我,我会为您调整食谱!希望您享受这道美味又健康的料理! 😊
可以发现,Agent具备记忆,接着前面的内容来回答。
测试通过!
三 LangSmith联调测试
LangChain的Agent底层是基于LangGraph实现的,而LangGraph提供了完整的后端部署功能,自带非常完善的API接口,无需我们额外处理。
同时,LangChain还提供了基于LangSmith的GUI控制台实现Agent的调试、监控、一键部署。
接下来我们就看看如何利用LangGraph在本地部署测试我们的Agent,并通过LangSmith做测试。
3.1 配置LangSmith
LangSmith提供了对Agent的GUI管理界面,而且还支持一键云部署功能。通常在测试阶段,建议大家在Agent中引入Simth,方便做测试和调试。
首先,我们要注册LangSmith,开通服务,生成API_KEY。
注册地址:https://smith.langchain.com/
注册成功后,登录,进入控制台,找到settings:

在settings页面找到API Keys菜单,创建自己的API_KEY:

千万要记住自己的API_KEY,不要弄丢了。
接着,我们无需额外安装依赖,只需要在项目的.env文件中添加配置即可:
python
# deepseek
DEEPSEEK_API_KEY=sk-5df6af828a04427da4d98fc53cebd63b
# aliyun dashscope
DASHSCOPE_API_KEY=sk-913a82aa121f412aa9a8c8c7b22f7792
DASHSCOPE_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
# web_search引擎
TAVILY_API_KEY=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
# langsmith
LANGSMITH_API_KEY=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
LANGSMITH_TRACING=true
# project name
LANGSMITH_PROJECT=lc-course
3.2 开发Agent
首先,我们把刚才的代码集中到一个py文件中:

代码如下:
python
from langchain.chat_models import init_chat_model
from langchain_tavily import TavilySearch
from langchain.agents import create_agent
import os
# 1.加载环境变量
from dotenv import load_dotenv
load_dotenv()
# 2.web搜索工具,使用tavily作为web搜索工具
web_search = TavilySearch(
max_results=5,
topic="general"
)
# 3.多模态模型
model = init_chat_model(
model="qwen3.5-plus", # 模型名称,这里选择qwen3.5-plus,这是一个多模态模型,支持图片、文本、音频、视频
model_provider="openai",
base_url=os.getenv("DASHSCOPE_BASE_URL"),
api_key=os.getenv("DASHSCOPE_API_KEY")
)
# 4.Agent系统提示词
system_prompt = """
你是一名私人厨师。收到用户提供的食材照片或清单后,请按以下流程操作:
1.识别和评估食材:若用户提供照片,首先辨识所有可见食材。基于食材的外观状态,评估其新鲜度与可用量,整理出一份"当前可用食材清单"。
2.智能食谱检索:优先调用 web_search 工具,以"可用食材清单"为核心关键词,查找可行菜谱。
3.多维度评估与排序:从营养价值和制作难度两个维度对检索到的候选食谱进行量化打分,并根据得分排序,制作简单且营养丰富的排名靠前。
4.结构化方案输出:把排序后的食谱整理为一份结构清晰的建议报告,要包含食谱信息、得分、推荐理由、食谱的参考图片,帮助用户快速做出决策。
请严格按照流程,优先调用 web_search 工具搜索食谱,搜索不到的情况下才能自己发挥。
"""
# 5.创建Agent
agent = create_agent(
model=model, # 模型
tools=[web_search], # 工具
system_prompt=system_prompt # 系统提示词
)
注意
- LangGraph会自动托管Agent的记忆,因此代码中不用自己添加checkpointer!
- LangGraph自带Restful的API接口,我们只要定义好Agent就可以,其它不用管
3.3 本地部署
我们使用LangGraph命令行在本地部署,所以要先安装LangGraph的依赖。
使用uv安装:
bash
uv add langgraph-cli[inmem]
然后,在项目根目录添加一个langgraph配置文件:

添加下面的内容:
python
{
"dependencies": ["."],
"graphs": {
"chief_agent": "./app/agents/personal_chief.py:agent"
},
"env": ".env"
}
注意:其中的agent配置格式为:
python
[Agent文件路径]:[Agent变量名]
例如,在我们的配置中:
- ./app/agents/personal_chief.py:就是文件路径
- agent:就是文件中定义的Agent名字
最后,点击左下角打开Pycharm终端:

使用LangGraph命令本地部署Agent:
bash
uv run langgraph dev
效果:

看到这个,说明你的Agent已经在本地部署成功了!!
LangGraph提供了基于RestFull的完整的服务端API接口,可以访问http://127.0.0.1:2024/docs查看。接下来,你就可以自己开发前端,与这些接口对接了。
当然,LangGraph也支持Docker部署方案,可参考以下链接:https://docs.langchain.com/langsmith/deploy-with-control-plane#step-2-build-docker-image
3.4 LangSmith Studio测试
由于我们部署时配置了LangSmith,所以可以直接访问LangSmith提供的调试GUI界面:
https://smith.langchain.com/studio/?baseUrl=http://127.0.0.1:2024
这里可以非常方便的调试我们的Agent,查看我们Agent的运行细节:

可以直接在界面中测试:

也可以查看详细的调用过程:

同时,LangSmith还提供了一键云部署功能,可以把Agent部署到云端:

但是要需要缴付昂贵的费用。。
所以,建议只在Agent测试阶段使用LangSmith吧~
四 实战开发
gent跑通了,但目前还存在几个问题:
- 目前的图片信息还是采用base64方式提交给模型,会占用大量内存,性能差
- 我们没有开发自己的前端,用户体验不好
接下来,我们就逐一解决这些问题。
在向模型提交多模态消息,比如:音频、视频、图片时,我们不建议直接发送文件数据(base64)给模型,这会大量占用内存和会话记忆。更常见的方案是:
- 先将多模态文件上传至通用的OSS服务,例如:阿里云OSS、腾讯云COS等
- 获取oss服务的文件url地址,组织多模态消息,发送给大模型
因此,我们需要单独开发一个文件上传的服务接口,让前端先上传好文件,再调用Agent.

我们的服务端需要具备以下接口:
- 对话接口:接收用户聊天消息,并调用Agent
- 会话管理接口:查询或删除会话历史
- 文件上传接口:调用OSS提供的客户端,实现文件上传授权,将来由前端完成文件上传,文件不经过服务器。
4.1 FastAPI服务端
首先,我们需要安装一些依赖,包括FastAPI和阿里云的OSS:
python
uv add fastapi alibabacloud-oss-v2
项目目录如下
python
app/
├── main.py # FastAPI 入口,配置路由和静态文件
│
├── agents/
│ └── personal_chief.py # AI 代理核心逻辑
│
├── api/
│ └── v1/
│ ├── chat.py # 对话 API
│ │ ├── POST /chat/stream 流式对话
│ │ ├── GET /chat/messages 获取历史
│ │ └── DELETE /chat/messages 清空历史
│ │
│ └── oss.py # OSS 上传签名 URL
│
├── models/
│ └── schemas.py # Pydantic 数据模型,请求/响应数据结构定义
│
├── common/
│ └── logger.py # 日志配置
│
└── static/ # Next.js 编译产出的静态网页
├── index.html # 前端入口
├── _next/ # Next.js 构建资源
└── ... # 其他静态资源
main.py作为程序入口:
python
import os
from fastapi import FastAPI
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles
from fastapi.middleware.cors import CORSMiddleware
from app.api.v1 import chat
from app.api.v1 import oss
from app.common.logger import setup_logging
# 初始化日志配置
setup_logging()
# 初始化FastAPI
app = FastAPI(
title="Personal Chief API",
description="私厨",
version="0.1.0"
)
# ...中间代码略...
if __name__ == "__main__":
import uvicorn
# 启动命令:python -m app.main
uvicorn.run("app.main:app", host="127.0.0.1", port=8001, reload=True)
直接运行main.py,访问:http://localhost:8001即可:

前端代码这里就不提供了,有需要的话评论区留言就可
4.2 OSS配置
虽然资料已经帮大家实现了oss的文件上传,但是大家需要自己去oss注册服务,并申请API_KEY
这里我们以阿里云OSS为例来说明
4.2.1 注册阿里云
注册地址:https://account.aliyun.com/login/login.htm?oauth_callback=https://oss.console.aliyun.com/index
注册登录成功后,访问链接:https://oss.console.aliyun.com/overview,即可看到oss控制台:

如果显示【尚未开通】,点击【立即开通】即可:

默认为按量付费,价格非常便宜,几乎可以忽略不计:

开通成功后,即可进入控制台页面:

4.2.2 申请API_KEY
访问链接:https://ram.console.aliyun.com/overview?activeTab=workflow,进入RAM访问控制页面:

点击:用户>创建用户,填写用户信息:

创建完成后,一定要记住你的AccessKey的ID和Secret:

然后,给新添加的用户《新增授权》:

在授权页面,给新用户添加oss的绝对控制权限:

4.2.3 开通OSS
访问链接:https://oss.console.aliyun.com/overview,进入oss控制台,选择创建Bucket:
填写bucket信息:

4.2.4 设置权限
目前,新建的Bucket还是无法访问的,是私有的。为了方便测试,这里我们暂时将其设置为公共读。
注意:
- 实际开发中oss中的图片应该设置为私有!不可对外暴露!!由额外的CDN服务对外暴露!
- 本例中,我们为了方便暂时将bucket设置为公共读,测试完毕后请及时关闭权限!
首先,进入Bucket管理页面:

进入具体的Bucket设置,关闭公共访问开关:

设置权限为公共读:

接着,我们要开启跨域访问权限:

点击《创建规则》,填写跨域规则:

4.2.5 配置API_KEY
最后,我们需要把阿里云OSS的API_KEY配置到环境变量中,也就是项目的.env文件内,格式如下:
bash
# 阿里OSS
OSS_ACCESS_KEY_ID=LTBI5tHzjC36KhCJfPqlbaCo
OSS_ACCESS_KEY_SECRET=aDPGBi1nIlYzcEmk5djWJGUv3w9Qkh
OSS_BUCKET=tmp9527
访问 http://localhost:8001,试试上传图片是否成功:

选择图片,可以看到预览:

点击发送按钮,上传成功应该能在聊天窗看到结果:

4.3 添加Checkpointer
由于上一节是基于LangGraph和LangSmith的部署,所以Agent并没有添加Checkpointer,由LangGraph自己处理。而现在既然要自定义Agent部署, 就必须自己添加Checkpointer了。
我们依然使用Sqlite作为存储方案,修改personal_chief.py文件,添加Checkpointer给Agent:
python
from langchain.chat_models import init_chat_model
from langchain_tavily import TavilySearch
from langchain.agents import create_agent
import os
from langgraph.checkpoint.sqlite import SqliteSaver
import sqlite3
# 1.加载环境变量
from dotenv import load_dotenv
load_dotenv()
# 2.web搜索工具,使用tavily作为web搜索工具
web_search = TavilySearch(
max_results=5,
topic="general"
)
# 3.多模态模型
model = init_chat_model(
model="qwen3.5-plus", # 模型名称,这里选择qwen3.5-plus,这是一个多模态模型,支持图片、文本、音频、视频
model_provider="openai",
base_url=os.getenv("DASHSCOPE_BASE_URL"),
api_key=os.getenv("DASHSCOPE_API_KEY")
)
# 4.初始化checkpointer
# 连接sqlite
connection = sqlite3.connect("../db/personal_chief.db", check_same_thread=False)
# 初始化checkpointer
checkpointer = SqliteSaver(connection)
# 自动建表
checkpointer.setup()
# 5.Agent系统提示词
system_prompt = """
你是一名私人厨师。收到用户提供的食材照片或清单后,请按以下流程操作:
1.识别和评估食材:若用户提供照片,首先辨识所有可见食材。基于食材的外观状态,评估其新鲜度与可用量,整理出一份"当前可用食材清单"。
2.智能食谱检索:优先调用 web_search 工具,以"可用食材清单"为核心关键词,查找可行菜谱。
3.多维度评估与排序:从营养价值和制作难度两个维度对检索到的候选食谱进行量化打分,并根据得分排序,制作简单且营养丰富的排名靠前。
4.结构化方案输出:把排序后的食谱整理为一份结构清晰的建议报告,要包含食谱信息、得分、推荐理由、食谱的参考图片,帮助用户快速做出决策。
请严格按照流程,优先调用 web_search 工具搜索食谱,搜索不到的情况下才能自己发挥。
"""
# 6.创建Agent
agent = create_agent(
model=model, # 模型
tools=[web_search], # 工具
checkpointer=checkpointer, # 记忆
system_prompt=system_prompt # 系统提示词
)
在app下创建一个db目录,用以存放Sqlite的db文件:

4.4 开发接口
目前在agents/personal_cheif.py文件中虽然已经开发了Agent,但是外界还是无法访问,我们需要定义Restful的接口供前端使用。
其中,在api/v1/chat.py中定义了Restful的接口,等待我们实现功能:
python
from fastapi import APIRouter
router = APIRouter()
@router.post("/chat/stream")
async def chat_endpoint(request: ChatRequest):
"""流式聊天接口"""
pass
@router.get("/chat/messages")
async def get_chat_messages(thread_id: str):
"""获取历史消息"""
pass
@router.delete("/chat/messages")
async def clear_chat_messages(thread_id: str):
"""清空历史消息"""
pass
4.4.1 功能实现
我们可以在agents/personal_cheif.py中基于agent开发所需的3个功能:
- 多模态聊天
- 获取会话历史
- 清空会话历史
agents/personal_cheif.py的完整代码如下:
python
from langchain.chat_models import init_chat_model
from langchain_core.messages import HumanMessage, AIMessageChunk, AIMessage
from langchain_core.tools import tool
from langchain_tavily import TavilySearch
from langchain.agents import create_agent
from app.common.logger import logger
import os
from langgraph.checkpoint.sqlite import SqliteSaver
import sqlite3
# 加载环境变量
from dotenv import load_dotenv
load_dotenv()
# web搜索工具,使用tavily作为web搜索工具
tavily = TavilySearch(
max_results=5,
topic="general"
)
# 多模态模型
model = init_chat_model(
model="qwen3-omni-flash", # 模型名称,这里选择qwen3.5-plus,这是一个多模态模型,支持图片、文本、音频、视频
model_provider="openai",
base_url=os.getenv("DASHSCOPE_BASE_URL"),
api_key=os.getenv("DASHSCOPE_API_KEY")
)
# 初始化checkpointer
checkpointer = SqliteSaver(sqlite3.connect("db/personal_chief.db", check_same_thread=False))
# 自动建表
checkpointer.setup()
# Agent系统提示词
system_prompt = """
你是一名私人厨师。收到用户提供的食材照片或清单后,请按以下流程操作:
1.识别和评估食材:若用户提供照片,首先辨识所有可见食材。基于食材的外观状态,评估其新鲜度与可用量,整理出一份"当前可用食材清单"。
2.智能食谱检索:优先调用 web_search 工具,以"可用食材清单"为核心关键词,查找可行菜谱。
3.多维度评估与排序:从营养价值和制作难度两个维度对检索到的候选食谱进行量化打分,并根据得分排序,制作简单且营养丰富的排名靠前。
4.结构化方案输出:把排序后的食谱整理为一份结构清晰的建议报告,要包含食谱信息、得分、推荐理由、食谱的参考图片,帮助用户快速做出决策。
请严格按照流程,优先调用 web_search 工具搜索食谱,搜索不到的情况下才能自己发挥。
"""
# 创建代理
agent = create_agent(
model=model, # 模型
tools=[tavily], # 工具
checkpointer=checkpointer, # 记忆
system_prompt=system_prompt # 系统提示词
)
# 流式对话
async def search_recipes(prompt: str, image: str, thread_id: str):
"""调用agent搜索食谱"""
logger.info(f"[用户]: {prompt}, image: {image}, thread_id: {thread_id}")
try:
# 判断是否有图片,封装不同格式的消息
if not image or image.strip() == "":
message = HumanMessage(content=prompt)
else:
message = HumanMessage(content=[
{"type": "image", "url": image},
{"type": "text", "text": prompt}
])
# 流式调用Agent
for chunk, metadata in agent.stream(
{"messages": [message]},
{"configurable": {"thread_id": thread_id}},
stream_mode="messages"
):
if isinstance(chunk, AIMessageChunk) and chunk.content:
yield chunk.content
except Exception as e:
logger.error(f"\n[错误]: {str(e)}")
yield "信息检索失败,试试看手动输入食物列表?"
# 清空会话
def clear_messages(thread_id: str):
"""清空会话"""
logger.info(f"清空历史消息,thread_id: {thread_id}")
checkpointer.delete_thread(thread_id)
# 查询会话历史
def get_messages(thread_id: str) -> list[dict[str, str]]:
"""获取会话历史"""
logger.info(f"获取历史消息,thread_id: {thread_id}")
# 根据 thread_id 查询 checkpoint
checkpoint = checkpointer.get({"configurable": {"thread_id": thread_id}})
# 如果不存在,返回空列表
if not checkpoint:
return []
# 安全获取 messages
channel_values = checkpoint.get("channel_values")
if not channel_values:
return []
messages = channel_values.get("messages", [])
if not messages:
return []
# 转换消息格式
result = []
for msg in messages:
if not msg.content:
continue
if isinstance(msg, HumanMessage):
result.append({"role": "user", "content": msg.content})
elif isinstance(msg, AIMessage):
result.append({"role": "assistant", "content": msg.content})
return result
4.4.2 完善接口
然后,修改chat.py文件,导入agents.personal_cheif中的三个方法,完善接口:
python
from fastapi import APIRouter
from app.models.schemas import ChatRequest
from fastapi.responses import StreamingResponse
from app.agents.personal_chief import search_recipes, get_messages, clear_messages
router = APIRouter()
@router.post("/chat/stream")
async def chat_endpoint(request: ChatRequest):
"""流式对话"""
return StreamingResponse(
search_recipes(request.message, request.image_url, request.thread_id),
media_type="text/event-stream"
)
@router.get("/chat/messages")
async def get_chat_messages(thread_id: str):
"""获取历史消息"""
messages = get_messages(thread_id)
return {"messages": messages}
@router.delete("/chat/messages")
async def clear_chat_messages(thread_id: str):
"""清空历史消息"""
clear_messages(thread_id)
return {"success": True}
4.5 测试
访问http://localhost:8001,即可实现多模态聊天了:
