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

推荐订阅源

H
Help Net Security
T
ThreatConnect
SecWiki News
SecWiki News
F
Future of Privacy Forum
AWS News Blog
AWS News Blog
C
Cisco Blogs
A
Arctic Wolf
Vercel News
Vercel News
The GitHub Blog
The GitHub Blog
Scott Helme
Scott Helme
V
V2EX
博客园 - 叶小钗
阮一峰的网络日志
阮一峰的网络日志
K
Kaspersky official blog
G
Google Developers Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
P
Privacy International News Feed
C
Cyber Attacks, Cyber Crime and Cyber Security
N
News | PayPal Newsroom
Schneier on Security
Schneier on Security
NISL@THU
NISL@THU
Microsoft Azure Blog
Microsoft Azure Blog
量子位
The Hacker News
The Hacker News
Stack Overflow Blog
Stack Overflow Blog
Security Latest
Security Latest
M
Microsoft Research Blog - Microsoft Research
Google Online Security Blog
Google Online Security Blog
博客园_首页
C
CXSECURITY Database RSS Feed - CXSecurity.com
I
InfoQ
Google DeepMind News
Google DeepMind News
Y
Y Combinator Blog
The Cloudflare Blog
Microsoft Security Blog
Microsoft Security Blog
Martin Fowler
Martin Fowler
Cisco Talos Blog
Cisco Talos Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
T
Troy Hunt's Blog
F
Fox-IT International blog
S
Security @ Cisco Blogs
博客园 - 司徒正美
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
C
Comments on: Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
L
LINUX DO - 最新话题
GbyAI
GbyAI
Project Zero
Project Zero
腾讯CDC
T
Tailwind CSS Blog

博客园_首页

