[对比学习LangChain和MAF-01]基本编程模式的差异(上篇) 📅 2026/7/6 4:12:24 这个系列中我们将通过与LangChain对比的方式来介绍MAF的编程模式和架构设计。我们知道LangChain的主流开发语言是Python如果通过Python来介绍MAF这样比较起来更加直观。但是如果选择MAFC#明显是更好的选择所以我还是决定使用C#来介绍MAF。好在这两种语言都很“人性化”即使你不会用总归能看得懂。MAF提供了多样化的Agent我们这里主要关注采用Chat模型采用多轮对话进行推理的ChatClientAgent。LangChain提供了多种编程模式来创建Agent最常见的就是使用create_agent函数根据指定的模型和工具来创建Agent。MAF也是如此我们不仅可用调用ChatClientAgent的构造函数利用提供的LLM客户端和工具来创建Agent它还通过定义扩展方法的形式直接将LLM客户端转换成ChatClientAgent。对应一些复杂的推理任务LangChain的解决方式是利用LangGraph构建具有对应结构的状态图来规范推理流程而MAF对应的解决方案是采用工作流状态图和工作流其实就是同一事物的两种不同说法而已。多轮对话需要通过一个上下文来维护状态LangChain将此上下文称为Thread而MAF则称之为Session。1. 从最简单的Agent开始最简单的Agent只需要一个LLM客户端就可以了。LLM客户端在LangChain中通过BaseLanguageModel来表示但是它主要代表基于文本补全的Completion模型继承于它的BaseChatModel才真正代表了Chat模型。具体的LLM客户端依赖于LLM类型和对应的服务提供商。下面的演示程序使用部署在Azure上的GPT-Chat模型gpt-5.2-chat。1.1 LangChain如下所示的是使用LangChain创建Agent的示例代码。如代码片段所示我们针对上述的模型名称gpt-5.2-chat创建了一个ChatOpenAI对象并将其作为参数传递给create_agent函数来创建Agent。然后调用异步方法ainvoke来执行Agent。from langchain.agents import create_agent from langchain_openai import ChatOpenAI from dotenv import load_dotenv import asyncio load_dotenv() async def main(): agent create_agent(modelChatOpenAI(modelgpt-5.2-chat)) result await agent.ainvoke(input { messages:[{role:user,content:目前的天气是晴天气温25°C。请给我一些穿衣建议。}] }) print(result[messages][-1].content) asyncio.run(main())LangChain中的LLM客户端组件都会利用环境变量来定义配置并且它会自动读取环境变量这一点与MAF的LLM客户端设计不太一样后者一般要求自行读取并设置环境变量并显式传递给LLM客户端。ChatOpenAI所需的Endpoint地址和API-Key就定义在如下所示的.env文件中并利用python-dotenv库的load_dotenv函数来加载环境变量。OPENAI_BASE_URLhttps://eap2410.cognitiveservices.azure.com/openai/v1 OPENAI_API_KEY**********************由于采用交谈式的推理方式作为输入的字典需要利用messages键来指定对话消息列表每条消息包含角色role和内容content。在这个示例中我们只提供了一条用户消息Agent会根据这条消息来生成穿衣建议。输出同样具有类似的结构我们通过访问输出字典中的messages键来获取生成的消息列表并打印最后一条消息的内容。晴天、25°C 的天气属于**比较舒适偏暖**的状态穿搭可以以**清爽、透气、防晒**为主。以下是一些具体建议 ### 上装 - 短袖T恤、POLO衫 - 薄款衬衫棉或亚麻材质更透气 - 如果在空调环境久待可备一件**薄外套或开衫** ### 下装 - 牛仔裤、休闲长裤 - 九分裤、轻薄休闲裤 - 喜欢清凉一点的可以选择**及膝短裤或半身裙** ### 鞋子 - 运动鞋、帆布鞋 - 乐福鞋、平底鞋 - 天气干燥时也可以穿凉鞋通勤场合注意正式度 ### 配件建议 - 遮阳帽、太阳镜晴天防晒 - 防晒霜尤其是长时间户外活动 - 轻便背包或手提包 ### ✅ 穿搭小提醒 - 选择**浅色系**衣服更不吸热 - 面料尽量选**棉、麻、莫代尔**等透气材质 - 早晚如果有风体感可能稍凉避免穿得太单薄 如果你要去的是**上班 / 约会 / 运动 / 旅行**我也可以帮你给出更具体的搭配方案。OpenAI的API具有两种形式Completion属于早期的补全式生成接口采用无状态设计。开发者需要手动构建包含system、user、assistant角色的消息并将完整的历史记录发送给模型以维持对话连贯性Response旨在统一所有生成能力文本、音频、视觉等的下一代接口。它具有有状态Stateful管理能力允许通过传递previous_response_id自动关联上下文无需每次都发送完整的历史消息ChatOpenAI默认情况下会使用Completion接口来调用Chat模型但是可以通过use_responses_api参数来切换到Response接口。agent create_agent(modelChatOpenAI(modelgpt-5.2-chat,use_responses_apiTrue))1.2 MAF上面这个简单的例子在MAF中可以玩出很多花样。首先针对部署在Azure上的GPT-Chat模型我们可以采用如下三种不同的客户端OpenAIClient微软官方提供的OpenAIClient类支持Azure OpenAI服务的Chat模型提供了丰富的功能和配置选项AzureOpenAIClient社区提供的AzureOpenAIClient类专门针对Azure OpenAI服务进行了优化简化了配置和使用流程AIProjectClient这是微软最新的Azure AI Foundry(原Azure AI Studio) 推出的项目级客户端。用于管理和调用整个AI项目的资源而不仅仅是单个模型我们可以利用它们来创建ChatClientAgent。虽然使用同一个部署的模型但是客户端采用的Endpoint地址却不同。由于Python使用了python-dotenv这里使用它的.NET版本dotenv.net所以我们依然将Endpoint地址、模型和API-Key定义在.env文件中并利用dotenv.net库的DotEnv类来加载环境变量。Foundry_PROJECT_URLhttps://eap2410.services.ai.azure.com/api/projects/ai-learning OPENAI_URLhttps://eap2410.cognitiveservices.azure.com/openai/v1 AZURE_OPENAI_URLhttps://eap2410.cognitiveservices.azure.com/ MODELgpt-5.2-chat API_KEY**********************我们的程序会同时演示上述三种客户端的使用方式来创建Agent并调用Agent来执行同样的推理任务。为此我们需要添加如下所示的NuGet包引用PackageReference IncludeAzure.AI.OpenAI Version2.9.0-beta.1 / PackageReference IncludeAzure.AI.Projects Version2.1.0-beta.1 / PackageReference IncludeAzure.Identity Version1.21.0 / PackageReference Includedotenv.net Version4.0.2 / PackageReference IncludeMicrosoft.Agents.AI.Foundry Version1.3.0 / PackageReference IncludeMicrosoft.Agents.AI.OpenAI Version1.3.0 / PackageReference IncludeMicrosoft.Extensions.AI.Ollama Version9.7.0-preview.1.25356.2 / PackageReference IncludeOpenAI Version2.10.0 /如下所示的是完整的示例代码。我们首先加载环境变量然后创建三个不同客户端的Agent并调用它们来执行同样的推理任务。如代码片段所示我们根据指定的地址和API-KEY创建了OpenAIClient和AzureOpenAIClient并指定模型名称调用其GetChatClient方法得到一个ChatClient对象然后直接调用它的扩展方法AsAIAgent来创建ChatClientAgent。对于AIProjectClient我们直接调用它的扩展方法AsAIAgent来创建ChatClientAgent并在参数中指定模型名称和一些指令。using Azure.AI.OpenAI; using Azure.AI.Projects; using Azure.Identity; using dotenv.net; using Microsoft.Extensions.AI; using OpenAI; using OpenAI.Chat; using OpenAI.Responses; using System.ClientModel; DotEnv.Load(); var model Environment.GetEnvironmentVariable(MODEL)!; var apiKey Environment.GetEnvironmentVariable(API_KEY)!; var openAIUrl Environment.GetEnvironmentVariable(OPENAI_URL)!; var azureOpenAIUrl Environment.GetEnvironmentVariable(AZURE_OPENAI_URL)!; var foundryProjectUrl Environment.GetEnvironmentVariable(FOUNDRY_PROJECT_URL)!; var prompt 目前的天气是晴天气温25°C。请给我一些穿衣建议。; // 1.Create an OpenAI client using the OpenAI SDK var openAIClient new OpenAIClient( credential: new ApiKeyCredential(key: apiKey), options: new OpenAIClientOptions { Endpoint new Uri(openAIUrl) }); var agent openAIClient.GetChatClient(model: model).AsAIAgent(); var result await agent.RunAsync(message: prompt); Console.WriteLine(result); // 2.Create an OpenAI client using the Azure OpenAI SDK var azureOpenAIClient new AzureOpenAIClient( endpoint: new Uri(azureOpenAIUrl), credential: new ApiKeyCredential(key: apiKey)); agent azureOpenAIClient.GetChatClient(deploymentName:model).AsAIAgent(); result await agent.RunAsync(message: prompt); Console.WriteLine(result); // 3.Create an AI Project client using the Azure AI Projects SDK var aiProjectClient new AIProjectClient( endpoint: new Uri(foundryProjectUrl), tokenProvider: new DefaultAzureCredential()); agent aiProjectClient.AsAIAgent(model: model, instructions: You are a helpful assistant.); result await agent.RunAsync(message: prompt); Console.WriteLine(result);最后我们调用每个Agent的RunAsync方法来执行推理任务。ChatClientAgent和LangChain的Agent一样都是基于对话的推理方式所以它们作为交谈历史的消息列表是输入、输出以及执行过程中的状态的核心内容。只是ChatClientAgent的RunAsync方法做了封装允许我们直接指定一个字符串作为输入消息。我们从输出结果中依然会得到我们希望的内容在**晴天、气温约 25°C**的情况下整体体感会比较舒适偏暖穿搭可以以**清爽、透气**为主 ### 上装建议 - **短袖T恤 / Polo衫 / 薄衬衫** 选择棉、麻或速干面料透气又吸汗。 - 如果需要防晒或室内空调较冷可带一件**薄外套 / 防晒衣**。 ### 下装建议 - **长裤**薄款牛仔裤、休闲裤、九分裤都很合适。 - **短裤 / 裙子**户外活动或休闲出行非常舒服。 ### 鞋子建议 - **运动鞋 / 帆布鞋 / 休闲鞋** - 如果活动轻松也可以选择**透气的凉鞋** ### 配件建议 - **遮阳帽 / 太阳镜**晴天防晒必备 - **防晒霜**尤其是中午外出时 - 可携带一瓶水防止轻微脱水 ### ✅ 小提醒 - 早晚如果有微风体感可能略凉**薄外套会更灵活** - 如果需要长时间户外活动尽量选择**浅色衣物**更凉爽 如果你要**上班、约会、运动或出游**我也可以帮你做更具体的搭配建议 OpenAIClient和AzureOpenAIClient的GetChatClient方法采用的是Completion接口来调用Chat模型所以我们需要在输入消息中构建完整的对话历史来维持上下文。而AIProjectClient的AsAIAgent方法默认采用Response接口来调用Chat模型它会自动管理对话状态我们只需要提供当前的输入消息即可无需担心上下文维护的问题。OpenAIClient和AzureOpenAIClient可以调用GetResponseClient方法切换到Response接口来调用Chat模型。// 1.Create an OpenAI client using the OpenAI SDK var openAIClient new OpenAIClient( credential: new ApiKeyCredential(key: apiKey), options: new OpenAIClientOptions { Endpoint new Uri(openAIUrl) }); var agent openAIClient.GetResponsesClient().AsAIAgent(model: model); var result await agent.RunAsync(message: prompt); Console.WriteLine(result); // 2.Create an OpenAI client using the Azure OpenAI SDK var azureOpenAIClient new AzureOpenAIClient( endpoint: new Uri(azureOpenAIUrl), credential: new ApiKeyCredential(key: apiKey)); agent azureOpenAIClient.GetResponsesClient().AsAIAgent(model: model); result await agent.RunAsync(message: prompt); Console.WriteLine(result);2. 注册工具只有LLM的Agent相当于一个只有大脑的“人”它只能通过对话来进行推理和决策无法直接与外部环境进行交互。工具就相当于这个“人”的手脚能够让Agent执行一些具体的操作来获取信息、处理数据或者完成任务。前面实例创建的Agent没有什么实质上的作用为了让Agent提供着装建议我们需要为Agent提供天气信息。这本质上是RAG的做法我们利用查询检索并向LLM提供它并不具有的知识作为推理上下文。更理想的做法是让Agent自己去查询天气信息这样就不需要我们事先准备好天气信息了。为此我们可以为Agent注册一个查询天气的工具让它在需要的时候调用这个工具来获取天气信息。2.1 LangChain如下的程序演示了LangChain注册工具的典型方式。我们首先定义了一个工具函数get_weather并使用tool装饰器将其注册为一个工具。这个工具函数接受一个位置参数并返回该位置的天气信息。在这个示例中我们只是返回了一个固定的天气信息实际应用中可以通过调用天气API来获取真实的天气数据。然后我们在创建Agent时将这个工具作为参数传递给create_agent函数这样Agent就可以在推理过程中调用这个工具来获取天气信息。from langchain.agents import create_agent from langchain_openai import ChatOpenAI from langchain.tools import tool from dotenv import load_dotenv import asyncio load_dotenv() tool def get_weather(location: str) - str: 根据给定的位置获取当前天气。 return f所在地 {location} 的天气是晴天最高气温25°C。 async def main(): agent create_agent( modelChatOpenAI(modelgpt-5.2-chat), tools[get_weather],) result await agent.ainvoke(input { messages:[{role:user,content:根据当前苏州的天气给我一些穿衣建议。}] }) print(result[messages][-1].content) asyncio.run(main())输出好的根据**当前苏州天气晴天最高气温约 25°C**给你一些穿衣建议 ### 白天穿搭 - **上衣**短袖 T 恤、薄衬衫、POLO 衫都很合适 - **下装**牛仔裤、休闲裤、薄款长裤或半身裙 - **鞋子**运动鞋、帆布鞋、透气的休闲鞋即可 ### 早晚/室内 - 早晚可能稍微凉一点或在空调房里 - 建议带一件**薄外套 / 开衫 / 轻薄风衣** ### 防晒与配件 - 晴天紫外线可能偏强 - **帽子、太阳镜** - **防晒霜** - 出汗较多的话选择**透气、吸汗面料**会更舒服 ### ✅ 总体建议 **“短袖 薄外套”是最稳妥的搭配**既不热也能应对温差。 如果你是**上班通勤、出游、运动或带孩子出门**我也可以给你更具体的搭配建议。2.2 MAF在MAF中注册工具的方式与LangChain类似。我们首先定义了一个工具函数GetWeather并使用Description特性将其注册为一个工具。这个工具函数接受一个位置参数并返回该位置的天气信息。在这个示例中我们同样返回了一个固定的天气信息实际应用中可以通过调用天气API来获取真实的天气数据。然后我们在创建Agent时将这个工具作为参数传递给AsAIAgent方法这样Agent就可以在推理过程中调用这个工具来获取天气信息。using Azure.AI.Projects; using Azure.Identity; using dotenv.net; using Microsoft.Extensions.AI; using OpenAI.Chat; using OpenAI.Responses; using System.ComponentModel; DotEnv.Load(); var model Environment.GetEnvironmentVariable(MODEL)!; var apiKey Environment.GetEnvironmentVariable(API_KEY)!; var foundryProjectUrl Environment.GetEnvironmentVariable(FOUNDRY_PROJECT_URL)!; var prompt 根据当前苏州的天气给我一些穿衣建议。; var aiProjectClient new AIProjectClient( endpoint: new Uri(foundryProjectUrl), tokenProvider: new DefaultAzureCredential()); var agent aiProjectClient.AsAIAgent( model: model, instructions: You are a helpful assistant., tools: [AIFunctionFactory.Create(GetWeather)]); var result await agent.RunAsync(message: prompt); Console.WriteLine(result); [Description(获取指定位置的天气信息)] static string GetWeather([Description(天气查询所在的位置)] string location) $ {location}当前添加晴天气温25°C。;输出根据当前**苏州晴天、气温约 25°C**的天气情况给你一些穿衣建议 ### 日常出行 - **上装**短袖T恤、薄衬衫都很合适怕晒的话可以选长袖但要透气。 - **下装**薄款长裤、休闲裤或半身裙都可以体感会比较舒适。 - **外套**白天基本不需要外套但如果早晚出门或在空调房久待可带一件**薄开衫/防晒衣**。 ### 鞋子 - 透气的运动鞋、休闲鞋或凉鞋都很合适适合步行。 ### 防晒与小提醒 - 晴天紫外线偏强建议 - 戴**遮阳帽 / 太阳镜** - 涂**防晒霜** - 多喝水避免中午时段长时间暴晒。 如果你是**通勤、旅游、还是户外活动**我也可以帮你更具体地搭配 3 流式响应Agent执行在绝大部分场景下都是一个长耗时操作执行几分钟、几小时甚至几天都是很常见的。如果等到Agent执行完成才返回结果那么用户体验就会非常差了。流式响应Streaming Response就是为了解决这个问题而设计的它允许Agent在执行过程中逐步返回结果让用户能够实时看到Agent的推理过程和中间结果从而提升交互的流畅性和响应速度。3.1 LangChain对于上面演示的这个实例我们只需要将针对ainvoke方法的调用改成astream方法的调用就可以了。astream方法会返回一个异步生成器我们可以通过异步迭代的方式来获取Agent执行过程中逐步返回的结果。如代码片段所示我们在异步迭代中获取每个返回的结果块并提取其中的最后一个消息来打印输出。通过这种方式我们可以在Agent执行的过程中实时看到它的推理结果而不需要等到整个执行完成。LangChain定义了多种流模式默认的values会在每次单步推理完成后更新状态的values并将更新后的状态作为结果返回。from langchain.agents import create_agent from langchain_openai import ChatOpenAI from langchain.tools import tool from dotenv import load_dotenv import asyncio load_dotenv() tool def get_weather(location: str) - str: 根据给定的位置获取当前天气。 return f所在地 {location} 的天气是晴天最高气温25°C。 async def main(): agent create_agent( modelChatOpenAI(modelgpt-5.2-chat), tools[get_weather],) async for chunk in agent.astream(input { messages:[{role:user,content:根据当前苏州的天气给我一些穿衣建议。}] }): result chunk[model] if model in chunk else chunk[tools] message result[messages][-1] message.pretty_print() asyncio.run(main())输出 Ai Message Tool Calls: get_weather (call_9TyRU0zLwY4tOZf8Hufy5qrJ) Call ID: call_9TyRU0zLwY4tOZf8Hufy5qrJ Args: location: 苏州 Tool Message Name: get_weather 所在地 苏州 的天气是晴天最高气温25°C。 Ai Message 好的根据**当前苏州的天气情况晴天最高气温约 25°C**给你一些穿衣建议 ### ️ 白天穿搭建议 - **上衣** - 薄款长袖衬衫 / T 恤 / 薄针织衫 - 如果怕晒可以选**透气的长袖**或带防晒功能的衣服 - **下装** - 牛仔裤、休闲裤、薄款西裤 - 也可以穿半身裙或连衣裙体感会更清爽 - **鞋子** - 运动鞋、帆布鞋、乐福鞋 - 如果走路多建议选择透气舒适的鞋 ### 早晚与细节 - 早晚可能稍微偏凉或有风**带一件薄外套/开衫**会更稳妥 - 晴天紫外线偏强 - ☀️ 帽子、太阳镜 - 防晒霜尤其是中午前后 ### 体感小提醒 - 25°C 属于**不冷不热、偏舒适**的温度 - 活动量大时容易出汗建议选择**吸汗、透气的面料** 如果你是**上班通勤 / 出游 / 运动 / 带小孩**我也可以按具体场景再帮你细化穿搭 输出结果体现了Agent的执行流程由于工具被注册到Agent中工具相关的描述会作为调用LLM提示词的一部分。对于指定的问题根据当前苏州的天气给我一些穿衣建议LLM经过推理认为在提供最终答案之前需要先调用工具来获取天气信息此时它会生成一个AIMessage并将工具调用信息包含在其中具体的工具调用信息不仅包括工具名称get_weather表示当前工具调用的IDcall_9TyRU0zLwY4tOZf8Hufy5qrJ还有它根据工具的输入Schema从请求中提取的参数列表ArgsAgent接收到这个AIMessage后会查看是否具有工具调用如果没有工具调用信息那么它就会将这个AIMessage作为最终的输出结果返回如果有工具调用信息那么它就会根据工具调用信息来执行对应的工具在这个示例中就是调用get_weather工具来获取天气信息。工具调用的结果被封装在一个ToolMessage中被添加到作为对话历史的消息列表中。对于我们的例子来说get_weather工具被调用后返回了一个ToolMessage其中包含了工具的输出结果所在地 苏州 的天气是晴天最高气温25°C。所有所有工具执行完毕采用并发执行Agent会将新的对话历史发送给LLM来继续推理直到LLM不再生成新的工具调用信息为止此时Agent会将最后一个AIMessage作为最终的输出结果返回。在这个示例中Agent在执行完工具调用后又生成了一个新的AIMessage这个AIMessage包含了根据工具输出的天气信息生成的穿衣建议。LLM根据新的对话历史继续推理认为根据目前拥有的信息所在地 苏州 的天气是晴天最高气温25°C已经足够生成穿衣建议了所以它就没有再生成新的工具调用信息而是直接将穿衣建议作为输出结果返回了Agent。按照前面的逻辑Agent接收到这个AIMessage后会发现它没有工具调用信息了于是就将这个AIMessage作为最终的输出结果返回了用户3.2 MAF我们同样将MAF版本做类似的修改来支持流式响应。MAF的ChatClientAgent的RunAsync方法也有一个对应的RunStreamingAsync方法来支持流式响应。这个方法会返回一个异步生成器我们可以通过异步迭代的方式来获取Agent执行过程中逐步返回的结果。与LangChain不同的是MAF在流式响应中直接将LLM生成的原始内容作为结果返回而不是封装在一个特定结构的字典中所以我们可以直接打印输出。如下所示的是修改后的示例代码using Azure.AI.Projects; using Azure.Identity; using dotenv.net; using Microsoft.Agents.AI; using Microsoft.Extensions.AI; using OpenAI.Chat; using OpenAI.Responses; using System.ComponentModel; DotEnv.Load(); var model Environment.GetEnvironmentVariable(MODEL)!; var apiKey Environment.GetEnvironmentVariable(API_KEY)!; var foundryProjectUrl Environment.GetEnvironmentVariable(FOUNDRY_PROJECT_URL)!; var prompt 根据当前苏州的天气给我一些穿衣建议。; var aiProjectClient new AIProjectClient( endpoint: new Uri(foundryProjectUrl), tokenProvider: new DefaultAzureCredential()); var agent aiProjectClient.AsAIAgent( model: model, instructions: You are a helpful assistant., tools: [AIFunctionFactory.Create(GetWeather)]); await foreach (AgentResponseUpdate update in agent.RunStreamingAsync(message: prompt)) { Console.Write(update.RawRepresentation); } [Description(获取指定位置的天气信息)] static string GetWeather([Description(天气查询所在的位置)] string location) $ {location}当前添加晴天气温25°C。;