当前位置: 首页 > news >正文

[MAF预定义的IChatClient中间件-01]LoggingChatClient——在LLM调用前后输出日志

LoggingChatClient是一个预定义的IChatClient中间件,它在调用前后输出日志,帮助我们更好地了解Agent的执行过程。它会记录每次调用的输入和输出,以及调用的时间戳等信息。这对于调试和监控Agent的行为非常有用。

1. 利用LoggingChatClient中间件来记录针对LLM的调用

如果将LoggingChatClient这个中间件至于连接LLM的IChatClient之前,那么针对后者对LLM的调用情况会以日志的形式记录下来。我们可以通过设置不同的日志级别来控制输出的详细程度。在如下的演示程序中,我们利用创建了一个基于OpenAIClientIChatClient对象。在调用AsBuilder扩展方法将ChatClientBuilder构建出来后,通过调用UseLogging方法来注册LoggingChatClient中间件,并且传入一个ILoggerFactory对象来控制日志的输出。由于我们在创建ILoggerFactory对象的时候设置了日志级别为Debug

usingAzure;usingdotenv.net;usingMicrosoft.Agents.AI;usingMicrosoft.Extensions.AI;usingMicrosoft.Extensions.DependencyInjection;usingMicrosoft.Extensions.Logging;usingOpenAI;DotEnv.Load();varmodel=Environment.GetEnvironmentVariable("MODEL")!;varapiKey=Environment.GetEnvironmentVariable("API_KEY")!;varendpoint=Environment.GetEnvironmentVariable("OPENAI_URL")!;varloggerFactory=newServiceCollection().AddLogging(logging=>logging.SetMinimumLevel(LogLevel.Trace).AddConsole()).BuildServiceProvider().GetRequiredService<ILoggerFactory>();varclient=newOpenAIClient(credential:newAzureKeyCredential(apiKey),options:newOpenAIClientOptions{Endpoint=newUri(endpoint)}).GetChatClient(model:model).AsIChatClient().AsBuilder().UseLogging(loggerFactory:loggerFactory).Build();awaitclient.GetResponseAsync("What is Azure OpenAI?");Console.ReadLine();

LoggingChatClientGetResponseAsync方法会在调用前输出一条日志,表示正在调用LLM,并且会记录调用的输入内容;在调用完成后会输出另一条日志,表示调用已经完成,并且会记录调用的输出内容。通过这些日志,我们可以清楚地看到每次调用的输入和输出,以及调用的时间戳等信息。

dbug: Microsoft.Extensions.AI.LoggingChatClient[1723383095] GetResponseAsync invoked. dbug: Microsoft.Extensions.AI.LoggingChatClient[1553703230] GetResponseAsync completed.

如果我们将日志等级设置为更低的Trace级别,那么LoggingChatClient还会输出更详细的日志信息,包括调用的输入内容和输出内容等。

varloggerFactory=newServiceCollection().AddLogging(logging=>logging.SetMinimumLevel(LogLevel.Trace).AddConsole()).BuildServiceProvider().GetRequiredService<ILoggerFactory>();

输出:

