如何让智能体具备执行能力并集成工具?
摘要:工具集成:让智能体具备执行能力 前言 在前两篇文章中,我们创建了一个知识丰富的技术支持智能体。它能够理解问题、提供建议,甚至创建工单。但是,这个智能体还停留在"说"的阶段——它知道应该做什
工具集成:让智能体具备执行能力
前言
在前两篇文章中,我们创建了一个知识丰富的技术支持智能体。它能够理解问题、提供建议,甚至创建工单。但是,这个智能体还停留在"说"的阶段——它知道应该做什么,但无法真正执行任何操作。
现实世界中的智能体需要具备执行能力。一个真正有用的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性能优化
"真正的智能不在于知道多少,而在于能够使用工具有效地解决问题。"