平台智能化到了分水岭:为什么配置代码化才是 AI Coding 的下一代接口 P.4文本统计工具 高光谱拼接算法(二)Harris 角点探测 - 哥布林学者 Claude Code “悄悄”装了 Python 包?别再让它“投错胎”了 - only赟 影刀 vs 八爪鱼 RPA:到底选哪个?一篇讲透 AI Coding开始进入第四个时代,我还没上车呢! 完整学习LLM(四):Token是什么 【Agentic RL / 强化学习 / OPD】OpenClaw-RL 源码阅读笔记 --- (1)---基础 LitCTF2026web部分wp CAD子系统,是自研还是外包? 什么是教程地狱?5个信号说明你已经陷入(附3步摆脱方法) polygon出题教程 Manim物理模拟:别自己写欧拉了! AI 学习笔记:Agent 的应用演示 - 凌杰 分享一个CAN报文编辑器软件 洛谷P13016 [GESP202506 六级] 最大因数 MiniCPM-V 4.6 部署实战:基于 GPUStack 与 SGLang 的端侧多模态模型部署 用 FRP 打通云服务器与本地 Ubuntu,让 Codex 远程调试本地硬件 软考 - 架构设计师 知识点总结 给 FastApiAdmin 加个“会议纪要”模块,我把后端二次开发的坑踩了个遍 聊一聊 MES系统如何实现多种标签打印并支持不同打印机 2026第四届LitCTF网络安全挑战赛Pwn的wp 断尺问题:戴德金分割现实悖论 给句子做个“语义审计”:从词向量到句子向量的方法论 当AI“卡壳”在生产环境:MCP Server 如何帮我们破局 ofdkit-harmony 0.2.0 发布:鸿蒙原生 OFD 阅读库,已上架 ohpm 有了AI测试工具,还需要掌握Playwright、Pytest、Selenium这些框架吗? 组织转型实录——我把传统研发团队改成AI驱动,踩了无数坑 为什么 AI Coding 难进生产环境?深入了解 Everything-Claude-Code ! 到底 TMD 用哪个: npm, pnpm, Yarn, Bun, Deno? 傻瓜, 当然用 npm 啦 上周热点回顾(5.18-5.24) [对比学习LangChain和MAF-04]针对消息的设计 TrueAsync Server 为 PHP 带来了原生的高性能 HTTP 服务器 规则漂移 帆软市场部为什么能成为高人效增长系统? 22. LangChain LCEL,用 | 串联AI的魔法语言 - 老陈说编程 完整学习LLM(二):大模型到底是什么 洛谷-P11942 [KTSC 2025] 重塑矩阵 题解 哈哈哈哈哈打不过我吧,没有办法我(vllm)就是这么强大! Hermes Edu Skills 从 170 到 188:一次中文教育 Agent Skill Pack 的工程化升级 一个外行,半年搞定机械臂:我的从0到1踩坑实录 新写了个直播录制工具,可录制抖音快手斗鱼直播 15天学会AI应用开发(一)搭建AI大模型应用开发环境 Childhood,23款童年卡牌游戏复刻 Github Copilot配置GPT5.5报错:'temperature' does not support 0.1 with this model. Only the default (1) value is supported. - Eric zhou 单曲循环 ClassIn 在 Linux 下无法播放音频 把 TeXstudio / LaTeX 工程交给 AI:texstudio-mcp 功能详解 .NET 8 Web开发入门(六):Blazor 全栈开发——告别 JavaScript 焦虑 别让 LLM 写文件:一套 Agent 进度跟踪的工程化范式 - BurningFish Qt Bridges for C# 深度技术解析 Multus 多网卡方案:IPVLAN 模式 被流量逼出来的架构:从一台服务器到云原生的 17 次蜕变 —— 集群、缓存、MQ、微服务、Docker、K8S 的前世今生 Claude Code安装全流程 Windows保姆级教程 awk 命令练习(从入门到进阶) Java + Spring实现Hermes Agent之龙虾、Skills、Mcp和沙箱代码执行环境思路 轨迹的蓝图:方程求解与交点计算 Agent新技术分享-Forge论文已被ACM接受 PowerMem 记忆系统的遗忘设计,从神经元到代码工程 我用了FastApiAdmin后,连夜把踩过的坑都整理出来了 一个程序员眼中的 AI 核心概念,讲透 LLM 、Agent 、MCP 、Skill 、RAG... 网络安全在线就能打的内网靶场推荐 & Dawn Breaker 单域靶场 WP CTF 中如何用提示词发挥大模型的最大实力:从聊天助手到大手子 PyTorch KernelAgent 源码解读 ---(6)--- Composer 高光谱拼接算法(一)扫推式成像和航带拼接算法 一文看懂fofa常用语法,告别混淆,精准打击! 从零搭建量化投资系统:用 Qlib 一行代码搞定均线分析 企业 AI 落地,第一件事不是买模型,而是建好企业知识库 如何在Oracle Agent Factory中配置国内厂商的LLM? Codex 换模型太麻烦?这个开源桌面工具帮你一键切换 Avalonia中的动画 2026软考|十大管理超全通俗笔记,备考闭眼记! rv1126b内置phy接hub交换机芯片 React 可拖拽列宽 + 点击行选中 ProTable 封装笔记 五大实锤证据:AI不会终结低代码,只会倒逼技术进化 【硬核脑洞】16位实模式最后的疯狂:我们能否在 640KB 常规内存里手搓一个 MD 模拟器? 基于.Net的NetCoreKevin框架中AgentFramework实现AI智能体Skill和工具动态管理和加载 PostgreSQL 高可用集群 patroni 自动故障转移测试 自己使用C++开发的仿OpenClaw、Hermes智能体工具 记一次 .NET 某集群管理软件 内存暴涨分析 StarBlog番外(5) 从1.6到1.10,基于Avalonia AOT 开发的 Publisher 半年进化之路 Anthropic 把 SOC 误报率从 33% 砍到 7%,真正在干活的不是 Claude SM2演示所有 PEM 功能(生成、加解密、签名/验签) 用 Solon AI 从零构建 MCP 工具服务:让 AI Agent 拥有真实世界的能力 完整学习LLM(一):为什么我要系统学习大模型 Agent Harness Runtime 架构深度解析:工具循环、状态外置与长程任务调度 [对比学习LangChain和MAF-03]完全不同的Agent设计哲学 毫不夸张地说,这将是目前最全的AI测试教程!测试必看! AI Coding 为什么选择 TUI ,前端的新机会在哪里? 无需安装cc switch,10行命令帮你配置Claude Code+deepseek Context Engineering 到 Harness Engineering —— 大模型时代软件工程的新范式 OpenClaw.NET 兼容性目录指南(Compatibility Catalog) Nginx 上游健康检查插件 nginx-healthcheck-module 三角形数 AScript异步执行与await关键字 - rockey627 Vector Quantization for Recommendation 笔记 21. “|”不只是按位或,90%的人不知道 这 3 年做教育相关项目,我把一些经验整理成了一个开源 Agent Skills 项目 2026 西安本土 GEO 测评:灵怡云凭差异化站稳第一梯队 PortSwigger SQL注入LAB7 & LAB8 & LAB9
[MAF的Agent管道详解-01]塑智能体边界,从AIAgent抽象类开始
Artech · 2026-05-26 · via 博客园_首页

和LangChain万法归一的设计哲学不同,MAF在设计上采用了多态的设计哲学,提供了一个Agent基类,通过继承这个基类来创建不同类型的Agent。虽然MAF的Agent类型多种多样,但最重要的莫过于ChatClientAgent,MAF语境下的Agent基本上指的就是这个对象。ChatClientAgent采用管道式设计,它利用一些列可扩展的组件构建了Agent和LLM消息交换的通道,还实现ReAct循环。这个管道之于MAF的重要性,可能比中间件管道对于ASP.NET Core的还要重要。MAF为Agent定义了一个名为AIAgent的基类,由于它涉及到一系列基础类型,所以我们先来看看这些基础类型以及它们在Agent管道中的作用。

