查看: 47|回复: 0

工具集成:让智能体具备执行能力

[复制链接]

1

主题

0

回帖

0

积分

热心网友

金币
0
阅读权限
220
精华
0
威望
0
贡献
0
在线时间
0 小时
注册时间
2010-6-8
发表于 2026-3-12 19:45:00 | 显示全部楼层 |阅读模式

工具集成:让智能体具备执行能力

前言

在前两篇文章中,我们创建了一个知识丰富的技术支持智能体。它能够理解问题、提供建议,甚至创建工单。但是,这个智能体还停留在"说"的阶段——它知道应该做什么,但无法真正执行任何操作。

现实世界中的智能体需要具备执行能力。一个真正有用的AI助手应该能够:

  1. 获取实时信息:查询天气、股票价格、新闻动态
  2. 操作系统资源:读写文件、执行命令、管理进程
  3. 访问外部服务:调用API、查询数据库、发送通知
  4. 集成业务系统:与ERP、CRM、OA等系统交互

这就是工具(Tools) 的概念。通过工具集成,智能体不再仅仅是"思考者",而是成为真正的"执行者"。

今天,我们将深入探索Agent Framework的工具系统,让智能体具备实际的操作能力。

一、工具系统架构解析

1.1 工具的核心概念

在Agent Framework中,工具是智能体可以调用的功能模块。每个工具包含:

1. 工具定义(Tool Definition)

  • 名称:唯一标识符
  • 描述:工具功能的自然语言描述
  • 参数:输入参数的JSON Schema
  • 返回值:输出数据的类型定义

2. 工具实现(Tool Implementation)

  • 实际的执行逻辑
  • 错误处理机制
  • 性能优化考虑
  • 安全权限控制

3. 工具注册(Tool Registration)

  • 将工具暴露给智能体
  • 配置工具可见性
  • 设置调用权限

1.2 工具调用的完整流程

用户问题 → 智能体分析 → 选择工具 → 执行工具 → 处理结果 → 返回用户
     ↓          ↓          ↓         ↓         ↓         ↓
自然语言  理解意图  匹配最适合  调用代码  解析输出  自然语言
            ↓          ↓         ↓         ↓         ↓
        上下文     工具描述   参数验证  错误处理  格式化

这个过程的关键在于:

  1. 智能体自动选择工具:基于问题描述自动匹配合适的工具
  2. 参数自动提取:从自然语言中提取工具所需的参数
  3. 结果自动处理:将工具返回的数据转换为自然语言回答

二、内置工具使用指南

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 场景设计:天气预报工具

让我们创建一个实用的天气预报工具。这个工具需要:

  1. 根据城市名称查询天气
  2. 支持多种天气服务提供商
  3. 提供详细的天气信息
  4. 处理各种错误情况

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 工具安全性设计原则

  1. 最小权限原则:每个工具只拥有完成任务所需的最小权限
  2. 输入验证:对所有输入进行严格的验证和清理
  3. 输出过滤:敏感信息在返回前进行过滤
  4. 访问控制:根据用户身份限制工具访问
  5. 审计日志:记录所有工具调用详情

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}ermissions")
            .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工具系统的核心:

  1. 理解工具架构:工具定义、实现、注册的完整流程
  2. 创建自定义工具:天气预报、数据库查询、文件操作等实用工具
  3. 实现工具组合:多工具协同处理复杂任务
  4. 保障工具安全:权限控制、输入验证、审计日志
  5. 优化工具性能:缓存策略、监控指标、错误处理
  6. 完整示例实现:数据分析助手等实际应用

关键收获:

  • 工具是智能体从"思考"到"执行"的关键桥梁
  • 良好的工具设计需要考虑安全性、性能和易用性
  • 工具组合能够创造无限的可能性
  • 生产环境中的工具需要完善的监控和错误处理

下一篇文章预告:
在第四篇文章中,我们将探索多轮对话与状态管理。智能体不仅需要执行任务,还需要在复杂的对话流程中保持状态,理解上下文,提供连贯的用户体验。

我们将学习:

  • 对话状态管理和持久化
  • 上下文窗口优化策略
  • 长对话的记忆管理
  • 复杂业务流程的状态机实现

这将让我们的智能体真正具备"对话智能",能够在复杂的业务场景中提供持续、一致的服务。


实践建议:

  1. 从简单的工具开始,逐步增加复杂性
  2. 为每个工具编写完整的单元测试
  3. 在生产环境中逐步引入工具,监控性能表现
  4. 建立工具开发规范和安全审查流程

相关资源:

  • Agent Framework工具系统文档
  • 工具安全最佳实践
  • .NET HttpClient性能优化

"真正的智能不在于知道多少,而在于能够使用工具有效地解决问题。"



来源:https://www.cnblogs.com/fanshaoO/p/19709754
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

相关侵权、举报、投诉及建议等,请发 E-mail:qiongdian@foxmail.com

Powered by Discuz! X5.0 © 2001-2026 Discuz! Team.

在本版发帖返回顶部