麦丘 發表於 2026-3-12 19:45:00

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

<h1 id="工具集成让智能体具备执行能力">工具集成:让智能体具备执行能力</h1>
<h2 id="前言">前言</h2>
<p>在前两篇文章中,我们创建了一个知识丰富的技术支持智能体。它能够理解问题、提供建议,甚至创建工单。但是,这个智能体还停留在"说"的阶段——它知道应该做什么,但无法真正执行任何操作。</p>
<p>现实世界中的智能体需要具备<strong>执行能力</strong>。一个真正有用的AI助手应该能够:</p>
<ol>
<li><strong>获取实时信息</strong>:查询天气、股票价格、新闻动态</li>
<li><strong>操作系统资源</strong>:读写文件、执行命令、管理进程</li>
<li><strong>访问外部服务</strong>:调用API、查询数据库、发送通知</li>
<li><strong>集成业务系统</strong>:与ERP、CRM、OA等系统交互</li>
</ol>
<p>这就是<strong>工具(Tools)</strong> 的概念。通过工具集成,智能体不再仅仅是"思考者",而是成为真正的"执行者"。</p>
<p>今天,我们将深入探索Agent Framework的工具系统,让智能体具备实际的操作能力。</p>
<h2 id="一工具系统架构解析">一、工具系统架构解析</h2>
<h3 id="11-工具的核心概念">1.1 工具的核心概念</h3>
<p>在Agent Framework中,工具是智能体可以调用的功能模块。每个工具包含:</p>
<p><strong>1. 工具定义(Tool Definition)</strong></p>
<ul>
<li>名称:唯一标识符</li>
<li>描述:工具功能的自然语言描述</li>
<li>参数:输入参数的JSON Schema</li>
<li>返回值:输出数据的类型定义</li>
</ul>
<p><strong>2. 工具实现(Tool Implementation)</strong></p>
<ul>
<li>实际的执行逻辑</li>
<li>错误处理机制</li>
<li>性能优化考虑</li>
<li>安全权限控制</li>
</ul>
<p><strong>3. 工具注册(Tool Registration)</strong></p>
<ul>
<li>将工具暴露给智能体</li>
<li>配置工具可见性</li>
<li>设置调用权限</li>
</ul>
<h3 id="12-工具调用的完整流程">1.2 工具调用的完整流程</h3>
<pre><code>用户问题 → 智能体分析 → 选择工具 → 执行工具 → 处理结果 → 返回用户
   ↓          ↓          ↓         ↓         ↓         ↓
自然语言理解意图匹配最适合调用代码解析输出自然语言
            ↓          ↓         ↓         ↓         ↓
      上下文   工具描述   参数验证错误处理格式化