1. 基础类型

与Agent相关的基础类型主要有AgentRunContextAgentRunOptionsAgentSessionChatMessage等。这些类型在Agent的运行过程中扮演着重要的角色,承载了运行时的上下文信息、运行选项、会话信息以及消息内容等。理解这些基础类型对于我们深入理解MAF中的Agent设计与实现是非常有帮助的。

1.1. AgentRunContext

AgentRunContext是一个类,包含了当前Agent的运行上下文信息,如下所示:

public sealed class AgentRunContext
{
    public AIAgent Agent { get; }
    public AgentSession? Session { get; }
    public IReadOnlyCollection<ChatMessage> RequestMessages { get; }
    public AgentRunOptions? RunOptions { get; }
}

属性成员说明如下:

  • Agent:当前正在执行的Agent对象;
  • Session:当前Agent的会话对象,可以通过它来实现会话保持;
  • RequestMessages:当前Agent的输入消息列表;
  • RunOptions:当前Agent的运行选项,可以通过它来设置一些运行时的参数,比如是否启用后台响应,指定输出格式等;

1.2. AgentRunOptions

AgentRunOptions包含了用于控制Agent的运行行为的配置选项:

public class AgentRunOptions
{    
    public ResponseContinuationToken? ContinuationToken { get; set; }
    public bool? AllowBackgroundResponses { get; set; }
    public AdditionalPropertiesDictionary? AdditionalProperties { get; set; }
    public ChatResponseFormat? ResponseFormat { get; set; }
}

public class ResponseContinuationToken
{    
    public static ResponseContinuationToken FromBytes(ReadOnlyMemory<byte> bytes); 
    public virtual ReadOnlyMemory<byte> ToBytes();
}

属性成员说明如下:

  • ContinuationToken:恢复/轮询令牌,用于处理长时间运行的任务被中断的任务
    • 流式中断恢复:如果在流式输出过程中连接中断,可以将上一次更新中获取的ContinuationToken重新传入,让Agent从中断点继续生成,而不是从头开始;
    • 非阻塞轮询:对于耗时较长的任务,Agent可能先返回一个令牌。调用者后续通过该令牌多次调用RunAsync来检查任务是否完成并获取最终结果;
  • AllowBackgroundResponses:是否允许后台响应,如果设置为true,则Agent在执行过程中可以在后台生成一些响应,而不必等到整个推理过程结束后才返回结果;
  • AdditionalProperties:一个字典,用于存储一些额外的属性信息;
  • ResponseFormat:定义Agent输出内容的结构。通常用于强制Agent返回特定格式(如JSON对象),这在需要将Agent的输出直接对接自动化工作流或结构化数据库时非常关键;

在MAF中,ContinuationToken是一个十分常见的类型。正如它的名字所体现的那样,如果服务端返回的内容包括这样一个ContinuationToken,以为着还有后续(Continuation),对于一个流来说,这个ContinuationToken是对这个流的某个中间位置的描述,客户端得到这个ContinuationToken后,不仅不知道流尚未结束,还能利用它从正确的位置继续获取数据。对于一个非流式的后台任务来说,这个ContinuationToken用于确定任务尚未结束,还需等待。

1.ChatResponseFormat

ResponseFormat属性类型ChatResponseFormat定义如下。如果我们指定ChatResponseFormat.Text或者ChatResponseFormat.Json,Agent会强制要求LLM返回纯文本(Markdown)或者JSON格式的内容。如果我们调用ChatResponseFormat.ForJsonSchema方法创建一个ChatResponseFormatJson对象作为AgentRunOptionsResponseFormat属性,Agent会强制要求模型返回符合该JSON Schema的JSON。结构化输出在很多场景下都是非常有用的,比如当我们需要将Agent的输出直接对接到自动化工作流或者结构化数据库时,结构化的输出可以让我们更方便地进行后续的处理和分析。

public class ChatResponseFormat
{
    public static ChatResponseFormatText Text { get; } = new ChatResponseFormatText();
    public static ChatResponseFormatJson Json { get; } = new ChatResponseFormatJson(null); 

    public static ChatResponseFormatJson ForJsonSchema(
        JsonElement schema, 
        string? schemaName = null, 
        string? schemaDescription = null) ;
    public static ChatResponseFormatJson ForJsonSchema<T>(
        JsonSerializerOptions? serializerOptions = null, 
        string? schemaName = null, 
        string? schemaDescription = null) ;
    public static ChatResponseFormatJson ForJsonSchema(
        Type schemaType, 
        JsonSerializerOptions? serializerOptions = null, 
        string? schemaName = null, 
        string? schemaDescription = null);
}

