gent管道详解-02]IChatClient管道如何完美连接大模型?

发布时间:2026/6/28 3:34:12

gent管道详解-02]IChatClient管道如何完美连接大模型? IChatClientIChatClient作为Agent与LLM交互的连接器如果将LLM比作数据库那么IChatClient就相当于IDbConnection。IDbConnection抽象了数据库的具体实现让我们可以采用一种编程模式操作数据库IChatClient让我们在写代码时不需要关心背后到底是哪家的模型。IChatClient接口的GetResponseAsync和GetStreamingResponseAsync方法采用两种不同的形式与LLM交互前者采用阻塞式调用的方式后者采用流式调用的方式。public interface IChatClient : IDisposable { TaskChatResponse GetResponseAsync( IEnumerableChatMessage messages, ChatOptions? options null, CancellationToken cancellationToken default); IAsyncEnumerableChatResponseUpdate GetStreamingResponseAsync( IEnumerableChatMessage messages, ChatOptions? options null, CancellationToken cancellationToken default); object? GetService(Type serviceType, object? serviceKey null); }GetResponseAsync和GetStreamingResponseAsync方法的参数除了表示一段对话历史的ChatMessage集合之外还可以接受一个ChatOptions对象来设置一些与当前对话相关的选项。ChatOptions将各大模型供应商OpenAI, Anthropic, Google等常用的参数进行了标准化。public class ChatOptions { public string? ConversationId { get; set; } public string? Instructions { get; set; } public float? Temperature { get; set; } public int? MaxOutputTokens { get; set; } public float? TopP { get; set; } public int? TopK { get; set; } public float? FrequencyPenalty { get; set; } public float? PresencePenalty { get; set; } public long? Seed { get; set; } public ReasoningOptions? Reasoning { get; set; } public ChatResponseFormat? ResponseFormat { get; set; } public string? ModelId { get; set; } public IListstring? StopSequences { get; set; } public bool? AllowMultipleToolCalls { get; set; } public ChatToolMode? ToolMode { get; set; } public IListAITool? Tools { get; set; } public bool? AllowBackgroundResponses{ get; set; } public ResponseContinuationToken? ContinuationToken{ get; set; } public FuncIChatClient, object?? RawRepresentationFactory { get; set; } public AdditionalPropertiesDictionary? AdditionalProperties { get; set; } }具体配置选项说明如下ConversationId对话ID用于将多个请求关联到同一个对话中Instructions对模型的系统指令或者系统提示词用于引导模型生成符合预期的响应Temperature控制生成文本的随机程度值越大生成的文本越随机值越小生成的文本越确定MaxOutputTokens生成文本的最大Token数量用于控制生成文本的长度TopP控制生成文本的多样性值越小生成的文本越集中在概率较高的选项上值越大生成的文本越分散TopK控制生成文本的多样性值表示在生成每个Token时考虑的候选Token数量值越小生成的文本越集中在概率较高的选项上值越大生成的文本越分散FrequencyPenalty控制生成文本中重复Token的惩罚程度值越大生成的文本中重复Token越少PresencePenalty控制生成文本中已经出现过的Token的惩罚程度值越大生成的文本中已经出现过的Token越少Seed随机数种子用于控制生成文本的随机性设置相同的种子可以得到相同的生成结果Reasoning推理选项用于控制模型在生成文本时的推理过程如是否启用链式思维、推理的深度等ResponseFormat响应格式用于指定模型生成的响应的格式如纯文本、JSON等。如果设置成具有某种格式的JSON Schema可以实现结构化输出ModelId模型ID用于指定使用哪个模型来生成响应StopSequences停止序列用于指定在生成文本时遇到这些序列就停止生成AllowMultipleToolCalls是否允许在生成响应的过程中调用多个工具ToolMode工具模式用于指定在生成响应时如何使用工具Tools工具列表用于指定在生成响应时可用的工具AllowBackgroundResponses是否允许生成后台响应ContinuationToken续订令牌用于在生成响应时继续之前的对话RawRepresentationFactory原始表示工厂用于生成原始表示对象AdditionalProperties附加属性字典用于存储额外的配置信息。1.1 ReasoningOptions设置推理配置选项的ReasoningOptions类型定义如下所示。它的Effort属性用于控制推理的努力程度Output属性用于控制推理输出的详细程度。ReasoningOptions可以帮助我们更好地控制模型在生成文本时的推理过程从而得到更符合预期的响应。public sealed class ReasoningOptions { public ReasoningEffort? Effort { get; set; } public ReasoningOutput? Output { get; set; } } public enum ReasoningEffort { None, Low, Medium, High, ExtraHigh } public enum ReasoningOutput { None, Summary, Full }1.2 ChatToolModeChatToolMode定义了AI模型在对话中如何对待和选择工具。你可以把它理解为给AI下达的工具使用指令。public class ChatToolMode { public static AutoChatToolMode Auto { get; } new AutoChatToolMode(); public static NoneChatToolMode None { get; } new NoneChatToolMode(); public static RequiredChatToolMode RequireAny { get; } new RequiredChatToolMode(null); public static RequiredChatToolMode RequireSpecific(string functionName) new RequiredChatToolMode(functionName); } public sealed class AutoChatToolMode : ChatToolMode { public override bool Equals(object? obj) public override int GetHashCode() } public sealed class NoneChatToolMode : ChatToolMode { public override bool Equals(object? obj)obj is NoneChatToolMode; public override int GetHashCode()typeof(NoneChatToolMode).GetHashCode(); } public sealed class RequiredChatToolMode : ChatToolMode { public string? RequiredFunctionName { get; } public RequiredChatToolMode(string? requiredFunctionName) { if (requiredFunctionName ! null) { Throw.IfNullOrWhitespace(requiredFunctionName, requiredFunctionName); } RequiredFunctionName requiredFunctionName; } public override bool Equals(object? obj) { if (obj is RequiredChatToolMode requiredChatToolMode) { return RequiredFunctionName requiredChatToolMode.RequiredFunctionName; } return false; } public override int GetHashCode()RequiredFunctionName?.GetHashCode(StringComparison.Ordinal) ?? typeof(RequiredChatToolMode).GetHashCode(); }ChatToolMode的四个静态属性返回的四个ChatToolMode对象分别表示四种工具使用模式Auto自动模式AI模型会根据对话的上下文自动决定是否使用工具以及使用哪个工具None无工具模式AI模型在生成响应时不会使用任何工具RequireAny要求使用任意工具模式AI模型在生成响应时必须使用至少一个工具RequireSpecific要求使用特定工具模式AI模型在生成响应时必须使用指定的工具2. DelegatingChatClientIChatClient管道的构建得益于如下这个DelegatingChatClient类。DelegatingChatClient实现了IChatClient接口并且持有一个InnerClient属性来引用管道中的下一个IChatClient对象。我们可以通过继承DelegatingChatClient来创建一个个的中间件组件在这些组件中我们可以在调用InnerClient的方法前后添加一些自定义的逻辑来对请求和响应进行处理从而实现对整个管道的控制和定制。public class DelegatingChatClient : IChatClient, IDisposable { protected IChatClient InnerClient { get; } protected DelegatingChatClient(IChatClient innerClient) InnerClient Throw.IfNull(innerClient, innerClient); public virtual TaskChatResponse GetResponseAsync(IEnumerableChatMessage messages, ChatOptions? options null, CancellationToken cancellationToken default) InnerClient.GetResponseAsync(messages, options, cancellationToken); public virtual IAsyncEnumerableChatResponseUpdate GetStreamingResponseAsync( IEnumerableChatMessage messages, ChatOptions? options null, CancellationToken cancellationToken default) InnerClient.GetStreamingResponseAsync(messages, options, cancellationToken); }我们可以通过继承DelegatingChatClient来创建一个自定义的ChatClient并通过重写GetResponseAsync和GetStreamingResponseAsync方法将调用请求状态给被封装的InnerClient同时在调用前后添加一些自定义的逻辑来处理请求和响应。实际上这就是中间件的一种实现方式这些DelegatingChatClient组成的委托链与中间件管道是一回事。通过这种方式我们可以在不修改原有IChatClient实现的基础上灵活地添加一些额外的功能如日志记录、性能监控、请求修改等从而增强整个IChatClientPipeline的功能和可定制性。2.1 IChatClient管道执行流程相面的程序很好的演示了将DelegatingChatClient作为IChatClient中间件。我们通过继承DelegatingChatClient创建了一个名为Middleware的中间件类在这个类中我们可以通过传入两个委托来分别处理请求和响应。在这个示例中我们创建了三个Middleware对象并将它们按照foo、bar、baz的顺序进行嵌套。每个Middleware对象在处理请求和响应时都会打印出相应的日志信息来展示它们的调用顺序。最后我们调用GetResponseAsync方法来触发整个IChatClientPipeline的执行并打印出最终的响应内容。using Microsoft.Extensions.AI; IChatClient chatClient new LLMChatClient(); chatClient new Middleware(chatClient, preHandler: (messages, options) { Console.WriteLine(baz.pre-handler); return ValueTask.FromResult(messages); }, postHandler: (response, options) { Console.WriteLine(baz.post-handler); return ValueTask.FromResult(response); }); chatClient new Middleware(chatClient, preHandler: (messages, options) { Console.WriteLine(bar.pre-handler); return ValueTask.FromResult(messages); }, postHandler: (response, options) { Console.WriteLine(bar.post-handler); return ValueTask.FromResult(response); }); chatClient new Middleware(chatClient, preHandler: (messages, options) { Console.WriteLine(foo.pre-handler); return ValueTask.FromResult(messages); }, postHandler: (response, options) { Console.WriteLine(foo.post-handler); return ValueTask.FromResult(response); }); var response await chatClient.GetResponseAsync([]); Console.WriteLine($response: {response.Messages.Single().Text}); class LLMChatClient : IChatClient { public void Dispose() { } public TaskChatResponse GetResponseAsync(IEnumerableChatMessage messages, ChatOptions? options null, CancellationToken cancellationToken default) Task.FromResult(new ChatResponse(new ChatMessage(role: ChatRole.Assistant, content: Hello world!))); public object? GetService(Type serviceType, object? serviceKey null) null; public IAsyncEnumerableChatResponseUpdate GetStreamingResponseAsync( IEnumerableChatMessage messages, ChatOptions? options null, CancellationToken cancellationToken default) throw new NotImplementedException(); } public class Middleware(IChatClient innerClient, FuncIEnumerableChatMessage, ChatOptions, ValueTaskIEnumerableChatMessage? preHandler null, FuncChatResponse, ChatOptions, ValueTaskChatResponse? postHandler null) : DelegatingChatClient(innerClient) { private readonly FuncIEnumerableChatMessage, ChatOptions, ValueTaskIEnumerableChatMessage? _preHandler preHandler; private readonly FuncChatResponse, ChatOptions, ValueTaskChatResponse? _postHandler postHandler; public override async TaskChatResponse GetResponseAsync(IEnumerableChatMessage messages, ChatOptions? options null, CancellationToken cancellationToken default) { messages _preHandler ! null ? await _preHandler.Invoke(messages, options!) : messages; var response await base.GetResponseAsync(messages, options, cancellationToken); if (_postHandler ! null) { response await _postHandler.Invoke(response, options!); } return response; } }输出foo.pre-handler bar.pre-handler baz.pre-handler baz.post-handler

相关新闻