</code></pre>
<p>这个过程的关键在于:</p>
<ol>
<li><strong>智能体自动选择工具</strong>:基于问题描述自动匹配合适的工具</li>
<li><strong>参数自动提取</strong>:从自然语言中提取工具所需的参数</li>
<li><strong>结果自动处理</strong>:将工具返回的数据转换为自然语言回答</li>
</ol>
<h2 id="二内置工具使用指南">二、内置工具使用指南</h2>
<h3 id="21-内置工具概览">2.1 内置工具概览</h3>
<p>Agent Framework提供了一系列内置工具,覆盖常见的使用场景:</p>
<table>
<thead>
<tr>
<th>工具类别</th>
<th>示例工具</th>
<th>用途</th>
</tr>
</thead>
<tbody>
<tr>
<td>网络工具</td>
<td>HttpGetTool</td>
<td>HTTP GET请求</td>
</tr>
<tr>
<td>文件工具</td>
<td>FileReadTool</td>
<td>读取文件内容</td>
</tr>
<tr>
<td>计算工具</td>
<td>CalculatorTool</td>
<td>数学计算</td>
</tr>
<tr>
<td>时间工具</td>
<td>DateTimeTool</td>
<td>获取当前时间</td>
</tr>
<tr>
<td>系统工具</td>
<td>ProcessTool</td>
<td>执行系统命令</td>
</tr>
</tbody>
</table>
<h3 id="22-使用内置工具的基本模式">2.2 使用内置工具的基本模式</h3>
<pre><code class="language-csharp">// 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
            );
    }
}
</code></pre>
<h3 id="23-工具参数和返回值">2.3 工具参数和返回值</h3>
<p>每个工具都有明确的参数定义和返回值类型:</p>
<pre><code class="language-csharp">// 查看工具定义
var dateTimeTool = new DateTimeTool();
Console.WriteLine($"工具名称: {dateTimeTool.Name}");
Console.WriteLine($"工具描述: {dateTimeTool.Description}");
Console.WriteLine($"参数定义: {dateTimeTool.Parameters}");
Console.WriteLine($"返回值类型: {dateTimeTool.ReturnType}");
</code></pre>
<p>输出示例:</p>
<pre><code>工具名称: get_current_time
工具描述: 获取当前日期和时间
参数定义: {}
返回值类型: string
</code></pre>
<h2 id="三自定义工具开发实战">三、自定义工具开发实战</h2>
<h3 id="31-场景设计天气预报工具">3.1 场景设计:天气预报工具</h3>
<p>让我们创建一个实用的天气预报工具。这个工具需要:</p>
<ol>
<li>根据城市名称查询天气</li>
<li>支持多种天气服务提供商</li>
<li>提供详细的天气信息</li>
<li>处理各种错误情况</li>
</ol>
<h3 id="32-工具定义类">3.2 工具定义类</h3>
<pre><code class="language-csharp">// 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&lt;object&gt; ExecuteAsync(
      object parameters,
      IToolContext context,
      CancellationToken cancellationToken)
    {
      try
      {
            // 解析参数
            var paramDict = parameters as IDictionary&lt;string, object&gt;;
            var city = paramDict?["city"]?.ToString()
                ?? throw new ArgumentException("城市参数不能为空");
            var days = paramDict?.ContainsKey("days") == true
                ? Convert.ToInt32(paramDict["days"])
                : 3;
            
            // 验证参数
            if (days &lt; 1 || days &gt; 7)
                throw new ArgumentException("预报天数必须在1-7天之间");
            
            // 构建请求URL(示例使用和风天气API)
            var url = $"{_config.BaseUrl}/weather/forecast?city={Uri.EscapeDataString(city)}&amp;days={days}&amp;key={_config.ApiKey}";
            
            // 发送请求
            var response = await _httpClient.GetAsync(url, cancellationToken);
            response.EnsureSuccessStatusCode();
            
            // 解析响应
            var json = await response.Content.ReadAsStringAsync(cancellationToken);
            var weatherData = JsonSerializer.Deserialize&lt;WeatherResponse&gt;(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 =&gt; 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 =&gt; "get_weather_forecast";
    public string Description =&gt; "获取指定城市的天气预报信息";
    public object Parameters =&gt; 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 =&gt; "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&lt;ForecastDay&gt; 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&lt;DailyForecast&gt; 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; }
}
</code></pre>
<h3 id="33-数据库查询工具">3.3 数据库查询工具</h3>
<p>另一个常见场景是数据库操作。让我们创建一个安全的数据库查询工具:</p>
<pre><code class="language-csharp">// 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&lt;DatabaseQueryTool&gt; _logger;
   
    public DatabaseQueryTool(
      DbConnection connection,
      ILogger&lt;DatabaseQueryTool&gt; logger)
    {
      _connection = connection;
      _logger = logger;
    }
   
    public async Task&lt;object&gt; ExecuteAsync(
      object parameters,
      IToolContext context,
      CancellationToken cancellationToken)
    {
      // 安全检查
      var paramDict = parameters as IDictionary&lt;string, object&gt;;
      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&lt;string&gt;(),
                Rows = new List&lt;Dictionary&lt;string, object&gt;&gt;()
            };
            
            // 读取列信息
            for (int i = 0; i &lt; reader.FieldCount; i++)
            {
                result.Columns.Add(reader.GetName(i));
            }
            
            // 读取数据
            while (await reader.ReadAsync(cancellationToken) &amp;&amp; result.RowCount &lt; limit)
            {
                var row = new Dictionary&lt;string, object&gt;();
               
                for (int i = 0; i &lt; reader.FieldCount; i++)
                {
                  var value = reader.GetValue(i);
                  row = 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&lt;string&gt; Columns { get; set; } = new();
    public List&lt;Dictionary&lt;string, object&gt;&gt; Rows { get; set; } = new();
}
</code></pre>
<h3 id="34-文件操作工具">3.4 文件操作工具</h3>
<p>文件操作是另一个重要场景,但需要严格控制权限:</p>
<pre><code class="language-csharp">// SafeFileTool.cs
using Microsoft.Agents.AI.Tools;

public class SafeFileTool : ITool
{
    private readonly string _allowedBasePath;
    private readonly ILogger&lt;SafeFileTool&gt; _logger;
   
    public SafeFileTool(string allowedBasePath, ILogger&lt;SafeFileTool&gt; logger)
    {
      _allowedBasePath = Path.GetFullPath(allowedBasePath);
      _logger = logger;
      
      // 确保基础路径存在
      Directory.CreateDirectory(_allowedBasePath);
    }
   
    public async Task&lt;object&gt; ExecuteAsync(
      object parameters,
      IToolContext context,
      CancellationToken cancellationToken)
    {
      var paramDict = parameters as IDictionary&lt;string, object&gt;;
      var operation = paramDict?["operation"]?.ToString()
            ?? throw new ArgumentException("操作类型不能为空");
      
      return operation.ToLower() switch
      {
            "read" =&gt; await ReadFileAsync(paramDict, cancellationToken),
            "list" =&gt; await ListDirectoryAsync(paramDict, cancellationToken),
            "info" =&gt; await GetFileInfoAsync(paramDict, cancellationToken),
            _ =&gt; throw new ArgumentException($"不支持的操作: {operation}")
      };
    }
   
    private async Task&lt;object&gt; ReadFileAsync(
      IDictionary&lt;string, object&gt; 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 &gt; 1024 * 1024) // 1MB
            throw new InvalidOperationException("文件太大,超过1MB限制");
      
      _logger.LogInformation("读取文件: {FilePath}", fullPath);
      return await File.ReadAllTextAsync(fullPath, cancellationToken);
    }
   
    private Task&lt;object&gt; ListDirectoryAsync(
      IDictionary&lt;string, object&gt; 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 =&gt; new
                {
                  Name = Path.GetFileName(f),
                  Size = new FileInfo(f).Length,
                  Modified = File.GetLastWriteTime(f)
                }),
            Directories = Directory.GetDirectories(fullPath)
                .Select(d =&gt; new
                {
                  Name = Path.GetFileName(d),
                  FileCount = Directory.GetFiles(d).Length
                })
      };
      
      return Task.FromResult&lt;object&gt;(result);
    }
   
    private Task&lt;object&gt; GetFileInfoAsync(
      IDictionary&lt;string, object&gt; parameters,
      CancellationToken cancellationToken)
    {
      var filePath = parameters["path"]?.ToString()
            ?? throw new ArgumentException("文件路径不能为空");
      
      var fullPath = GetSafePath(filePath);
      var fileInfo = new FileInfo(fullPath);
      
      return Task.FromResult&lt;object&gt;(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 =&gt; "file_operations";
    public string Description =&gt; "安全的文件操作(只读)";
    public object Parameters =&gt; 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 =&gt; "object";
}
</code></pre>
<h2 id="四工具组合与复杂场景">四、工具组合与复杂场景</h2>
<h3 id="41-多工具协同工作">4.1 多工具协同工作</h3>
<p>真正的力量来自工具的组合。让我们创建一个能够处理复杂场景的智能体:</p>
<pre><code class="language-csharp">// 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&lt;DatabaseQueryTool&gt;()));
      
      // 添加文件工具
      tools.Add(new SafeFileTool(
            Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "data"),
            loggerFactory.CreateLogger&lt;SafeFileTool&gt;()));
      
      // 添加内置工具
      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&lt;string&gt; 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&lt;string&gt; PlanTravel(string destination)
    {
      var request = $"""
            我计划去{destination}旅行,请帮我:
            1. 查询当地的天气预报
            2. 如果是大城市,查询有什么著名景点(从数据库中)
            3. 根据天气和景点信息,给出旅行建议
            """;
      
      return await HandleComplexRequest(request);
    }
   
    // 模拟场景2:数据分析
    public async Task&lt;string&gt; AnalyzeSalesData(string period)
    {
      var request = $"""
            请分析{period}的销售数据:
            1. 从数据库查询销售记录
            2. 计算总销售额和平均订单金额
            3. 将结果保存到文件中
            4. 提供分析报告
            """;
      
      return await HandleComplexRequest(request);
    }
}
</code></pre>
<h3 id="42-工具链和流程控制">4.2 工具链和流程控制</h3>
<p>有时我们需要多个工具按顺序执行:</p>
<pre><code class="language-csharp">// ToolChainExecutor.cs
public class ToolChainExecutor
{
    private readonly Dictionary&lt;string, ITool&gt; _tools;
   