如下所示的ChatResponseFormatTextChatResponseFormatJson分别是ChatResponseFormat的两个子类,分别用于表示纯文本格式和JSON格式的响应格式。只有ChatResponseFormatJson与结构化输出有关,ChatResponseFormatText仅仅并不定义文本的结构,单纯地要求采用自然语言输出。

public sealed class ChatResponseFormatText : ChatResponseFormat
{
    public override bool Equals(object? obj) 
    public override int GetHashCode()  
}

public sealed class ChatResponseFormatJson : ChatResponseFormat
{
    public JsonElement? Schema { get; }
    public string? SchemaName { get; }
    public string? SchemaDescription { get; }  
    public ChatResponseFormatJson(JsonElement? schema, string? schemaName = null, string? schemaDescription = null) 
}

1.3. AgentSession

AgentSession是一个抽象类,代表了一个Agent的会话对象。如果希望多次针对Agent的调用在同一个Session中进行,只需要传递相同的AgentSession即可。其核心成员是AgentSessionStateBag类型的属性StateBag。我们将在很多类型中看到GetServiceGetService<TService>看到这两个方法,它来源于宿主程序作为Dependecy Injection容器的IServiceProvider对象。我们会在很多类型中看到这两个方法的身影,之后我们就不再赘述了。

public abstract class AgentSession
{
    public AgentSessionStateBag StateBag { get; protected set; } 
    private string DebuggerDisplay => $"StateBag Count = {StateBag.Count}"; 

    public virtual object? GetService(Type serviceType, object? serviceKey = null) ;
    public TService? GetService<TService>(object? serviceKey = null) ;
}

AgentSessionStateBag代表了一个Agent会话状态的容器,它提供了TryGetValueGetValueSetValueTryRemoveValue方法来操作状态数据。它还提供了SerializeDeserialize方法来实现状态的序列化和反序列化,以便于在分布式环境中进行状态的持久化和恢复。

public class AgentSessionStateBag
{
    public int Count {get;} 
    public bool TryGetValue<T>(
        string key, 
        out T? value, 
        JsonSerializerOptions? jsonSerializerOptions = null) where T : class
    public T? GetValue<T>(
        string key, 
        JsonSerializerOptions? jsonSerializerOptions = null) where T : class
    public void SetValue<T>(
        string key, 
        T? value, 
        JsonSerializerOptions? jsonSerializerOptions = null) where T : class
    public bool TryRemoveValue(string key) ;

    public JsonElement Serialize();
    public static AgentSessionStateBag Deserialize(JsonElement jsonElement);
}

1.4. ChatMessage

所有的基于对话的Agent都采用消息进行交互,消息不仅承载了多模态的内容,还绑定了一个在作为消息发送发的角色。MAF中的消息通过如下这个ChatMessage类来表示。

public class ChatMessage
{
    public string? AuthorName{ get; set; }
    public DateTimeOffset? CreatedAt { get; set; }
    public ChatRole Role { get; set; }    
    public string Text => Contents.ConcatText();
    public IList<AIContent> Contents{ get; set; }
    public string? MessageId { get; set; }    
    public object? RawRepresentation { get; set; }
    public AdditionalPropertiesDictionary? AdditionalProperties { get; set; }
}

ChatMessage的属性成员说明如下:

  • AuthorName:消息发送者的名称,可以是用户、模型或者工具等;
  • CreatedAt:消息创建的时间戳;
  • Role:消息发送者的角色,通常是一个枚举类型,如用户、模型、工具等;
  • Text:消息的文本内容,实际上是对Contents中所有内容的文本进行拼接后的结果;
  • Contents:消息的内容列表,每个内容都是一个AIContent对象,AIContent是一个抽象类,代表了消息内容的基类,可以有不同类型的内容,如文本、图片、文件等;
  • MessageId:消息的唯一标识符,可以用于消息的追踪和管理;
  • RawRepresentation:消息的原始表示,可以用于存储一些与消息相关的原始数据,如模型返回的原始响应等;
  • AdditionalProperties:一个字典,用于存储一些额外的属性信息,可以在Agent的运行过程中使用这些属性来进行一些自定义的逻辑处理;

1.4.1 ChatRole

出于可扩展的考虑,Role并不是一个简单的枚举类型,而是一个具有更丰富功能的结构体。它预定义了SystemAssistantUserTool四个角色,并且允许用户自定义角色。ChatMessage中的Role属性就是利用这个ChatRole结构体来表示消息发送者的角色。

