惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Vercel News
Vercel News
Recorded Future
Recorded Future
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
The GitHub Blog
The GitHub Blog
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Google DeepMind News
Google DeepMind News
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Microsoft Azure Blog
Microsoft Azure Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
M
MIT News - Artificial intelligence
云风的 BLOG
云风的 BLOG
Y
Y Combinator Blog
N
News | PayPal Newsroom
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Help Net Security
Help Net Security
博客园 - Franky
SecWiki News
SecWiki News
Recent Announcements
Recent Announcements
T
Troy Hunt's Blog
The Register - Security
The Register - Security
The Last Watchdog
The Last Watchdog
Webroot Blog
Webroot Blog
S
Security Affairs
博客园 - 司徒正美
S
Schneier on Security
I
InfoQ
博客园_首页
www.infosecurity-magazine.com
www.infosecurity-magazine.com
T
Threat Research - Cisco Blogs
Forbes - Security
Forbes - Security
腾讯CDC
N
Netflix TechBlog - Medium
N
News and Events Feed by Topic
Cloudbric
Cloudbric
T
The Exploit Database - CXSecurity.com
P
Proofpoint News Feed
A
About on SuperTechFans
Engineering at Meta
Engineering at Meta
Recent Commits to openclaw:main
Recent Commits to openclaw:main
B
Blog
V
Vulnerabilities – Threatpost
C
Check Point Blog
Google DeepMind News
Google DeepMind News
Google Online Security Blog
Google Online Security Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
Hacker News - Newest:
Hacker News - Newest: "LLM"
C
Cisco Blogs
Schneier on Security
Schneier on Security
O
OpenAI News
K
Kaspersky official blog

博客园 - Artech

[MAF预定义的AIContextProvider-05]CompactionProvider——采用多种策略压缩对话历史 [MAF预定义的AIContextProvider-03]ChatHistoryMemoryProvider——赋予Agent从经验中学习的能力 [MAF预定义的AIContextProvider-02]AgentSkillsProvider——将Agent Skills引入MAF [MAF预定义的AIContextProvider-01]TextSearchProvider——RAG在MAF中的实现 [MAF预定义ChatClient中间件-09]MessageInjectingChatClient-赋予工具消息注入的能力 [MAF预定义ChatClient中间件-08]OpenTelemetryChatClient-实现链路跟踪和性能监控 [MAF预定义ChatClient中间件-07]PerServiceCallChatHistoryPersistingChatClient——基于ReAct循环的一步一存档 [MAF预定义ChatClient中间件-06]利用ImageGeneratingChatClient开发专业图片生成Agent [MAF预定义ChatClient中间件-05]动态修改ChatOptions和请求消息 [MAF预定义ChatClient中间件-04]ReducingChatClient——精减对话历史又不丢失基本语义 [MAF预定义ChatClient中间件-03]CachingChatClient——利用缓存省钱省时间 [MAF预定义ChatClient中间件-02]FunctionInvokingChatClient——实现ReAct循环和人机交互的大功臣 [MAF预定义ChatClient中间件-01]LoggingChatClient——在调用LLM前后输出日志 [MAF的Agent管道详解-07]利用AIAgent中间件构建Agent管道 [MAF的Agent管道详解-06]ChatClientAgent对IChatClient和输入输出增强管道的整合 [MAF的Agent管道详解-06]ChatClientAgent对IChatClient和输入输出增强管道的整合 - Artech [MAF的Agent管道详解-05]对话历史的持久化和输入输出的增强 [MAF的Agent管道详解-04]如何让LLM按照要求的结构输出数据? [MAF的Agent管道详解-03]连接LLM的IChatClient对象 [MAF的Agent管道详解-02]IChatClient管道如何完美连接大模型? [MAF的Agent管道详解-01]塑智能体边界,从AIAgent抽象类开始 [对比学习LangChain和MAF-04]针对消息的设计 [对比学习LangChain和MAF-03]完全不同的Agent设计哲学 [对比学习LangChain和MAF-02]基本编程模式的差异(下篇) [对比学习LangChain和MAF-01]基本编程模式的差异(上篇) 我所理解的Python元模型 除了按值和引用,方法参数的第三种传递方式 方法的三种调用形式 可以调用Null的实例方法吗? 自定义Key类型的字典无法序列化的N种解决方案 为什么ASP.NET Core的路由处理器可以使用一个任意类型的Delegate 深入解析ASP.NET Core MVC的模块化设计[下篇] 深入解析ASP.NET Core MVC的模块化设计[上篇] ASP.NET Core MVC应用模型的构建[4]: Action的选择 ASP.NET Core MVC应用模型的构建[3]: Controller的收集 ASP.NET Core MVC应用模型的构建[2]: 定制应用模型
[MAF预定义的AIContextProvider-04]Mem0Provider——长期记忆基于的云端解决方案
Artech · 2026-06-22 · via 博客园 - Artech