    public ToolChainExecutor(IEnumerable&lt;ITool&gt; tools)
    {
      _tools = tools.ToDictionary(t =&gt; t.Name, t =&gt; t);
    }
   
    public async Task&lt;string&gt; ExecuteChainAsync(string chainDefinition)
    {
      // 解析工具链定义
      var steps = ParseChainDefinition(chainDefinition);
      var results = new List&lt;object&gt;();
      
      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) &amp;&amp; step.IsCritical)
                  break;
            }
            catch (Exception ex)
            {
                // 根据策略处理错误
                if (step.ContinueOnError)
                  results.Add(new { Error = ex.Message });
                else
                  throw;
            }
      }
      
      // 汇总结果
      return FormatResults(results);
    }
   
    private List&lt;ToolStep&gt; ParseChainDefinition(string definition)
    {
      // 简化实现,实际应该使用更复杂的解析
      return definition.Split(';')
            .Select(step =&gt; step.Trim())
            .Where(step =&gt; !string.IsNullOrEmpty(step))
            .Select(step =&gt;
            {
                var parts = step.Split('|');
                return new ToolStep
                {
                  ToolName = parts,
                  Parameters = ParseParameters(parts.Length &gt; 1 ? parts : "{}"),
                  IsCritical = parts.Length &gt; 2 &amp;&amp; bool.Parse(parts),
                  ContinueOnError = parts.Length &gt; 3 &amp;&amp; bool.Parse(parts)
                };
            })
            .ToList();
    }
   
    private object ParseParameters(string paramString)
    {
      return JsonSerializer.Deserialize&lt;Dictionary&lt;string, object&gt;&gt;(paramString)
            ?? new Dictionary&lt;string, object&gt;();
    }
   
    private bool IsFailedResult(object result)
    {
      // 根据实际结果类型判断
      return result.ToString()?.Contains("Error") == true
            || result.ToString()?.Contains("失败") == true;
    }
   
    private string FormatResults(List&lt;object&gt; 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;
    }
}
</code></pre>
<h2 id="五安全考虑与最佳实践">五、安全考虑与最佳实践</h2>
<h3 id="51-工具安全性设计原则">5.1 工具安全性设计原则</h3>
<ol>
<li><strong>最小权限原则</strong>:每个工具只拥有完成任务所需的最小权限</li>
<li><strong>输入验证</strong>:对所有输入进行严格的验证和清理</li>
<li><strong>输出过滤</strong>:敏感信息在返回前进行过滤</li>
<li><strong>访问控制</strong>:根据用户身份限制工具访问</li>
<li><strong>审计日志</strong>:记录所有工具调用详情</li>
</ol>
<h3 id="52-实施安全检查">5.2 实施安全检查</h3>
<pre><code class="language-csharp">// 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&lt;object&gt; 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 =&gt; toolPermissions.Contains(role));
    }
   
    private object SanitizeParameters(object parameters)
    {
      // 参数清理逻辑
      // 例如:移除脚本标签、限制长度等
      return parameters;
    }
   
    private object SanitizeResult(object result)
    {
      // 结果清理逻辑
      // 例如:屏蔽敏感数据、限制数据大小等
      return result;
    }
   
    private IEnumerable&lt;string&gt; GetToolPermissions(string toolName)
    {
      // 从配置中获取工具权限
      return _configuration.GetSection($"Tools:{toolName}:Permissions")
            .Get&lt;List&lt;string&gt;&gt;() ?? new List&lt;string&gt;();
    }
   
    // 其他属性和方法...
}
</code></pre>
<h2 id="六性能优化与监控">六、性能优化与监控</h2>
<h3 id="61-工具性能监控">6.1 工具性能监控</h3>
<pre><code class="language-csharp">// 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&lt;object&gt; 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")}");
      }
    }
   
    // 其他属性和方法...
}
</code></pre>
<h3 id="62-工具缓存策略">6.2 工具缓存策略</h3>
<pre><code class="language-csharp">// 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&lt;object&gt; ExecuteAsync(
      object parameters,
      IToolContext context,
      CancellationToken cancellationToken)
    {
      // 生成缓存键
      var cacheKey = GenerateCacheKey(parameters);
      
      // 尝试从缓存获取
      var cachedResult = await _cache.GetStringAsync(cacheKey, cancellationToken);
      if (cachedResult != null)
      {
            return JsonSerializer.Deserialize&lt;object&gt;(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" =&gt; TimeSpan.FromHours(1),
            "query_database" =&gt; TimeSpan.FromMinutes(5),
            _ =&gt; _defaultCacheDuration
      };
    }
   
    // 其他属性和方法...
}
</code></pre>
<h2 id="七完整示例智能数据分析助手">七、完整示例:智能数据分析助手</h2>
<p>让我们把所有内容整合起来,创建一个完整的数据分析助手:</p>
<pre><code class="language-csharp">// 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&lt;DatabaseQueryTool&gt;()));
      
      // 天气工具
      tools.Add(new WeatherForecastTool(weatherConfig));
      
      // 文件工具
      tools.Add(new SafeFileTool("data", loggerFactory.CreateLogger&lt;SafeFileTool&gt;()));
      
      // 计算工具
      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&lt;string&gt; AnalyzeSalesTrends(string timePeriod)
    {
      var request = $"""
            请分析{timePeriod}的销售趋势:
            1. 查询每日销售额数据
            2. 计算周增长率和月增长率
            3. 识别销售高峰期和低谷期
            4. 分析天气对销售的影响(如适用)
            5. 生成详细的趋势分析报告
            """;
      
      return await _agent.RunAsync(request);
    }
   
    public async Task&lt;string&gt; CompareRegions(string region1, string region2)
    {
      var request = $"""
            请对比分析{region1}和{region2}两个地区的销售表现:
            1. 分别查询两个地区的销售数据
            2. 对比销售额、订单量、客单价等关键指标
            3. 分析地区差异的可能原因
            4. 提供针对性的改进建议
            """;
      
      return await _agent.RunAsync(request);
    }
}
</code></pre>
<h2 id="八测试与部署">八、测试与部署</h2>
<h3 id="81-工具单元测试">8.1 工具单元测试</h3>
<pre><code class="language-csharp">// WeatherForecastToolTests.cs
public class WeatherForecastToolTests
{
   
    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&lt;string, object&gt;
      {
            ["city"] = "北京",
            ["days"] = 3
      };
      