public readonly struct ChatRole : IEquatable<ChatRole>
{
    public static ChatRole System { get; } = new ChatRole("system");
    public static ChatRole Assistant { get; } = new ChatRole("assistant");
    public static ChatRole User { get; } = new ChatRole("user");
    public static ChatRole Tool { get; } = new ChatRole("tool");

    public string Value { get; }
}

1.4.2 AIContent

AIContent是MAF 框架中定义一切交互内容的原子基类。它采用高度多态的设计,将AI与用户之间的对话拆解为多种专业化的内容块。在传统的AI开发中,消息通常只有Text。而AIContent将对话模型化为一个多模态、多状态的流。通过JsonDerivedTypeAttribute声明,它支持了19种(且在增加)子类型,涵盖了现代Agent交互的方方面面。

[JsonPolymorphic(TypeDiscriminatorPropertyName = "$type")]
[JsonDerivedType(typeof(DataContent), typeDiscriminator: "data")]
[JsonDerivedType(typeof(ErrorContent), typeDiscriminator: "error")]
[JsonDerivedType(typeof(FunctionCallContent), typeDiscriminator: "functionCall")]
[JsonDerivedType(typeof(FunctionResultContent), typeDiscriminator: "functionResult")]
[JsonDerivedType(typeof(HostedFileContent), typeDiscriminator: "hostedFile")]
[JsonDerivedType(typeof(HostedVectorStoreContent), typeDiscriminator: "hostedVectorStore")]
[JsonDerivedType(typeof(TextContent), typeDiscriminator: "text")]
[JsonDerivedType(typeof(TextReasoningContent), typeDiscriminator: "reasoning")]
[JsonDerivedType(typeof(UriContent), typeDiscriminator: "uri")]
[JsonDerivedType(typeof(UsageContent), typeDiscriminator: "usage")]
[JsonDerivedType(typeof(ToolCallContent), typeDiscriminator: "toolCall")]
[JsonDerivedType(typeof(ToolResultContent), typeDiscriminator: "toolResult")]
[JsonDerivedType(typeof(InputRequestContent), typeDiscriminator: "inputRequest")]
[JsonDerivedType(typeof(InputResponseContent), typeDiscriminator: "inputResponse")]
[JsonDerivedType(typeof(ToolApprovalRequestContent), typeDiscriminator: "toolApprovalRequest")]
[JsonDerivedType(typeof(ToolApprovalResponseContent), typeDiscriminator: "toolApprovalResponse")]
[JsonDerivedType(typeof(McpServerToolCallContent), typeDiscriminator: "mcpServerToolCall")]
[JsonDerivedType(typeof(McpServerToolResultContent), typeDiscriminator: "mcpServerToolResult")]
public class AIContent
{
    public IList<AIAnnotation>? Annotations { get; set; }
    [JsonIgnore]
    public object? RawRepresentation { get; set; }
    public AdditionalPropertiesDictionary? AdditionalProperties { get; set; }
}

1.4.3 AIAnnotation

AIContentAnnotations返回一个AIAnnotation的列表,AIAnnotation是一个注解类,用于为内容提供一些额外的信息或者标记。AIAnnotation也是一个多态类型,通过JsonDerivedTypeAttribute声明了不同的子类型,如CitationAnnotation等。

[JsonPolymorphic(TypeDiscriminatorPropertyName = "$type")]
[JsonDerivedType(typeof(CitationAnnotation), typeDiscriminator: "citation")]
public class AIAnnotation
{
    public IList<AnnotatedRegion>? AnnotatedRegions { get; set; }
    [JsonIgnore]
    public object? RawRepresentation { get; set; }
    public AdditionalPropertiesDictionary? AdditionalProperties { get; set; }
}

public class CitationAnnotation : AIAnnotation
{
    public string? Title { get; set; }
    public Uri? Url { get; set; }
    public string? FileId { get; set; }
    public string? ToolName { get; set; }
    public string? Snippet { get; set; }
}

CitationAnnotationAIAnnotation的一个子类,用于表示引用注解,它具有如下的属性成员:

  • Title:引用的标题,可以是文章标题、网页标题等;
  • Url:引用的URL地址,可以是文章链接、网页链接等;
  • FileId:引用的文件ID,如果引用的是一个文件,可以通过这个ID来获取文件的相关信息;
  • ToolName:引用的工具名称,如果引用的是一个工具,可以通过这个名称来获取工具的相关信息;
  • Snippet:引用的内容片段,可以是引用内容的摘要、引用内容的一部分等;

1.4.4 AnnotatedRegion

AnnotatedRegionAIAnnotation中的一个属性成员,它代表了被注解的内容区域,可以是文本区域、图像区域等。AnnotatedRegion同样是一个多态类型,通过JsonDerivedTypeAttribute声明了不同的子类型,如TextSpanAnnotatedRegion等。

