尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

从工具函数中注入消息

从工具函数中注入消息
📅 发布时间:2026/7/8 16:46:27

面的演示程序展示了如何通过MessageInjectingChatClient来实现工具函数中注入消息的能力。这个程序模拟了一个银行转账的业务场景,转账的工具函数是TransferMoney。如代码片段所示,我们从当前Agent执行上下文(AIAgent.CurrentRunContext)中获取当前的Session对象,并检查Session的StateBag中是否存在一个键为UserConfirmed的值来判断用户是否已经确认过转账。如果用户没有确认,工具函数会通过MessageInjectingChatClient的EnqueueMessages方法来注入一条Assistant消息到对话历史中,提示用户存在欺诈风险并要求提供手机验证码。与此同时,工具函数会返回一条消息告知用户转账指令已提交至系统缓冲区,等待合规审查。

using Azure; using Microsoft.Agents.AI; using Microsoft.Extensions.AI; using Microsoft.Extensions.DependencyInjection; using OpenAI; dotenv.net.DotEnv.Load(); var model = Environment.GetEnvironmentVariable("MODEL")!; var apiKey = Environment.GetEnvironmentVariable("API_KEY")!; var endpoint = Environment.GetEnvironmentVariable("OPENAI_URL")!; var agent = new OpenAIClient( credential: new AzureKeyCredential(apiKey), options: new OpenAIClientOptions { Endpoint = new Uri(endpoint) }) .GetChatClient(model: model) .AsIChatClient() .AsBuilder() .UseMessageInjection() .Build() .AsAIAgent(tools: [AIFunctionFactory.Create(TransferMoney, name:nameof(TransferMoney))]); var prompt = "从账号`4242 4242 4242 4242` 转账100块到账号 `5555 5555 5555 4444`"; var response = await agent.RunAsync(prompt); Console.WriteLine(response); Console.WriteLine($"\n{new string('-', 100)}\n"); var session = await agent.CreateSessionAsync(); session.StateBag.SetValue("UserConfirmed", (object)true); response = await agent.RunAsync(prompt, session); Console.WriteLine(response); static string TransferMoney(string from, string to, decimal ammount) { var session = AIAgent.CurrentRunContext?.Session ?? throw new InvalidOperationException("No active session found."); if (session.StateBag.TryGetValue<object>(key: "UserConfirmed", out var confirmed) && (confirmed is bool b && b)) { return $"成功从`{from}`转账`{ammount}`元到`{to}`账户。"; } else { var chatClient = (AIAgent.CurrentRunContext.Agent as ChatClientAgent)?.ChatClient ?? throw new InvalidOperationException("Not a ChatClientAgent."); var injectingChatClient = chatClient.GetService<MessageInjectingChatClient>() ?? throw new InvalidOperationException("Underlying chat client is not an InjectingChatClient."); var content = "风控系统检测到该笔交易存在欺诈风险, 你必须向用户说明情况,要求其提供手机验证码,并在未验证前拒绝完成转账。"; injectingChatClient.EnqueueMessages(session, [new ChatMessage(ChatRole.Assistant, content)]); return "转账指令已提交至系统缓冲区,等待合规审查。"; } }

在调用AsIChatClient扩展方法将OpenAIClient转换成IChatClient对象之后,我们调用了AsBuilder扩展方法生成了构建ChatClient管道的ChatClientBuilder对象,并在此基础上通过调用UseMessageInjection扩展方法注册了MessageInjectingChatClient中间件来启用消息注入的能力。TransferMoney方法调用IChatClient的GetService方法获取MessageInjectingChatClient对象就是此对象。最后基于构建的ChatClient管道创建了Agent对象。

我们两次调用Agent的RunAsync方法来测试工具函数中注入消息的功能。第一次调用时,我们没有在Session的StateBag中设置UserConfirmed键值对,所以工具函数会注入一条Assistant消息来提示用户存在欺诈风险,并且返回一条消息告知用户转账指令已提交至系统缓冲区,等待合规审查。第二次调用时,我们先创建了一个Session对象,并在StateBag中设置了UserConfirmed键值对为true,表示用户已经确认过转账了。这一次工具函数就不会注入提示风险的消息,而是直接返回一条成功转账的消息。如下是两次调用的输出结果:

为保障账户安全,我无法在此渠道收集或处理手机验证码等一次性敏感信息。 由于系统检测到该笔交易存在风险,请您通过官方银行 App 或拨打银行客服热线,在安全的验证流程中完成身份确认和转账操作。 在未通过官方安全验证前,本次转账将不会继续执行。 ---------------------------------------------------------------------------------------------------- ✅ 转账成功! 已从账户 **4242 4242 4242 4242** 转出 **100 元** 至账户 **5555 5555 5555 4444**。 如需继续操作,请告诉我 😊

