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

MAF快速入门(17)用户智能体交互协议AG-UI(下)

1 什么是AG-UI Tools?

AG-UI Tools 是 AG-UI 的 工具系统,分为 Backend Tools 和 Frontend Tools,它们是AI Agent和外部世界交互的桥梁,让AI Agent能够执行实际操作,而不仅仅是生成文本。

Backend Tools

顾名思义,Backend Tools即后端工具,它在服务端执行,主要用于做一些类似数据库查询、API调用 或 敏感操作等用途,主要访问服务端资源,安全性要求较高。

何时需要使用Backend Tools?

  • 🔐 涉及敏感数据或 API 密钥

  • 💾 需要访问数据库或后端服务

  • ⚡ 计算密集型任务

  • 🔒 需要服务端验证和审计

Frontend Tools

顾名思义,Frontend Tools即前端工具,它在客户端执行,主要用于做一些类似GPS定位、剪贴板 或者 设备传感器等用途,主要访问客户端设备资源,安全性上仅需做客户端验证。

何时需要使用Frontend Tools?

  • 📍 需要访问设备功能(GPS、相机等)

  • 📋 需要读取本地资源(剪贴板、文件)

  • 🔑 涉及用户隐私数据

  • ⚡ 需要快速响应的本地操作

2 快速开始:前后端工具混合使用

假设我们有这样一个需求:我们向Agent询问“附近有什么好吃的餐厅”,AG-UI前端会使用前端工具获取当前定位信息,然后将问题和定位发给AG-UI服务端,服务端又调用服务端工具搜索附近的餐馆信息,最后由大模型整合结果输出最终信息。

整个调用流程如下图所示:

接下来,我们就一步一步完成一个AG-UI Tools示例应用涉及到的Server 和 Client。

AG-UI Tools Server

首先,我们创建一个ASP.NET Web应用,安装以下NuGet包:

Microsoft.Agents.AI.Hosting.AGUI.AspNetCore Microsoft.Agents.AI.OpenAI Microsoft.Extensions.AI.OpenAI

然后,就是整个示例的核心部分,我们一块一块来说:

(1)定义相关数据模型

说明:这里配置 JsonSerializerContext (JSON源生成器)主要用以提高序列化性能,确保在Native AOT环境下也能够正常序列化,即AOT友好。

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ // 📦 数据模型定义 // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ internal sealed class RestaurantSearchResult { public string Location { get; set; } = string.Empty; public double SearchRadius { get; set; } public int TotalResults { get; set; } public NearbyRestaurant[] Restaurants { get; set; } = []; } internal sealed class NearbyRestaurant { public string Name { get; set; } = string.Empty; public string Cuisine { get; set; } = string.Empty; public double Distance { get; set; } public double Rating { get; set; } public string Address { get; set; } = string.Empty; } internal sealed class RestaurantDetail { public string Name { get; set; } = string.Empty; public string Description { get; set; } = string.Empty; public string OpeningHours { get; set; } = string.Empty; public string PriceRange { get; set; } = string.Empty; public string[] RecommendedDishes { get; set; } = []; public string Phone { get; set; } = string.Empty; public bool HasParking { get; set; } public bool AcceptsReservation { get; set; } } // JSON 序列化上下文 [JsonSerializable(typeof(RestaurantSearchResult))] [JsonSerializable(typeof(NearbyRestaurant))] [JsonSerializable(typeof(RestaurantDetail))] internal sealed partial class MixedToolsJsonContext : JsonSerializerContext;

(2)定义backend tools