trce: Microsoft.Extensions.AI.LoggingChatClient[805843669] GetResponseAsync invoked: [ { "role": "user", "contents": [ { "$type": "text", "text": "What is Azure OpenAI?" } ] } ]. Options: null. Metadata: { "providerName": "openai", "providerUri": "https://eap2410.cognitiveservices.azure.com/openai/v1", "defaultModelId": "gpt-5.2-chat" }. trce: Microsoft.Extensions.AI.LoggingChatClient[384896670] GetResponseAsync completed: { "messages": [ { "createdAt": "2026-05-22T01:28:42+00:00", "role": "assistant", "contents": [ { "$type": "text", "text": "**Azure OpenAI** is Microsoft’s cloud-based service that provides access to advanced AI models (like OpenAI’s GPT, GPT‑4, and image generation models) through the **Microsoft Azure** platform.\n\nIn simple terms, it lets businesses and developers use powerful AI models within Microsoft’s secure cloud environment.\n\n### Key Features:\n- **Access to OpenAI models** (GPT‑4, GPT‑4o, embeddings, image generation, etc.)\n- **Enterprise-grade security and compliance**\n- **Data privacy** — your data isn’t used to train the base models\n- **Integration with Azure services** (Azure AI Search, Azure Functions, Power BI, etc.)\n- **Scalable infrastructure** for production workloads\n\n### What It’s Used For:\n- Chatbots and virtual assistants \n- Document summarization \n- Code generation \n- Data analysis \n- Image generation \n- Semantic search and embeddings \n\n### How It’s Different from OpenAI’s public API:\n- Runs within the **Azure ecosystem**\n- Offers enterprise security controls\n- Regional data hosting options\n- Integrated billing through Azure\n\nIn short: \n**Azure OpenAI = OpenAI models + Microsoft Azure’s enterprise cloud platform.**" } ], "messageId": "chatcmpl-Di8yoRfX62nycHbngYbn11qNFWvJk" } ], "responseId": "chatcmpl-Di8yoRfX62nycHbngYbn11qNFWvJk", "modelId": "gpt-5.2-chat-latest", "createdAt": "2026-05-22T01:28:42+00:00", "finishReason": "stop", "usage": { "inputTokenCount": 12, "outputTokenCount": 252, "totalTokenCount": 264, "cachedInputTokenCount": 0, "reasoningTokenCount": 0, "additionalCounts": { "InputTokenDetails.AudioTokenCount": 0, "OutputTokenDetails.AudioTokenCount": 0, "OutputTokenDetails.AcceptedPredictionTokenCount": 0, "OutputTokenDetails.RejectedPredictionTokenCount": 0 } } }.

2. LoggingChatClient

LoggingChatClient直接继承自DelegatingChatClient,是一个非常简单的中间件实现,它直接利用构造函数传入的ILogger对象来输出日志信息。DelegatingChatClient在没有出错的情况下只会输出等级分别为DebugTrace的日志信息,如果最低日志等级设置为Debug,那么就只会输出调用前和调用后的日志;如果最低日志等级设置为Trace,那么就会输出更详细的日志信息,包括调用的输入内容和输出内容等。Trace等级的日志的内容以JSON形式输出,所以它提供了一个JsonSerializerOptions属性来控制日志中输入输出内容的序列化方式。我们可以通过设置这个属性来控制日志中输入输出内容的格式,比如是否使用驼峰命名、是否忽略空值等。

publicpartialclassLoggingChatClient:DelegatingChatClient{publicLoggingChatClient(IChatClientinnerClient,ILoggerlogger);publicJsonSerializerOptionsJsonSerializerOptions{get;set;}publicoverrideasyncTask<ChatResponse>GetResponseAsync(IEnumerable<ChatMessage>messages,ChatOptions?options=null,CancellationTokencancellationToken=default);publicoverrideasyncIAsyncEnumerable<ChatResponseUpdate>GetStreamingResponseAsync(IEnumerable<ChatMessage>messages,ChatOptions?options=null,CancellationTokencancellationToken=default);}

针对GetResponseAsync的日志输出采用如下的逻辑:

  • 在调用innerClientGetResponseAsync方法之前,输出一条Debug/Trace等级的日志,表示正在调用LLM,并且会记录调用的输入内容;
  • 在成功调用并得到响应之后,输出另一条Debug/Trace等级的日志,表示调用已经完成,并且会记录调用的输出内容;
  • 如果调用过程中发生了异常,那么会输出一条Error等级的日志,表示调用失败,并且会记录异常信息;

针对GetStreamingResponseAsync的日志输出采用如下的逻辑:

  • 在调用innerClientGetStreamingResponseAsync方法之前,输出一条Debug/Trace等级的日志,表示正在调用LLM,并且会记录调用的输入内容;
  • 如果调用失败,那么会输出一条Error等级的日志,表示调用失败,并且会记录异常信息;
  • GetStreamingResponseAsync会对返回的IAsyncEnumerable<ChatResponseUpdate>进行迭代,对于每一次迭代:
    • 如果成功获取到一个ChatResponseUpdate,并且最低日志等级设置为Trace,那么会输出一条Trace等级的日志,表示获取到了一个更新,并且会记录这个更新的内容;
    • 如果在迭代过程中发生了异常,那么会输出一条Error等级的日志,表示迭代失败,并且会记录异常信息;
  • 在迭代完成之后,输出一条Debug等级的日志,表示调用已经完成;