[JsonPolymorphic(TypeDiscriminatorPropertyName = "$type")]
[JsonDerivedType(typeof(TextSpanAnnotatedRegion), "textSpan")]
public class AnnotatedRegion
{
}

public sealed class TextSpanAnnotatedRegion : AnnotatedRegion
{
    [JsonPropertyName("start")]
    public int? StartIndex { get; set; }

    [JsonPropertyName("end")]
    public int? EndIndex { get; set; }
}

1.5. ChatResponse

Agent具有两种执行方式,一种是阻塞式调用,它会等到整个执行完全结束后才会得到结果;另一种是流式调用,它会在执行过程中不断地返回结果更新。两者的响应类型分别是AgentResponseAgentResponseUpdate。如下所示的是AgentResponse的定义,它封装了AI的回答、消耗统计、任务状态以及上下文标识等。

public class ChatResponse
{
    public IList<ChatMessage> Messages{ get; set; }
    public string Text { get; }
    public string? ResponseId { get; set; }
    public string? ConversationId { get; set; }
    public string? ModelId { get; set; }
    public DateTimeOffset? CreatedAt { get; set; }
    public ChatFinishReason? FinishReason { get; set; }
    public UsageDetails? Usage { get; set; }

    public ResponseContinuationToken? ContinuationToken { get; set; }
    public object? RawRepresentation { get; set; }
    public AdditionalPropertiesDictionary? AdditionalProperties { get; set; }

    public ChatResponseUpdate[] ToChatResponseUpdates();
}

属性成员说明如下:

  • Messages:当前响应包含的消息列表,通常是一个ChatMessage的列表;
  • Text:当前响应的文本内容,实际上是对Messages中所有消息的文本内容进行拼接后的结果;
  • ResponseId:当前响应的唯一标识符,可以用于响应的追踪和管理;
  • ConversationId:当前响应所属的对话ID,可以用于将多个响应关联到同一个对话中;
  • ModelId:当前响应使用的模型ID,可以用于追踪模型的使用情况;
  • CreatedAt:当前响应的创建时间,可以用于记录响应的时间戳;
  • FinishReason:当前响应的结束原因,可以用于了解响应的完成状态;
  • Usage:当前响应的使用情况,可以用于统计和分析模型的使用情况;
  • ContinuationToken:当Agent执行非常耗时的任务(如复杂的推理或生成超长文档)时,如果启用了AllowBackgroundResponsesChatResponse可能会先返回。此时ContinuationToken不为空。你需要利用这个令牌传进行轮询,直到任务真正完成并拿到最终结果;
  • RawRepresentation:当前响应的原始表示,可以用于存储一些与响应相关的原始数据,如模型返回的原始响应等;
  • AdditionalProperties:一个字典,用于存储一些额外的属性信息,可以在Agent的运行过程中使用这些属性来进行一些自定义的逻辑处理;

ChatResponse 是开发者直接打交道最多的类。它不仅告诉你AI说了什么(Messages/Text),还告诉你怎么继续聊 (ConversationId);任务完没完 (ContinuationToken);花了多少钱 (Usage);模型跑得怎么样 (FinishReason)。

1.5.1 ChatFinishReason

ChatRole一样,表示响应的结束原因的ChatFinishReason也是一个具有更丰富功能的结构体,而不是一个简单的枚举类型。它预定义了StopLengthToolCallsContentFilter四个结束原因,并且允许用户自定义结束原因。

public readonly struct ChatFinishReason(string value) : IEquatable<ChatFinishReason>
{    
    public string Value {get;} ;
    public static ChatFinishReason Stop { get; } = new ChatFinishReason("stop");
    public static ChatFinishReason Length { get; } = new ChatFinishReason("length");
    public static ChatFinishReason ToolCalls { get; } = new ChatFinishReason("tool_calls");
    public static ChatFinishReason ContentFilter { get; } = new ChatFinishReason("content_filter");
}

1.5.2 UsageDetails

Usage属性返回的UsageDetails相当于一本账单,详细记录了模型调用的各种花费。我们知道Token就是AI领域的货币,所以这本账单使用Token数量来记账,它记录了模型调用过程中的各种Token数量,包括输入输出的Token数量、是否命中缓存、推理过程中使用的Token数量等。这些信息对于我们了解模型的使用情况、优化提示词和控制成本都非常有帮助。

比如,如果我们发现输入Token数量过多,可能需要优化提示词来减少不必要的上下文;如果输出Token数量过多,可能需要调整模型的生成策略来控制输出的长度;如果命中缓存的Token数量较多,说明模型在重复使用之前的结果,可能需要调整提示词来引导模型生成更多新的内容;如果推理过程中使用的Token数量较多,可能需要优化模型的推理过程来减少不必要的计算。总之,UsageDetails提供了一个全面的视角来审视模型的使用情况,帮助我们更好地管理和优化AI的使用。

