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

推荐订阅源

P
Proofpoint News Feed
Martin Fowler
Martin Fowler
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
C
Cisco Blogs
Last Week in AI
Last Week in AI
T
The Blog of Author Tim Ferriss
The GitHub Blog
The GitHub Blog
T
Tenable Blog
A
Arctic Wolf
小众软件
小众软件
Google DeepMind News
Google DeepMind News
aimingoo的专栏
aimingoo的专栏
PCI Perspectives
PCI Perspectives
博客园 - 司徒正美
The Last Watchdog
The Last Watchdog
H
Hacker News: Front Page
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Stack Overflow Blog
Stack Overflow Blog
N
News and Events Feed by Topic
Security Archives - TechRepublic
Security Archives - TechRepublic
博客园 - 【当耐特】
S
Security @ Cisco Blogs
P
Proofpoint News Feed
Cloudbric
Cloudbric
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Jina AI
Jina AI
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
月光博客
月光博客
Schneier on Security
Schneier on Security
Hacker News: Ask HN
Hacker News: Ask HN
V
Visual Studio Blog
D
DataBreaches.Net
H
Help Net Security
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Project Zero
Project Zero
阮一峰的网络日志
阮一峰的网络日志
Cyberwarzone
Cyberwarzone
博客园 - Franky
Y
Y Combinator Blog
Spread Privacy
Spread Privacy
N
News and Events Feed by Topic
The Cloudflare Blog
Simon Willison's Weblog
Simon Willison's Weblog
S
SegmentFault 最新的问题
W
WeLiveSecurity
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
I
Intezer
Hugging Face - Blog
Hugging Face - Blog
Attack and Defense Labs
Attack and Defense Labs

博客园_首页

