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

从工具函数中注入消息

下面的演示程序展示了如何通过MessageInjectingChatClient来实现工具函数中注入消息的能力。这个程序模拟了一个银行转账的业务场景,转账的工具函数是TransferMoney。如代码片段所示,我们从当前Agent执行上下文(AIAgent.CurrentRunContext)中获取当前的Session对象,并检查Session的StateBag中是否存在一个键为UserConfirmed的值来判断用户是否已经确认过转账。如果用户没有确认,工具函数会通过MessageInjectingChatClientEnqueueMessages方法来注入一条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方法调用IChatClientGetService方法获取MessageInjectingChatClient对象就是此对象。最后基于构建的ChatClient管道创建了Agent对象。

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

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

2. 查看注入的消息

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

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); } }

我们调用ChatClientBuilderUse方法将这个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一般位于FunctionInvokingChatClientPerServiceCallChatHistoryPersistingChatClient之间(如下所示),这一点非常重要,它决定了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的消息存储

http://www.jsqmd.com/news/1092149/

相关文章:

  • Python自动化工具:5分钟快速创建Gmail账号的完整指南
  • 【保姆级教程】小米6X编译LineageOS 20.0完整指南(Android 13)
  • 从高斯光学到凸轮曲线:机械补偿式三组元连续变焦系统设计全流程解析
  • 错误码429频发?OpenAI官方文档未明说的限流逻辑,如何用3种动态退避策略实现零失败调用,
  • HarmonyOS NEXT 实战:RelativeContainer 百分比/比例定位全面指南
  • 二维数组知识
  • DIN EN ISO 5084
  • 3D Web 服务器环境搭建
  • Android 17 新特性全览
  • SpringBoot自动装配底层全流程
  • Agent的诞生(二):让模型开始调用工具
  • AES与Serpent对称加密算法:原理、对比与Python/Android/Qt实战
  • 为什么你用光模块测试FPGA IBERT不通
  • OneMore插件终极指南:如何用160+个强大功能彻底改造你的OneNote体验
  • GESP4级C++考试语法知识(一、指针(9、指针与函数调用)
  • 特殊上位机权限管理方案
  • AI插件开发实战:基于JS脚本的Illustrator色标生成器设计与实现
  • Matlab2020b 从零到一:一份详尽的个人安装与避坑指南
  • 今天发现采用360下载wps比网页版快多了,下载的是同一个版本。-但是重新安装了wps,还是有些卡顿,稍微好了一丢丢,这个到底什么原因?
  • 三角洲S10裂变新赛季上线[特殊字符]Mac玩家再也不用错过核电站新图!
  • SMUDebugTool完全指南:专业级AMD Ryzen处理器硬件调试工具深度解析
  • C# CAD二次开发消息提示技巧
  • 如何免费解锁Wand专业版:告别订阅费的终极指南
  • 抖音无水印下载器:三步免费保存高清视频的完整指南
  • TUSB4020B评估模块拆解:从电源设计到信号完整性,打造稳定USB集线器
  • 【技巧揭秘】告别LaTeX插图虚线阴影:从Visio到PDF的完美转换链
  • 开发了一个浏览器新标签页,欢迎大家体验
  • 如何通过R3nzSkin项目掌握游戏内存修改技术:5个实战应用场景解析
  • 从习题到实战:TCP拥塞控制与窗口机制深度解析
  • LangGraph 架构避坑:智能体职责拆分与流式回调透传机制剖析