对于我们前面演示的例子,如果我们将日志等级设置为Trace,那么在调用GetStreamingResponseAsync方法时,我们就可以看到每一次迭代获取到的ChatResponseUpdate的内容都被记录在日志中了,这对于调试和监控Agent的行为非常有用。由于这种情况下输出内容容量可能会非常大,所以当我们将日志等级设置为Trace时,得评估一下日志对性能带来得影响。

usingAzure;usingdotenv.net;usingMicrosoft.Agents.AI;usingMicrosoft.Extensions.AI;usingMicrosoft.Extensions.DependencyInjection;usingMicrosoft.Extensions.Logging;usingOpenAI;DotEnv.Load();varmodel=Environment.GetEnvironmentVariable("MODEL")!;varapiKey=Environment.GetEnvironmentVariable("API_KEY")!;varendpoint=Environment.GetEnvironmentVariable("OPENAI_URL")!;varloggerFactory=newServiceCollection().AddLogging(logging=>logging.SetMinimumLevel(LogLevel.Trace).AddConsole()).BuildServiceProvider().GetRequiredService<ILoggerFactory>();varclient=newOpenAIClient(credential:newAzureKeyCredential(apiKey),options:newOpenAIClientOptions{Endpoint=newUri(endpoint)}).GetChatClient(model:model).AsIChatClient().AsBuilder().UseLogging(loggerFactory:loggerFactory).Build();awaitforeach(varupdateinclient.GetStreamingResponseAsync("世界上最深的淡水湖是哪个?在10字内作答!")){}

输出:

trce: Microsoft.Extensions.AI.LoggingChatClient[805843669] GetStreamingResponseAsync invoked: [ { "role": "user", "contents": [ { "$type": "text", "text": "世界上最深的淡水湖是哪个?在10字内作答!" } ] } ]. Options: null. Metadata: { "providerName": "openai", "providerUri": "https://eap2410.cognitiveservices.azure.com/openai/v1", "defaultModelId": "gpt-5.2-chat" }. trce: Microsoft.Extensions.AI.LoggingChatClient[1513570378] GetStreamingResponseAsync received update: { "contents": [], "responseId": "", "messageId": "", "createdAt": "1970-01-01T00:00:00+00:00", "modelId": "" } trce: Microsoft.Extensions.AI.LoggingChatClient[1513570378] GetStreamingResponseAsync received update: { "role": "assistant", "contents": [ { "$type": "text", "text": "" } ], "responseId": "chatcmpl-Di9WdprY6ZgpHqiLY25t8Y23kmsRa", "messageId": "chatcmpl-Di9WdprY6ZgpHqiLY25t8Y23kmsRa", "createdAt": "2026-05-22T02:03:39+00:00", "modelId": "" } trce: Microsoft.Extensions.AI.LoggingChatClient[1513570378] GetStreamingResponseAsync received update: { "role": "assistant", "contents": [ { "$type": "text", "text": "贝" } ], "responseId": "chatcmpl-Di9WdprY6ZgpHqiLY25t8Y23kmsRa", "messageId": "chatcmpl-Di9WdprY6ZgpHqiLY25t8Y23kmsRa", "createdAt": "2026-05-22T02:03:39+00:00", "modelId": "" } trce: Microsoft.Extensions.AI.LoggingChatClient[1513570378] GetStreamingResponseAsync received update: { "role": "assistant", "contents": [ { "$type": "text", "text": "加" } ], "responseId": "chatcmpl-Di9WdprY6ZgpHqiLY25t8Y23kmsRa", "messageId": "chatcmpl-Di9WdprY6ZgpHqiLY25t8Y23kmsRa", "createdAt": "2026-05-22T02:03:39+00:00", "modelId": "" } trce: Microsoft.Extensions.AI.LoggingChatClient[1513570378] GetStreamingResponseAsync received update: { "role": "assistant", "contents": [ { "$type": "text", "text": "尔" } ], "responseId": "chatcmpl-Di9WdprY6ZgpHqiLY25t8Y23kmsRa", "messageId": "chatcmpl-Di9WdprY6ZgpHqiLY25t8Y23kmsRa", "createdAt": "2026-05-22T02:03:39+00:00", "modelId": "" } trce: Microsoft.Extensions.AI.LoggingChatClient[1513570378] GetStreamingResponseAsync received update: { "role": "assistant", "contents": [ { "$type": "text", "text": "湖" } ], "responseId": "chatcmpl-Di9WdprY6ZgpHqiLY25t8Y23kmsRa", "messageId": "chatcmpl-Di9WdprY6ZgpHqiLY25t8Y23kmsRa", "createdAt": "2026-05-22T02:03:39+00:00", "modelId": "" } trce: Microsoft.Extensions.AI.LoggingChatClient[1513570378] GetStreamingResponseAsync received update: { "role": "assistant", "contents": [], "responseId": "chatcmpl-Di9WdprY6ZgpHqiLY25t8Y23kmsRa", "messageId": "chatcmpl-Di9WdprY6ZgpHqiLY25t8Y23kmsRa", "createdAt": "2026-05-22T02:03:39+00:00", "finishReason": "stop", "modelId": "" } trce: Microsoft.Extensions.AI.LoggingChatClient[1513570378] GetStreamingResponseAsync received update: { "role": "assistant", "contents": [ { "$type": "usage", "details": { "inputTokenCount": 24, "outputTokenCount": 78, "totalTokenCount": 102, "cachedInputTokenCount": 0, "reasoningTokenCount": 64, "additionalCounts": { "InputTokenDetails.AudioTokenCount": 0, "OutputTokenDetails.AudioTokenCount": 0, "OutputTokenDetails.AcceptedPredictionTokenCount": 0, "OutputTokenDetails.RejectedPredictionTokenCount": 0 } } } ], "responseId": "chatcmpl-Di9WdprY6ZgpHqiLY25t8Y23kmsRa", "messageId": "chatcmpl-Di9WdprY6ZgpHqiLY25t8Y23kmsRa", "createdAt": "2026-05-22T02:03:39+00:00", "finishReason": "stop", "modelId": "" } dbug: Microsoft.Extensions.AI.LoggingChatClient[1553703230] GetStreamingResponseAsync completed.

