[MAF的Agent管道详解-08]依赖注入的应用
借助于MAF提供的这个极具扩展性的Agent管道我们可以利用它提供的众多扩展点来实现我们针对Agent执行的控制。具体来说它提供的扩展点主要体现在如下几个方面三大中间件Agent、ChatClient和AIFunction中间件运行我们将基于请求拦截的横切关注点注入应用到针对Agent、ChatClient和AIFunction的调用AIContextProvider通过在调用ChatClient管道前后对AIContext和响应结果响应消息或者异常处理的定制将输入和输出增强机制应用到Agent管道ChatHistoryProvider实现针对AIAgent的对话历史持久化。当我们在自定义上述组件的时候很多情况下都需要使用到注入的依赖服务。如何提取这些依赖服务对象以及这些服务对象来源于何处这就是这篇文章需要解答的问题。1. 获取当前AgentRunContext当AIAgent通过调用RunAsync或者RunStreamingAsync方法开始运行后会创建一个表示Agent运行上下文的AgentRunContext对象。此上下文会被设置到AIAgent类型的静态字段s_currentContex表示的AsyncLocalAgentRunContext?对象上我们可以通过调用静态属性CurrentRunContext得到它。publicabstractclassAIAgent{privatestaticreadonlyAsyncLocalAgentRunContext?s_currentContextnewAsyncLocalAgentRunContext();publicstaticAgentRunContext?CurrentRunContext{get{returns_currentContext.Value;}protectedset{s_currentContext.Valuevalue;}}}publicsealedclassAgentRunContext{publicAIAgentAgent{get;}publicAgentSession?Session{get;}publicIReadOnlyCollectionChatMessageRequestMessages{get;}publicAgentRunOptions?RunOptions{get;}}AgentRunContext通过四个属性提供了如下的上下文Agent 当前执行的Agent对象Session执行Agent所在的AgentSessionRequestMessages调用Agent输入的消息列表RunOptions 用于控制Agent运行的配置选项。2. 利用当前AIAgent提取服务对象在得到了当前运行的AIAgent对象后我们可以调用它的GetService或者GetServiceTService方法提取所需的依赖服务。我们知道AIAgent相关的很多组件类型和接口比如IChatClient、AIContextProvider和ChatHistoryProvider都定义了这两个方法这意味着可以将这些组件视为一个IServiceProvider。publicabstractclassAIAgent{publicvirtualobject?GetService(TypeserviceType,object?serviceKeynull)(serviceKeynullserviceType.IsInstanceOfType(this))?this:null;publicTService?GetServiceTService(object?serviceKeynull)(GetService(typeof(TService),serviceKey)isTServiceval)?val:default(TService);}定义在AIAgent中的GetService和GetServiceTService方法也体现了在其他组件基类中的标准定义只能把自己作为服务实例提供出去。如果需要对外提供依赖服务可以重写虚方法GetService。作为整个Agent管道核心的ChatClientAgent采用如下的方式重写了GetService方法。publicsealedclassChatClientAgent:AIAgent{publicoverrideobject?GetService(TypeserviceType,object?serviceKeynull)base.GetService(serviceType,serviceKey)??(serviceTypetypeof(AIAgentMetadata)?this._agentMetadata:serviceTypetypeof(IChatClient)?this.ChatClient:serviceTypetypeof(ChatOptions)?this._agentOptions?.ChatOptions:serviceTypetypeof(ChatClientAgentOptions)?this._agentOptions:this.AIContextProviders?.Select(providerprovider.GetService(serviceType,serviceKey)).FirstOrDefault(ssisnotnull)??this.ChatHistoryProvider?.GetService(serviceType,serviceKey)??this.ChatClient.GetService(serviceType,serviceKey));}重写的GetService方法采用如下的策略顺序提供指定类型的服务对象调用基类的同名方法意味着如果指定的服务类型为ChatClientAgent会返回当前这个ChatClientAgent对象否则返回null如果服务类型为AIAgentMetadata目前的提供的元数据只有提供商的名称返回_agentMetadata字段如果服务类型为IChatClient返回ChatClient属性该属性表示有注册的ChatClient中间件和连接LLM的IChatClient对象组成的ChatClient管道如果服务类型为ChatClientAgentOptions返回_agentOptions字段该字段表示自身的配置选项依次利用注册的AIContextProvider、ChatHistoryProvider和ChatClient管道来提供指定的服务对象。表示Agent中间件的DelegatingAIAgent以虚方法的形式实现了此方法如果类型为当前类型则返回自己否则调用内部AIAgent的同名方法。publicabstractclassDelegatingAIAgent:AIAgent{protectedAIAgentInnerAgent{get;}publicoverrideobject?GetService(TypeserviceType,object?serviceKeynull){return(serviceKeynullserviceType.IsInstanceOfType(this))?this:InnerAgent.GetService(serviceType,serviceKey);}}3. 作为服务提供者的AIContextProvider和ChatHistoryProvider正如上面所说AIContextProvider和ChatHistoryProvider这些基础组件和AIAgent一样定义了GetService和GetServiceTService方法并且它们的定义方式如出一辙只能把自己作为服务实例提供出去。publicabstractclassAIContextProvider{publicvirtualobject?GetService(TypeserviceType,object?serviceKeynull)(serviceKeynullserviceType.IsInstanceOfType(this))?this:null;publicTService?GetServiceTService(object?serviceKeynull)(GetService(typeof(TService),serviceKey)isTServiceval)?val:default(TService);}publicabstractclassChatHistoryProvider{publicvirtualobject?GetService(TypeserviceType,object?serviceKeynull)(serviceKeynullserviceType.IsInstanceOfType(this))?this:null;publicTService?GetServiceTService(object?serviceKeynull)(GetService(typeof(TService),serviceKey)isTServiceval)?val:default(TService);}我已经多次诟病MAF的设计我觉得这里的设计有违基本的设计原则。作为最外层组件的AIAgent将它设计成服务提供者我还能接受但是将这种设计应用到内部组件类型比如AIContextProvider和ChatHistoryProvider就说不过去了。作为一个独立的组件它只需要关注自身的功能实现就可以了意味着它只需要关注实现自身的功能需要依赖怎样的服务对象而不需要关心需要对外提供哪些服务对象。采用这样的设计单一职责被破坏基础组件既要完成自己的核心功能又要承担服务发现。封装性被削弱调用方不再通过明确依赖注入而是通过GetService动态解析。耦合性增强从头到尾都必须遵循同样的服务发现接口否则链条断裂。这样的设计有违依赖倒置的原则如果我们将Agent最为最终的产物正确的做法是框架在构建Agent的时候利用自身的服务注册来为具体的组件注入依赖服务而不是让Agent作为一个代理的服务提供者反过来向这些基础组件拉取依赖服务。可能有人依然无法理解这种反模式。我们具体个例子假设我们需要自定义一个ChatHistoryProvider原则上我们只需要实现基于AgentSession针对对话历史的读写就可以我们无法确定也没有必要对外提供额外的服务对象。所以你会发现MAF提供的系统预定义组件几乎没有一个会重写GetService方法。换句话说每个组件只能将自己作为服务对象提供出去实现在基类的GetService方法中。既然如此只需要将提供组件自身实例的逻辑实现在自身的GetService方法上就可以了完全没有必要将这个Service Location链条延申下去。4. 依赖在ChatClient管道构建上的应用从GetService方法在ChatClientAgent中的定义可知此方法同样定义在IChatClient接口上。表示ChatClient中间件的DelegatingChatClient以虚方法的形式实现了此方法如果类型为当前类型则返回自己否则调用内部IChatClient的同名方法。我认为在此接口上定义GetService方法依然是一种反模式设计理由同上。publicinterfaceIChatClient:IDisposable{...object?GetService(TypeserviceType,object?serviceKeynull);}publicclassDelegatingChatClient:IChatClient,IDisposable{...protectedIChatClientInnerClient{get;}publicvirtualobject?GetService(TypeserviceType,object?serviceKeynull){if(serviceKey!null||!serviceType.IsInstanceOfType(this)){returnInnerClient.GetService(serviceType,serviceKey);}returnthis;}}但是在实现在ChatClientBuilder上基于依赖注入的方式来构件ChatClient管道是没有问题的。如下面代码所示不论是针对内部IChatClient对象还是ChatClient中间件的创建都体现为一个FuncIServiceProvider, IChatClient委托并在Build方法中利用提供的IServiceProvider对象作为依赖注入容器为这些IChatClient对象含中间件的创建提供依赖服务。publicsealedclassChatClientBuilder{privatereadonlyFuncIServiceProvider,IChatClient_innerClientFactory;privateListFuncIChatClient,IServiceProvider,IChatClient?_clientFactories;publicChatClientBuilder(IChatClientinnerClient)_innerClientFactory(IServiceProvider_)innerClient;publicChatClientBuilder(FuncIServiceProvider,IChatClientinnerClientFactory)_innerClientFactoryinnerClientFactory;publicIChatClientBuild(IServiceProvider?servicesnull){if(servicesnull){servicesEmptyServiceProvider.Instance;}IChatClientchatClient_innerClientFactory(services);if(_clientFactories!null){for(intnum_clientFactories.Count-1;num0;num--){chatClient_clientFactories[num](chatClient,services);if(chatClientnull){Microsoft.Shared.Diagnostics.Throw.InvalidOperationException($The{ChatClientBuilder}entry at index{num}returned null. Ensure that the callbacks passed to{Use}return non-null{IChatClient}instances.);}}}returnchatClient;}}如果调用Build方法没有显示指定IServiceProvider对象或默认使用如下这个直接返回null的EmptyServiceProvider单例对象。privatesealedclassEmptyServiceProvider:IServiceProvider,IKeyedServiceProvider{publicstaticEmptyServiceProviderInstance{get;}newEmptyServiceProvider();publicobject?GetService(TypeserviceType)null;publicobject?GetKeyedService(TypeserviceType,object?serviceKey)null;publicobjectGetRequiredKeyedService(TypeserviceType,object?serviceKey)thrownewInvalidOperationException($No service for type {serviceType} has been registered.);}在ChatClientAgent如下这个构造函数中如果UseProvidedChatClientAsIs配置选项没有被显式设置成trueIServiceProvider会以参数的形式传入WithDefaultAgentMiddleware扩展方法用来注册一系列默认的ChatClient中间件。该方法内部会利用ChatClientBuilder来构建ChatClient管道并利用提供的IServiceProvider来提供这些中间件所需的依赖服务。目前WithDefaultAgentMiddleware方法在将中间件注册到ChatClientBuilder时根本没有使用到ISeriviceProvider意味着目前ChatClientAgent构造函数的services参数根本不需要传传了了没有用。虽然这种设计冗余没有意义不过考虑到services是一个可选的此参数也能接受。publicsealedclassChatClientAgent:AIAgent{publicChatClientAgent(IChatClientchatClient,ChatClientAgentOptions?options,,ILoggerFactory?loggerFactorynull,IServiceProvider?servicesnull){...ChatClient((options?.UseProvidedChatClientAsIs??false)?chatClient:chatClient.WithDefaultAgentMiddleware(options,services));}}5. AIAgent的GetService究竟可以提供什么回到最初的问题当我们自定义一个组件时可以从AIAgent的静态属性CurrentRunContext得到代表当前AgentRunContext上下文。有了此上下文我们就能得到当前运行的AIAgent自然就可以调用其GetService方法得到所需的依赖服务。根据上面对这个方法实现逻辑的介绍我们总结了一下指定的服务类型具体返回的服务对象具体的Agent中间件类型组成Agent管道的指定类型的Agent中间件。ChatClientAgent: 当前ChatClientAgent对象AIAgentMetadata: 承载供应上名称的AIAgentMetadata对象IChatClient当前ChatClient管道中间件连接LLM的IChatClient对象ChatClientAgentOptions 当前ChatClientAgent的配置选项具体的AIContextProvider类型注册的指定类型的AIContextProvider对象具体的ChatHistoryProvider注册的指定类型的ChatHistoryProviderChatClient中间件类型组成ChatClient管道的指定类型的ChatClient中间件。当然如果我们注册了自定义的AIContextProvider、ChatHistoryProvider或者Agent中间件和ChatClient中间件对应的类型重写了GetService方法提供的类型也可以通过当前AIAgent的GetService方法提取到。