2. 查看注入的消息

为了查看工具函数注入的消息,我们定义了如下这个MessageTrackingChatClient中间件。在它重写的GetResponseAsync方法中,我们遍历当前请求的消息列表,并根据消息内容的不同类型(FunctionCallContent、FunctionResultContent、TextContent等)来格式化输出(对于我们的例子,每个消息有且只有一个内容)。

class MessageTrackingChatClient(IChatClient innerClient) : DelegatingChatClient(innerClient) { public override Task<ChatResponse> GetResponseAsync( IEnumerable<ChatMessage> messages, ChatOptions? options = null, CancellationToken cancellationToken = default) { foreach (var message in messages) { var role = message.Role; var content = message.Contents.Single(); var line = content switch { FunctionCallContent functionCallContent => $"[{role}]function-call: {functionCallContent.Name}", FunctionResultContent functionResultContent => $"[{role}]function-result: {functionResultContent.Result}", TextContent textContent => $"[{role}]{textContent.Text}", _ => $"[{role}]{content.ToString() ?? string.Empty}" }; Console.WriteLine(line); } Console.WriteLine(); return base.GetResponseAsync(messages, options, cancellationToken); } }

我们调用ChatClientBuilder的Use方法将这个MessageTrackingChatClient中间件注册到管道上,并且置于注册的MessageInjectingChatClient中间件的后面,这样就能在MessageInjectingChatClient注入消息之后看到这些消息被添加到请求消息列表中了。

using Azure; using Microsoft.Agents.AI; using Microsoft.Extensions.AI; using Microsoft.Extensions.DependencyInjection; using OpenAI; dotenv.net.DotEnv.Load(); var model = Environment.GetEnvironmentVariable("MODEL")!; var apiKey = Environment.GetEnvironmentVariable("API_KEY")!; var endpoint = Environment.GetEnvironmentVariable("OPENAI_URL")!; var agent = new OpenAIClient( credential: new AzureKeyCredential(apiKey), options: new OpenAIClientOptions { Endpoint = new Uri(endpoint) }) .GetChatClient(model: model) .AsIChatClient() .AsBuilder() .UseMessageInjection() .Use(inner => new MessageTrackingChatClient(inner)) .Build() .AsAIAgent(tools: [AIFunctionFactory.Create(TransferMoney, name:nameof(TransferMoney))]); var prompt = "从账号`4242 4242 4242 4242` 转账100块到账号 `5555 5555 5555 4444`"; await agent.RunAsync(prompt);

输出结果:

[user]从账号`4242 4242 4242 4242` 转账100块到账号 `5555 5555 5555 4444` [user]从账号`4242 4242 4242 4242` 转账100块到账号 `5555 5555 5555 4444` [assistant]function-call: TransferMoney [tool]function-result: 转账指令已提交至系统缓冲区,等待合规审查。 [assistant]风控系统检测到该笔交易存在欺诈风险, 你必须向用户说明情况,要求其提供手机验证码,并在未验证前拒绝完成转账。

输出的最后一条消息正是工具函数中注入的消息,说明MessageInjectingChatClient确实成功地将这条消息添加到了请求消息列表中,并且在后续调用LLM时被包含在输入上下文里了。为了保证对话历史具有合法的结构,注入的消息会放在承载工具函数调用结果的Tool消息之后。

3. MessageInjectingChatClient

在了解了MessageInjectingChatClient的作用和基本编程模式之后,我们继续介绍MAF针对这个ChatClient中间件的设计和实现。

3.1 在ChatClient管道中的位置

MessageInjectingChatClient一般位于FunctionInvokingChatClient与PerServiceCallChatHistoryPersistingChatClient之间(如下所示),这一点非常重要,它决定了MessageInjectingChatClient中间件在每个ReAct循环中都会被执行。如果开启了针对每个ReAct循环的及时存档,注入的消息会被PerServiceCallChatHistoryPersistingChatClient捕获并存储到ChatHistoryMemoryProvider中,这样就能让Agent在后续的ReAct循环中基于这些注入的消息进行推理了。

[外部请求] => FunctionInvokingChatClient => MessageInjectingChatClient => PerServiceCallChatHistoryPersistingChatClient => LLM