Plist 二进制格式 Milvus 和 PGVector,哪个更好? OpenClaw 已过时?在 VS Code 中运行 Hermes Agent! 第30篇文章:一个大三计科生的自白 Manim如何在数学公式中完美显示中文? Docker 部署 RocketMQ 5 并发编程核心概念辨析 C#事务处理最佳实践:别再让“主表存了、明细丢了”的破事发生 CLI 是什么?为什么大厂突然集体卷命令行? 【从0到1构建一个ClaudeAgent】协作-自主Agent UIImageView 设置图片不生效的原因排查 最小二乘问题详解20:无先验约束下的增量式SFM自由网平差 痞子衡嵌入式:大话双核i.MXRT1180之XIP应用里借助MU实现可靠Flash IAP的方法 AI Chat 封装, SemanticKerne.AiProvider.Unified 已发布 Windows下右键编辑js文件无法打开记事本——在注册表中使用环境变量 在后台服务中使用 Scoped 服务,为什么总是报错? H200 安装驱动并使用sglang启动模型 wireshark 抓包Trap上报告警内容 我用 AI 辅助开发了一系列小工具(2):图片压缩工具 [A Primer On MC and CC] 2.1 Memory Consistency 1 - 指令重排序和 SC 模型 Oracle数据库SCN推进技术详解与实践指南 玩转控件:封装个带图片的Label控件 Claude Code 4.7 真正该升级的不是模型,而是你的工作流 前端小白一句话,AI 帮我做了个颜值拉满的桌面媒体播放器。当代码不再是门槛,一句话编程就是现实。 5. WorkBuddy: 小龙虾的灵魂三件套,让你的小龙虾不只是工具 SQLite 分片方案实战:三种分片策略的深度对比 告别简陋 UI!一款基于 Fluent Design 和基于 WinUI 的开源免费、现代化的 Avalonia UI 控件库 关于二进制排列组合枚举的总结 AI开发-python-LangGraph框架(3-27-LangGraph从零实现大模型智能决策工作流) ElasticSearch主分片和副本分片概念详解 【002】HTTPS 粗解:证书、TLS 握手与对后端配置的影响 Hermes Agent 一周暴涨五万 Star,但我劝你别急着追 明明连接的是Redis的DB0,为什么能查到DB3的数据? 【从0到1构建一个ClaudeAgent】协作-Agent团队 熟悉电子元器件之后,电子小白下一步该怎么走? MAF快速入门(23)通过C#类定义Skills .NET 高级开发 | 手写一个对象映射框架 FastAPI数据库ORM怎么选?我肝了三个Demo后,终于不再纠结了 mysqldump 参数拾遗:在遗忘与铭记之间 C# .NET 周刊|2026年3月5期 Claude code入门 - 陈彦斌 一文学习入门 ThingsBoard 开源物联网平台 GitHub 热门项目 | 2026年04月16日 如何为GIT设置全局勾子,为每次提交追加信息 Number.isFinite和isFinite与isNaN()和Number.isNaN的区别 PortSwigger SQL注入LAB2 推荐一个测试人必备的Skills,从功能到性能全搞定(附详细实操和安装下载方式) 筑基期:掌握Odoo基础核心知识点02(Odoo XML 开发方式详解) GLM模型这么火,咱们用vllm也咧一个呗! 深入理解 AbortController:从底层原理到跨语言设计哲学 字符串学习笔记 多租户系统框架的基础模块设计和分析设计 Apache SeaTunnel Zeta 为什么能做到“又快又稳”? AI开发-python-LangGraph框架(3-26-LangGraph基本概念及第一个简单样例) Vue 3 组件通信,别只会用 Props 和 Emits 了,这几个狠活儿你得看看 ElasticSearch7.X版本配置密码 用Manim实现动态交点计算--从一个动点问题说起 团结引擎+Addressable+Instant Game打包抖音小游戏 function call 实战:让 LLM 自动判断 pod 异常、调用日志工具并完成故障分析 bubseek —— 让 Agent 的足迹,变成团队的洞察 通过 C# 读取并导出 PDF 书签 如何用 GitHub Actions 实现 Steam 自动化发布 【从0到1构建一个ClaudeAgent】并发-后台任务 .NET 高级开发 | 定制 ASP.NET Core 框架 电子小白:什么是运算放大器(运放) zero2Agent:面向大厂面试的 Agent 工程教程,从概念到生产的完整学习路线 堆上的ORW HC32F460 USB CDC通信异常:非对齐访问异常排查 20260413-Hyperbridge 攻击事件:发生在默克尔山上的验证绕过 那些喊着AI 要淘汰你的人,正在靠你的焦虑赚大钱! 深度学习进阶(八)Swin Transformer 最小二乘问题详解19:带先验约束的增量式SFM优化与实现 SnapTranslate 3.0 正式发布:全局划词翻译 + 完整英语学习闭环,一站式搞定查词、记词、复习 工作的意义、工作的困难认知再思考 .NET + AI 进阶实战:基于类的技能开发 - 打造可治理的 Agent 能力模块 【从0到1构建一个ClaudeAgent】规划与协调-技能 上周热点回顾(4.6-4.12) 电子小白的工具三件套:面包板、杜邦线、万能板 单表五亿数据的查询优化 | Mysql、StarRocks 2. WorkBuddy:从“我是谁”到“帮我干活” C# 如何减少代码运行时间:7 个实战技巧 基于HelixToolkit.SharpDX 渲染3D模型 - 笺上知微 从零开始的双臂具身VLA起源及现阶段发展综述 - SkyXZ 记对 xonsh shell 的使用, 脚本编写, 迁移及调优 - pluvium27 受够了Vibe Coding的失控?换个起点,让AI事半功倍 从开始配置漏洞环境到漏洞复现流程 - 難しい 关于10年工作经验的程序员对OpenClaw的实战经验分享以及看法 - 虚无境 Any metadata 的内存布局 C# .NET 周刊|2026年3月2期 - InCerry 我帮你测过了,测试圈排名第二的 Skill 依然很牛逼 Skill Discovery | 无监督技能发现的经典工作总结 - MoonOut 上下文工程是什么?过时了么?一文讲明白! - 一枫说码 开了 TUN 模式还是直连?90% 的人都踩过这个坑 AScript扩展多种脚本语言 - rockey627 AI 学习笔记:Agent 的记忆机制 你能被装进一个文件里吗?——7 万人把同事"蒸馏"成了 AI - 我没有三颗心脏 Claude Code 通关手册(七):给 AI 装上技能包——Skills 完全指南 - 暮色之狐 在浏览器中快速编辑代码:VSCode Web 集成实践 - Newbe36524 蒸馏自己 skill?基于 Deepseek 的蒸馏器,丐版蒸馏方式,简单便捷 - To_Carpe_Diem Spring AI Aliababa和AgentScope,哪个更好? - 苏三说技术
[对比学习LangChain和MAF-01]基本编程模式的差异(上篇)
Artech · 2026-05-20 · via 博客园_首页