相关新闻

论文省心了!2026年刚需首选的专业降AI率平台

论文省心了!2026年刚需首选的专业降AI率平台

2026年论文降AI率工具已从“基础去痕”进化为多维度智能优化平台,核心差异体现在文献真实性、格式合规性、长文本逻辑、查重降重、AIGC合规五大维度。本次测评覆盖6款主流工具,涵盖中文/英文、全流程/专项、免费/付费场景,帮你高效匹配最适合…

2026/7/20 15:31:34阅读更多 →
Scroll Reverser:彻底解决macOS触控板与鼠标滚动方向冲突的专业方案

Scroll Reverser:彻底解决macOS触控板与鼠标滚动方向冲突的专业方案

Scroll Reverser:彻底解决macOS触控板与鼠标滚动方向冲突的专业方案 【免费下载链接】Scroll-Reverser Per-device scrolling prefs on macOS. 项目地址: https://gitcode.com/gh_mirrors/sc/Scroll-Reverser 还在为macOS上触控板和鼠标的滚动方向冲突而烦恼…

2026/7/20 15:31:34阅读更多 →
Carnac未来路线图:键盘可视化工具的发展趋势与创新方向

Carnac未来路线图:键盘可视化工具的发展趋势与创新方向

Carnac未来路线图:键盘可视化工具的发展趋势与创新方向 【免费下载链接】carnac A utility to give some insight into how you use your keyboard 项目地址: https://gitcode.com/gh_mirrors/car/carnac Carnac作为一款实用的键盘可视化工具,能够…