3. 利用Source Generator生成日志输出代码

日志是典型得高频操作,尤其是当我们将日志等级设置得很低得时候更是如此,所以针对日志输出的每一个微小的细节都会高倍放大,比如字符串拼接和值类型转换成引用类型导致的装箱等。在此方面,Source Generator就能派上用场了。我们可以利用Source Generator来生成日志输出的代码,从而避免手写日志输出代码可能带来的性能问题。Microsoft.Extensions.Logging库已经提供了一个名为LoggerMessageAttribute的Source Generator,我们可以利用它来生成日志输出的代码。

LoggingChatClient涉及的日志输出被定义成对应的方法,并在这些方法上使用LoggerMessageAttribute特性来标记日志的级别和消息模板。LoggerMessageAttribute特性会告诉Source Generator生成对应的日志输出代码,从而避免了手写日志输出代码可能带来的性能问题。这也是LoggingChatClient被定义成partial类的原因。

publicpartialclassLoggingChatClient:DelegatingChatClient{[LoggerMessage(LogLevel.Debug,"{MethodName} invoked.")]privatepartialvoidLogInvoked(stringmethodName);[LoggerMessage(LogLevel.Trace,"{MethodName} invoked: {Messages}. Options: {ChatOptions}. Metadata: {ChatClientMetadata}.")]privatepartialvoidLogInvokedSensitive(stringmethodName,stringmessages,stringchatOptions,stringchatClientMetadata);[LoggerMessage(LogLevel.Debug,"{MethodName} completed.")]privatepartialvoidLogCompleted(stringmethodName);[LoggerMessage(LogLevel.Trace,"{MethodName} completed: {ChatResponse}.")]privatepartialvoidLogCompletedSensitive(stringmethodName,stringchatResponse);[LoggerMessage(LogLevel.Trace,"GetStreamingResponseAsync received update: {ChatResponseUpdate}")]privatepartialvoidLogStreamingUpdateSensitive(stringchatResponseUpdate);[LoggerMessage(LogLevel.Debug,"{MethodName} canceled.")]privatepartialvoidLogInvocationCanceled(stringmethodName);[LoggerMessage(LogLevel.Error,"{MethodName} failed.")]privatepartialvoidLogInvocationFailed(stringmethodName,Exceptionerror);}