      // 执行(使用模拟的HttpClient)
      var result = await tool.ExecuteAsync(
            parameters,
            Mock.Of&lt;IToolContext&gt;(),
            CancellationToken.None);
      
      // 验证
      Assert.NotNull(result);
      var forecast = result as WeatherForecast;
      Assert.NotNull(forecast);
      Assert.Equal("北京", forecast.City);
      Assert.Equal(3, forecast.ForecastDays);
    }
   
   
    public void ExecuteAsync_EmptyCity_ThrowsArgumentException()
    {
      var tool = new WeatherForecastTool(new WeatherServiceConfig());
      var parameters = new Dictionary&lt;string, object&gt;
      {
            ["city"] = ""
      };
      
      var exception = await Assert.ThrowsAsync&lt;ArgumentException&gt;(() =&gt;
            tool.ExecuteAsync(parameters, Mock.Of&lt;IToolContext&gt;(), CancellationToken.None));
      
      Assert.Contains("城市参数不能为空", exception.Message);
    }
}
</code></pre>
<h3 id="82-集成测试">8.2 集成测试</h3>
<pre><code class="language-csharp">// ToolIntegrationTests.cs
public class ToolIntegrationTests : IClassFixture&lt;TestFixture&gt;
{
    private readonly TestFixture _fixture;
   