ChatHistoryMemoryProvider——赋予Agent从经验中学习的能力中,我们介绍了如何利用ChatHistoryMemoryProvider赋予Agent长期的记忆,使之具备从过去经验学习进化的能力。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不会改变,所以这套实现方式肯定是使用的。

1. 将Mem0Provide应用到点餐Agent上

上一篇文章中,我们将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维度。

我们通过注册两个工具(GetMenuPlaceOrder)和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(清爽解腻)

荤素搭配均衡,口味丰富又不油腻。
祝你用餐愉快呀 🍚😋

面的输出显示了5次点餐的结果。可以看到,Alice的三次点餐都没有辣椒炒肉和剁椒鱼头,因为她不能吃辣;Bob的两次点餐都包含了辣椒炒肉和剁椒鱼头,因为对话中并没有涉及Bob的口味偏好。

2. 检索和存储的Scope

Mem0ProviderChatHistoryMemoryProvider,前者通过指定的HttpClient调用远程的Mem0服务来实现对话历史的存储和检索,后者则是直接操作我们提供的向量数据库来实现存储和检索。除了这点不之外,其他方面的实现非常类似。所以我们会看到类似到几乎一样的类型定义,比如内嵌于Mem0Provider封装了检索和存储Scope的State类型。除了Mem0ProviderScopeSessionId属性重写命名为ThreadId之外,其他定义完全一样。

public sealed class Mem0Provider : MessageAIContextProvider
{
	public sealed class State
	{
		public Mem0ProviderScope StorageScope { get; }
		public Mem0ProviderScope SearchScope { get; }
		public State(
            Mem0ProviderScope storageScope, 
            Mem0ProviderScope? searchScope = null);
	}

}
public sealed class Mem0ProviderScope
{
    public string? ApplicationId { get; set; }
    public string? AgentId { get; set; }
    public string? ThreadId { get; set; }
    public string? UserId { get; set; }
}

Socpe四个维度的值来源于Session状态,构造Mem0Provider的时候需要提供一个StateInitializer类型的委托来根据当前的Session来初始化State对象。构造函数会根据此委托对象,结合由Mem0ProviderOptions指定的键(如果没有指定,则使用Mem0Provider的类名)创建一个ProviderSessionState<State>对象在Session中维护这个State对象。对于上面点餐的例子来说,我们将UserId作为Scope的维度,所以StateInitializer委托对象的实现逻辑就是从Session状态中获取UserId的值,并将其分别赋值给StorageScopeSearchScope的UserId属性。

public sealed class Mem0Provider : MessageAIContextProvider
{
    private readonly ProviderSessionState<State> _sessionState;
    private IReadOnlyList<string>? _stateKeys;
    public override IReadOnlyList<string> StateKeys => _stateKeys ??= [_sessionState.StateKey];

    public Mem0Provider(
        HttpClient httpClient,
        Func<AgentSession?, State> stateInitializer, 
        Mem0ProviderOptions? options = null, 
        ILoggerFactory? loggerFactory = null)
    : base(
        options?.SearchInputMessageFilter, 
        options?.StorageInputRequestMessageFilter, 
        options?.StorageInputResponseMessageFilter)
    {
         _sessionState = new ProviderSessionState<State>(
            stateInitializer,
            options?.StateKey ?? GetType().Name,
            AgentJsonUtilities.DefaultOptions);
    }    
}

3. Mem0ProviderOptions