2026/7/20 15:29:33阅读更多 →
块元素与行内元素差异及CSS布局实战指南

块元素与行内元素差异及CSS布局实战指南

1. 块元素与行内元素的核心差异解析 作为前端开发的基础概念&#xff0c;块元素&#xff08;Block-level elements&#xff09;和行内元素&#xff08;Inline elements&#xff09;的差异直接影响页面布局的实现方式。先看个典型例子&#xff1a; <!-- 块元素示例 --> …

2026/7/21 8:23:11阅读更多 →
C++23 标准库新增内容全面解析

C++23 标准库新增内容全面解析

1. 引言 C23 是 C20 之后的一个重要标准更新&#xff0c;虽然不像 C11 或 C20 那样带来颠覆性的变化&#xff0c;但在标准库方面仍然提供了许多实用的新功能和改进。这些新增内容主要集中在简化常见任务、填补功能空白、提升性能以及增强类型安全等方面。本文将详细介绍 C23 标…

2026/7/21 8:23:11阅读更多 →
开源项目维护停滞的应对策略与风险评估指南

开源项目维护停滞的应对策略与风险评估指南

这次我们来看一个比较特殊的项目——"第五季第三集完结了&#xff0c;但是我不想做了"。从标题就能感受到开发者的一种疲惫和无奈&#xff0c;这很可能是一个长期维护的开源项目&#xff0c;作者在完成某个重要版本后决定暂停或放弃。这类项目往往具有很高的实用价值…

