如何让智能体具备执行能力并集成工具?

摘要:工具集成:让智能体具备执行能力 前言 在前两篇文章中,我们创建了一个知识丰富的技术支持智能体。它能够理解问题、提供建议,甚至创建工单。但是,这个智能体还停留在"说"的阶段——它知道应该做什
工具集成:让智能体具备执行能力 前言 在前两篇文章中,我们创建了一个知识丰富的技术支持智能体。它能够理解问题、提供建议,甚至创建工单。但是,这个智能体还停留在"说"的阶段——它知道应该做什么,但无法真正执行任何操作。 现实世界中的智能体需要具备执行能力。一个真正有用的AI助手应该能够: 获取实时信息:查询天气、股票价格、新闻动态 操作系统资源:读写文件、执行命令、管理进程 访问外部服务:调用API、查询数据库、发送通知 集成业务系统:与ERP、CRM、OA等系统交互 这就是工具(Tools) 的概念。通过工具集成,智能体不再仅仅是"思考者",而是成为真正的"执行者"。 今天,我们将深入探索Agent Framework的工具系统,让智能体具备实际的操作能力。 一、工具系统架构解析 1.1 工具的核心概念 在Agent Framework中,工具是智能体可以调用的功能模块。每个工具包含: 1. 工具定义(Tool Definition) 名称:唯一标识符 描述:工具功能的自然语言描述 参数:输入参数的JSON Schema 返回值:输出数据的类型定义 2. 工具实现(Tool Implementation) 实际的执行逻辑 错误处理机制 性能优化考虑 安全权限控制 3. 工具注册(Tool Registration) 将工具暴露给智能体 配置工具可见性 设置调用权限 1.2 工具调用的完整流程 用户问题 → 智能体分析 → 选择工具 → 执行工具 → 处理结果 → 返回用户 ↓ ↓ ↓ ↓ ↓ ↓ 自然语言 理解意图 匹配最适合 调用代码 解析输出 自然语言 ↓ ↓ ↓ ↓ ↓ 上下文 工具描述 参数验证 错误处理 格式化 这个过程的关键在于: 智能体自动选择工具:基于问题描述自动匹配合适的工具 参数自动提取:从自然语言中提取工具所需的参数 结果自动处理:将工具返回的数据转换为自然语言回答 二、内置工具使用指南 2.1 内置工具概览 Agent Framework提供了一系列内置工具,覆盖常见的使用场景: 工具类别 示例工具 用途 网络工具 HttpGetTool HTTP GET请求 文件工具 FileReadTool 读取文件内容 计算工具 CalculatorTool 数学计算 时间工具 DateTimeTool 获取当前时间 系统工具 ProcessTool 执行系统命令 2.2 使用内置工具的基本模式 // BasicToolUsage.cs using Microsoft.Agents.AI; using Microsoft.Agents.AI.Tools; public class BasicToolUsage { public async Task DemonstrateBuiltinTools() { // 创建带有工具的智能体 var agent = CreateAgentWithTools(); // 示例1:获取当前时间 var timeResponse = await agent.RunAsync("现在是什么时间?"); Console.WriteLine($"时间查询: {timeResponse}"); // 示例2:简单计算 var calcResponse = await agent.RunAsync("计算 125 乘以 38 等于多少?"); Console.WriteLine($"计算: {calcResponse}"); // 示例3:文件操作(需要权限) var fileResponse = await agent.RunAsync("读取当前目录下的README.md文件"); Console.WriteLine($"文件读取: {fileResponse}"); } private IAIAgent CreateAgentWithTools() { // 创建工具集合 var tools = new ToolCollection(); // 添加内置工具 tools.Add(new DateTimeTool()); // 时间工具 tools.Add(new CalculatorTool()); // 计算工具 tools.Add(new HttpGetTool()); // HTTP GET工具 // 创建带工具的智能体 return _client .GetResponsesClient("gpt-4o") .AsAIAgent( name: "ToolAssistant", instructions: "你是一个能够使用各种工具的助手。", tools: tools ); } } 2.3 工具参数和返回值 每个工具都有明确的参数定义和返回值类型: // 查看工具定义 var dateTimeTool = new DateTimeTool(); Console.WriteLine($"工具名称: {dateTimeTool.Name}"); Console.WriteLine($"工具描述: {dateTimeTool.Description}"); Console.WriteLine($"参数定义: {dateTimeTool.Parameters}"); Console.WriteLine($"返回值类型: {dateTimeTool.ReturnType}"); 输出示例: 工具名称: get_current_time 工具描述: 获取当前日期和时间 参数定义: {} 返回值类型: string 三、自定义工具开发实战 3.1 场景设计:天气预报工具 让我们创建一个实用的天气预报工具。这个工具需要: 根据城市名称查询天气 支持多种天气服务提供商 提供详细的天气信息 处理各种错误情况 3.2 工具定义类 // WeatherForecastTool.cs using Microsoft.Agents.AI.Tools; using System.Text.Json; [ToolDefinition( Name = "get_weather_forecast", Description = "获取指定城市的天气预报信息", Parameters = """ { "type": "object", "properties": { "city": { "type": "string", "description": "城市名称,例如:北京、上海、广州" }, "days": { "type": "integer", "description": "预报天数,1-7天", "default": 3 } }, "required": ["city"] } """, ReturnType = "WeatherForecast" )] public class WeatherForecastTool : ITool { // 天气服务配置 private readonly WeatherServiceConfig _config; private readonly HttpClient _httpClient; public WeatherForecastTool(WeatherServiceConfig config) { _config = config; _httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(30) }; } public async Task<object> ExecuteAsync( object parameters, IToolContext context, CancellationToken cancellationToken) { try { // 解析参数 var paramDict = parameters as IDictionary<string, object>; var city = paramDict?["city"]?.ToString() ?? throw new ArgumentException("城市参数不能为空"); var days = paramDict?.ContainsKey("days") == true ? Convert.ToInt32(paramDict["days"]) : 3; // 验证参数 if (days < 1 || days > 7) throw new ArgumentException("预报天数必须在1-7天之间"); // 构建请求URL(示例使用和风天气API) var url = $"{_config.BaseUrl}/weather/forecast?city={Uri.EscapeDataString(city)}&days={days}&key={_config.ApiKey}"; // 发送请求 var response = await _httpClient.GetAsync(url, cancellationToken); response.EnsureSuccessStatusCode(); // 解析响应 var json = await response.Content.ReadAsStringAsync(cancellationToken); var weatherData = JsonSerializer.Deserialize<WeatherResponse>(json) ?? throw new InvalidOperationException("无法解析天气数据"); // 转换为工具返回格式 return new WeatherForecast { City = city, ForecastDays = days, CurrentTemperature = weatherData.Current.Temp, Condition = weatherData.Current.Condition, Forecast = weatherData.Forecast .Take(days) .Select(f => new DailyForecast { Date = f.Date, MaxTemp = f.MaxTemp, MinTemp = f.MinTemp, Condition = f.Condition, Precipitation = f.Precipitation }) .ToList() }; } catch (HttpRequestException ex) { throw new ToolExecutionException($"网络请求失败: {ex.Message}", ex); } catch (JsonException ex) { throw new ToolExecutionException($"数据解析失败: {ex.Message}", ex); } catch (Exception ex) { throw new ToolExecutionException($"天气查询失败: {ex.Message}", ex); } } // 工具元数据 public string Name => "get_weather_forecast"; public string Description => "获取指定城市的天气预报信息"; public object Parameters => new { type = "object", properties = new { city = new { type = "string", description = "城市名称" }, days = new { type = "integer", description = "预报天数", minimum = 1, maximum = 7 } }, required = new[] { "city" } }; public string ReturnType => "WeatherForecast"; } // 数据模型 public class WeatherServiceConfig { public string BaseUrl { get; set; } = "https://api.qweather.com/v7"; public string ApiKey { get; set; } = string.Empty; } public class WeatherResponse { public CurrentWeather Current { get; set; } = new(); public List<ForecastDay> Forecast { get; set; } = new(); } public class CurrentWeather { public double Temp { get; set; } public string Condition { get; set; } = string.Empty; } public class ForecastDay { public string Date { get; set; } = string.Empty; public double MaxTemp { get; set; } public double MinTemp { get; set; } public string Condition { get; set; } = string.Empty; public double Precipitation { get; set; } } public class WeatherForecast { public string City { get; set; } = string.Empty; public int ForecastDays { get; set; } public double CurrentTemperature { get; set; } public string Condition { get; set; } = string.Empty; public List<DailyForecast> Forecast { get; set; } = new(); } public class DailyForecast { public string Date { get; set; } = string.Empty; public double MaxTemp { get; set; } public double MinTemp { get; set; } public string Condition { get; set; } = string.Empty; public double Precipitation { get; set; } } 3.3 数据库查询工具 另一个常见场景是数据库操作。让我们创建一个安全的数据库查询工具: // DatabaseQueryTool.cs using Microsoft.Agents.AI.Tools; using System.Data.Common; [ToolDefinition( Name = "query_database", Description = "执行安全的数据库查询(只读操作)", Parameters = """ { "type": "object", "properties": { "query": { "type": "string", "description": "SQL查询语句(仅支持SELECT)" }, "limit": { "type": "integer", "description": "结果集最大行数", "default": 100 } }, "required": ["query"] } """, ReturnType = "QueryResult" )] public class DatabaseQueryTool : ITool { private readonly DbConnection _connection; private readonly ILogger<DatabaseQueryTool> _logger; public DatabaseQueryTool( DbConnection connection, ILogger<DatabaseQueryTool> logger) { _connection = connection; _logger = logger; } public async Task<object> ExecuteAsync( object parameters, IToolContext context, CancellationToken cancellationToken) { // 安全检查 var paramDict = parameters as IDictionary<string, object>; var query = paramDict?["query"]?.ToString() ?? throw new ArgumentException("查询语句不能为空"); var limit = paramDict?.ContainsKey("limit") == true ? Convert.ToInt32(paramDict["limit"]) : 100; // 验证SQL语句 if (!IsSafeQuery(query)) throw new SecurityException("查询语句包含不安全操作"); // 执行查询 await using var command = _connection.CreateCommand(); command.CommandText = query; // 添加查询超时 command.CommandTimeout = 30; try { await _connection.OpenAsync(cancellationToken); await using var reader = await command.ExecuteReaderAsync(cancellationToken); var result = new QueryResult { Success = true, RowCount = 0, Columns = new List<string>(), Rows = new List<Dictionary<string, object>>() }; // 读取列信息 for (int i = 0; i < reader.FieldCount; i++) { result.Columns.Add(reader.GetName(i)); } // 读取数据 while (await reader.ReadAsync(cancellationToken) && result.RowCount < limit) { var row = new Dictionary<string, object>(); for (int i = 0; i < reader.FieldCount; i++) { var value = reader.GetValue(i); row[reader.GetName(i)] = value is DBNull ? null : value; } result.Rows.Add(row); result.RowCount++; } _logger.LogInformation("数据库查询成功,返回 {RowCount} 行", result.RowCount); return result; } catch (DbException ex) { _logger.LogError(ex, "数据库查询失败"); return new QueryResult { Success = false, Error = $"数据库错误: {ex.Message}" }; } finally { await _connection.CloseAsync(); } } private bool IsSafeQuery(string query) { var upperQuery = query.ToUpperInvariant(); // 只允许SELECT查询 if (!upperQuery.TrimStart().StartsWith("SELECT")) return false; // 禁止危险操作 var dangerousKeywords = new[] { "INSERT", "UPDATE", "DELETE", "DROP", "TRUNCATE", "CREATE", "ALTER", "EXEC", "EXECUTE", "GRANT", "REVOKE", "MERGE", "--", "/*", "*/", ";" }; foreach (var keyword in dangerousKeywords) { if (upperQuery.Contains(keyword)) return false; } return true; } } public class QueryResult { public bool Success { get; set; } public string? Error { get; set; } public int RowCount { get; set; } public List<string> Columns { get; set; } = new(); public List<Dictionary<string, object>> Rows { get; set; } = new(); } 3.4 文件操作工具 文件操作是另一个重要场景,但需要严格控制权限: // SafeFileTool.cs using Microsoft.Agents.AI.Tools; public class SafeFileTool : ITool { private readonly string _allowedBasePath; private readonly ILogger<SafeFileTool> _logger; public SafeFileTool(string allowedBasePath, ILogger<SafeFileTool> logger) { _allowedBasePath = Path.GetFullPath(allowedBasePath); _logger = logger; // 确保基础路径存在 Directory.CreateDirectory(_allowedBasePath); } public async Task<object> ExecuteAsync( object parameters, IToolContext context, CancellationToken cancellationToken) { var paramDict = parameters as IDictionary<string, object>; var operation = paramDict?["operation"]?.ToString() ?? throw new ArgumentException("操作类型不能为空"); return operation.ToLower() switch { "read" => await ReadFileAsync(paramDict, cancellationToken), "list" => await ListDirectoryAsync(paramDict, cancellationToken), "info" => await GetFileInfoAsync(paramDict, cancellationToken), _ => throw new ArgumentException($"不支持的操作: {operation}") }; } private async Task<object> ReadFileAsync( IDictionary<string, object> parameters, CancellationToken cancellationToken) { var filePath = parameters["path"]?.ToString() ?? throw new ArgumentException("文件路径不能为空"); var fullPath = GetSafePath(filePath); // 验证文件类型(只允许文本文件) var extension = Path.GetExtension(fullPath).ToLower(); var allowedExtensions = new[] { ".txt", ".md", ".json", ".xml", ".csv", ".log" }; if (!allowedExtensions.Contains(extension)) throw new SecurityException($"不允许读取 {extension} 类型的文件"); // 验证文件大小(限制1MB) var fileInfo = new FileInfo(fullPath); if (fileInfo.Length > 1024 * 1024) // 1MB throw new InvalidOperationException("文件太大,超过1MB限制"); _logger.LogInformation("读取文件: {FilePath}", fullPath); return await File.ReadAllTextAsync(fullPath, cancellationToken); } private Task<object> ListDirectoryAsync( IDictionary<string, object> parameters, CancellationToken cancellationToken) { var dirPath = parameters.ContainsKey("path") ? parameters["path"]?.ToString() : "."; var fullPath = GetSafePath(dirPath); _logger.LogInformation("列出目录: {DirectoryPath}", fullPath); var result = new { Path = fullPath, Files = Directory.GetFiles(fullPath) .Select(f => new { Name = Path.GetFileName(f), Size = new FileInfo(f).Length, Modified = File.GetLastWriteTime(f) }), Directories = Directory.GetDirectories(fullPath) .Select(d => new { Name = Path.GetFileName(d), FileCount = Directory.GetFiles(d).Length }) }; return Task.FromResult<object>(result); } private Task<object> GetFileInfoAsync( IDictionary<string, object> parameters, CancellationToken cancellationToken) { var filePath = parameters["path"]?.ToString() ?? throw new ArgumentException("文件路径不能为空"); var fullPath = GetSafePath(filePath); var fileInfo = new FileInfo(fullPath); return Task.FromResult<object>(new { fileInfo.Name, fileInfo.FullName, fileInfo.Length, fileInfo.CreationTime, fileInfo.LastWriteTime, fileInfo.LastAccessTime, fileInfo.Extension }); } private string GetSafePath(string relativePath) { // 防止目录遍历攻击 var fullPath = Path.GetFullPath( Path.Combine(_allowedBasePath, relativePath)); if (!fullPath.StartsWith(_allowedBasePath, StringComparison.OrdinalIgnoreCase)) throw new SecurityException($"访问路径超出允许范围: {relativePath}"); return fullPath; } public string Name => "file_operations"; public string Description => "安全的文件操作(只读)"; public object Parameters => new { type = "object", properties = new { operation = new { type = "string", description = "操作类型: read, list, info", @enum = new[] { "read", "list", "info" } }, path = new { type = "string", description = "文件或目录路径(相对路径)" } }, required = new[] { "operation" } }; public string ReturnType => "object"; } 四、工具组合与复杂场景 4.1 多工具协同工作 真正的力量来自工具的组合。让我们创建一个能够处理复杂场景的智能体: // ComplexAgentDemo.cs public class ComplexAgentDemo { private readonly IAIAgent _agent; public ComplexAgentDemo( WeatherServiceConfig weatherConfig, DbConnection dbConnection, ILoggerFactory loggerFactory) { // 创建工具集合 var tools = new ToolCollection(); // 添加天气工具 tools.Add(new WeatherForecastTool(weatherConfig)); // 添加数据库工具 tools.Add(new DatabaseQueryTool( dbConnection, loggerFactory.CreateLogger<DatabaseQueryTool>())); // 添加文件工具 tools.Add(new SafeFileTool( Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "data"), loggerFactory.CreateLogger<SafeFileTool>())); // 添加内置工具 tools.Add(new DateTimeTool()); tools.Add(new CalculatorTool()); // 创建智能体 _agent = CreateAgent(tools); } private IAIAgent CreateAgent(ToolCollection tools) { const string instructions = """ 你是一个多功能助手,能够使用各种工具帮助用户解决问题。 可用工具包括: 1. 天气预报 - 查询城市天气 2. 数据库查询 - 执行安全的SQL查询 3. 文件操作 - 读取文本文件、查看目录 4. 计算器 - 进行数学计算 5. 时间查询 - 获取当前时间 使用原则: 1. 优先使用最合适的工具 2. 复杂任务可以分步使用多个工具 3. 处理工具返回的错误信息 4. 为用户提供清晰、有用的回答 安全注意事项: 1. 不要执行危险的文件操作 2. 不要执行非SELECT的数据库查询 3. 保护用户隐私信息 """; return _client .GetResponsesClient("gpt-4o") .AsAIAgent( name: "MultiToolAssistant", instructions: instructions, tools: tools ); } public async Task<string> HandleComplexRequest(string request) { Console.WriteLine($"处理请求: {request}"); try { var response = await _agent.RunAsync(request); // 记录工具使用情况 LogToolUsage(response); return response; } catch (Exception ex) { _logger.LogError(ex, "处理请求失败"); return $"抱歉,处理请求时出现错误: {ex.Message}"; } } // 模拟场景1:旅行规划 public async Task<string> PlanTravel(string destination) { var request = $""" 我计划去{destination}旅行,请帮我: 1. 查询当地的天气预报 2. 如果是大城市,查询有什么著名景点(从数据库中) 3. 根据天气和景点信息,给出旅行建议 """; return await HandleComplexRequest(request); } // 模拟场景2:数据分析 public async Task<string> AnalyzeSalesData(string period) { var request = $""" 请分析{period}的销售数据: 1. 从数据库查询销售记录 2. 计算总销售额和平均订单金额 3. 将结果保存到文件中 4. 提供分析报告 """; return await HandleComplexRequest(request); } } 4.2 工具链和流程控制 有时我们需要多个工具按顺序执行: // ToolChainExecutor.cs public class ToolChainExecutor { private readonly Dictionary<string, ITool> _tools; public ToolChainExecutor(IEnumerable<ITool> tools) { _tools = tools.ToDictionary(t => t.Name, t => t); } public async Task<string> ExecuteChainAsync(string chainDefinition) { // 解析工具链定义 var steps = ParseChainDefinition(chainDefinition); var results = new List<object>(); foreach (var step in steps) { try { if (!_tools.TryGetValue(step.ToolName, out var tool)) throw new InvalidOperationException($"找不到工具: {step.ToolName}"); // 执行工具 var result = await tool.ExecuteAsync( step.Parameters, new DefaultToolContext(), CancellationToken.None); results.Add(result); // 如果某个步骤失败,可以决定是否继续 if (IsFailedResult(result) && step.IsCritical) break; } catch (Exception ex) { // 根据策略处理错误 if (step.ContinueOnError) results.Add(new { Error = ex.Message }); else throw; } } // 汇总结果 return FormatResults(results); } private List<ToolStep> ParseChainDefinition(string definition) { // 简化实现,实际应该使用更复杂的解析 return definition.Split(';') .Select(step => step.Trim()) .Where(step => !string.IsNullOrEmpty(step)) .Select(step => { var parts = step.Split('|'); return new ToolStep { ToolName = parts[0], Parameters = ParseParameters(parts.Length > 1 ? parts[1] : "{}"), IsCritical = parts.Length > 2 && bool.Parse(parts[2]), ContinueOnError = parts.Length > 3 && bool.Parse(parts[3]) }; }) .ToList(); } private object ParseParameters(string paramString) { return JsonSerializer.Deserialize<Dictionary<string, object>>(paramString) ?? new Dictionary<string, object>(); } private bool IsFailedResult(object result) { // 根据实际结果类型判断 return result.ToString()?.Contains("Error") == true || result.ToString()?.Contains("失败") == true; } private string FormatResults(List<object> results) { return JsonSerializer.Serialize(results, new JsonSerializerOptions { WriteIndented = true }); } private class ToolStep { public string ToolName { get; set; } = string.Empty; public object Parameters { get; set; } = new(); public bool IsCritical { get; set; } = true; public bool ContinueOnError { get; set; } = false; } } 五、安全考虑与最佳实践 5.1 工具安全性设计原则 最小权限原则:每个工具只拥有完成任务所需的最小权限 输入验证:对所有输入进行严格的验证和清理 输出过滤:敏感信息在返回前进行过滤 访问控制:根据用户身份限制工具访问 审计日志:记录所有工具调用详情 5.2 实施安全检查 // SecurityToolWrapper.cs public class SecurityToolWrapper : ITool { private readonly ITool _innerTool; private readonly IUserContext _userContext; private readonly ILogger _logger; public SecurityToolWrapper( ITool innerTool, IUserContext userContext, ILogger logger) { _innerTool = innerTool; _userContext = userContext; _logger = logger; } public async Task<object> ExecuteAsync( object parameters, IToolContext context, CancellationToken cancellationToken) { // 1. 身份验证检查 if (!_userContext.IsAuthenticated) throw new UnauthorizedAccessException("用户未认证"); // 2. 权限检查 if (!HasPermission(_innerTool.Name)) throw new SecurityException($"没有执行 {_innerTool.Name} 的权限"); // 3. 输入安全检查 var safeParameters = SanitizeParameters(parameters); // 4. 记录审计日志 _logger.LogInformation( "工具调用: 用户={UserId}, 工具={ToolName}, 参数={Parameters}", _userContext.UserId, _innerTool.Name, JsonSerializer.Serialize(safeParameters)); try { // 5. 执行原始工具 var result = await _innerTool.ExecuteAsync( safeParameters, context, cancellationToken); // 6. 输出安全检查 var safeResult = SanitizeResult(result); // 7. 记录成功日志 _logger.LogInformation( "工具执行成功: 工具={ToolName}, 用户={UserId}", _innerTool.Name, _userContext.UserId); return safeResult; } catch (Exception ex) { // 8. 记录错误日志(避免泄露敏感信息) _logger.LogError(ex, "工具执行失败: 工具={ToolName}, 用户={UserId}", _innerTool.Name, _userContext.UserId); // 返回安全的错误信息 return new { Error = "执行失败,请联系管理员" }; } } private bool HasPermission(string toolName) { // 基于角色的权限检查 var userRoles = _userContext.Roles; var toolPermissions = GetToolPermissions(toolName); return userRoles.Any(role => toolPermissions.Contains(role)); } private object SanitizeParameters(object parameters) { // 参数清理逻辑 // 例如:移除脚本标签、限制长度等 return parameters; } private object SanitizeResult(object result) { // 结果清理逻辑 // 例如:屏蔽敏感数据、限制数据大小等 return result; } private IEnumerable<string> GetToolPermissions(string toolName) { // 从配置中获取工具权限 return _configuration.GetSection($"Tools:{toolName}:Permissions") .Get<List<string>>() ?? new List<string>(); } // 其他属性和方法... } 六、性能优化与监控 6.1 工具性能监控 // InstrumentedTool.cs public class InstrumentedTool : ITool { private readonly ITool _innerTool; private readonly IMetrics _metrics; public InstrumentedTool(ITool innerTool, IMetrics metrics) { _innerTool = innerTool; _metrics = metrics; } public async Task<object> ExecuteAsync( object parameters, IToolContext context, CancellationToken cancellationToken) { var stopwatch = Stopwatch.StartNew(); var success = false; try { _metrics.IncrementCounter($"tool.{Name}.calls"); var result = await _innerTool.ExecuteAsync( parameters, context, cancellationToken); success = true; return result; } finally { stopwatch.Stop(); // 记录执行时间 _metrics.RecordHistogram( $"tool.{Name}.duration", stopwatch.ElapsedMilliseconds); // 记录成功率 _metrics.IncrementCounter( $"tool.{Name}.{(success ? "success" : "failure")}"); } } // 其他属性和方法... } 6.2 工具缓存策略 // CachedTool.cs public class CachedTool : ITool { private readonly ITool _innerTool; private readonly IDistributedCache _cache; private readonly TimeSpan _defaultCacheDuration; public CachedTool( ITool innerTool, IDistributedCache cache, TimeSpan defaultCacheDuration) { _innerTool = innerTool; _cache = cache; _defaultCacheDuration = defaultCacheDuration; } public async Task<object> ExecuteAsync( object parameters, IToolContext context, CancellationToken cancellationToken) { // 生成缓存键 var cacheKey = GenerateCacheKey(parameters); // 尝试从缓存获取 var cachedResult = await _cache.GetStringAsync(cacheKey, cancellationToken); if (cachedResult != null) { return JsonSerializer.Deserialize<object>(cachedResult); } // 执行原始工具 var result = await _innerTool.ExecuteAsync(parameters, context, cancellationToken); // 缓存结果 var resultJson = JsonSerializer.Serialize(result); await _cache.SetStringAsync( cacheKey, resultJson, new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = GetCacheDuration(parameters) }, cancellationToken); return result; } private string GenerateCacheKey(object parameters) { var parametersJson = JsonSerializer.Serialize(parameters); var hash = SHA256.HashData(Encoding.UTF8.GetBytes(parametersJson)); return $"tool:{Name}:{Convert.ToBase64String(hash)}"; } private TimeSpan GetCacheDuration(object parameters) { // 根据参数决定缓存时间 // 例如:天气数据缓存1小时,计算数据不缓存 return _innerTool.Name switch { "get_weather_forecast" => TimeSpan.FromHours(1), "query_database" => TimeSpan.FromMinutes(5), _ => _defaultCacheDuration }; } // 其他属性和方法... } 七、完整示例:智能数据分析助手 让我们把所有内容整合起来,创建一个完整的数据分析助手: // DataAnalysisAssistant.cs public class DataAnalysisAssistant { private readonly IAIAgent _agent; public DataAnalysisAssistant( DbConnection dbConnection, ILoggerFactory loggerFactory, WeatherServiceConfig weatherConfig) { // 创建工具集合 var tools = new ToolCollection(); // 数据库工具 tools.Add(new DatabaseQueryTool( dbConnection, loggerFactory.CreateLogger<DatabaseQueryTool>())); // 天气工具 tools.Add(new WeatherForecastTool(weatherConfig)); // 文件工具 tools.Add(new SafeFileTool("data", loggerFactory.CreateLogger<SafeFileTool>())); // 计算工具 tools.Add(new CalculatorTool()); // 创建智能体 _agent = CreateAgent(tools); } private IAIAgent CreateAgent(ToolCollection tools) { const string instructions = """ 你是一个数据分析专家,专门帮助用户分析数据、生成报告。 可用能力: 1. 数据库查询:执行SQL查询获取数据 2. 数据计算:进行统计分析、聚合计算 3. 天气数据:获取天气信息用于相关性分析 4. 文件操作:保存分析结果到文件 分析流程: 1. 理解用户的分析需求 2. 设计查询和计算方法 3. 执行分析获取结果 4. 生成易懂的分析报告 报告格式要求: 1. 包含关键指标和数据 2. 使用可视化建议(图表类型) 3. 提供业务洞见和建议 4. 列出数据来源和分析方法 """; return _client .GetResponsesClient("gpt-4o") .AsAIAgent( name: "DataAnalysisExpert", instructions: instructions, tools: tools ); } public async Task<string> AnalyzeSalesTrends(string timePeriod) { var request = $""" 请分析{timePeriod}的销售趋势: 1. 查询每日销售额数据 2. 计算周增长率和月增长率 3. 识别销售高峰期和低谷期 4. 分析天气对销售的影响(如适用) 5. 生成详细的趋势分析报告 """; return await _agent.RunAsync(request); } public async Task<string> CompareRegions(string region1, string region2) { var request = $""" 请对比分析{region1}和{region2}两个地区的销售表现: 1. 分别查询两个地区的销售数据 2. 对比销售额、订单量、客单价等关键指标 3. 分析地区差异的可能原因 4. 提供针对性的改进建议 """; return await _agent.RunAsync(request); } } 八、测试与部署 8.1 工具单元测试 // WeatherForecastToolTests.cs public class WeatherForecastToolTests { [Fact] public async Task ExecuteAsync_ValidCity_ReturnsWeatherData() { // 准备 var config = new WeatherServiceConfig { BaseUrl = "https://test-api.com", ApiKey = "test-key" }; var tool = new WeatherForecastTool(config); var parameters = new Dictionary<string, object> { ["city"] = "北京", ["days"] = 3 }; // 执行(使用模拟的HttpClient) var result = await tool.ExecuteAsync( parameters, Mock.Of<IToolContext>(), CancellationToken.None); // 验证 Assert.NotNull(result); var forecast = result as WeatherForecast; Assert.NotNull(forecast); Assert.Equal("北京", forecast.City); Assert.Equal(3, forecast.ForecastDays); } [Fact] public void ExecuteAsync_EmptyCity_ThrowsArgumentException() { var tool = new WeatherForecastTool(new WeatherServiceConfig()); var parameters = new Dictionary<string, object> { ["city"] = "" }; var exception = await Assert.ThrowsAsync<ArgumentException>(() => tool.ExecuteAsync(parameters, Mock.Of<IToolContext>(), CancellationToken.None)); Assert.Contains("城市参数不能为空", exception.Message); } } 8.2 集成测试 // ToolIntegrationTests.cs public class ToolIntegrationTests : IClassFixture<TestFixture> { private readonly TestFixture _fixture; public ToolIntegrationTests(TestFixture fixture) { _fixture = fixture; } [Fact] public async Task Agent_WithTools_CanHandleComplexRequest() { // 准备 var agent = _fixture.CreateAgentWithTools(); // 执行 var response = await agent.RunAsync( "查询北京的天气,然后计算明天的温度比今天高多少度"); // 验证 Assert.NotNull(response); Assert.NotEmpty(response); Assert.Contains("北京", response); Assert.Contains("天气", response); } } 8.3 部署配置 # appsettings.json 中的工具配置 { "Tools": { "WeatherForecastTool": { "BaseUrl": "https://api.qweather.com/v7", "ApiKey": "${WEATHER_API_KEY}", "CacheDuration": "01:00:00" }, "DatabaseQueryTool": { "ConnectionString": "${DB_CONNECTION_STRING}", "QueryTimeout": 30, "MaxRows": 1000 }, "FileOperations": { "AllowedBasePath": "./data", "MaxFileSize": 1048576, "AllowedExtensions": [".txt", ".md", ".json", ".csv"] } } } 九、总结与下一步 通过本文的学习,我们已经掌握了Agent Framework工具系统的核心: ✅ 理解工具架构:工具定义、实现、注册的完整流程 ✅ 创建自定义工具:天气预报、数据库查询、文件操作等实用工具 ✅ 实现工具组合:多工具协同处理复杂任务 ✅ 保障工具安全:权限控制、输入验证、审计日志 ✅ 优化工具性能:缓存策略、监控指标、错误处理 ✅ 完整示例实现:数据分析助手等实际应用 关键收获: 工具是智能体从"思考"到"执行"的关键桥梁 良好的工具设计需要考虑安全性、性能和易用性 工具组合能够创造无限的可能性 生产环境中的工具需要完善的监控和错误处理 下一篇文章预告: 在第四篇文章中,我们将探索多轮对话与状态管理。智能体不仅需要执行任务,还需要在复杂的对话流程中保持状态,理解上下文,提供连贯的用户体验。 我们将学习: 对话状态管理和持久化 上下文窗口优化策略 长对话的记忆管理 复杂业务流程的状态机实现 这将让我们的智能体真正具备"对话智能",能够在复杂的业务场景中提供持续、一致的服务。 实践建议: 从简单的工具开始,逐步增加复杂性 为每个工具编写完整的单元测试 在生产环境中逐步引入工具,监控性能表现 建立工具开发规范和安全审查流程 相关资源: Agent Framework工具系统文档 工具安全最佳实践 .NET HttpClient性能优化 "真正的智能不在于知道多少,而在于能够使用工具有效地解决问题。"