4. UseLogging扩展方法

UseLogging是一个ChatClientBuilder的扩展方法,它提供了一种简便的方式来注册LoggingChatClient中间件。我们只需要在构建IChatClient对象的时候调用UseLogging方法,并传入一个ILoggerFactory对象来控制日志的输出,就可以轻松地将LoggingChatClient中间件添加到我们的IChatClient对象中了。除此之外,UseLogging方法还提供了一个可选的configure参数,它允许我们在注册LoggingChatClient中间件的时候对其进行一些额外的配置,比如设置JsonSerializerOptions属性来控制日志中输入输出内容的序列化方式等。

publicstaticclassLoggingChatClientBuilderExtensions{publicstaticChatClientBuilderUseLogging(thisChatClientBuilderbuilder,ILoggerFactory?loggerFactory=null,Action<LoggingChatClient>?configure=null);}
http://www.zskr.cn/news/1389989.html

相关文章:

  • 汕头市贵金属全品类回收同城靠谱回收门店权威:黄金+白银+铂金+钯金当场检测当面结算及联系方式推荐 - 亦辰小黄鸭
  • Linux中实现开机自启动的几种常见方式及区别详解
  • SchoolCMS:如何用开源系统彻底解决中小学校教务管理难题
  • ROS 2自主移动机器人(AMR)数据通信与共享(2)
  • 从新手到专家:BilibiliHistoryFetcher日志监控与邮件告警配置
  • iniparser与C++集成:如何在C++项目中安全使用C语言INI解析库
  • Python驱动CFD革命:5步掌握PyFluent的终极指南
  • 大模型自主智能体记忆与反思机制设计如何落地企业?一篇深度解构与提效实战
  • 韶山市贵金属全品类回收同城靠谱回收门店权威:黄金+白银+铂金+钯金当场检测当面结算及联系方式推荐 - 亦辰小黄鸭
  • 使用Qwen3-Coder-30B-A3B-Instruct-FP8进行企业级代码审查与重构:提升代码质量的终极指南
  • DDrawCompat完整指南:让经典DirectDraw游戏在现代Windows上完美运行的免费兼容层
  • 【创新未发表】离散开停机制氨调度与多场景全年评估研究(Matlab代码、Python、数据、word论文)
  • ModEngine2配置文件详解:TOML配置系统的高级用法指南
  • LTC1668IG#PBF 、16位/50MSPS高性能差分电流输出数模转换器
  • 从零开始写小说:novelWriter如何让你专注创作不被打扰?
  • 无锡萧邦定期专业保养哪家强?快乐钻石灵动卡顿、L.U.C系列机芯油泥干涸怎么救?带你走进恒隆广场授权网点,体验从外观翻新到机芯深度注油的透明化全流程服务 - 亨得利官方维修中心
  • 2026发膜口碑榜:年度用户最爱发膜TOP10 - 速递信息
  • Agent赋能智能运维:如何实现AI自动监控服务器并触发故障工单的闭环架构?
  • 数字记忆守护者:如何用WeChatExporter永久保存你的微信聊天时光
  • SDXL模型架构解析:深入理解PyTorch-NPU实现细节 [特殊字符]
  • 私有化部署的AI智能体架构是怎样的?深度解析企业级AI Agent落地路径与避坑指南
  • 苏州黄金回收指南,福正美免费上门变现无忧 - 上门黄金回收
  • OpenSesame:从零到一构建心理学实验的完整指南
  • 终极GitHub加速方案:告别龟速下载,体验飞一般的高效开发
  • 618发膜预售清单:提前锁定发膜品牌的明星产品 - 速递信息
  • 市面上知名的剥壳机供应厂家推荐,麻籽剥壳机/元宝枫脱壳机/紫苏脱皮机/葵花籽剥壳机/黄豆去皮机,剥壳机制造商怎么选购 - 品牌推荐师
  • 杭州太空袋采购:看不见的成本账与长期保障逻辑(2026年5月最新) - GEO排行榜
  • 解剖一个桌面级 AI Copilot 的架构:Stargazer AI Copilot(.NET 10 + Avalonia)是怎么“分层”的
  • 哔咔漫画下载器完整指南:3步打造个人离线漫画库
  • SSD Keras可视化工具:特征图、检测结果与训练过程可视化