    public ToolIntegrationTests(TestFixture fixture)
    {
      _fixture = fixture;
    }
   
   
    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);
    }
}
</code></pre>
<h3 id="83-部署配置">8.3 部署配置</h3>
<pre><code class="language-yaml"># 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"]
    }
}
}
</code></pre>
<h2 id="九总结与下一步">九、总结与下一步</h2>
<p>通过本文的学习,我们已经掌握了Agent Framework工具系统的核心:</p>
<ol>
<li>✅ <strong>理解工具架构</strong>:工具定义、实现、注册的完整流程</li>
<li>✅ <strong>创建自定义工具</strong>:天气预报、数据库查询、文件操作等实用工具</li>
<li>✅ <strong>实现工具组合</strong>:多工具协同处理复杂任务</li>
<li>✅ <strong>保障工具安全</strong>:权限控制、输入验证、审计日志</li>
<li>✅ <strong>优化工具性能</strong>:缓存策略、监控指标、错误处理</li>
<li>✅ <strong>完整示例实现</strong>:数据分析助手等实际应用</li>
</ol>
<p><strong>关键收获:</strong></p>
<ul>
<li>工具是智能体从"思考"到"执行"的关键桥梁</li>
<li>良好的工具设计需要考虑安全性、性能和易用性</li>
<li>工具组合能够创造无限的可能性</li>
<li>生产环境中的工具需要完善的监控和错误处理</li>
</ul>
<p><strong>下一篇文章预告:</strong><br>
在第四篇文章中,我们将探索<strong>多轮对话与状态管理</strong>。智能体不仅需要执行任务,还需要在复杂的对话流程中保持状态,理解上下文,提供连贯的用户体验。</p>
<p>我们将学习:</p>
<ul>
<li>对话状态管理和持久化</li>
<li>上下文窗口优化策略</li>
<li>长对话的记忆管理</li>
<li>复杂业务流程的状态机实现</li>
</ul>
<p>这将让我们的智能体真正具备"对话智能",能够在复杂的业务场景中提供持续、一致的服务。</p>
<hr>
<p><strong>实践建议:</strong></p>
<ol>
<li>从简单的工具开始,逐步增加复杂性</li>
<li>为每个工具编写完整的单元测试</li>
<li>在生产环境中逐步引入工具,监控性能表现</li>
<li>建立工具开发规范和安全审查流程</li>
</ol>
<p><strong>相关资源:</strong></p>
<ul>
<li>Agent Framework工具系统文档</li>
<li>工具安全最佳实践</li>
<li>.NET HttpClient性能优化</li>
</ul>
<hr>
<p><em>"真正的智能不在于知道多少,而在于能够使用工具有效地解决问题。"</em></p><br><br>
来源:https://www.cnblogs.com/fanshaoO/p/19709754
頁: [1]
查看完整版本: 工具集成:让智能体具备执行能力