作为一个资深的.NET开发者,当初决定转移到Agent赛道时,我首先选择的是微软的Semantic Kernel、AutoGen以及后来将二者统一在一起的MAF(Microsoft Agent Framework),但是我总觉得这个框架在设计上差点意思。再加上既然都决定决定投入到AI领域,就没有必要继续把自己困在.NET的茧房,于是选择了LangChain。经过对LangChain深入学习和使用后,再回头看看MAF,会有不一样的体会。通过对比,我觉得我现在对MAF有了更深的理解,而且我发现通过对比学习能够有效了理解MAF的精髓所在。

在这个系列中,我们将通过与LangChain对比的方式来介绍MAF的编程模式和架构设计。我们知道LangChain的主流开发语言是Python,如果通过Python来介绍MAF,这样比较起来更加直观。但是如果选择MAF,C#明显是更好的选择,所以我还是决定使用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(model=ChatOpenAI(model="gpt-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_URL=https://eap2410.cognitiveservices.azure.com/openai/v1
OPENAI_API_KEY=**********************

由于采用交谈式的推理方式,作为输入的字典需要利用messages键来指定对话消息列表,每条消息包含角色(role)和内容(content)。在这个示例中,我们只提供了一条用户消息,Agent会根据这条消息来生成穿衣建议。输出同样具有类似的结构,我们通过访问输出字典中的messages键来获取生成的消息列表,并打印最后一条消息的内容。

晴天、25°C 的天气属于**比较舒适偏暖**的状态,穿搭可以以**清爽、透气、防晒**为主。以下是一些具体建议:

### 👕 上装
- 短袖T恤、POLO衫  
- 薄款衬衫(棉或亚麻材质更透气)
- 如果在空调环境久待,可备一件**薄外套或开衫**

### 👖 下装
- 牛仔裤、休闲长裤  
- 九分裤、轻薄休闲裤  
- 喜欢清凉一点的,可以选择**及膝短裤或半身裙**

### 👟 鞋子
- 运动鞋、帆布鞋  
- 乐福鞋、平底鞋  
- 天气干燥时也可以穿凉鞋(通勤场合注意正式度)

### 🧢 配件建议
- 遮阳帽、太阳镜(晴天防晒)
- 防晒霜(尤其是长时间户外活动)
- 轻便背包或手提包

### ✅ 穿搭小提醒
- 选择**浅色系**衣服更不吸热  
- 面料尽量选**棉、麻、莫代尔**等透气材质  
- 早晚如果有风,体感可能稍凉,避免穿得太单薄

如果你要去的是**上班 / 约会 / 运动 / 旅行**,我也可以帮你给出更具体的搭配方案。

OpenAI的API具有两种形式:

  • Completion:属于早期的补全式生成接口,采用无状态设计。开发者需要手动构建包含systemuserassistant角色的消息,并将完整的历史记录发送给模型以维持对话连贯性;
  • Response:旨在统一所有生成能力(文本、音频、视觉等)的下一代接口。它具有有状态(Stateful)管理能力,允许通过传递 previous_response_id 自动关联上下文,无需每次都发送完整的历史消息;

ChatOpenAI默认情况下会使用Completion接口来调用Chat模型,但是可以通过use_responses_api参数来切换到Response接口。