2026/7/21 8:23:11阅读更多 →
最近试了下AI Offer,用AI来进化简历和模拟面试还挺简单的

最近试了下AI Offer,用AI来进化简历和模拟面试还挺简单的

写简历最难的&#xff0c;往往不是“没有经历” 每次开始找工作&#xff0c;我都会在简历上反复纠结&#xff1a;项目明明做了不少&#xff0c;写出来却像流水账&#xff1b;同一份简历投不同岗位&#xff0c;又担心重点不够匹配&#xff1b;好不容易改完&#xff0c;排版和导…

2026/7/21 8:23:11阅读更多 →
C++实现OPC DA客户端:从COM原理到工业数据采集实战

C++实现OPC DA客户端:从COM原理到工业数据采集实战

1. 项目概述&#xff1a;为什么我们需要自己动手写一个OPC客户端&#xff1f;在工业自动化领域&#xff0c;数据是流淌的血液。无论是PLC的温度读数、机器人的运行状态&#xff0c;还是生产线的产量统计&#xff0c;这些数据都需要被采集、监控和分析。OPC&#xff08;OLE for …

2026/7/21 8:23:10阅读更多 →
爆款视频预测教程,2026年爆款视频分析工作流,5款横评实测

爆款视频预测教程,2026年爆款视频分析工作流,5款横评实测

发片前为什么一定要做爆款视频预测 很多做短视频矩阵和口播账号的人&#xff0c;都有过类似的经历&#xff1a;一条视频从写脚本、录口播、剪字幕到上字幕特效&#xff0c;忙了大半天&#xff0c;发出去播放量却只有几百。反复试了几次&#xff0c;才发现是开头钩子不够强、节…