internal class BackendTools { // 🔧 餐厅搜索工具(后端执行) [Description("根据位置搜索附近的餐厅。Search for nearby restaurants based on location.")] public static RestaurantSearchResult SearchNearbyRestaurants( [Description("用户当前位置描述")] string location, [Description("搜索半径(公里)")] double radiusKm = 2.0, [Description("菜系偏好(可选)")] string? cuisinePreference = null) { Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine($"\n🔍 [后端工具] SearchNearbyRestaurants"); Console.WriteLine($" 📍 位置: {location}"); Console.WriteLine($" 📏 半径: {radiusKm} km"); Console.WriteLine($" 🍽️ 菜系偏好: {cuisinePreference ?? "不限"}"); Console.ResetColor(); // 模拟数据库查询 var restaurants = new List<NearbyRestaurant> { new() { Name = "老北京炸酱面", Cuisine = "北京菜", Distance = 0.5, Rating = 4.7, Address = $"{location}东路100号" }, new() { Name = "川香阁", Cuisine = "川菜", Distance = 0.8, Rating = 4.5, Address = $"{location}西路200号" }, new() { Name = "粤味轩", Cuisine = "粤菜", Distance = 1.2, Rating = 4.8, Address = $"{location}南路300号" }, new() { Name = "日式料理屋", Cuisine = "日本料理", Distance = 1.5, Rating = 4.6, Address = $"{location}北路400号" }, new() { Name = "烤匠", Cuisine = "川菜", Distance = 2.5, Rating = 4.8, Address = $"{location}天府四街银泰城5楼" } }; // 如果有菜系偏好,过滤结果 if (!string.IsNullOrEmpty(cuisinePreference)) { restaurants = restaurants .Where(r => r.Cuisine.Contains(cuisinePreference, StringComparison.OrdinalIgnoreCase)) .ToList(); } Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine($" ✅ 找到 {restaurants.Count} 家餐厅"); Console.ResetColor(); return new RestaurantSearchResult { Location = location, SearchRadius = radiusKm, TotalResults = restaurants.Count, Restaurants = restaurants.ToArray() }; } // 🔧 获取餐厅详情(后端执行) [Description("获取指定餐厅的详细信息,包括营业时间、菜单推荐等。Get detailed information about a specific restaurant.")] public static RestaurantDetail GetRestaurantDetail( [Description("餐厅名称")] string restaurantName) { Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine($"\n📖 [后端工具] GetRestaurantDetail"); Console.WriteLine($" 🏪 餐厅: {restaurantName}"); Console.ResetColor(); // 模拟数据库查询 var detail = new RestaurantDetail { Name = restaurantName, Description = $"{restaurantName}是一家知名的特色餐厅,拥有20年历史。", OpeningHours = "10:00 - 22:00", PriceRange = "人均 80-150 元", RecommendedDishes = ["招牌菜", "特色小吃", "主厨推荐"], Phone = "010-12345678", HasParking = true, AcceptsReservation = true }; Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine($" ✅ 获取成功"); Console.ResetColor(); return detail; } }

(3)创建应用并注册AG-UI服务

// Step0. Create WebApplication builder var builder = WebApplication.CreateBuilder(args); builder.Services.AddHttpClient().AddLogging(); // ⭐ 配置 JSON 序列化上下文 (用于数据模型的序列化) builder.Services.ConfigureHttpJsonOptions(options => options.SerializerOptions.TypeInfoResolverChain.Add(MixedToolsJsonContext.Default)); // Step1. Register AG-UI services builder.Services.AddAGUI();

(4)加载可用backend tools并创建Agent

var app = builder.Build(); // Step3. Create AI Agent var jsonOptions = app.Services.GetRequiredService<IOptions<JsonOptions>>().Value; AITool[] backendTools = [ AIFunctionFactory.Create(BackendTools.SearchNearbyRestaurants, serializerOptions: jsonOptions.SerializerOptions), AIFunctionFactory.Create(BackendTools.GetRestaurantDetail, serializerOptions: jsonOptions.SerializerOptions) ]; Console.WriteLine("🔧 已注册后端工具:"); foreach (var tool in backendTools) { Console.WriteLine($" • {tool.Name} (服务端执行)"); } Console.WriteLine("📝 前端工具将由客户端注册"); var agent = chatClient.AsIChatClient() .AsAIAgent( name: "MixedToolsAssistant", instructions: """ 你是一个智能餐厅推荐助手,具备以下能力: 🌍 位置感知: - 可以获取用户的当前位置(使用 GetUserLocation 工具) - 当用户说"附近"、"周围"时,先获取位置 🍽️ 餐厅推荐: - 使用 SearchNearbyRestaurants 搜索附近餐厅 - 使用 GetRestaurantDetail 获取详细信息 🎯 使用流程: 1. 如果用户问"附近有什么餐厅",先调用 GetUserLocation 获取位置 2. 然后调用 SearchNearbyRestaurants 搜索 3. 如果用户想了解某家餐厅,调用 GetRestaurantDetail 请用中文友好地回答用户。 """, tools: backendTools); Console.WriteLine("✅ AI Agent 创建成功");

(5)映射AGUI端点并启动应用:

// Step5. Mapping AG-UI Endpoints app.MapAGUI("/", agent); app.Run();

AG-UI Tools Client

首先,我们创建一个控制台应用,安装以下NuGet包:

Microsoft.Agents.AI.AGUI Microsoft.Agents.AI

然后,定义frontend tools:

internal class FrontendTools { // 🔧 获取用户位置(前端工具 - 只有客户端能访问 GPS) [Description("获取用户当前的地理位置信息。这是客户端设备功能,用于获取 GPS 位置。Get the user's current location from GPS.")] public static string GetUserLocation() { Console.ForegroundColor = ConsoleColor.Magenta; Console.WriteLine("\n📍 [前端工具] GetUserLocation"); Console.WriteLine(" 🔄 正在访问设备 GPS..."); Console.ResetColor(); // 模拟 GPS 获取延迟 Thread.Sleep(800); // 模拟不同的位置(随机选择) string[] locations = [ "北京市朝阳区三里屯", "上海市浦东新区陆家嘴", "广州市天河区珠江新城", "深圳市南山区科技园", "成都市高新区天府软件园" ]; Random random = new(); string location = locations[random.Next(locations.Length)]; Console.ForegroundColor = ConsoleColor.Magenta; Console.WriteLine($" ✅ GPS 定位成功: {location}"); Console.ResetColor(); return location; } // 🔧 获取用户偏好设置(前端工具 - 访问本地存储) [Description("获取用户保存的餐饮偏好设置。Get user's saved dining preferences.")] public static string GetUserPreferences() { Console.ForegroundColor = ConsoleColor.Magenta; Console.WriteLine("\n⚙️ [前端工具] GetUserPreferences"); Console.WriteLine(" 🔄 读取本地偏好设置..."); Console.ResetColor(); // 模拟读取本地存储 Thread.Sleep(300); string preferences = "偏好菜系: 川菜、粤菜; 价位: 中等; 忌口: 无"; Console.ForegroundColor = ConsoleColor.Magenta; Console.WriteLine($" ✅ 读取成功: {preferences}"); Console.ResetColor(); return preferences; } }

随后,我们创建AG-UI Client 和 AI Agent:

...... var serverEndpoint = config.GetValue<string>("AGUI_SERVER_URL") ?? "https://localhost:8443"; // 加载前端工具 AITool[] frontendTools = [ AIFunctionFactory.Create(FrontendTools.GetUserLocation), AIFunctionFactory.Create(FrontendTools.GetUserPreferences) ]; Console.WriteLine("🔧 已注册前端工具:"); foreach (var tool in frontendTools) { Console.WriteLine($" • {tool.Name} (客户端执行)"); } Console.WriteLine("📝 后端工具由服务端提供 (SearchNearbyRestaurants, GetRestaurantDetail)"); Console.WriteLine(); // Step1. Create HTTP Client using HttpClient httpClient = new() { Timeout = TimeSpan.FromSeconds(180) // 延长超时,因为可能有多个工具调用 }; // Step2. Create AG-UI Client var chatClient = new AGUIChatClient(httpClient, serverEndpoint); // Step3. Create AI Agent var agent = chatClient.AsAIAgent( name: "agui-client", description: "AG-UI 混合工具客户端", tools: frontendTools); // 👈 注册前端工具

最后,准备开始对话:

// Step4. Prepare for Conversation var session = await agent.GetNewSessionAsync(); var messages = new List<ChatMessage>() { new ChatMessage(ChatRole.System, """ 你是一个智能餐厅推荐助手。 你可以使用多种工具来帮助用户找到合适的餐厅。 当用户问"附近有什么餐厅"时,先获取他们的位置,再搜索餐厅。 """) }; Console.WriteLine("💬 开始对话(输入 :q 或 quit 退出)\n"); while (true) { Console.Write("👤 用户: "); string? message = Console.ReadLine(); if (string.IsNullOrWhiteSpace(message)) { Console.WriteLine("⚠️ 消息不能为空,请重新输入。"); continue; } if (message is ":q" or "quit") { Console.WriteLine("\n👋 再见!"); break; } // 添加用户消息 messages.Add(new ChatMessage(ChatRole.User, message)); // 统计工具调用 int frontendToolCalls = 0; int backendToolCalls = 0; bool isFirstUpdate = true; List<string> toolCallChain = []; Console.WriteLine(); Console.WriteLine("━━━━━━━━━━━━━━ 开始处理 ━━━━━━━━━━━━━━━"); // 流式接收响应 Console.Write("🤖 助手: "); await foreach (var update in agent.RunStreamingAsync(messages, session)) { var chatUpdate = update.AsChatResponseUpdate(); if (isFirstUpdate) { Console.ForegroundColor = ConsoleColor.DarkGray; Console.WriteLine($"[🔄 Run Started - Thread: {chatUpdate.ConversationId?.Substring(0, 8)}...]"); Console.ResetColor(); isFirstUpdate = false; } foreach (AIContent content in update.Contents) { switch (content) { case TextContent textContent: Console.ForegroundColor = ConsoleColor.Cyan; Console.Write(textContent.Text); Console.ResetColor(); break; case FunctionCallContent functionCall: // 判断是前端还是后端工具 var isFrontendTool = frontendTools.Any(t => t.Name == functionCall.Name); if (isFrontendTool) { frontendToolCalls++; Console.ForegroundColor = ConsoleColor.Magenta; Console.WriteLine($"\n📱 [前端工具调用] {functionCall.Name}"); } else { backendToolCalls++; Console.ForegroundColor = ConsoleColor.Yellow; Console.WriteLine($"\n🖥️ [后端工具调用] {functionCall.Name}"); // 显示后端工具参数 if (functionCall.Arguments != null) { Console.WriteLine(" 📝 参数:"); foreach (var kvp in functionCall.Arguments) { Console.WriteLine($" • {kvp.Key}: {kvp.Value}"); } } } Console.ResetColor(); toolCallChain.Add(isFrontendTool ? $"📱{functionCall.Name}" : $"🖥️{functionCall.Name}"); break; case FunctionResultContent functionResult: // 后端工具结果显示 if (!frontendTools.Any(t => toolCallChain.LastOrDefault()?.Contains(t.Name) ?? false)) { Console.ForegroundColor = ConsoleColor.Green; string resultPreview = functionResult.Result?.ToString() ?? "null"; if (resultPreview.Length > 150) { resultPreview = resultPreview.Substring(0, 150) + "..."; } Console.WriteLine($" ✅ 结果: {resultPreview}"); Console.ResetColor(); } break; case ErrorContent errorContent: Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine($"\n❌ 错误: {errorContent.Message}"); Console.ResetColor(); break; } } } // 显示工具调用链 Console.WriteLine(); Console.WriteLine("━━━━━━━━━━━━━━ 处理完成 ━━━━━━━━━━━━━━━"); Console.ForegroundColor = ConsoleColor.DarkGray; Console.WriteLine($"[✅ Run Finished]"); Console.WriteLine($" 📱 前端工具调用: {frontendToolCalls}"); Console.WriteLine($" 🖥️ 后端工具调用: {backendToolCalls}"); if (toolCallChain.Count > 0) { Console.WriteLine($" 🔗 调用链: {string.Join(" → ", toolCallChain)}"); } Console.ResetColor(); Console.WriteLine(); } Console.WriteLine("👋 再见!"); Console.ReadKey();

3 测试结果

测试场景1

用户:附近有什么好吃的餐厅?

调用链路:📱GetUserLocation → 🖥️SearchNearbyRestaurants

测试场景2

用户:根据我的偏好推荐餐厅

调用链路:📱GetUserPreferences → 📱GetUserLocation → 🖥️SearchNearbyRestaurants

测试场景3

用户:烤匠这家店怎么样?

调用链路:🖥️GetRestaurantDetail

可以看到,Agent可以自动协调工具调用顺序,前后端工具能够实现协同工作,同时我们也能看到完整的工具调用链路。

4 小结

本文介绍了AG-UI Tools即AG-UI的工具系统,演示了一个结合前后端工具使用的混合模式案例,通过案例可以了解如何在AG-UI中调用工具并观察调用链。

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

相关文章:

