一 需求分析

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中有以下配置:

# .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 依赖

参考下面的依赖:

[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 加载配置

首先,我们要加载环境变量:

# 加载环境变量
from dotenv import load_dotenv

load_dotenv()

2.4 定义工具

然后,我们要定义工具:

from langchain_tavily import TavilySearch

# web搜索工具,使用tavily作为web搜索工具
web_search = TavilySearch(
    max_results=5,
    topic="general",
)

2.5 初始化模型

接着,初始化多模态模型:

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:

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

接下来,就是智能体了:

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 测试

我们在网上找到一张冰箱食物图片:

在这里插入图片描述

测试一下:

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)

打印:

# 友好打印
for message in response['messages']:
    message.pretty_print()

结果:

================================ 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分钟,直到鸡肉熟透,蔬菜变软。

这些食谱不仅充分利用了您现有的食材,还兼顾了营养均衡和制作的便捷性。希望您能享受这些美味的料理!如果有任何其他需求,随时告诉我。

继续询问:

response = agent.invoke(
    {"messages": [HumanMessage(content="我喜欢第1道菜,可以说的更详细点吗?")]},
    config
)

# 友好打印
response['messages'][-1].pretty_print()

结果:

================================== 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文件中添加配置即可:

# 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文件中:

在这里插入图片描述

代码如下:

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安装:

uv add langgraph-cli[inmem]

然后,在项目根目录添加一个langgraph配置文件:

在这里插入图片描述

添加下面的内容:

{
    "dependencies": ["."],
    "graphs": {
        "chief_agent": "./app/agents/personal_chief.py:agent"
    },
    "env": ".env"
}

注意:其中的agent配置格式为:

[Agent文件路径]:[Agent变量名]

例如,在我们的配置中:

  • ./app/agents/personal_chief.py:就是文件路径
  • agent:就是文件中定义的Agent名字

最后,点击左下角打开Pycharm终端:

在这里插入图片描述

使用LangGraph命令本地部署Agent:

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:

uv add fastapi alibabacloud-oss-v2

项目目录如下


  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作为程序入口:

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文件内,格式如下:

# 阿里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:

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的接口,等待我们实现功能:

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的完整代码如下:

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中的三个方法,完善接口:

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,即可实现多模态聊天了:

在这里插入图片描述

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