大家好,我是Edison。
最近我一直在跟着圣杰的《.NET+AI智能体开发进阶》课程学习MAF开发多智能体工作流,我强烈推荐你也上车跟我一起出发!
上一篇,我们初步学习了AG-UI。本篇,我们来了解AG-UI Tools 以及 实现一个前后端工具混合使用的案例。
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中调用工具并观察调用链。
示例源码
GitHub: https://github.com/EdisonTalk/MAFD
参考资料
圣杰,《.NET + AI 智能体开发进阶》(推荐指数:★★★★★)
Microsoft Learn,《Agent Framework Tutorials》

作者:爱迪生
出处:https://edisontalk.cnblogs.com
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。