作为Mem0Provider的配置选项类型,Mem0ProviderOptionsChatHistoryMemoryProviderOptions的定义也有很多相似之处。但是Mem0ProviderOptions中没有定义SearchTime属性,意味着Mem0Provider需要在调用LLM之前自行完成上下文的检索,并将检索到的上下文消息添加到输入消息列表中去。而不像ChatHistoryMemoryProvider那样还将检索工作实现在注册的工具中,由LLM自行决定调用此工具补充上下文信息。也许在未来的版本中,Mem0Provider也会提供对应的功能也未可知。

Mem0ProviderOptions还提供了如下的配置选项,当检索内容被格式化成提示词文本后,还添加由配置选项ContextPrompt指定的前缀。如果没有显式设置,会采用默认值## Memories\nConsider the following memories when answering user questions:EnableSensitiveTelemetryDataRedactor用来控制是否以及如何对包含敏感数据的消息进行脱敏处理;StateKey用来指定在Session中维护Mem0Provider.State对象所使用的键;SearchInputMessageFilterStorageInputRequestMessageFilterStorageInputResponseMessageFilter分别用来指定在检索输入消息、存储请求消息和存储响应消息时需要应用的过滤器。

public sealed class Mem0ProviderOptions
{	    
    public string? ContextPrompt { get; set; }
	public bool EnableSensitiveTelemetryData { get; set; }
	public Redactor? Redactor { get; set; }
	public string? StateKey { get; set; }

	public Func<IEnumerable<ChatMessage>, IEnumerable<ChatMessage>>? SearchInputMessageFilter { get; set; }
	public Func<IEnumerable<ChatMessage>, IEnumerable<ChatMessage>>? StorageInputRequestMessageFilter { get; set; }
	public Func<IEnumerable<ChatMessage>, IEnumerable<ChatMessage>>? StorageInputResponseMessageFilter { get; set; }
}

4. 基于对话历史的检索和存储

由于Mem0Provider并为提供基于注册工具的上下文检索方法,所以它只需要重写MessageAIContextProviderProvideMessagesAsync方法来实现基于对话历史的检索就可以了。具体的检索工作通过调用Mem0ClientSearchAsync方法来实现的,作为输入的正是初始化时设置的检索Scope的维度列表。这个Mem0Client对象则是通过构造函数提供的HttpClient创建的。SearchAsync方法返回的消息文本列表被拼接在一起后,会添加上ContextPrompt指定的前缀。最终返回的ChatMesage将生成的这段文本作为内容,并且角色被设置为User。这个消息最终会被添加到输入消息列表中去参与LLM的推理。

public sealed class Mem0Provider : MessageAIContextProvider
{
    protected override async ValueTask<IEnumerable<ChatMessage>> ProvideMessagesAsync(
        InvokingContext context, 
        CancellationToken cancellationToken = default);
    protected override async ValueTask StoreAIContextAsync(
        InvokedContext context, 
        CancellationToken cancellationToken = default)
}

internal sealed class Mem0Client
{    
    public Mem0Client(HttpClient httpClient);
    public async Task<IEnumerable<string>> SearchAsync(
        string? applicationId, 
        string? agentId, 
        string? threadId, 
        string? userId, 
        string? inputText, 
        CancellationToken cancellationToken);
    public async Task CreateMemoryAsync(
        string? applicationId, 
        string? agentId, 
        string? threadId, 
        string? userId, 
        string messageContent, 
        string messageRole, 
        CancellationToken cancellationToken);
    public async Task ClearMemoryAsync(
        string? applicationId, 
        string? agentId, 
        string? threadId, 
        string? userId, 
        CancellationToken cancellationToken);
}

针对对话历史的存储实现在重写的StoreAIContextAsync方法中,最终通过调用Mem0ClientCreateMemoryAsync方法来实现的。就目前的实现来说,StoreAIContextAsync方法会针对每个消息调用一次Mem0ClientCreateMemoryAsync方法,很明显这是不合理的。这些瑕疵都说明了这个Mem0Provider还是个半吊子

5. Mem0的运维工具

虽然Mem0Provider尚未发布,但是只要了解了Mem0 API的定义,可以自己提供实现。在不考虑收费的前提下,利用这种云平台现成的解决方案,有效避免了自行运维的麻烦。Memo0站点还提供一些运维工具来帮助我们查看和管理存储在Mem0上的记忆内容,比如针对每次调用的跟踪等等。如下所示的是它提供的仪表盘:

Alternative Text