  • VINS_Fusion轨迹评估实战:如何用evo工具搞定MH_01_easy数据集测试(附完整代码修改指南)
  • 想留存QQ空间记忆?这款Python工具让备份更简单
  • 从大模型到智能体:核心逻辑全解析
  • 2026年隐形车衣GEO优化服务商深度测评:效果与口碑的选型指南 - 小白条111
  • 赶考状元AI学伴的优势是什么:不止于解题,更在于育人
  • 如何高效保存抖音无水印视频?开源工具抖音下载器的创新方案
  • LFM2.5-1.2B-Thinking-GGUF快速部署:JDK1.8环境下的Java客户端集成
  • BCompare_Keygen:解决Beyond Compare 5评估期限制的本地化密钥生成方案
  • StructBERT文本相似度模型SolidWorks技术文档智能检索系统
  • CRNN OCR文字识别镜像实战:路牌文档识别案例分享
  • 【古代言情小说推荐】被弃妃子逆袭记:《锁茜香》
  • 百度网盘直链解析终极指南:免费突破限速,实现3倍下载加速
  • 开源项目管理工具GanttProject:专业级项目规划与协作解决方案
  • 如何根据用户所在地区,自动跳转到不同的落地页?
  • ccmusic-database/music_genre故障排查:音频格式/损坏/路径错误解决方案
  • GESP 一级考 编程题详解
  • 零基础能当陪诊师吗?北京守嘉+国开大培训,手把手带你入行 - 品牌排行榜单
  • 餐饮系统毕业设计入门指南:从零搭建高内聚低耦合的点餐后端
  • OpenClaw配置优化:让QwQ-32B响应速度提升30%的秘诀
  • 2026汉正街女装批发新格局:五家核心服务商深度测评与趋势洞察 - 2026年企业推荐榜
  • JHU-计算机科学统计学笔记-全-
  • ViGEmBus虚拟游戏手柄驱动终极指南:Windows内核级控制器模拟深度解析
  • 2026年第一季度,如何甄选四川专业麦冬头供应商?深度盘点与科学决策指南 - 2026年企业推荐榜
  • ICLR 2026 | MindTS:首个多模态时间序列异常检测模型
  • OpenClaw对接ollama模型:GLM-4.7-Flash接口配置详解
  • 揭秘携程任我行卡高价回收技巧,快速变现不是梦! - 团团收购物卡回收
  • 桌游卡牌制作终极指南:用CardEditor快速生成专业级卡牌 [特殊字符]
  • 前瞻2026:四川无公害麦冬核心供应商综合实力解析 - 2026年企业推荐榜
  • 手把手教你用FastAPI封装FireRed-OCR:告别手动上传,实现批量文档解析
  • 2026年汽车车灯改装服务推荐:郑州市金水区光普汽车装饰用品商行,改大灯/透镜/升级一站式服务 - 品牌推荐官