这个特定的拓扑结构,说明了以下关键底座逻辑:

  • 工具函数具备改写认知的能力:由于FunctionInvokingChatClient处于MessageInjectingChatClient的上游,决定了:
    • 工具执行的副作用可被捕获:当FunctionInvokingChatClient触发并执行某个工具函数时,如果该工具内部触发了前文提到的风控逻辑、反思逻辑或上下文切换,它排队的临时消息正处于FunctionInvokingChatClient的处理边界之内。
    • 下游管道立即可见:工具函数产生的注入消息,能立刻在向下传递给PerServiceCallChatHistoryPersistingChatClient之前被消费并合并。
  • 注入的消息是临时干预,还是永久记忆:MessageInjectingChatClient位于PerServiceCallChatHistoryPersistingChatClient的上游,这个顺序界定了注入消息的生命周期:
    • 被持久化组件捕获:当MessageInjectingChatClient将排队的消息动态拼接到当前的对话历史后,这些新组合的消息会原封不动地流向底部的PerServiceCallChatHistoryPersistingChatClient。
    • 实现“单次服务调用”的存档:这意味着,在工具函数中注入的消息,会被当做本次生命周期的一部分,一并写入对话历史。在后续的用户多轮对话中,注入的消息会变成不可分割的永久记忆,而不是一次性的临时缓存;

MessageInjectingChatClient所处的位置说明了MAF将消息注入视为一种连接动态运行时(Tools/Agent 决策)与静态持久层(Database/Session)的管道桥梁。如果把MessageInjectingChatClient挪到FunctionInvokingChatClient之前,工具函数内部就失去了操作注入客户端的上下文权限;如果把它挪到PerServiceCallChatHistoryPersistingChatClient之后,注入的消息就只能直达大模型,而无法在数据库中留下任何历史存档。目前这个位置,是支持工具内隐式风控、自我反思闭环的黄金分水岭。

3.2 基于Session的消息存储

MessageInjectingChatClient将注入的消息存储在当前Session的StateBag中,这样就能保证这些消息在当前Session的生命周期内都是可见的了。具体存储的是一个List<ChatMessage>对象,对应的键为MessageInjectingChatClient.PendingInjectedMessages。如下所示的是用来注入消息列表的EnqueueMessages方法的定义。

public sealed class MessageInjectingChatClient : DelegatingChatClient { public void EnqueueMessages(AgentSession session, IEnumerable<ChatMessage> messages) { var queue = GetOrCreateQueue(session); lock (queue) { foreach (var message in messages) { queue.Add(message); } } } private static List<ChatMessage> GetOrCreateQueue(AgentSession session) { if (session.StateBag.TryGetValue<List<ChatMessage>>("MessageInjectingChatClient.PendingInjectedMessages", out var queue)) { return queue!; }

相关新闻

  • 基于ADS127L11与dsPIC33FJ的高精度数据采集系统设计
  • MATLAB版OCT光学成像仿真工具集:从光源建模到B-scan图像一键生成
  • STM32与TLE 6208-6G实现直流电机精确控制方案

最新新闻

  • Fastjson 反序列化漏洞实战排查:3 种检测方法与 2 种应急缓解脚本
  • iwebsec靶场第8关源码解析:preg_match过滤缺陷与5种绕过方案对比
  • Apache 2.4 安全加固:3步关闭TRACE/TRACK方法,修复CVE-2003-1567等12个漏洞
  • CVE-2017-12615漏洞修复与配置审计:从1个高危参数到5项安全加固实践
  • Uni-app 的真相:它不是跨端框架,而是中国移动互联网的“方言翻译器“
  • YOLOv5/v8/v11/v12 4版本车牌检测模型对比:mAP 40.6%的YOLO12n实测分析

日新闻

  • PROPKA 3深度解析:蛋白质pKa预测的实战指南与算法原理
  • 微信小程序 globalData 监听:基于 Object.defineProperty 的 3 种实现方案对比
  • MySQL 8.0 数据清洗实战:3类异常值识别与 UPDATE/DELETE 批量处理

周新闻

  • 基于YOLOv12的番茄成熟度智能检测系统开发
  • 终极RimWorld模组管理指南:用RimSort告别模组冲突烦恼
  • AI Agent框架开发:从理论到实践的完整指南

月新闻

  • 2026年6月公司网站搭建最新热门渠道测评:四大低成本/零代码平台对比+避坑
  • 【Linux】Linux arm 编译QT程序,出现expected “}“报错
  • 【MATLAB例程】四基站二维AOA定位与距离辅助增强对比仿真。基于角度观测和测距修正的固定目标平面定位精度分析

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号