2026/7/21 8:21:10阅读更多 →
Go语言静态资源打包方案对比与实践指南

Go语言静态资源打包方案对比与实践指南

1. 项目背景与核心需求在Go语言开发中&#xff0c;我们经常需要处理静态资源文件的打包问题。无论是Web应用的模板文件、前端资源&#xff0c;还是配置文件、证书等&#xff0c;都需要随程序一起分发。传统做法是将这些文件与编译后的二进制文件放在同一目录下&#xff0c;但这…

2026/7/21 0:51:49阅读更多 →
Go语言实现高性能LDAP认证服务的架构与实践

Go语言实现高性能LDAP认证服务的架构与实践

1. 项目背景与核心价值LDAP&#xff08;轻量级目录访问协议&#xff09;作为企业级身份认证的黄金标准&#xff0c;已经服务了超过80%的财富500强公司。我在金融科技领域实施统一认证体系时&#xff0c;发现传统Java方案存在启动慢、内存占用高等痛点。而Go语言凭借其协程并发模…

2026/7/21 0:51:49阅读更多 →
【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

更多请点击&#xff1a; https://intelliparadigm.com 第一章&#xff1a;AI面试官实战指南的核心价值与适用场景 AI面试官并非替代人类HR的“黑箱工具”&#xff0c;而是以可解释、可审计、可迭代的方式&#xff0c;赋能招聘全链路的关键基础设施。其核心价值在于将主观经验沉…

2026/7/21 0:51:49阅读更多 →
Windows+macOS 通用 OpenClaw 部署流程,内置依赖一键启动智能桌面助手

Windows+macOS 通用 OpenClaw 部署流程,内置依赖一键启动智能桌面助手

&#x1f4cc;教程适配&#xff1a;OpenClaw v2.7.9 | 兼容 Windows10/11、macOS 双系统 &#x1f4d6;前言 当下各类本地 AI 工具层出不穷&#xff0c;多数产品仅能完成文字问答交互&#xff0c;很难直接操控电脑执行实际操作。OpenClaw&#xff0c;业内常称小龙虾 AI&#…

2026/7/21 0:01:46阅读更多 →
Codex 接入后 Bug 反增?复盘从个人演示到团队协作的“流程陷阱”

Codex 接入后 Bug 反增?复盘从个人演示到团队协作的“流程陷阱”

聊《一次Codex项目复盘&#xff0c;问题最后出在流程而不是模型》之前&#xff0c;先说一句实在的&#xff1a;别急着背概念&#xff0c;先看它在真实项目里到底解决什么问题。摘要先把这篇文章的目标说清楚&#xff1a;看完之后&#xff0c;你应该能判断这件事值不值得做&…

2026/7/21 0:01:46阅读更多 →
手把手搓一个五子棋游戏,零代码也能当“游戏开发者”

手把手搓一个五子棋游戏,零代码也能当“游戏开发者”

大家好&#xff0c;还是我。前几期带大家做了心情日记本和可视化大屏&#xff0c;后台有朋友留言&#xff1a;“能不能教点好玩的&#xff1f;我想做游戏&#xff0c;但一行代码都不会。”行&#xff0c;这期就安排。今天的目标&#xff1a;从零做一个五子棋游戏。 带AI对战、三…

2026/7/21 0:03:46阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时&#xff0c;发现推理速度只有可怜的 1-2 FPS&#xff0c;而别人的演示视频却能跑到 30 FPS 以上&#xff0c;那么问题很可能不在模型本身&#xff0c;而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后&#xff0c;会直接使用官方示例…

2026/7/20 22:51:39阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一&#xff1a;为什么你需要了解 Coze 和 Dify&#xff1f;如果你对 AI 应用开发感兴趣&#xff0c;但一看到“大模型”、“智能体”、“工作流”这些词就头疼&#xff0c;觉得门槛太高&#xff0c;那这篇文章就是为你准备的。很多开发者&#xff0c;包括我自己&#…

2026/7/20 18:51:18阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会&#xff1a;配图一直是个让人头疼的问题。2026年&#xff0c;AI生图工具已经非常成熟了&#xff0c;但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1&#xff1a;速度之王2026年6月11日&#xff0c…

2026/7/20 18:51:18阅读更多 →