public class UsageDetails
{
    public long? InputTokenCount { get; set; }
    public long? OutputTokenCount { get; set; }
    public long? TotalTokenCount { get; set; }
    public long? CachedInputTokenCount { get; set; }
    public long? ReasoningTokenCount { get; set; }

    public AdditionalPropertiesDictionary<long>? AdditionalCounts { get; set; }
    public void Add(UsageDetails usage)
}

1.6. AgentResponseUpdate

针对流式调用的每次迭代,响应的内容都会封装成一个ChatResponseUpdate对象,ChatResponseToChatResponseUpdates属性返回这些对象的数组。ChatResponseUpdateChatResponse的属性基本相同,唯一的区别是它多了一个IsFinal属性来表示当前响应是否是最终的响应。当IsFinaltrue时,意味着当前响应是整个Agent执行过程中的最后一个响应,此时ChatResponseUpdate中的内容就是最终的结果;当IsFinalfalse时,意味着当前响应只是整个Agent执行过程中的一个中间更新,此时ChatResponseUpdate中的内容可能只是部分结果或者一些临时的状态信息。

public class ChatResponseUpdate
{
    public string? AuthorName{ get; set; }
    public ChatRole? Role { get; set; }
    public string Text{ get;}
    public IList<AIContent> Contents{ get; set; }

    public object? RawRepresentation { get; set; }
    public string? ResponseId { get; set; }
    public string? MessageId { get; set; }
    public string? ConversationId { get; set; }
    public DateTimeOffset? CreatedAt { get; set; }
    public ChatFinishReason? FinishReason { get; set; }
    public string? ModelId { get; set; }
    public ResponseContinuationToken? ContinuationToken{ get; set; }    
    public AdditionalPropertiesDictionary? AdditionalProperties { get; set; }
}

很多LLM都支持结构化输出。我们在调用LLM的通过提供描述输出的JSON Schema,LLM会按照这个JSON Schema来生成符合要求的JSON格式的输出。为了让开发者更方便地处理这种结构化的输出,MAF提供了ChatResponse<T>这个泛型类,它继承自ChatResponse,并添加了一个Result属性来表示解析后的结果对象。

public class ChatResponse<T> : ChatResponse
{
    public T Result {get;}
    public ChatResponse(ChatResponse response, JsonSerializerOptions serializerOptions);
    public bool TryGetResult([NotNullWhen(true)] out T? result);
}

2. AIAgent

AIAgent是MAF中所有Agent的基类,定义了所有Agent共享的基本属性成员和操作方式。它提供了Agent运行的核心方法,如RunAsyncRunStreamingAsync等,以及与AgentSession相关的方法,如CreateSessionAsyncSerializeSessionAsyncDeserializeSessionAsync等。通过继承AIAgent,我们可以创建不同类型的Agent来满足不同的需求。

2.1. 属性成员

AIAgent是MAF中所有Agent的基类,定义了所有Agent共享的基本属性成员和操作方式。

public abstract class AIAgent
{    
    public string Id { get; }
    protected virtual string? IdCore => null;
    public virtual string? Name { get; }
    public virtual string? Description { get; }
    public static AgentRunContext? CurrentRunContext { get; }
}

属性成员说明如下:

  • Id:Agent的唯一标识符,通常由IdCore属性生成;
  • IdCore:生成Id的核心字符串,通常由子类重写来提供;
  • Name:Agent的名称,通常由子类重写来提供;
  • Description:Agent的描述信息,通常由子类重写来提供;
  • CurrentRunContext:当前正在执行的Agent的运行上下文信息,可以通过它来获取当前Agent的输入、输出、工具调用等信息;

2.2. AgentSession的创建和序列化

AIAgent定义了三个与AgentSession相关的方法,分别是CreateSessionAsyncSerializeSessionAsyncDeserializeSessionAsync方法。分别实现了针对AgentSession的创建、序列化和反序列化功能。它们分别调用了对应的CreateSessionCoreAsyncSerializeSessionCoreAsyncDeserializeSessionCoreAsync方法来实现具体的功能,这些方法是抽象方法,需要在子类中进行实现。

public abstract class AIAgent
{    
    public ValueTask<AgentSession> CreateSessionAsync(CancellationToken cancellationToken = default);
    public ValueTask<JsonElement> SerializeSessionAsync(
        AgentSession session, 
        JsonSerializerOptions? jsonSerializerOptions = null, 
        CancellationToken cancellationToken = default);
    public ValueTask<AgentSession> DeserializeSessionAsync(
        JsonElement serializedState, 
        JsonSerializerOptions? jsonSerializerOptions = null, 
        CancellationToken cancellationToken = default);