agent = create_agent(model=ChatOpenAI(model="gpt-5.2-chat",use_responses_api=True))

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_URL=https://eap2410.services.ai.azure.com/api/projects/ai-learning
OPENAI_URL=https://eap2410.cognitiveservices.azure.com/openai/v1
AZURE_OPENAI_URL=https://eap2410.cognitiveservices.azure.com/
MODEL=gpt-5.2-chat
API_KEY=**********************

我们的程序会同时演示上述三种客户端的使用方式来创建Agent,并调用Agent来执行同样的推理任务。为此我们需要添加如下所示的NuGet包引用:

<PackageReference Include="Azure.AI.OpenAI" Version="2.9.0-beta.1" />
<PackageReference Include="Azure.AI.Projects" Version="2.1.0-beta.1" />
<PackageReference Include="Azure.Identity" Version="1.21.0" />
<PackageReference Include="dotenv.net" Version="4.0.2" />
<PackageReference Include="Microsoft.Agents.AI.Foundry" Version="1.3.0" />
<PackageReference Include="Microsoft.Agents.AI.OpenAI" Version="1.3.0" />
<PackageReference Include="Microsoft.Extensions.AI.Ollama" Version="9.7.0-preview.1.25356.2" />
<PackageReference Include="OpenAI" Version="2.10.0" />

如下所示的是完整的示例代码。我们首先加载环境变量,然后创建三个不同客户端的Agent,并调用它们来执行同样的推理任务。如代码片段所示,我们根据指定的地址和API-KEY创建了OpenAIClientAzureOpenAIClient,并指定模型名称调用其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一样,都是基于对话的推理方式,所以它们作为交谈历史的消息列表是输入、输出以及执行过程中的状态的核心内容。只是ChatClientAgentRunAsync方法做了封装,允许我们直接指定一个字符串作为输入消息。我们从输出结果中依然会得到我们希望的内容:

在**晴天、气温约 25°C**的情况下,整体体感会比较舒适偏暖,穿搭可以以**清爽、透气**为主:

### 👕 上装建议
- **短袖T恤 / Polo衫 / 薄衬衫**
  选择棉、麻或速干面料,透气又吸汗。
- 如果需要防晒或室内空调较冷,可带一件**薄外套 / 防晒衣**。

### 👖 下装建议
- **长裤**:薄款牛仔裤、休闲裤、九分裤都很合适。
- **短裤 / 裙子**:户外活动或休闲出行非常舒服。

### 👟 鞋子建议
- **运动鞋 / 帆布鞋 / 休闲鞋**
- 如果活动轻松,也可以选择**透气的凉鞋**

### 🧢 配件建议
- **遮阳帽 / 太阳镜**:晴天防晒必备
- **防晒霜**:尤其是中午外出时
- 可携带一瓶水,防止轻微脱水

### ✅ 小提醒
- 早晚如果有微风,体感可能略凉,**薄外套会更灵活**
- 如果需要长时间户外活动,尽量选择**浅色衣物**,更凉爽

如果你要**上班、约会、运动或出游**,我也可以帮你做更具体的搭配建议 😊

OpenAIClientAzureOpenAIClientGetChatClient方法采用的是Completion接口来调用Chat模型,所以我们需要在输入消息中构建完整的对话历史来维持上下文。而AIProjectClientAsAIAgent方法默认采用Response接口来调用Chat模型,它会自动管理对话状态,我们只需要提供当前的输入消息即可,无需担心上下文维护的问题。OpenAIClientAzureOpenAIClient可以调用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(
        model=ChatOpenAI(model="gpt-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(
        model=ChatOpenAI(model="gpt-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),表示当前工具调用的ID(call_9TyRU0zLwY4tOZf8Hufy5qrJ),还有它根据工具的输入Schema从请求中提取的参数列表(Args);
  • Agent接收到这个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的ChatClientAgentRunAsync方法也有一个对应的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。";

但是这样修改与Langchain版本的效果不太一样了,它会实现类似于你在使用ChatGPT时逐字输出的效果,而不是等到每一步推理完成后才输出结果了。