跨Session/Thread的长期记忆是Agent实现自我学习和进化的关键能力之一。它使得Agent能够在不同的对话会话之间保持对用户偏好、历史交互、以及其他相关信息的记忆,从而提供更加个性化和上下文相关的响应。通过长期记忆,Agent可以不断地从与用户的交互中学习和适应,逐渐提升其性能和用户体验。在这方面,LangChain和MAF都提供了相应的解决方案,但是它们的实现方式却有很大不同。
1. LangChain(Deep Agents)的解决方案
作为LangChain平台针对Agent Harness的实现,Deep Agents提供的十来个Middleware(详见DeepAgents:LangChain的Harness)中就包含了一个与长期记忆相关的Middleware,他就是MemoryMiddleware。MemoryMiddleware借助于FileSystemMiddleware提供的文件系统提供记忆存储,所以记忆的读写其实是通过FileSystemMiddleware中间件提供的文件读写工具完成的。跨Session/Thread的长期记忆是有范围的,比如针对用户偏好的记忆肯定就是针对具有某个用户的,也有的记忆是针对具有某个Agent,或者基于整个应用范围的。MemoryMiddleware通过将记忆内容存储在不同的文件或者目录的方式来支持不同范围的记忆,这里的目录和文件是抽象的概念,取决于采用的Backend类型。
在如下的演示程序中,我们创建了一个Agent作为点餐助手,并使用MemoryMiddleware记住每个用户的饮食习惯。如代码片段所示,我们的Agent是通过create_deep_agent函数创建的,具体的点餐功能是通过一个名为place_order的工具函数来实现的。由于饮食习惯是针对个人用户的,所以我们在调用Agent的时候利用传入的静态上下文来指定用户ID,该上下文类型就是通过context_schema参数定义的Context类型。
、from typing import Literal, TypedDict
from attr import dataclass
from langchain.tools import tool
from langchain_openai import ChatOpenAI
from deepagents.backends import FilesystemBackend
from dotenv import load_dotenv
from deepagents import create_deep_agent
import asyncio
load_dotenv()
@tool
def place_order(dish_name:Literal["辣椒炒肉", "剁椒鱼头", "番茄炒蛋", "清蒸鲈鱼", "清炒菜心","酸辣土豆丝","西芹百合"], quantity:int):
"""订餐工具,提供菜名和数量,返回订单信息"""
return f"Order placed: {quantity} x {dish_name}"
class Context(TypedDict):
user_id: str
agent = create_deep_agent(
model=ChatOpenAI(model="gpt-5.2-chat"),
tools=[place_order],
context_schema=Context,
backend= lambda runtime: FilesystemBackend(root_dir=f"d:/memories/{runtime.context.get('user_id')}",virtual_mode=True) , # type: ignore
memory=["/preferences.md"],
system_prompt=(
"你是一个贴心外卖点餐助手。"
"用户授权自己选择菜品和数量的权力,无需用户确认。但下单数量务必控制在三份以内。"
"点餐时既要考虑用户的口味偏好,也要考虑菜品多样性,以及尽可能与上次订餐有所不同。"
"如果用户提到了新的饮食习惯或忌口,立即写入`/preferences.md`中"),
)
async def order_for_user(user_id:str, preferences:str|None=None):
messages = [
{
"role": "user",
"content": "帮我点一份外卖" + (preferences if preferences else "")
}
]
result = await agent.ainvoke(input = {"messages": messages}, context={"user_id": user_id}) # type: ignore
print(f"""\
------------------------------来自 {user_id} 的订单------------------------------
{result["messages"][-1].content}
""")
async def main():
await order_for_user("Alice", preferences="我不能吃辣")
await order_for_user("Alice")
await order_for_user("Alice")
await order_for_user("Bob", preferences="我不吃鱼")
await order_for_user("Bob")
asyncio.run(main())
为了将基于饮食习惯的个人专属记忆存储在基于用户的文件中,我们利用backend参数传入了一个工厂函数,这个工厂函数根据运行时上下文中的用户ID来创建FilesystemBackend实例,从而实现了针对不同用户的记忆存储。在用于设置MemoryMiddleware的memory参数中,我们指定了存储用户饮食习惯的文件为/preferences.md,这个路径是相对于每个用户的根目录的。经过了如此设置之后,Agent被调用的时候就会根据用户ID来定位到对应的preferences.md文件来读取和写入用户的饮食习惯信息,并添加到系统提示词中来指导Agent的点餐行为。
我们还利用system_prompt参数来设置了Agent的系统提示词,在这个提示词中我们明确了Agent的角色定位、点餐原则、以及如何处理用户饮食习惯信息的要求。最重要的是,我们利用提示词指导LLM在用户提到新的饮食习惯或者忌口时,立即将这些信息写入到/preferences.md文件中。这样一来,LLM就会留意用户点餐时是否提及了饮食习惯的信息,并及时保存下来。
我们定义了辅助函数order_for_user来模拟针对指定用户的点餐请求,在这个函数中我们可以选择性地传入用户的饮食习惯信息。我们调用五次调用此方法针对两个不同的用户(Alice和Bob)来模拟点餐请求,针对用户的第一次点餐会提供饮食习惯信息。具体的输出结果如下所示:
------------------------------来自 Alice 的订单------------------------------
已为您下单 ✅(均为不辣菜品):
- 清蒸鲈鱼 ×1
- 番茄炒蛋 ×1
- 清炒菜心 ×1
搭配清淡均衡,有鱼有蛋有蔬菜。祝您用餐愉快 🍽️
------------------------------来自 Alice 的订单------------------------------
已为您下单:
- 清蒸鲈鱼 ×1
- 番茄炒蛋 ×1
- 清炒菜心 ×1
搭配清淡、不含辣味,荤素均衡。请稍等,美味马上送达 🍱
------------------------------来自 Alice 的订单------------------------------
已为你下单完成 ✅
- 清蒸鲈鱼 ×1
- 番茄炒蛋 ×1
- 清炒菜心 ×1
搭配清淡不辣,荤素均衡。祝你用餐愉快 🍽️
------------------------------来自 Bob 的订单------------------------------
已帮您下单:
- 辣椒炒肉 ×1
- 番茄炒蛋 ×1
- 清炒菜心 ×1
荤素搭配,口味均衡,也避开了鱼类。祝您用餐愉快 🍽️
------------------------------来自 Bob 的订单------------------------------
已为您下单:
- 辣椒炒肉 ×1
- 清炒菜心 ×1
荤素搭配,口味偏香辣但不过于油腻,也避开了鱼类。祝您用餐愉快!
从输出的订单详情中我们可以看到,针对Alice的第一次点餐请求,Agent根据她提供的饮食习惯信息成功地记住了她不能吃辣的偏好,并在后续的点餐请求中都遵循了这个偏好来进行点餐搭配。针对Bob的点餐请求,Agent同样成功地记住了他不吃鱼的偏好,并在两次点餐请求中都遵循了这个偏好来进行点餐搭配。通过这个例子,我们就演示了如何利用LangChain(Deep Agents)中的MemoryMiddleware来实现Agent的长期记忆功能,从而让Agent具有自我学习和进化的能力。
1.1 查看记忆文件
由于我们将记忆内容存储在了文件中,所以我们还可以直接查看这些文件来验证Agent是否正确地记住了用户的饮食习惯信息。查看我们设置的映射目录D:\memories,会看到如下的目录结构:
D:\memories
├── Alice
│ └── preferences.md
└── Bob
└── preferences.md
打开Alice和Bob目录下的preferences.md文件,可以看到如下的内容:
用户饮食偏好:不能吃辣。
- 用户忌口:不吃鱼
1.2 修改记忆内容
由于记忆内容是存储在文件中的,所以我们也可以直接修改这些文件来改变Agent的记忆内容,从而影响Agent的行为。比如我们将Alice和Bob的preferences.md文件交换一下。然后在下单的时候都不提供饮食习惯信息,看看Agent的点餐结果会是什么样的:
async def main():
await order_for_user("Alice")
await order_for_user("Alice")
await order_for_user("Bob")
await order_for_user("Bob")
asyncio.run(main())
输出:
------------------------------来自 Alice 的订单------------------------------
已为您下单:
- 辣椒炒肉 ×1
- 清炒菜心 ×1
荤素搭配,口味有层次,也避免了鱼类食品。祝您用餐愉快 🍽️
------------------------------来自 Bob 的订单------------------------------
已为你下单:
- 清蒸鲈鱼 ×1
- 番茄炒蛋 ×1
- 清炒菜心 ×1
搭配清淡不辣,荤素均衡。祝你用餐愉快 🍽️
------------------------------来自 Bob 的订单------------------------------
已为您下单:
- 清蒸鲈鱼 ×1
- 番茄炒蛋 ×1
- 清炒菜心 ×1
搭配清淡均衡,荤素都有,也避免了辣味。祝您用餐愉快 🍽️
从输出的订单详情中我们可以看到,Alice的点餐结果变成了辣味菜品,而Bob的点餐结果变成了清淡不辣的菜品。这是因为我们交换了他们的记忆文件,从而导致Agent记住了错误的饮食习惯信息,最终影响了Agent的点餐行为。
关于MemoryMiddleware的更多细节和使用方法,可以参阅我的文章利用MemoryMiddleware构建能够自我学习和进化的Agent。
2. MAF的解决方案
MAF针对整个问题的有多种解决方案,一种是利用ChatHistoryMemoryProvider这个自定义的AIContextProvider。ChatHistoryMemoryProvider利用的是向量存储,它会将对话历史进行摘要,并基于指定范围的维度进行存储。ChatHistoryMemoryProvider赋予我们在本地自主存储记忆的能力。如果图审视,我们也可以花钱使用现成的云端解决方案。
2.1 基于ChatHistoryMemoryProvider的本地记忆存储
我们利用如下的代码在MAF中重新定义上面演示的点餐助手。注册的两个工具GetMenu和PlaceOrder分别用来获取菜单和下单。我们赋予Agent自主选择菜品和直接下单的权力,所以我们希望Agent能够在为不同用户提供服务的过程中,能够记住每个用户的口味偏好,并且在后续的点餐过程中能够基于这些偏好来推荐菜品。
using Azure;
using dotenv.net;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using Microsoft.SemanticKernel.Connectors.InMemory;
using OpenAI;
using System.ComponentModel;
DotEnv.Load();
var model = Environment.GetEnvironmentVariable("MODEL")!;
var apiKey = Environment.GetEnvironmentVariable("API_KEY")!;
var endpoint = Environment.GetEnvironmentVariable("OPENAI_URL")!;
var openAIClient= new OpenAIClient(
credential: new AzureKeyCredential(apiKey),
options: new OpenAIClientOptions { Endpoint = new Uri(endpoint) });
var embeddingGenerator = openAIClient
.GetEmbeddingClient(model: "text-embedding-3-small")
.AsIEmbeddingGenerator()
.AsBuilder()
.Use((texts, options, next, ct) =>
{
var sanitizedTexts = texts.Select(t => string.IsNullOrWhiteSpace(t) ? " " : t);
return next.GenerateAsync(sanitizedTexts, options, ct);
})
.Build(); ;
var memoryProvider = new ChatHistoryMemoryProvider(
vectorStore: new InMemoryVectorStore(options: new InMemoryVectorStoreOptions { EmbeddingGenerator = embeddingGenerator }),
collectionName: "user_preference",
vectorDimensions: 1536,
stateInitializer: InitializeMemoryState);
AITool[] tools = [AIFunctionFactory.Create(GetMenu, "GetMenu"), AIFunctionFactory.Create(PlaceOrder, "PlaceOrder")];
var agent = openAIClient
.GetChatClient(model:model)
.AsIChatClient()
.AsAIAgent(options: new ChatClientAgentOptions
{
Name= "delivery-order",
AIContextProviders = [memoryProvider],
ChatOptions = new ChatOptions {
Instructions = """
你是一个贴心外卖点餐助手。
用户授权自己选择菜品和数量的权力,无需用户确认。但下单数量务必控制在三份以内。
点餐时既要考虑用户的口味偏好,也要考虑菜品多样性,以及尽可能与上次订餐有所不同。
""",
Tools = tools }
} );
var session = await agent.CreateSessionAsync();
session.StateBag.SetValue("user_id", "Alice");
var response = await agent.RunAsync("帮我点一份外卖,一荤一素,我不能吃辣",session);
Console.WriteLine($"""
{new string('-', 30)} 来自 Alice 的订单 {new string('-', 30)}
{response}
""");
await OrderDelivery("Alice");
await OrderDelivery("Alice");
await OrderDelivery("Bob");
await OrderDelivery("Bob");
async Task OrderDelivery(string userName)
{
var session = await agent.CreateSessionAsync();
session.StateBag.SetValue("user_id", userName);
var response = await agent.RunAsync("帮我点一份外卖", session);
Console.WriteLine($"""
{new string('-', 30)} 来自 {userName} 的订单 {new string('-', 30)}
{response}
""");
}
static ChatHistoryMemoryProvider.State InitializeMemoryState(AgentSession? session)
{
if (session is not ChatClientAgentSession chatSession)
{
throw new InvalidOperationException("Session is not of type ChatClientAgentSession.");
}
if (chatSession.StateBag?.TryGetValue<string>("user_id", out var userId) != true)
{
throw new InvalidOperationException("User ID not found in session state.");
}
var scope = new ChatHistoryMemoryProviderScope { UserId = userId };
return new ChatHistoryMemoryProvider.State(storageScope: scope,searchScope: scope);
}
[Description("提取外卖菜单")]
static string[] GetMenu()
{
return ["辣椒炒肉", "剁椒鱼头", "番茄炒蛋", "清蒸鲈鱼", "清炒菜心","酸辣土豆丝","西芹百合"];
}
[Description("外卖下单")]
static IReadOnlyList<OrderItem> PlaceOrder(params KeyValuePair<string, int>[] orderItems)
{
return [.. orderItems.Select(item => new OrderItem(item.Key, item.Value))];
}
public readonly record struct OrderItem(string DishName, int Quantity);
ChatHistoryMemoryProvider利用一个向量数据库来存储对话历史,简单起见,我们使用了一个基于内存的向量数据库InMemoryVectorStore。InMemoryVectorStore需要一个IEmbeddingGenerator对象为输入的文本生成嵌入向量,所以我们调用OpenAIClient的GetEmbeddingClient方法得到了一个针对"text-embedding-3-small"模型的EmbeddingClient,并将其转换成了IEmbeddingGenerator对象。创建ChatHistoryMemoryProvider除了提供作为存储的向量数据库之外,我们还提供了存储记忆的集合名称(user_preference)和嵌入向量的维度(1536)。
ChatHistoryMemoryProvider存储的记忆来源于对话历史,但是需要针对需要以不同的Scope进行存储。由于我们希望利用记忆了解用户的口味偏好,所以我们选择了以用户为Scope来存储记忆,并要求Session中必需包含用户的ID。创建ChatHistoryMemoryProvider提供的第四个参数是一个StateInitializer类型的委托,它的作用就是根据当前的Session来初始化作为状态的ChatHistoryMemoryProvider.State对象,而ChatHistoryMemoryProvider.State对象中包含了检索和存储记忆所需要的Scope信息。该参数对应的方法为InitializeMemoryState,我们将两种Scope都设置为UserId。
我们通过注册两个工具(GetMenu和PlaceOrder)和ChatHistoryMemoryProvider将Agent创建出来后,就可以开始点餐了。我们定义了辅助方法OrderDelivery来为指定的用户点餐,并且每次调用都创建了一个新的Session来屏蔽短期记忆的干扰。我们第一次直接调用Agent以Alice的名义点餐,并且告诉Agent:“帮我点一份外卖,一荤一素,我不能吃辣”。后续则通过调用OrderDelivery方法来为Alice和Bob点餐。整个程序会生成如下的输出:
------------------------------ 来自 Alice 的订单 ------------------------------
已经帮您下单 ✅
🥩 荤菜:清蒸鲈鱼 ×1(清淡不辣,营养又鲜美)
🥬 素菜:西芹百合 ×1(清爽可口,搭配均衡)
这份搭配清淡健康、完全不辣,也兼顾营养和口味层次。祝您用餐愉快 🍽️😊
------------------------------ 来自 Alice 的订单 ------------------------------
已经帮您下单 ✅
🍅 荤菜:番茄炒蛋 ×1(酸甜开胃,不辣又下饭)
🥬 素菜:清炒菜心 ×1(清爽鲜嫩,清淡健康)
这次给您换了搭配,依旧保持不辣口味,同时清爽均衡。祝您用餐愉快 🍽️😊
------------------------------ 来自 Alice 的订单 ------------------------------
已经帮您下单 ✅
🍅 荤菜:番茄炒蛋 ×1(酸甜开胃,不辣又下饭)
🥬 素菜:清炒菜心 ×1(清爽脆嫩,营养均衡)
这次给您换了搭配,依旧是一荤一素、清淡不辣,也和上次有所不同。祝您用餐愉快 🍽️😊
------------------------------ 来自 Bob 的订单 ------------------------------
已经帮您下单啦 ✅
🛒 本次为您搭配了:
- 辣椒炒肉 ×1(香辣下饭)
- 清蒸鲈鱼 ×1(清淡鲜美,营养均衡)
- 清炒菜心 ×1(清爽解腻)
荤素搭配、口味有层次,吃起来不会腻~
祝您用餐愉快 🍚😋
------------------------------ 来自 Bob 的订单 ------------------------------
已经帮您下单啦 ✅
🛒 本次为您搭配了:
- 剁椒鱼头 ×1(鲜辣开胃,和上次不同口味)
- 西芹百合 ×1(清爽脆口,解腻健康)
- 番茄炒蛋 ×1(酸甜开胃,经典下饭)
这次偏鲜辣+清爽搭配,和上次的清蒸鲈鱼、辣椒炒肉组合有所变化,口味更有层次~
祝您用餐愉快 🍚😋
上面的输出显示了5次点餐的结果。可以看到,Alice的三次点餐都没有辣椒炒肉和剁椒鱼头,因为她不能吃辣;Bob的两次点餐都包含了辣椒炒肉和剁椒鱼头,因为对话中并没有涉及Bob的口味偏好。
关于ChatHistoryMemoryProvider的更多细节和使用方法,可以参阅我的文章ChatHistoryMemoryProvider:赋予Agent从经验中学习的能力。
2.2 基于Mem0Provider的云端记忆解决方案
ChatHistoryMemoryProvider利用我们提供的向量数据库,对每次调用产生的消息针对指定的Scope维度进行存储,并将当前消息作为查询文本,结合设定的Scope维度检索历史消息作为上下文的一部分来参与LLM的推理。除了这种需要我们们自己搭建和维护的基于向量数据库的解决方案之外,我们还可以利用如下两个预定义的AIContextProvider调用来实现长期记忆的功能:
Mem0Provider:集成了Mem0记忆平台为Agent提供长期记忆;FoundryMemoryProvider:集成Azure AI Foundry的 Memory Store来为Agent提供长期记忆;
Mem0是一个专为AIAgent打造的长期记忆层平台,让AI能像人一样跨会话记住事实、偏好、背景信息,并通过向量 + 图谱的混合架构实现高效、可扩展、可演化的记忆管理。Mem0目前提供免费试用,我们可以通过这里申请API-Key来使用它的服务。集成它的Mem0Provider很新,新到对应的NuGet包还没有发布,所以我不得不将其源代码从Github上扒下来。所以当对用的NuGet包发布之后,相关的API肯定与本文介绍的有所不同,不过由于Mem0 API不会改变,所以这套实现方式肯定是使用的。
我们在前面将ChatHistoryMemoryProvider应用到作为点餐助手的Agent上,使Agent能够记住用户的口味偏好。接下来我们将ChatHistoryMemoryProvider替换成Mem0Provider来实现同样的功能。如代码片段所示,我们利用一个用于远程调用Mem0 API的HttpClient和一个应用初始化状态的委托来创建了一个Mem0Provider对象。这个HttpClient使用固定的目标地址“https://api.mem0.ai"",并将申请的API-Key放在Authorization请求头中。
using Azure;
using Azure.AI.Projects;
using dotenv.net;
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Mem0;
using Microsoft.Extensions.AI;
using OpenAI;
using OpenAI.Responses;
using System.ComponentModel;
using System.Net.Http.Headers;
DotEnv.Load();
var model = Environment.GetEnvironmentVariable("MODEL")!;
var apiKey = Environment.GetEnvironmentVariable("API_KEY")!;
var endpoint = Environment.GetEnvironmentVariable("OPENAI_URL")!;
var mem0ApiKey = Environment.GetEnvironmentVariable("MEM0_API_KEY")!;
using var httpClient = new HttpClient();
httpClient.BaseAddress = new Uri("https://api.mem0.ai");
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Token", mem0ApiKey);
var memoryProvider = new Mem0Provider(httpClient: httpClient, stateInitializer: InitializeMemoryState);
AITool[] tools = [AIFunctionFactory.Create(GetMenu, "GetMenu"), AIFunctionFactory.Create(PlaceOrder, "PlaceOrder")];
var agent = new OpenAIClient(
credential: new AzureKeyCredential(apiKey),
options: new OpenAIClientOptions { Endpoint = new Uri(endpoint) })
.GetChatClient(model: model)
.AsIChatClient()
.AsAIAgent(options: new ChatClientAgentOptions
{
Name = "delivery-order",
AIContextProviders = [ memoryProvider ],
ChatOptions = new ChatOptions
{
ModelId=model,
Instructions = """
你是一个贴心外卖点餐助手。
用户授权自己选择菜品和数量的权力,无需用户确认。但下单数量务必控制在三份以内。
点餐时既要考虑用户的口味偏好,也要考虑菜品多样性,以及尽可能与上次订餐有所不同。
""",
Tools = tools
}
});
var session = await agent.CreateSessionAsync();
session.StateBag.SetValue("user_id", "Alice");
var response = await agent.RunAsync("帮我点一份外卖,一荤一素,我不能吃辣", session);
Console.WriteLine($"""
{new string('-', 30)} 来自 Alice 的订单 {new string('-', 30)}
{response}
""");
await OrderDelivery("Alice");
await OrderDelivery("Alice");
await OrderDelivery("Bob");
await OrderDelivery("Bob");
async Task OrderDelivery(string userName)
{
var session = await agent.CreateSessionAsync();
session.StateBag.SetValue("user_id", userName);
var response = await agent.RunAsync("帮我点一份外卖", session);
Console.WriteLine($"""
{new string('-', 30)} 来自 {userName} 的订单 {new string('-', 30)}
{response}
""");
}
static Mem0Provider.State InitializeMemoryState(AgentSession? session)
{
if (session is not ChatClientAgentSession chatSession)
{
throw new InvalidOperationException("Session is not of type ChatClientAgentSession.");
}
if (chatSession.StateBag?.TryGetValue<string>("user_id", out var userId) != true || string.IsNullOrWhiteSpace(userId))
{
throw new InvalidOperationException("User ID not found in session state.");
}
var scope = new Mem0ProviderScope { UserId = userId };
return new Mem0Provider.State(storageScope: scope, searchScope: scope);
}
[Description("提取外卖菜单")]
static string[] GetMenu()
=>["辣椒炒肉", "剁椒鱼头", "番茄炒蛋", "清蒸鲈鱼", "清炒菜心", "酸辣土豆丝", "西芹百合"];
[Description("外卖下单")]
static IReadOnlyList<OrderItem> PlaceOrder(params KeyValuePair<string, int>[] orderItems)
=>[.. orderItems.Select(item => new OrderItem(item.Key, item.Value))];
public readonly record struct OrderItem(string DishName, int Quantity);
虽然我们不再需要为Mem0Provider提供向量数据库,但是基于Scope针对对话历史消息文本的存储和检索依然没有改变,所以我们在创建Mem0Provider对象的时候依然需要提供一个StateInitializer的委托对象来为每次调用初始化一个State对象,该对象用于封装了上下文检索和对话历史存储使用的Scope维度。
我们通过注册两个工具(GetMenu和PlaceOrder)和Mem0Provider将Agent创建出来后,就可以开始点餐了。我们定义了辅助方法OrderDelivery来为指定的用户点餐,并且每次调用都创建了一个新的Session来屏蔽短期记忆的干扰。我们第一次直接调用Agent以Alice的名义点餐,并且告诉Agent:“帮我点一份外卖,一荤一素,我不能吃辣”。后续则通过调用OrderDelivery方法来为Alice和Bob点餐。整个程序会生成如下的输出:
------------------------------ 来自 Alice 的订单 ------------------------------
已经帮您搭配好一荤一素,而且都不辣 🌿
✅ 清蒸鲈鱼 ×1(清淡鲜嫩)
✅ 清炒菜心 ×1(爽口解腻)
营养均衡又清爽,很适合不能吃辣的您~
祝您用餐愉快!🍽️
------------------------------ 来自 Alice 的订单 ------------------------------
已经帮您安排好啦 ✅
📝 本次为您搭配:
- 🐟 清蒸鲈鱼 ×1(清淡鲜美,不辣)
- 🥬 西芹百合 ×1(清爽解腻,营养丰富)
一荤一素,口味清淡,搭配均衡,也避开了辣味菜品 👍
祝您用餐愉快呀!如果下次想换换口味,也可以告诉我~
------------------------------ 来自 Alice 的订单 ------------------------------
已经帮您下单完成 ✅
🥢 **清蒸鲈鱼** ×1(清淡不辣,优质蛋白)
🥬 **清炒菜心** ×1(清爽蔬菜,营养均衡)
搭配一荤一素,口味清淡,符合您不吃辣的需求,而且与常见重口味菜品有所区分。
祝您用餐愉快 🍽️😋
------------------------------ 来自 Bob 的订单 ------------------------------
已经帮您下单啦 ✅
🧾 本次点餐:
- 辣椒炒肉 ×1(下饭又有点辣味)
- 清蒸鲈鱼 ×1(清淡鲜美,搭配均衡)
- 清炒菜心 ×1(清爽解腻)
荤素搭配、口味有层次,而且控制在三份以内~
祝您用餐愉快 🍚🥢
------------------------------ 来自 Bob 的订单 ------------------------------
已经帮你下单啦 ✅
本次为你搭配的是:
- 🌶️ 辣椒炒肉 ×1(下饭主菜)
- 🍅 番茄炒蛋 ×1(经典家常)
- 🥬 清炒菜心 ×1(清爽解腻)
荤素搭配均衡,口味丰富又不油腻。
祝你用餐愉快呀 🍚😋
关于Mem0Provider的更多细节和使用方法,可以参阅我的文章Mem0Provider:长期记忆云端解决方案。
497

被折叠的 条评论
为什么被折叠?