    protected abstract ValueTask<AgentSession> CreateSessionCoreAsync(CancellationToken cancellationToken = default);
    protected abstract ValueTask<JsonElement> SerializeSessionCoreAsync(AgentSession session, 
        JsonSerializerOptions? jsonSerializerOptions = null, 
        CancellationToken cancellationToken = default);
    protected abstract ValueTask<AgentSession> DeserializeSessionCoreAsync(
        JsonElement serializedState, 
        JsonSerializerOptions? jsonSerializerOptions = null, 
        CancellationToken cancellationToken = default);    
}

2.3. 阻塞式调用

针对AIAgent的调用分两种,一种是阻塞式调用,另一种是异步流式调用。阻塞式调用通过如下这些重载的RunCoreAsync方法来完成,返回的AgentResponse的作为响应内容。我们可以在调用它们时指定绑定的AgentSession将当前调用纳入一个会话中。还可以指定AgentRunOptions来设置一些运行时的参数,如是否启用流式输出、是否启用工具调用等,如果没有指定将会采用默认的运行选项。四个重载体现了不同的输入形式,分别是不指定输入以及将字符串、ChatMessageChatMessage的集合。这四个重载的RunAsync方法最终都会调用同一个RunCoreAsync方法来完成具体的执行逻辑,而这个RunCoreAsync方法是一个抽象方法,需要在子类中进行实现。

public abstract class AIAgent
{    
    public Task<AgentResponse> RunAsync(
        AgentSession? session = null,
        AgentRunOptions? options = null,
        CancellationToken cancellationToken = default) 
    => RunAsync([], session, options, cancellationToken);

    public Task<AgentResponse> RunAsync(
        string message,
        AgentSession? session = null,
        AgentRunOptions? options = null,
        CancellationToken cancellationToken = default)
    => RunAsync(new ChatMessage(ChatRole.User, message), session, options, cancellationToken);

    public Task<AgentResponse> RunAsync(
        ChatMessage message,
        AgentSession? session = null,
        AgentRunOptions? options = null,
        CancellationToken cancellationToken = default)
    => RunAsync([message], session, options, cancellationToken);

    public Task<AgentResponse> RunAsync(
        IEnumerable<ChatMessage> messages,
        AgentSession? session = null,
        AgentRunOptions? options = null,
        CancellationToken cancellationToken = default)
    {
        CurrentRunContext = new(this, session, messages as IReadOnlyCollection<ChatMessage> ?? messages.ToList(), options);
        return this.RunCoreAsync(messages, session, options, cancellationToken);
    }

    protected abstract Task<AgentResponse> RunCoreAsync(
        IEnumerable<ChatMessage> messages,
        AgentSession? session = null,
        AgentRunOptions? options = null,
        CancellationToken cancellationToken = default);
}

2.4 流式响应

AIAgent提供的RunStreamingAsync方法来进行流式调用。与阻塞式调用不同,流式调用会在执行过程中不断地产生结果,而不是等到整个执行完全结束后才返回结果。RunStreamingAsync方法返回一个IAsyncEnumerable<AgentResponseUpdate>对象,AgentResponseUpdate代表了Agent在执行过程中的一个更新,可以是一个新的响应、一个工具调用的结果或者一个状态更新等。
RunAsync方法类似,RunStreamingAsync方法也提供了四个重载,分别是不指定输入以及将字符串、ChatMessageChatMessage的集合。这些重载的RunStreamingAsync方法最终都会调用同一个RunCoreStreamingAsync方法来完成具体的执行逻辑,而这个RunCoreStreamingAsync方法是一个抽象方法,需要在子类中进行实现。

public abstract class AIAgent
{  
    public IAsyncEnumerable<AgentResponseUpdate> RunStreamingAsync(
        AgentSession? session = null,
        AgentRunOptions? options = null,
        CancellationToken cancellationToken = default) ;

    public IAsyncEnumerable<AgentResponseUpdate> RunStreamingAsync(
        string message,
        AgentSession? session = null,
        AgentRunOptions? options = null,
        CancellationToken cancellationToken = default);

    public IAsyncEnumerable<AgentResponseUpdate> RunStreamingAsync(
        ChatMessage message,
        AgentSession? session = null,
        AgentRunOptions? options = null,
        CancellationToken cancellationToken = default);

    public async IAsyncEnumerable<AgentResponseUpdate> RunStreamingAsync(
        IEnumerable<ChatMessage> messages,
        AgentSession? session = null,
        AgentRunOptions? options = null,
        CancellationToken cancellationToken = default);

    protected abstract IAsyncEnumerable<AgentResponseUpdate> RunCoreStreamingAsync(
        IEnumerable<ChatMessage> messages,
        AgentSession? session = null,
        AgentRunOptions? options = null,
        CancellationToken cancellationToken = default);
}