性能优化

概述

在开发 AI 代理应用时,性能优化是确保应用能够高效运行、提供良好用户体验的关键。本文将介绍 AI 代理应用中的性能优化关键点、实用技巧和测试方法。

为什么性能优化很重要?

想象一下,如果你的 AI 客服助手每次回答问题都需要等待 30 秒,用户会有什么感受?性能优化就像给你的代理装上"涡轮增压器",让它更快、更高效地工作。

性能问题的常见表现

  1. 响应时间过长:用户等待时间超过 5 秒

  2. 资源消耗过高:CPU、内存占用过大

  3. 并发能力不足:无法同时处理多个请求

  4. 成本过高:API 调用费用超出预算

性能优化的关键点

1. 减少 API 调用次数

每次调用 AI 模型都需要时间和费用。减少不必要的调用是最直接的优化方法。

优化技巧

❌ 不好的做法:每次都重新调用

// 每次用户输入都创建新的代理和对话
public async Task<string> ProcessMessage(string userMessage)
{
    var agent = new ChatCompletionAgent(/* ... */);
    var thread = new AgentThread();
    
    await thread.AddUserMessageAsync(userMessage);
    var response = await agent.InvokeAsync(thread);
    
    return response.Content;
}

✅ 好的做法:复用代理和对话线程

// 复用代理实例和对话线程
private readonly ChatCompletionAgent _agent;
private readonly Dictionary<string, AgentThread> _userThreads;

public async Task<string> ProcessMessage(string userId, string userMessage)
{
    // 获取或创建用户的对话线程
    if (!_userThreads.TryGetValue(userId, out var thread))
    {
        thread = new AgentThread();
        _userThreads[userId] = thread;
    }
    
    await thread.AddUserMessageAsync(userMessage);
    var response = await _agent.InvokeAsync(thread);
    
    return response.Content;
}

性能提升:减少 50% 的初始化开销

2. 使用缓存策略

对于相同或相似的问题,可以使用缓存避免重复调用 AI 模型。

实现简单缓存
using System.Collections.Concurrent;
using System.Security.Cryptography;
using System.Text;

public class AgentResponseCache
{
    private readonly ConcurrentDictionary<string, CacheEntry> _cache = new();
    private readonly TimeSpan _expirationTime = TimeSpan.FromMinutes(30);
    
    private class CacheEntry
    {
        public string Response { get; set; }
        public DateTime CreatedAt { get; set; }
    }
    
    // 生成缓存键
    private string GenerateCacheKey(string message)
    {
        using var sha256 = SHA256.Create();
        var hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(message.ToLower()));
        return Convert.ToBase64String(hash);
    }
    
    // 尝试从缓存获取响应
    public bool TryGetCachedResponse(string message, out string response)
    {
        var key = GenerateCacheKey(message);
        
        if (_cache.TryGetValue(key, out var entry))
        {
            // 检查是否过期
            if (DateTime.UtcNow - entry.CreatedAt < _expirationTime)
            {
                response = entry.Response;
                return true;
            }
            else
            {
                // 移除过期条目
                _cache.TryRemove(key, out _);
            }
        }
        
        response = null;
        return false;
    }
    
    // 添加响应到缓存
    public void CacheResponse(string message, string response)
    {
        var key = GenerateCacheKey(message);
        _cache[key] = new CacheEntry
        {
            Response = response,
            CreatedAt = DateTime.UtcNow
        };
    }
    
    // 清理过期缓存
    public void CleanupExpiredEntries()
    {
        var expiredKeys = _cache
            .Where(kvp => DateTime.UtcNow - kvp.Value.CreatedAt >= _expirationTime)
            .Select(kvp => kvp.Key)
            .ToList();
            
        foreach (var key in expiredKeys)
        {
            _cache.TryRemove(key, out _);
        }
    }
}
使用缓存的代理
public class CachedAgent
{
    private readonly ChatCompletionAgent _agent;
    private readonly AgentResponseCache _cache;
    
    public CachedAgent(ChatCompletionAgent agent)
    {
        _agent = agent;
        _cache = new AgentResponseCache();
    }
    
    public async Task<string> ProcessMessageAsync(AgentThread thread, string message)
    {
        // 先检查缓存
        if (_cache.TryGetCachedResponse(message, out var cachedResponse))
        {
            Console.WriteLine("✓ 从缓存返回响应");
            return cachedResponse;
        }
        
        // 缓存未命中,调用 AI 模型
        Console.WriteLine("→ 调用 AI 模型");
        await thread.AddUserMessageAsync(message);
        var response = await _agent.InvokeAsync(thread);
        var content = response.Content;
        
        // 缓存响应
        _cache.CacheResponse(message, content);
        
        return content;
    }
}

性能提升:缓存命中时响应时间减少 90%

3. 优化提示词(Prompt)长度

提示词越长,处理时间越长,费用也越高。

优化技巧

❌ 冗长的提示词

var instructions = @"
你是一个非常专业的客服助手。你需要帮助用户解决各种各样的问题。
你应该始终保持礼貌和专业。你需要仔细理解用户的问题,然后给出详细的回答。
如果你不知道答案,你应该诚实地告诉用户你不知道。
你应该使用简单易懂的语言,避免使用过于专业的术语。
你应该确保你的回答是准确的、有帮助的。
...(还有很多重复的内容)
";

✅ 简洁的提示词

var instructions = @"
你是专业的客服助手。
- 礼貌、准确地回答用户问题
- 使用简单易懂的语言
- 不确定时诚实说明
";

性能提升:减少 30-40% 的 token 消耗

4. 使用流式响应

对于长文本响应,使用流式输出可以让用户更快看到结果。

public async Task StreamResponseAsync(AgentThread thread, string message)
{
    await thread.AddUserMessageAsync(message);
    
    Console.Write("AI: ");
    
    // 使用流式响应
    await foreach (var update in _agent.InvokeStreamingAsync(thread))
    {
        if (update.Content != null)
        {
            Console.Write(update.Content);
            await Task.Delay(10); // 模拟打字效果
        }
    }
    
    Console.WriteLine();
}

用户体验提升:用户感知的等待时间减少 70%

5. 并行处理多个请求

当需要处理多个独立的请求时,使用并行处理可以显著提升性能。

public async Task<List<string>> ProcessMultipleQuestionsAsync(List<string> questions)
{
    // 为每个问题创建独立的任务
    var tasks = questions.Select(async question =>
    {
        var thread = new AgentThread();
        await thread.AddUserMessageAsync(question);
        var response = await _agent.InvokeAsync(thread);
        return response.Content;
    });
    
    // 并行执行所有任务
    var results = await Task.WhenAll(tasks);
    return results.ToList();
}

性能提升:处理 10 个问题的时间从 50 秒减少到 8 秒

6. 限制对话历史长度

对话历史越长,每次调用的成本越高。合理限制历史长度很重要。

public class OptimizedAgentThread
{
    private readonly List<ChatMessage> _messages = new();
    private const int MaxHistoryMessages = 20; // 最多保留 20 条消息
    
    public void AddMessage(ChatMessage message)
    {
        _messages.Add(message);
        
        // 如果超过限制,移除最旧的消息(保留系统消息)
        if (_messages.Count > MaxHistoryMessages)
        {
            var systemMessages = _messages.Where(m => m.Role == ChatRole.System).ToList();
            var recentMessages = _messages
                .Where(m => m.Role != ChatRole.System)
                .TakeLast(MaxHistoryMessages - systemMessages.Count)
                .ToList();
                
            _messages.Clear();
            _messages.AddRange(systemMessages);
            _messages.AddRange(recentMessages);
        }
    }
    
    public IReadOnlyList<ChatMessage> GetMessages() => _messages.AsReadOnly();
}

7. 选择合适的模型

不同的模型有不同的性能特点和成本。

模型 速度 质量 成本 适用场景
GPT-4 最高 复杂推理、创意写作
GPT-4-turbo 平衡性能和质量
GPT-3.5-turbo 简单对话、分类任务
// 根据任务复杂度选择模型
public ChatCompletionAgent CreateAgentForTask(TaskComplexity complexity)
{
    string modelId = complexity switch
    {
        TaskComplexity.Simple => "gpt-3.5-turbo",      // 快速、低成本
        TaskComplexity.Medium => "gpt-4-turbo",        // 平衡
        TaskComplexity.Complex => "gpt-4",             // 高质量
        _ => "gpt-3.5-turbo"
    };
    
    return new ChatCompletionAgent(
        chatClient: _chatClient,
        name: "OptimizedAgent",
        instructions: "你是一个高效的助手",
        modelId: modelId
    );
}

public enum TaskComplexity
{
    Simple,   // 简单任务:问候、简单问答
    Medium,   // 中等任务:信息检索、总结
    Complex   // 复杂任务:推理、创意生成
}

性能测试方法

1. 响应时间测试

using System.Diagnostics;

public class PerformanceTester
{
    public async Task<PerformanceMetrics> MeasureResponseTimeAsync(
        Func<Task<string>> agentCall)
    {
        var stopwatch = Stopwatch.StartNew();
        
        var response = await agentCall();
        
        stopwatch.Stop();
        
        return new PerformanceMetrics
        {
            ResponseTime = stopwatch.Elapsed,
            ResponseLength = response.Length,
            TokensPerSecond = response.Length / stopwatch.Elapsed.TotalSeconds
        };
    }
}

public class PerformanceMetrics
{
    public TimeSpan ResponseTime { get; set; }
    public int ResponseLength { get; set; }
    public double TokensPerSecond { get; set; }
    
    public override string ToString()
    {
        return $"响应时间: {ResponseTime.TotalSeconds:F2}秒, " +
               $"响应长度: {ResponseLength} 字符, " +
               $"速度: {TokensPerSecond:F2} 字符/秒";
    }
}

2. 并发性能测试

public async Task<ConcurrencyTestResult> TestConcurrencyAsync(
    int concurrentRequests,
    Func<Task<string>> agentCall)
{
    var stopwatch = Stopwatch.StartNew();
    var tasks = new List<Task<string>>();
    
    // 创建并发请求
    for (int i = 0; i < concurrentRequests; i++)
    {
        tasks.Add(agentCall());
    }
    
    // 等待所有请求完成
    var results = await Task.WhenAll(tasks);
    stopwatch.Stop();
    
    return new ConcurrencyTestResult
    {
        TotalRequests = concurrentRequests,
        TotalTime = stopwatch.Elapsed,
        AverageTime = stopwatch.Elapsed.TotalSeconds / concurrentRequests,
        RequestsPerSecond = concurrentRequests / stopwatch.Elapsed.TotalSeconds
    };
}

public class ConcurrencyTestResult
{
    public int TotalRequests { get; set; }
    public TimeSpan TotalTime { get; set; }
    public double AverageTime { get; set; }
    public double RequestsPerSecond { get; set; }
    
    public override string ToString()
    {
        return $"总请求数: {TotalRequests}, " +
               $"总时间: {TotalTime.TotalSeconds:F2}秒, " +
               $"平均时间: {AverageTime:F2}秒, " +
               $"吞吐量: {RequestsPerSecond:F2} 请求/秒";
    }
}

3. 完整的性能测试示例

public class Program
{
    public static async Task Main(string[] args)
    {
        // 初始化代理
        var chatClient = new AzureOpenAIClient(
            new Uri(Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")),
            new ApiKeyCredential(Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY"))
        ).GetChatClient("gpt-35-turbo");
        
        var agent = new ChatCompletionAgent(
            chatClient: chatClient,
            name: "PerformanceTestAgent",
            instructions: "你是一个测试助手"
        );
        
        var tester = new PerformanceTester();
        
        Console.WriteLine("=== 性能测试开始 ===\n");
        
        // 测试 1: 单次响应时间
        Console.WriteLine("测试 1: 单次响应时间");
        var thread1 = new AgentThread();
        var metrics = await tester.MeasureResponseTimeAsync(async () =>
        {
            await thread1.AddUserMessageAsync("你好,请介绍一下自己");
            var response = await agent.InvokeAsync(thread1);
            return response.Content;
        });
        Console.WriteLine(metrics);
        Console.WriteLine();
        
        // 测试 2: 缓存效果
        Console.WriteLine("测试 2: 缓存效果对比");
        var cachedAgent = new CachedAgent(agent);
        var thread2 = new AgentThread();
        
        // 第一次调用(无缓存)
        var metrics1 = await tester.MeasureResponseTimeAsync(async () =>
        {
            return await cachedAgent.ProcessMessageAsync(thread2, "什么是 AI?");
        });
        Console.WriteLine($"无缓存: {metrics1}");
        
        // 第二次调用(有缓存)
        var metrics2 = await tester.MeasureResponseTimeAsync(async () =>
        {
            return await cachedAgent.ProcessMessageAsync(thread2, "什么是 AI?");
        });
        Console.WriteLine($"有缓存: {metrics2}");
        Console.WriteLine($"性能提升: {(1 - metrics2.ResponseTime.TotalSeconds / metrics1.ResponseTime.TotalSeconds) * 100:F1}%");
        Console.WriteLine();
        
        // 测试 3: 并发性能
        Console.WriteLine("测试 3: 并发性能");
        var concurrencyResult = await tester.TestConcurrencyAsync(10, async () =>
        {
            var thread = new AgentThread();
            await thread.AddUserMessageAsync("你好");
            var response = await agent.InvokeAsync(thread);
            return response.Content;
        });
        Console.WriteLine(concurrencyResult);
        
        Console.WriteLine("\n=== 性能测试完成 ===");
    }
}

性能优化检查清单

在部署应用之前,使用这个清单检查性能优化:

  • [ ] 代理复用:是否复用了代理实例?

  • [ ] 缓存策略:是否对常见问题使用了缓存?

  • [ ] 提示词优化:提示词是否简洁明了?

  • [ ] 流式响应:长文本是否使用了流式输出?

  • [ ] 并行处理:独立任务是否并行执行?

  • [ ] 历史限制:对话历史是否有合理的长度限制?

  • [ ] 模型选择:是否根据任务选择了合适的模型?

  • [ ] 错误重试:是否实现了指数退避的重试机制?

  • [ ] 资源释放:是否正确释放了资源?

  • [ ] 性能监控:是否添加了性能监控?

实际案例:优化前后对比

优化前的代码

// 性能问题:每次都创建新代理,没有缓存,提示词冗长
public class SlowCustomerService
{
    public async Task<string> HandleQuestionAsync(string question)
    {
        // 问题 1: 每次都创建新的客户端和代理
        var chatClient = new AzureOpenAIClient(/* ... */).GetChatClient("gpt-4");
        
        // 问题 2: 提示词过长
        var agent = new ChatCompletionAgent(
            chatClient: chatClient,
            name: "CustomerService",
            instructions: @"你是一个非常专业的客服助手。你需要帮助用户解决各种各样的问题。
                          你应该始终保持礼貌和专业。你需要仔细理解用户的问题,然后给出详细的回答。
                          如果你不知道答案,你应该诚实地告诉用户你不知道。
                          你应该使用简单易懂的语言,避免使用过于专业的术语。
                          你应该确保你的回答是准确的、有帮助的。"
        );
        
        // 问题 3: 没有缓存
        var thread = new AgentThread();
        await thread.AddUserMessageAsync(question);
        var response = await agent.InvokeAsync(thread);
        
        return response.Content;
    }
}

性能指标

  • 平均响应时间:8.5 秒

  • 每月 API 费用:$450

  • 并发能力:5 请求/秒

优化后的代码

// 优化后:复用代理,使用缓存,简化提示词,选择合适模型
public class FastCustomerService
{
    private readonly ChatCompletionAgent _agent;
    private readonly AgentResponseCache _cache;
    
    public FastCustomerService()
    {
        // 优化 1: 复用客户端和代理
        var chatClient = new AzureOpenAIClient(/* ... */)
            .GetChatClient("gpt-3.5-turbo"); // 优化 2: 使用更快的模型
        
        // 优化 3: 简化提示词
        _agent = new ChatCompletionAgent(
            chatClient: chatClient,
            name: "CustomerService",
            instructions: "你是专业客服。礼貌、准确地回答问题,使用简单语言。"
        );
        
        // 优化 4: 添加缓存
        _cache = new AgentResponseCache();
    }
    
    public async Task<string> HandleQuestionAsync(string question)
    {
        // 优化 5: 先检查缓存
        if (_cache.TryGetCachedResponse(question, out var cachedResponse))
        {
            return cachedResponse;
        }
        
        var thread = new AgentThread();
        await thread.AddUserMessageAsync(question);
        var response = await _agent.InvokeAsync(thread);
        
        // 缓存响应
        _cache.CacheResponse(question, response.Content);
        
        return response.Content;
    }
}

优化后性能指标

  • 平均响应时间:2.1 秒(提升 75%)

  • 每月 API 费用:$180(节省 60%)

  • 并发能力:25 请求/秒(提升 400%)

小结

性能优化是一个持续的过程,关键要点:

  1. 测量优先:先测量,再优化,避免过早优化

  2. 找到瓶颈:使用性能测试找出真正的性能瓶颈

  3. 逐步优化:一次优化一个点,验证效果

  4. 平衡取舍:在性能、成本、质量之间找到平衡

  5. 持续监控:部署后持续监控性能指标

记住:最好的优化是避免不必要的工作。在编写代码时就考虑性能,比事后优化要容易得多。

安全最佳实践

概述

在开发 AI 代理应用时,安全性至关重要。本文将介绍如何保护 API 密钥、用户数据和应用安全的最佳实践。

为什么安全性很重要?

想象一下,如果你的 API 密钥被泄露,攻击者可能会:

  • 使用你的账户调用 AI 服务,产生巨额费用

  • 访问你的用户数据

  • 破坏你的应用

安全就像给你的应用加上"防盗门"和"保险箱",保护你的资产和用户的隐私。

1. API 密钥管理

1.1 永远不要硬编码密钥

❌ 危险的做法:硬编码密钥

// 千万不要这样做!
var apiKey = "sk-1234567890abcdef"; // 密钥直接写在代码里
var endpoint = "https://myopenai.openai.azure.com/";

问题

  • 代码提交到 Git 后,密钥会被永久记录

  • 任何能访问代码的人都能看到密钥

  • 密钥泄露后很难追踪

1.2 使用环境变量

✅ 安全的做法:使用环境变量

// 从环境变量读取密钥
var apiKey = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY");
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT");

if (string.IsNullOrEmpty(apiKey) || string.IsNullOrEmpty(endpoint))
{
    throw new InvalidOperationException("请配置 AZURE_OPENAI_API_KEY 和 AZURE_OPENAI_ENDPOINT 环境变量");
}

设置环境变量(Windows)

# PowerShell
$env:AZURE_OPENAI_API_KEY = "your-api-key"
$env:AZURE_OPENAI_ENDPOINT = "https://your-endpoint.openai.azure.com/"

设置环境变量(Linux/Mac)

export AZURE_OPENAI_API_KEY="your-api-key"
export AZURE_OPENAI_ENDPOINT="https://your-endpoint.openai.azure.com/"

1.3 使用配置文件(但不要提交到 Git)

创建 appsettings.Development.json(本地开发用):

{
  "AzureOpenAI": {
    "Endpoint": "https://your-endpoint.openai.azure.com/",
    "ApiKey": "your-api-key",
    "DeploymentName": "gpt-35-turbo"
  }
}

重要:在 .gitignore 中排除配置文件

# .gitignore
appsettings.Development.json
appsettings.*.json
*.env
.env

读取配置文件

using Microsoft.Extensions.Configuration;

public class SecureConfigurationManager
{
    private readonly IConfiguration _configuration;
    
    public SecureConfigurationManager()
    {
        _configuration = new ConfigurationBuilder()
            .SetBasePath(Directory.GetCurrentDirectory())
            .AddJsonFile("appsettings.json", optional: true)
            .AddJsonFile("appsettings.Development.json", optional: true)
            .AddEnvironmentVariables() // 环境变量优先级最高
            .Build();
    }
    
    public string GetApiKey()
    {
        return _configuration["AzureOpenAI:ApiKey"] 
            ?? throw new InvalidOperationException("未配置 API 密钥");
    }
    
    public string GetEndpoint()
    {
        return _configuration["AzureOpenAI:Endpoint"] 
            ?? throw new InvalidOperationException("未配置 Endpoint");
    }
}

1.4 使用 Azure Key Vault(生产环境推荐)

Azure Key Vault 是专门用于存储密钥的安全服务。

using Azure.Identity;
using Azure.Security.KeyVault.Secrets;

public class KeyVaultSecretManager
{
    private readonly SecretClient _secretClient;
    
    public KeyVaultSecretManager(string keyVaultUrl)
    {
        // 使用托管标识或默认凭据
        _secretClient = new SecretClient(
            new Uri(keyVaultUrl),
            new DefaultAzureCredential()
        );
    }
    
    public async Task<string> GetSecretAsync(string secretName)
    {
        try
        {
            KeyVaultSecret secret = await _secretClient.GetSecretAsync(secretName);
            return secret.Value;
        }
        catch (Exception ex)
        {
            throw new InvalidOperationException($"无法获取密钥 {secretName}", ex);
        }
    }
}

// 使用示例
var keyVaultManager = new KeyVaultSecretManager("https://your-keyvault.vault.azure.net/");
var apiKey = await keyVaultManager.GetSecretAsync("AzureOpenAI-ApiKey");

1.5 密钥轮换策略

定期更换 API 密钥可以降低泄露风险。

public class ApiKeyRotationManager
{
    private string _currentKey;
    private string _backupKey;
    private DateTime _keyRotationDate;
    private readonly TimeSpan _rotationInterval = TimeSpan.FromDays(90);
    
    public ApiKeyRotationManager(string primaryKey, string secondaryKey)
    {
        _currentKey = primaryKey;
        _backupKey = secondaryKey;
        _keyRotationDate = DateTime.UtcNow;
    }
    
    public string GetCurrentKey()
    {
        // 检查是否需要轮换
        if (DateTime.UtcNow - _keyRotationDate > _rotationInterval)
        {
            Console.WriteLine("⚠️ 警告:API 密钥需要轮换");
        }
        
        return _currentKey;
    }
    
    public void RotateKeys(string newKey)
    {
        _backupKey = _currentKey;
        _currentKey = newKey;
        _keyRotationDate = DateTime.UtcNow;
        Console.WriteLine("✓ API 密钥已轮换");
    }
    
    public string GetBackupKey() => _backupKey;
}

2. 数据保护策略

2.1 敏感数据脱敏

在发送给 AI 模型之前,应该脱敏敏感信息。

using System.Text.RegularExpressions;

public class DataMasker
{
    // 脱敏手机号
    public string MaskPhoneNumber(string text)
    {
        // 匹配中国手机号:1[3-9]\d{9}
        return Regex.Replace(text, @"1[3-9]\d{9}", m =>
        {
            var phone = m.Value;
            return phone.Substring(0, 3) + "****" + phone.Substring(7);
        });
    }
    
    // 脱敏身份证号
    public string MaskIdCard(string text)
    {
        // 匹配身份证号:18位数字
        return Regex.Replace(text, @"\d{17}[\dXx]", m =>
        {
            var id = m.Value;
            return id.Substring(0, 6) + "********" + id.Substring(14);
        });
    }
    
    // 脱敏邮箱
    public string MaskEmail(string text)
    {
        return Regex.Replace(text, @"[\w\.-]+@[\w\.-]+\.\w+", m =>
        {
            var email = m.Value;
            var parts = email.Split('@');
            if (parts[0].Length <= 2)
                return "***@" + parts[1];
            return parts[0].Substring(0, 2) + "***@" + parts[1];
        });
    }
    
    // 脱敏银行卡号
    public string MaskBankCard(string text)
    {
        return Regex.Replace(text, @"\d{16,19}", m =>
        {
            var card = m.Value;
            return card.Substring(0, 4) + " **** **** " + card.Substring(card.Length - 4);
        });
    }
    
    // 综合脱敏
    public string MaskSensitiveData(string text)
    {
        text = MaskPhoneNumber(text);
        text = MaskIdCard(text);
        text = MaskEmail(text);
        text = MaskBankCard(text);
        return text;
    }
}

使用示例

var masker = new DataMasker();
var userMessage = "我的手机号是13812345678,邮箱是zhangsan@example.com";
var maskedMessage = masker.MaskSensitiveData(userMessage);
// 结果: "我的手机号是138****5678,邮箱是zh***@example.com"

// 发送脱敏后的消息给 AI
await thread.AddUserMessageAsync(maskedMessage);

2.2 数据加密存储

如果需要存储对话历史,应该加密存储。

using System.Security.Cryptography;
using System.Text;

public class DataEncryption
{
    private readonly byte[] _key;
    private readonly byte[] _iv;
    
    public DataEncryption(string encryptionKey)
    {
        // 从密钥派生加密密钥和 IV
        using var sha256 = SHA256.Create();
        var keyBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(encryptionKey));
        _key = keyBytes;
        _iv = keyBytes.Take(16).ToArray();
    }
    
    // 加密文本
    public string Encrypt(string plainText)
    {
        using var aes = Aes.Create();
        aes.Key = _key;
        aes.IV = _iv;
        
        using var encryptor = aes.CreateEncryptor();
        var plainBytes = Encoding.UTF8.GetBytes(plainText);
        var encryptedBytes = encryptor.TransformFinalBlock(plainBytes, 0, plainBytes.Length);
        
        return Convert.ToBase64String(encryptedBytes);
    }
    
    // 解密文本
    public string Decrypt(string encryptedText)
    {
        using var aes = Aes.Create();
        aes.Key = _key;
        aes.IV = _iv;
        
        using var decryptor = aes.CreateDecryptor();
        var encryptedBytes = Convert.FromBase64String(encryptedText);
        var decryptedBytes = decryptor.TransformFinalBlock(encryptedBytes, 0, encryptedBytes.Length);
        
        return Encoding.UTF8.GetString(decryptedBytes);
    }
}

// 使用示例
var encryption = new DataEncryption(Environment.GetEnvironmentVariable("ENCRYPTION_KEY"));

// 加密存储
var message = "用户的敏感对话内容";
var encrypted = encryption.Encrypt(message);
await SaveToDatabase(encrypted);

// 解密读取
var encryptedFromDb = await LoadFromDatabase();
var decrypted = encryption.Decrypt(encryptedFromDb);

2.3 限制数据访问

实现基于角色的访问控制(RBAC)。

public enum UserRole
{
    User,        // 普通用户
    Admin,       // 管理员
    Developer    // 开发者
}

public class AccessControl
{
    private readonly Dictionary<UserRole, HashSet<string>> _permissions = new()
    {
        [UserRole.User] = new HashSet<string> { "chat", "view_history" },
        [UserRole.Admin] = new HashSet<string> { "chat", "view_history", "view_all_users", "manage_users" },
        [UserRole.Developer] = new HashSet<string> { "chat", "view_history", "view_logs", "debug" }
    };
    
    public bool HasPermission(UserRole role, string permission)
    {
        return _permissions.TryGetValue(role, out var perms) && perms.Contains(permission);
    }
    
    public void CheckPermission(UserRole role, string permission)
    {
        if (!HasPermission(role, permission))
        {
            throw new UnauthorizedAccessException($"角色 {role} 没有权限执行 {permission}");
        }
    }
}

// 使用示例
public class SecureAgentService
{
    private readonly AccessControl _accessControl = new();
    
    public async Task<List<string>> GetUserHistoryAsync(UserRole role, string userId)
    {
        // 检查权限
        _accessControl.CheckPermission(role, "view_history");
        
        // 如果不是管理员,只能查看自己的历史
        if (role != UserRole.Admin && userId != GetCurrentUserId())
        {
            throw new UnauthorizedAccessException("只能查看自己的对话历史");
        }
        
        return await LoadHistoryFromDatabase(userId);
    }
    
    private string GetCurrentUserId() => "current-user-id"; // 实际实现
    private Task<List<string>> LoadHistoryFromDatabase(string userId) => Task.FromResult(new List<string>());
}

3. 输入验证和清理

3.1 验证用户输入

public class InputValidator
{
    private const int MaxMessageLength = 4000;
    private const int MaxMessagesPerMinute = 20;
    
    private readonly Dictionary<string, Queue<DateTime>> _userRequestTimes = new();
    
    // 验证消息长度
    public bool ValidateMessageLength(string message, out string error)
    {
        if (string.IsNullOrWhiteSpace(message))
        {
            error = "消息不能为空";
            return false;
        }
        
        if (message.Length > MaxMessageLength)
        {
            error = $"消息长度不能超过 {MaxMessageLength} 字符";
            return false;
        }
        
        error = null;
        return true;
    }
    
    // 速率限制
    public bool CheckRateLimit(string userId, out string error)
    {
        var now = DateTime.UtcNow;
        
        if (!_userRequestTimes.TryGetValue(userId, out var times))
        {
            times = new Queue<DateTime>();
            _userRequestTimes[userId] = times;
        }
        
        // 移除一分钟前的请求
        while (times.Count > 0 && (now - times.Peek()).TotalMinutes > 1)
        {
            times.Dequeue();
        }
        
        if (times.Count >= MaxMessagesPerMinute)
        {
            error = $"请求过于频繁,每分钟最多 {MaxMessagesPerMinute} 次请求";
            return false;
        }
        
        times.Enqueue(now);
        error = null;
        return true;
    }
    
    // 检测恶意内容
    public bool DetectMaliciousContent(string message, out string error)
    {
        var maliciousPatterns = new[]
        {
            @"<script",           // XSS 攻击
            @"javascript:",       // JavaScript 注入
            @"onerror=",         // 事件处理器注入
            @"eval\(",           // 代码执行
            @"exec\(",           // 命令执行
        };
        
        foreach (var pattern in maliciousPatterns)
        {
            if (Regex.IsMatch(message, pattern, RegexOptions.IgnoreCase))
            {
                error = "检测到潜在的恶意内容";
                return true;
            }
        }
        
        error = null;
        return false;
    }
    
    // 综合验证
    public bool ValidateInput(string userId, string message, out string error)
    {
        if (!ValidateMessageLength(message, out error))
            return false;
            
        if (!CheckRateLimit(userId, out error))
            return false;
            
        if (DetectMaliciousContent(message, out error))
            return false;
            
        return true;
    }
}

3.2 清理输出内容

public class OutputSanitizer
{
    // 移除潜在的危险内容
    public string SanitizeOutput(string output)
    {
        // 移除 HTML 标签
        output = Regex.Replace(output, @"<[^>]+>", "");
        
        // 移除 JavaScript
        output = Regex.Replace(output, @"<script.*?</script>", "", RegexOptions.IgnoreCase | RegexOptions.Singleline);
        
        // 转义特殊字符
        output = output
            .Replace("<", "&lt;")
            .Replace(">", "&gt;")
            .Replace("\"", "&quot;")
            .Replace("'", "&#39;");
        
        return output;
    }
}

4. 安全配置示例

4.1 完整的安全配置类

public class SecureAgentConfiguration
{
    // 从安全来源加载配置
    public static SecureAgentConfiguration LoadFromSecureSource()
    {
        return new SecureAgentConfiguration
        {
            ApiKey = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY"),
            Endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT"),
            EncryptionKey = Environment.GetEnvironmentVariable("ENCRYPTION_KEY"),
            MaxRequestsPerMinute = 20,
            MaxMessageLength = 4000,
            EnableDataMasking = true,
            EnableEncryption = true,
            EnableRateLimit = true
        };
    }
    
    public string ApiKey { get; set; }
    public string Endpoint { get; set; }
    public string EncryptionKey { get; set; }
    public int MaxRequestsPerMinute { get; set; }
    public int MaxMessageLength { get; set; }
    public bool EnableDataMasking { get; set; }
    public bool EnableEncryption { get; set; }
    public bool EnableRateLimit { get; set; }
    
    // 验证配置
    public void Validate()
    {
        if (string.IsNullOrEmpty(ApiKey))
            throw new InvalidOperationException("未配置 API 密钥");
            
        if (string.IsNullOrEmpty(Endpoint))
            throw new InvalidOperationException("未配置 Endpoint");
            
        if (EnableEncryption && string.IsNullOrEmpty(EncryptionKey))
            throw new InvalidOperationException("启用加密但未配置加密密钥");
    }
}

4.2 安全的代理服务

public class SecureAgentService
{
    private readonly ChatCompletionAgent _agent;
    private readonly SecureAgentConfiguration _config;
    private readonly InputValidator _validator;
    private readonly DataMasker _masker;
    private readonly DataEncryption _encryption;
    private readonly OutputSanitizer _sanitizer;
    
    public SecureAgentService()
    {
        _config = SecureAgentConfiguration.LoadFromSecureSource();
        _config.Validate();
        
        var chatClient = new AzureOpenAIClient(
            new Uri(_config.Endpoint),
            new ApiKeyCredential(_config.ApiKey)
        ).GetChatClient("gpt-35-turbo");
        
        _agent = new ChatCompletionAgent(
            chatClient: chatClient,
            name: "SecureAgent",
            instructions: "你是一个安全的助手"
        );
        
        _validator = new InputValidator();
        _masker = new DataMasker();
        _encryption = new DataEncryption(_config.EncryptionKey);
        _sanitizer = new OutputSanitizer();
    }
    
    public async Task<string> ProcessMessageAsync(string userId, string message)
    {
        // 1. 验证输入
        if (!_validator.ValidateInput(userId, message, out var error))
        {
            throw new ArgumentException(error);
        }
        
        // 2. 脱敏敏感数据
        if (_config.EnableDataMasking)
        {
            message = _masker.MaskSensitiveData(message);
        }
        
        // 3. 调用 AI 代理
        var thread = new AgentThread();
        await thread.AddUserMessageAsync(message);
        var response = await _agent.InvokeAsync(thread);
        var output = response.Content;
        
        // 4. 清理输出
        output = _sanitizer.SanitizeOutput(output);
        
        // 5. 加密存储(如果需要)
        if (_config.EnableEncryption)
        {
            var encrypted = _encryption.Encrypt(output);
            await SaveToSecureStorage(userId, encrypted);
        }
        
        return output;
    }
    
    private Task SaveToSecureStorage(string userId, string data)
    {
        // 实际实现:保存到数据库
        return Task.CompletedTask;
    }
}

5. 安全检查清单

在部署应用之前,使用这个清单检查安全性:

  • [ ] API 密钥管理

    • [ ] 密钥不在代码中硬编码

    • [ ] 使用环境变量或 Key Vault

    • [ ] 配置文件已添加到 .gitignore

    • [ ] 实施密钥轮换策略

  • [ ] 数据保护

    • [ ] 敏感数据已脱敏

    • [ ] 存储的数据已加密

    • [ ] 实施了访问控制

    • [ ] 定期清理过期数据

  • [ ] 输入验证

    • [ ] 验证消息长度

    • [ ] 实施速率限制

    • [ ] 检测恶意内容

    • [ ] 清理用户输入

  • [ ] 输出安全

    • [ ] 清理 AI 输出

    • [ ] 移除潜在危险内容

    • [ ] 转义特殊字符

  • [ ] 网络安全

    • [ ] 使用 HTTPS

    • [ ] 实施 CORS 策略

    • [ ] 配置防火墙规则

  • [ ] 日志和监控

    • [ ] 记录安全事件

    • [ ] 不记录敏感信息

    • [ ] 配置告警

小结

安全是一个持续的过程,关键要点:

  1. 永远不要硬编码密钥:使用环境变量或 Key Vault

  2. 保护用户数据:脱敏、加密、访问控制

  3. 验证所有输入:长度、速率、恶意内容

  4. 清理所有输出:移除危险内容

  5. 持续监控:记录安全事件,及时响应

记住:安全不是一次性的工作,而是持续的实践

错误处理策略

概述

在开发 AI 代理应用时,错误处理是确保应用稳定性和用户体验的关键。本文将介绍完整的错误处理方法、代码示例和优雅处理失败的策略。

为什么错误处理很重要?

想象一下,如果你的 AI 助手在用户提问时突然崩溃,或者显示一堆技术错误信息,用户会有什么感受?

好的错误处理就像给你的应用装上"安全气囊",在出现问题时:

  • 保护应用不崩溃

  • 给用户友好的提示

  • 记录问题以便修复

  • 自动恢复或重试

常见的错误类型

1. 网络错误

  • API 调用超时

  • 网络连接中断

  • DNS 解析失败

2. API 错误

  • API 密钥无效

  • 配额超限

  • 速率限制

  • 模型不可用

3. 输入错误

  • 消息过长

  • 格式不正确

  • 包含非法内容

4. 系统错误

  • 内存不足

  • 磁盘空间不足

  • 依赖服务不可用

错误处理的基本原则

1. 捕获所有异常

❌ 不好的做法:不处理异常

public async Task<string> ProcessMessage(string message)
{
    // 如果出错,整个应用会崩溃
    var thread = new AgentThread();
    await thread.AddUserMessageAsync(message);
    var response = await _agent.InvokeAsync(thread);
    return response.Content;
}

✅ 好的做法:捕获并处理异常

public async Task<string> ProcessMessage(string message)
{
    try
    {
        var thread = new AgentThread();
        await thread.AddUserMessageAsync(message);
        var response = await _agent.InvokeAsync(thread);
        return response.Content;
    }
    catch (Exception ex)
    {
        // 记录错误
        Console.WriteLine($"错误: {ex.Message}");
        
        // 返回友好的错误消息
        return "抱歉,我遇到了一些问题。请稍后再试。";
    }
}

2. 区分不同类型的错误

public async Task<string> ProcessMessageWithDetailedErrorHandling(string message)
{
    try
    {
        var thread = new AgentThread();
        await thread.AddUserMessageAsync(message);
        var response = await _agent.InvokeAsync(thread);
        return response.Content;
    }
    catch (HttpRequestException ex)
    {
        // 网络错误
        Console.WriteLine($"网络错误: {ex.Message}");
        return "网络连接出现问题,请检查网络后重试。";
    }
    catch (TaskCanceledException ex)
    {
        // 超时错误
        Console.WriteLine($"请求超时: {ex.Message}");
        return "请求超时,请稍后重试。";
    }
    catch (UnauthorizedAccessException ex)
    {
        // 认证错误
        Console.WriteLine($"认证失败: {ex.Message}");
        return "服务认证失败,请联系管理员。";
    }
    catch (ArgumentException ex)
    {
        // 输入错误
        Console.WriteLine($"输入错误: {ex.Message}");
        return $"输入有误: {ex.Message}";
    }
    catch (Exception ex)
    {
        // 其他未知错误
        Console.WriteLine($"未知错误: {ex.Message}");
        return "抱歉,发生了未知错误。我们会尽快修复。";
    }
}

3. 实现重试机制

对于临时性错误(如网络波动),应该自动重试。

public class RetryPolicy
{
    private readonly int _maxRetries;
    private readonly TimeSpan _initialDelay;
    
    public RetryPolicy(int maxRetries = 3, TimeSpan? initialDelay = null)
    {
        _maxRetries = maxRetries;
        _initialDelay = initialDelay ?? TimeSpan.FromSeconds(1);
    }
    
    // 指数退避重试
    public async Task<T> ExecuteWithRetryAsync<T>(Func<Task<T>> action)
    {
        int attempt = 0;
        TimeSpan delay = _initialDelay;
        
        while (true)
        {
            try
            {
                return await action();
            }
            catch (Exception ex) when (IsTransientError(ex) && attempt < _maxRetries)
            {
                attempt++;
                Console.WriteLine($"尝试 {attempt}/{_maxRetries} 失败: {ex.Message}");
                Console.WriteLine($"等待 {delay.TotalSeconds} 秒后重试...");
                
                await Task.Delay(delay);
                
                // 指数退避:每次延迟时间翻倍
                delay = TimeSpan.FromSeconds(delay.TotalSeconds * 2);
            }
            catch (Exception ex)
            {
                // 非临时性错误或重试次数用尽
                Console.WriteLine($"操作失败: {ex.Message}");
                throw;
            }
        }
    }
    
    // 判断是否为临时性错误
    private bool IsTransientError(Exception ex)
    {
        return ex is HttpRequestException ||
               ex is TaskCanceledException ||
               ex is TimeoutException ||
               (ex.Message?.Contains("429") ?? false) || // 速率限制
               (ex.Message?.Contains("503") ?? false);   // 服务不可用
    }
}

使用重试策略

public class ResilientAgentService
{
    private readonly ChatCompletionAgent _agent;
    private readonly RetryPolicy _retryPolicy;
    
    public ResilientAgentService(ChatCompletionAgent agent)
    {
        _agent = agent;
        _retryPolicy = new RetryPolicy(maxRetries: 3);
    }
    
    public async Task<string> ProcessMessageAsync(string message)
    {
        return await _retryPolicy.ExecuteWithRetryAsync(async () =>
        {
            var thread = new AgentThread();
            await thread.AddUserMessageAsync(message);
            var response = await _agent.InvokeAsync(thread);
            return response.Content;
        });
    }
}

4. 实现断路器模式

当服务持续失败时,应该暂时停止调用,避免雪崩效应。

public class CircuitBreaker
{
    private int _failureCount = 0;
    private DateTime _lastFailureTime = DateTime.MinValue;
    private CircuitState _state = CircuitState.Closed;
    
    private readonly int _failureThreshold;
    private readonly TimeSpan _timeout;
    
    public CircuitBreaker(int failureThreshold = 5, TimeSpan? timeout = null)
    {
        _failureThreshold = failureThreshold;
        _timeout = timeout ?? TimeSpan.FromMinutes(1);
    }
    
    public async Task<T> ExecuteAsync<T>(Func<Task<T>> action)
    {
        // 检查断路器状态
        if (_state == CircuitState.Open)
        {
            // 检查是否可以尝试恢复
            if (DateTime.UtcNow - _lastFailureTime > _timeout)
            {
                _state = CircuitState.HalfOpen;
                Console.WriteLine("断路器进入半开状态,尝试恢复...");
            }
            else
            {
                throw new InvalidOperationException("服务暂时不可用,请稍后重试");
            }
        }
        
        try
        {
            var result = await action();
            
            // 成功,重置计数器
            if (_state == CircuitState.HalfOpen)
            {
                _state = CircuitState.Closed;
                Console.WriteLine("断路器已关闭,服务恢复正常");
            }
            _failureCount = 0;
            
            return result;
        }
        catch (Exception ex)
        {
            _failureCount++;
            _lastFailureTime = DateTime.UtcNow;
            
            Console.WriteLine($"操作失败 ({_failureCount}/{_failureThreshold}): {ex.Message}");
            
            // 达到阈值,打开断路器
            if (_failureCount >= _failureThreshold)
            {
                _state = CircuitState.Open;
                Console.WriteLine($"断路器已打开,将在 {_timeout.TotalSeconds} 秒后尝试恢复");
            }
            
            throw;
        }
    }
    
    public CircuitState State => _state;
}

public enum CircuitState
{
    Closed,    // 正常状态
    Open,      // 断开状态(停止调用)
    HalfOpen   // 半开状态(尝试恢复)
}

使用断路器

public class ProtectedAgentService
{
    private readonly ChatCompletionAgent _agent;
    private readonly CircuitBreaker _circuitBreaker;
    
    public ProtectedAgentService(ChatCompletionAgent agent)
    {
        _agent = agent;
        _circuitBreaker = new CircuitBreaker(failureThreshold: 5);
    }
    
    public async Task<string> ProcessMessageAsync(string message)
    {
        try
        {
            return await _circuitBreaker.ExecuteAsync(async () =>
            {
                var thread = new AgentThread();
                await thread.AddUserMessageAsync(message);
                var response = await _agent.InvokeAsync(thread);
                return response.Content;
            });
        }
        catch (InvalidOperationException ex) when (ex.Message.Contains("服务暂时不可用"))
        {
            return "服务正在维护中,请稍后再试。";
        }
    }
}

完整的错误处理框架

1. 自定义异常类型

// 基础异常类
public class AgentException : Exception
{
    public AgentException(string message) : base(message) { }
    public AgentException(string message, Exception innerException) 
        : base(message, innerException) { }
}

// API 相关异常
public class ApiException : AgentException
{
    public int? StatusCode { get; }
    
    public ApiException(string message, int? statusCode = null) 
        : base(message)
    {
        StatusCode = statusCode;
    }
}

// 配额超限异常
public class QuotaExceededException : ApiException
{
    public QuotaExceededException(string message) 
        : base(message, 429) { }
}

// 输入验证异常
public class ValidationException : AgentException
{
    public ValidationException(string message) : base(message) { }
}

// 配置异常
public class ConfigurationException : AgentException
{
    public ConfigurationException(string message) : base(message) { }
}

2. 错误处理中间件

public class ErrorHandlingMiddleware
{
    private readonly ILogger _logger;
    
    public ErrorHandlingMiddleware(ILogger logger)
    {
        _logger = logger;
    }
    
    public async Task<Result<T>> ExecuteAsync<T>(Func<Task<T>> action)
    {
        try
        {
            var result = await action();
            return Result<T>.Success(result);
        }
        catch (ValidationException ex)
        {
            _logger.LogWarning(ex, "输入验证失败");
            return Result<T>.Failure("输入验证失败: " + ex.Message);
        }
        catch (QuotaExceededException ex)
        {
            _logger.LogWarning(ex, "配额超限");
            return Result<T>.Failure("请求过于频繁,请稍后再试");
        }
        catch (ApiException ex)
        {
            _logger.LogError(ex, "API 调用失败");
            return Result<T>.Failure($"服务调用失败 (状态码: {ex.StatusCode})");
        }
        catch (ConfigurationException ex)
        {
            _logger.LogError(ex, "配置错误");
            return Result<T>.Failure("服务配置错误,请联系管理员");
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "未知错误");
            return Result<T>.Failure("发生未知错误,请稍后重试");
        }
    }
}

// 结果类型
public class Result<T>
{
    public bool IsSuccess { get; }
    public T Value { get; }
    public string Error { get; }
    
    private Result(bool isSuccess, T value, string error)
    {
        IsSuccess = isSuccess;
        Value = value;
        Error = error;
    }
    
    public static Result<T> Success(T value) => new(true, value, null);
    public static Result<T> Failure(string error) => new(false, default, error);
}

3. 日志记录

public interface ILogger
{
    void LogInfo(string message);
    void LogWarning(Exception ex, string message);
    void LogError(Exception ex, string message);
}

public class ConsoleLogger : ILogger
{
    public void LogInfo(string message)
    {
        Console.WriteLine($"[INFO] {DateTime.Now:yyyy-MM-dd HH:mm:ss} - {message}");
    }
    
    public void LogWarning(Exception ex, string message)
    {
        Console.WriteLine($"[WARN] {DateTime.Now:yyyy-MM-dd HH:mm:ss} - {message}");
        Console.WriteLine($"       异常: {ex.Message}");
    }
    
    public void LogError(Exception ex, string message)
    {
        Console.WriteLine($"[ERROR] {DateTime.Now:yyyy-MM-dd HH:mm:ss} - {message}");
        Console.WriteLine($"        异常: {ex.Message}");
        Console.WriteLine($"        堆栈: {ex.StackTrace}");
    }
}

4. 完整的错误处理服务

public class RobustAgentService
{
    private readonly ChatCompletionAgent _agent;
    private readonly RetryPolicy _retryPolicy;
    private readonly CircuitBreaker _circuitBreaker;
    private readonly ErrorHandlingMiddleware _errorHandler;
    private readonly ILogger _logger;
    
    public RobustAgentService(ChatCompletionAgent agent)
    {
        _agent = agent;
        _retryPolicy = new RetryPolicy(maxRetries: 3);
        _circuitBreaker = new CircuitBreaker(failureThreshold: 5);
        _logger = new ConsoleLogger();
        _errorHandler = new ErrorHandlingMiddleware(_logger);
    }
    
    public async Task<Result<string>> ProcessMessageAsync(string message)
    {
        return await _errorHandler.ExecuteAsync(async () =>
        {
            // 输入验证
            ValidateInput(message);
            
            // 使用断路器和重试策略
            return await _circuitBreaker.ExecuteAsync(async () =>
            {
                return await _retryPolicy.ExecuteWithRetryAsync(async () =>
                {
                    _logger.LogInfo($"处理消息: {message.Substring(0, Math.Min(50, message.Length))}...");
                    
                    var thread = new AgentThread();
                    await thread.AddUserMessageAsync(message);
                    var response = await _agent.InvokeAsync(thread);
                    
                    _logger.LogInfo("消息处理成功");
                    return response.Content;
                });
            });
        });
    }
    
    private void ValidateInput(string message)
    {
        if (string.IsNullOrWhiteSpace(message))
            throw new ValidationException("消息不能为空");
            
        if (message.Length > 4000)
            throw new ValidationException("消息长度不能超过 4000 字符");
    }
}

使用示例

基本使用

public class Program
{
    public static async Task Main(string[] args)
    {
        // 初始化代理
        var chatClient = new AzureOpenAIClient(
            new Uri(Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")),
            new ApiKeyCredential(Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY"))
        ).GetChatClient("gpt-35-turbo");
        
        var agent = new ChatCompletionAgent(
            chatClient: chatClient,
            name: "RobustAgent",
            instructions: "你是一个可靠的助手"
        );
        
        var service = new RobustAgentService(agent);
        
        // 处理用户消息
        Console.WriteLine("请输入消息(输入 'exit' 退出):");
        
        while (true)
        {
            Console.Write("\n用户: ");
            var input = Console.ReadLine();
            
            if (input?.ToLower() == "exit")
                break;
            
            // 处理消息
            var result = await service.ProcessMessageAsync(input);
            
            if (result.IsSuccess)
            {
                Console.WriteLine($"AI: {result.Value}");
            }
            else
            {
                Console.WriteLine($"错误: {result.Error}");
            }
        }
    }
}

高级使用:带监控和告警

public class MonitoredAgentService
{
    private readonly RobustAgentService _service;
    private int _totalRequests = 0;
    private int _successfulRequests = 0;
    private int _failedRequests = 0;
    
    public MonitoredAgentService(RobustAgentService service)
    {
        _service = service;
    }
    
    public async Task<Result<string>> ProcessMessageAsync(string message)
    {
        _totalRequests++;
        var startTime = DateTime.UtcNow;
        
        var result = await _service.ProcessMessageAsync(message);
        
        var duration = DateTime.UtcNow - startTime;
        
        if (result.IsSuccess)
        {
            _successfulRequests++;
        }
        else
        {
            _failedRequests++;
            
            // 如果失败率过高,发送告警
            if (GetFailureRate() > 0.5) // 失败率超过 50%
            {
                SendAlert($"警告:失败率过高 ({GetFailureRate():P})");
            }
        }
        
        // 记录指标
        Console.WriteLine($"[指标] 总请求: {_totalRequests}, " +
                         $"成功: {_successfulRequests}, " +
                         $"失败: {_failedRequests}, " +
                         $"成功率: {GetSuccessRate():P}, " +
                         $"耗时: {duration.TotalSeconds:F2}秒");
        
        return result;
    }
    
    private double GetSuccessRate() => 
        _totalRequests > 0 ? (double)_successfulRequests / _totalRequests : 0;
    
    private double GetFailureRate() => 
        _totalRequests > 0 ? (double)_failedRequests / _totalRequests : 0;
    
    private void SendAlert(string message)
    {
        Console.WriteLine($"\n🚨 告警: {message}\n");
        // 实际实现:发送邮件、短信或推送通知
    }
}

错误处理检查清单

在部署应用之前,使用这个清单检查错误处理:

  • [ ] 异常捕获

    • [ ] 所有 API 调用都有 try-catch

    • [ ] 区分不同类型的异常

    • [ ] 记录所有异常信息

  • [ ] 重试机制

    • [ ] 实现了指数退避重试

    • [ ] 设置了合理的重试次数

    • [ ] 只对临时性错误重试

  • [ ] 断路器

    • [ ] 实现了断路器模式

    • [ ] 设置了合理的失败阈值

    • [ ] 配置了恢复超时时间

  • [ ] 用户体验

    • [ ] 错误消息友好易懂

    • [ ] 不暴露技术细节

    • [ ] 提供解决建议

  • [ ] 日志记录

    • [ ] 记录所有错误

    • [ ] 包含足够的上下文信息

    • [ ] 不记录敏感信息

  • [ ] 监控告警

    • [ ] 监控错误率

    • [ ] 配置告警阈值

    • [ ] 及时响应告警

小结

错误处理是构建可靠应用的基础,关键要点:

  1. 捕获所有异常:不要让应用崩溃

  2. 区分错误类型:不同错误不同处理

  3. 实现重试机制:自动恢复临时性错误

  4. 使用断路器:防止雪崩效应

  5. 友好的错误消息:让用户知道发生了什么

  6. 完善的日志:帮助快速定位问题

  7. 持续监控:及时发现和解决问题

记住:好的错误处理不是避免错误,而是优雅地处理错误

测试方法

概述

测试是确保 AI 代理应用质量的关键。本文将介绍单元测试和集成测试的方法,并提供实用的测试代码示例。

为什么测试很重要?

想象一下,如果你的 AI 客服在上线后才发现无法正确回答用户问题,或者工具调用失败,会造成多大的损失?

测试就像给你的应用做"体检",在问题发生之前就发现并修复它们。

测试的好处

  1. 提前发现问题:在开发阶段就发现 bug

  2. 保证质量:确保功能按预期工作

  3. 安全重构:修改代码时不怕破坏现有功能

  4. 文档作用:测试代码展示了如何使用功能

测试的类型

1. 单元测试

测试单个函数或类的功能,不依赖外部服务。

2. 集成测试

测试多个组件协同工作,包括与外部服务的交互。

3. 端到端测试

测试完整的用户场景,从输入到输出。

单元测试

1. 测试框架选择

在 .NET 中,常用的测试框架有:

  • xUnit:现代、简洁(推荐)

  • NUnit:功能丰富

  • MSTest:微软官方

本文使用 xUnit 作为示例。

2. 创建测试项目

# 创建测试项目
dotnet new xunit -n AgentApp.Tests

# 添加项目引用
cd AgentApp.Tests
dotnet add reference ../AgentApp/AgentApp.csproj

# 添加必要的包
dotnet add package Moq
dotnet add package FluentAssertions

3. 测试工具函数

假设我们有一个数据脱敏工具:

// AgentApp/DataMasker.cs
public class DataMasker
{
    public string MaskPhoneNumber(string text)
    {
        return Regex.Replace(text, @"1[3-9]\d{9}", m =>
        {
            var phone = m.Value;
            return phone.Substring(0, 3) + "****" + phone.Substring(7);
        });
    }
    
    public string MaskEmail(string text)
    {
        return Regex.Replace(text, @"[\w\.-]+@[\w\.-]+\.\w+", m =>
        {
            var email = m.Value;
            var parts = email.Split('@');
            if (parts[0].Length <= 2)
                return "***@" + parts[1];
            return parts[0].Substring(0, 2) + "***@" + parts[1];
        });
    }
}

单元测试

// AgentApp.Tests/DataMaskerTests.cs
using Xunit;
using FluentAssertions;

public class DataMaskerTests
{
    private readonly DataMasker _masker;
    
    public DataMaskerTests()
    {
        _masker = new DataMasker();
    }
    
    [Fact]
    public void MaskPhoneNumber_ShouldMaskMiddleDigits()
    {
        // Arrange
        var input = "我的手机号是13812345678";
        
        // Act
        var result = _masker.MaskPhoneNumber(input);
        
        // Assert
        result.Should().Be("我的手机号是138****5678");
    }
    
    [Theory]
    [InlineData("13812345678", "138****5678")]
    [InlineData("15912345678", "159****5678")]
    [InlineData("18812345678", "188****5678")]
    public void MaskPhoneNumber_ShouldHandleVariousPhoneNumbers(string phone, string expected)
    {
        // Act
        var result = _masker.MaskPhoneNumber(phone);
        
        // Assert
        result.Should().Be(expected);
    }
    
    [Fact]
    public void MaskEmail_ShouldMaskUsername()
    {
        // Arrange
        var input = "联系我:zhangsan@example.com";
        
        // Act
        var result = _masker.MaskEmail(input);
        
        // Assert
        result.Should().Be("联系我:zh***@example.com");
    }
    
    [Fact]
    public void MaskEmail_ShortUsername_ShouldMaskCompletely()
    {
        // Arrange
        var input = "邮箱:ab@test.com";
        
        // Act
        var result = _masker.MaskEmail(input);
        
        // Assert
        result.Should().Be("邮箱:***@test.com");
    }
}

运行测试

dotnet test

4. 测试输入验证

// AgentApp/InputValidator.cs
public class InputValidator
{
    private const int MaxMessageLength = 4000;
    
    public bool ValidateMessageLength(string message, out string error)
    {
        if (string.IsNullOrWhiteSpace(message))
        {
            error = "消息不能为空";
            return false;
        }
        
        if (message.Length > MaxMessageLength)
        {
            error = $"消息长度不能超过 {MaxMessageLength} 字符";
            return false;
        }
        
        error = null;
        return true;
    }
}

单元测试

// AgentApp.Tests/InputValidatorTests.cs
public class InputValidatorTests
{
    private readonly InputValidator _validator;
    
    public InputValidatorTests()
    {
        _validator = new InputValidator();
    }
    
    [Fact]
    public void ValidateMessageLength_ValidMessage_ShouldReturnTrue()
    {
        // Arrange
        var message = "这是一条有效的消息";
        
        // Act
        var result = _validator.ValidateMessageLength(message, out var error);
        
        // Assert
        result.Should().BeTrue();
        error.Should().BeNull();
    }
    
    [Theory]
    [InlineData(null)]
    [InlineData("")]
    [InlineData("   ")]
    public void ValidateMessageLength_EmptyMessage_ShouldReturnFalse(string message)
    {
        // Act
        var result = _validator.ValidateMessageLength(message, out var error);
        
        // Assert
        result.Should().BeFalse();
        error.Should().Be("消息不能为空");
    }
    
    [Fact]
    public void ValidateMessageLength_TooLongMessage_ShouldReturnFalse()
    {
        // Arrange
        var message = new string('a', 4001);
        
        // Act
        var result = _validator.ValidateMessageLength(message, out var error);
        
        // Assert
        result.Should().BeFalse();
        error.Should().Contain("不能超过");
    }
}

5. 使用 Mock 测试依赖

当测试的代码依赖外部服务时,使用 Mock 对象模拟这些依赖。

// AgentApp/AgentService.cs
public interface IChatClient
{
    Task<string> SendMessageAsync(string message);
}

public class AgentService
{
    private readonly IChatClient _chatClient;
    private readonly InputValidator _validator;
    
    public AgentService(IChatClient chatClient, InputValidator validator)
    {
        _chatClient = chatClient;
        _validator = validator;
    }
    
    public async Task<string> ProcessMessageAsync(string message)
    {
        // 验证输入
        if (!_validator.ValidateMessageLength(message, out var error))
        {
            throw new ArgumentException(error);
        }
        
        // 调用 AI 服务
        return await _chatClient.SendMessageAsync(message);
    }
}

使用 Mock 的单元测试

// AgentApp.Tests/AgentServiceTests.cs
using Moq;

public class AgentServiceTests
{
    private readonly Mock<IChatClient> _mockChatClient;
    private readonly InputValidator _validator;
    private readonly AgentService _service;
    
    public AgentServiceTests()
    {
        _mockChatClient = new Mock<IChatClient>();
        _validator = new InputValidator();
        _service = new AgentService(_mockChatClient.Object, _validator);
    }
    
    [Fact]
    public async Task ProcessMessageAsync_ValidMessage_ShouldCallChatClient()
    {
        // Arrange
        var message = "你好";
        var expectedResponse = "你好!我是 AI 助手";
        
        _mockChatClient
            .Setup(x => x.SendMessageAsync(message))
            .ReturnsAsync(expectedResponse);
        
        // Act
        var result = await _service.ProcessMessageAsync(message);
        
        // Assert
        result.Should().Be(expectedResponse);
        _mockChatClient.Verify(x => x.SendMessageAsync(message), Times.Once);
    }
    
    [Fact]
    public async Task ProcessMessageAsync_EmptyMessage_ShouldThrowException()
    {
        // Arrange
        var message = "";
        
        // Act & Assert
        await Assert.ThrowsAsync<ArgumentException>(
            async () => await _service.ProcessMessageAsync(message)
        );
        
        // 验证没有调用 ChatClient
        _mockChatClient.Verify(x => x.SendMessageAsync(It.IsAny<string>()), Times.Never);
    }
}

集成测试

集成测试验证多个组件协同工作,包括与真实服务的交互。

1. 测试与 AI 服务的集成

// AgentApp.Tests/Integration/AgentIntegrationTests.cs
using Microsoft.Extensions.Configuration;

public class AgentIntegrationTests : IDisposable
{
    private readonly ChatCompletionAgent _agent;
    private readonly IConfiguration _configuration;
    
    public AgentIntegrationTests()
    {
        // 加载配置
        _configuration = new ConfigurationBuilder()
            .AddEnvironmentVariables()
            .Build();
        
        // 创建真实的代理
        var endpoint = _configuration["AZURE_OPENAI_ENDPOINT"];
        var apiKey = _configuration["AZURE_OPENAI_API_KEY"];
        
        if (string.IsNullOrEmpty(endpoint) || string.IsNullOrEmpty(apiKey))
        {
            throw new InvalidOperationException("请配置 AZURE_OPENAI_ENDPOINT 和 AZURE_OPENAI_API_KEY");
        }
        
        var chatClient = new AzureOpenAIClient(
            new Uri(endpoint),
            new ApiKeyCredential(apiKey)
        ).GetChatClient("gpt-35-turbo");
        
        _agent = new ChatCompletionAgent(
            chatClient: chatClient,
            name: "TestAgent",
            instructions: "你是一个测试助手,回答要简洁"
        );
    }
    
    [Fact]
    public async Task Agent_ShouldRespondToSimpleQuestion()
    {
        // Arrange
        var thread = new AgentThread();
        await thread.AddUserMessageAsync("1+1等于几?");
        
        // Act
        var response = await _agent.InvokeAsync(thread);
        
        // Assert
        response.Should().NotBeNull();
        response.Content.Should().Contain("2");
    }
    
    [Fact]
    public async Task Agent_ShouldMaintainContext()
    {
        // Arrange
        var thread = new AgentThread();
        
        // 第一轮对话
        await thread.AddUserMessageAsync("我叫张三");
        var response1 = await _agent.InvokeAsync(thread);
        
        // 第二轮对话
        await thread.AddUserMessageAsync("我叫什么名字?");
        var response2 = await _agent.InvokeAsync(thread);
        
        // Assert
        response2.Content.Should().Contain("张三");
    }
    
    public void Dispose()
    {
        // 清理资源
    }
}

2. 测试工具调用

// AgentApp/Tools/CalculatorTool.cs
public class CalculatorTool
{
    [Description("计算两个数的和")]
    public int Add(
        [Description("第一个数")] int a,
        [Description("第二个数")] int b)
    {
        return a + b;
    }
    
    [Description("计算两个数的乘积")]
    public int Multiply(
        [Description("第一个数")] int a,
        [Description("第二个数")] int b)
    {
        return a * b;
    }
}

集成测试

// AgentApp.Tests/Integration/ToolIntegrationTests.cs
public class ToolIntegrationTests
{
    private readonly ChatCompletionAgent _agent;
    
    public ToolIntegrationTests()
    {
        var chatClient = CreateChatClient(); // 创建真实客户端
        var calculator = new CalculatorTool();
        
        _agent = new ChatCompletionAgent(
            chatClient: chatClient,
            name: "CalculatorAgent",
            instructions: "你是一个计算助手,使用工具进行计算"
        )
        {
            Tools = [AIFunctionFactory.Create(calculator.Add), 
                     AIFunctionFactory.Create(calculator.Multiply)]
        };
    }
    
    [Fact]
    public async Task Agent_ShouldUseAddTool()
    {
        // Arrange
        var thread = new AgentThread();
        await thread.AddUserMessageAsync("计算 15 + 27");
        
        // Act
        var response = await _agent.InvokeAsync(thread);
        
        // Assert
        response.Content.Should().Contain("42");
    }
    
    [Fact]
    public async Task Agent_ShouldUseMultiplyTool()
    {
        // Arrange
        var thread = new AgentThread();
        await thread.AddUserMessageAsync("计算 6 乘以 7");
        
        // Act
        var response = await _agent.InvokeAsync(thread);
        
        // Assert
        response.Content.Should().Contain("42");
    }
    
    private IChatClient CreateChatClient()
    {
        // 实际实现
        return null;
    }
}

3. 测试多轮对话

public class ConversationIntegrationTests
{
    [Fact]
    public async Task MultiTurnConversation_ShouldMaintainContext()
    {
        // Arrange
        var agent = CreateAgent();
        var thread = new AgentThread();
        
        // 第一轮:设置上下文
        await thread.AddUserMessageAsync("我想买一台笔记本电脑");
        var response1 = await agent.InvokeAsync(thread);
        response1.Content.Should().NotBeNullOrEmpty();
        
        // 第二轮:基于上下文提问
        await thread.AddUserMessageAsync("预算在 5000 元左右");
        var response2 = await agent.InvokeAsync(thread);
        response2.Content.Should().Contain("笔记本");
        
        // 第三轮:继续对话
        await thread.AddUserMessageAsync("有什么推荐吗?");
        var response3 = await agent.InvokeAsync(thread);
        response3.Content.Should().NotBeNullOrEmpty();
    }
    
    private ChatCompletionAgent CreateAgent()
    {
        // 实际实现
        return null;
    }
}

测试最佳实践

1. 测试命名规范

使用清晰的命名让测试易于理解:

// 格式:MethodName_Scenario_ExpectedBehavior

[Fact]
public void MaskPhoneNumber_ValidPhone_ShouldMaskMiddleDigits() { }

[Fact]
public void ValidateInput_EmptyMessage_ShouldReturnFalse() { }

[Fact]
public void ProcessMessage_WithRetry_ShouldSucceedAfterFailure() { }

2. AAA 模式

每个测试分为三个部分:

[Fact]
public void Example_Test()
{
    // Arrange(准备):设置测试数据和依赖
    var input = "test input";
    var expected = "expected output";
    
    // Act(执行):调用被测试的方法
    var result = MethodUnderTest(input);
    
    // Assert(断言):验证结果
    result.Should().Be(expected);
}

3. 一个测试只验证一件事

// ❌ 不好:一个测试验证多件事
[Fact]
public void ProcessMessage_ShouldValidateAndCallApiAndReturnResult()
{
    // 测试太多东西
}

// ✅ 好:分成多个测试
[Fact]
public void ProcessMessage_InvalidInput_ShouldThrowException() { }

[Fact]
public void ProcessMessage_ValidInput_ShouldCallApi() { }

[Fact]
public void ProcessMessage_ApiSuccess_ShouldReturnResult() { }

4. 使用测试数据生成器

public class TestDataBuilder
{
    public static string CreateValidMessage(int length = 100)
    {
        return new string('a', length);
    }
    
    public static string CreateInvalidMessage()
    {
        return new string('a', 5000); // 超过限制
    }
    
    public static AgentThread CreateThreadWithHistory(int messageCount)
    {
        var thread = new AgentThread();
        for (int i = 0; i < messageCount; i++)
        {
            thread.AddUserMessageAsync($"消息 {i}").Wait();
        }
        return thread;
    }
}

// 使用
[Fact]
public void Test_WithGeneratedData()
{
    var message = TestDataBuilder.CreateValidMessage(50);
    // 使用 message 进行测试
}

5. 测试异步代码

[Fact]
public async Task AsyncMethod_ShouldReturnExpectedResult()
{
    // Arrange
    var service = new AgentService();
    
    // Act
    var result = await service.ProcessMessageAsync("test");
    
    // Assert
    result.Should().NotBeNull();
}

[Fact]
public async Task AsyncMethod_ShouldThrowException()
{
    // Arrange
    var service = new AgentService();
    
    // Act & Assert
    await Assert.ThrowsAsync<ArgumentException>(
        async () => await service.ProcessMessageAsync("")
    );
}

测试覆盖率

1. 安装覆盖率工具

dotnet add package coverlet.collector

2. 运行覆盖率测试

dotnet test --collect:"XPlat Code Coverage"

3. 生成覆盖率报告

# 安装报告生成工具
dotnet tool install -g dotnet-reportgenerator-globaltool

# 生成报告
reportgenerator -reports:"**/coverage.cobertura.xml" -targetdir:"coveragereport" -reporttypes:Html

# 查看报告
start coveragereport/index.html

4. 覆盖率目标

  • 核心业务逻辑:80% 以上

  • 工具函数:90% 以上

  • UI 代码:50% 以上(可选)

持续集成中的测试

1. GitHub Actions 配置

# .github/workflows/test.yml
name: Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v2
    
    - name: Setup .NET
      uses: actions/setup-dotnet@v1
      with:
        dotnet-version: '8.0.x'
    
    - name: Restore dependencies
      run: dotnet restore
    
    - name: Build
      run: dotnet build --no-restore
    
    - name: Run tests
      run: dotnet test --no-build --verbosity normal --collect:"XPlat Code Coverage"
    
    - name: Upload coverage
      uses: codecov/codecov-action@v2

测试检查清单

在提交代码之前,使用这个清单检查测试:

  • [ ] 单元测试

    • [ ] 所有工具函数都有测试

    • [ ] 所有验证逻辑都有测试

    • [ ] 边界条件都有测试

    • [ ] 异常情况都有测试

  • [ ] 集成测试

    • [ ] AI 服务调用有测试

    • [ ] 工具调用有测试

    • [ ] 多轮对话有测试

  • [ ] 测试质量

    • [ ] 测试命名清晰

    • [ ] 使用 AAA 模式

    • [ ] 一个测试只验证一件事

    • [ ] 测试是独立的(不依赖其他测试)

  • [ ] 覆盖率

    • [ ] 核心逻辑覆盖率 > 80%

    • [ ] 关键路径都有测试

  • [ ] 持续集成

    • [ ] 配置了自动测试

    • [ ] 测试失败会阻止合并

小结

测试是保证代码质量的重要手段,关键要点:

  1. 单元测试:测试单个函数,使用 Mock 隔离依赖

  2. 集成测试:测试组件协作,验证真实场景

  3. 测试命名:清晰描述测试内容

  4. AAA 模式:准备、执行、断言

  5. 测试覆盖率:核心逻辑要有足够覆盖

  6. 持续集成:自动运行测试

记住:好的测试不仅能发现 bug,还能作为代码的文档

生产环境清单

概述

在将 AI 代理应用部署到生产环境之前,需要进行全面的检查和准备。本文提供了一个完整的上线前检查清单、监控告警配置指南和运维最佳实践。

为什么需要生产环境清单?

想象一下,如果你的应用在生产环境中出现问题,但你没有监控、没有日志、没有备份,会是什么情况?

生产环境清单就像飞行员起飞前的检查清单,确保所有关键系统都已就绪,避免在生产环境中出现意外。

上线前检查清单

1. 安全检查

1.1 密钥管理
  • [ ] API 密钥不在代码中硬编码

  • [ ] 使用环境变量或 Azure Key Vault 存储密钥

  • [ ] 配置文件已添加到 .gitignore

  • [ ] 生产环境和开发环境使用不同的密钥

  • [ ] 实施了密钥轮换策略

  • [ ] 密钥访问权限已正确配置

验证方法

# 检查代码中是否有硬编码的密钥
grep -r "sk-" . --exclude-dir=node_modules --exclude-dir=bin
grep -r "api_key" . --exclude-dir=node_modules --exclude-dir=bin
1.2 数据保护
  • [ ] 敏感数据已脱敏

  • [ ] 存储的数据已加密

  • [ ] 实施了访问控制(RBAC)

  • [ ] 配置了数据备份策略

  • [ ] 符合数据保护法规(GDPR、个人信息保护法等)

1.3 网络安全
  • [ ] 使用 HTTPS

  • [ ] 配置了 CORS 策略

  • [ ] 实施了速率限制

  • [ ] 配置了防火墙规则

  • [ ] 启用了 DDoS 防护

2. 性能检查

2.1 性能优化
  • [ ] 实现了代理复用

  • [ ] 配置了缓存策略

  • [ ] 优化了提示词长度

  • [ ] 实现了流式响应

  • [ ] 配置了并行处理

  • [ ] 限制了对话历史长度

  • [ ] 选择了合适的模型

性能基准测试

public class PerformanceBenchmark
{
    public async Task RunBenchmark()
    {
        Console.WriteLine("=== 性能基准测试 ===\n");
        
        // 测试响应时间
        var avgResponseTime = await MeasureAverageResponseTime();
        Console.WriteLine($"平均响应时间: {avgResponseTime:F2} 秒");
        
        // 测试并发能力
        var throughput = await MeasureThroughput();
        Console.WriteLine($"吞吐量: {throughput:F2} 请求/秒");
        
        // 测试缓存命中率
        var cacheHitRate = await MeasureCacheHitRate();
        Console.WriteLine($"缓存命中率: {cacheHitRate:P}");
    }
    
    private async Task<double> MeasureAverageResponseTime()
    {
        // 实现测量逻辑
        return 0;
    }
    
    private async Task<double> MeasureThroughput()
    {
        // 实现测量逻辑
        return 0;
    }
    
    private async Task<double> MeasureCacheHitRate()
    {
        // 实现测量逻辑
        return 0;
    }
}

性能目标

  • 平均响应时间 < 3 秒

  • P95 响应时间 < 5 秒

  • 吞吐量 > 10 请求/秒

  • 缓存命中率 > 30%

2.2 资源配置
  • [ ] 配置了合适的 CPU 和内存

  • [ ] 配置了自动扩展规则

  • [ ] 设置了资源限制

  • [ ] 配置了负载均衡

3. 可靠性检查

3.1 错误处理
  • [ ] 实现了完整的异常捕获

  • [ ] 配置了重试机制

  • [ ] 实现了断路器模式

  • [ ] 错误消息对用户友好

  • [ ] 记录了所有错误日志

3.2 容错能力
  • [ ] 配置了健康检查

  • [ ] 实现了优雅降级

  • [ ] 配置了故障转移

  • [ ] 实现了数据备份和恢复

健康检查示例

public class HealthCheck
{
    private readonly ChatCompletionAgent _agent;
    
    public async Task<HealthStatus> CheckHealthAsync()
    {
        var status = new HealthStatus();
        
        try
        {
            // 检查 AI 服务连接
            var thread = new AgentThread();
            await thread.AddUserMessageAsync("健康检查");
            var response = await _agent.InvokeAsync(thread);
            
            status.IsHealthy = true;
            status.Message = "服务正常";
        }
        catch (Exception ex)
        {
            status.IsHealthy = false;
            status.Message = $"服务异常: {ex.Message}";
        }
        
        return status;
    }
}

public class HealthStatus
{
    public bool IsHealthy { get; set; }
    public string Message { get; set; }
    public DateTime CheckTime { get; set; } = DateTime.UtcNow;
}

4. 监控和日志

4.1 日志配置
  • [ ] 配置了结构化日志

  • [ ] 日志级别设置正确(生产环境使用 Warning 或 Error)

  • [ ] 不记录敏感信息

  • [ ] 配置了日志轮转

  • [ ] 日志可以集中查看

日志配置示例

public class ProductionLogger : ILogger
{
    private readonly string _logPath;
    private readonly LogLevel _minLevel;
    
    public ProductionLogger(string logPath, LogLevel minLevel = LogLevel.Warning)
    {
        _logPath = logPath;
        _minLevel = minLevel;
    }
    
    public void Log(LogLevel level, string message, Exception ex = null)
    {
        if (level < _minLevel)
            return;
        
        var logEntry = new
        {
            Timestamp = DateTime.UtcNow,
            Level = level.ToString(),
            Message = message,
            Exception = ex?.ToString(),
            MachineName = Environment.MachineName,
            ProcessId = Environment.ProcessId
        };
        
        var json = JsonSerializer.Serialize(logEntry);
        File.AppendAllText(_logPath, json + Environment.NewLine);
    }
}

public enum LogLevel
{
    Debug,
    Info,
    Warning,
    Error,
    Critical
}
4.2 监控指标
  • [ ] 配置了性能监控(响应时间、吞吐量)

  • [ ] 配置了错误率监控

  • [ ] 配置了资源使用监控(CPU、内存、磁盘)

  • [ ] 配置了业务指标监控(用户数、对话数)

  • [ ] 配置了成本监控(API 调用费用)

监控指标收集

public class MetricsCollector
{
    private long _totalRequests = 0;
    private long _successfulRequests = 0;
    private long _failedRequests = 0;
    private readonly List<double> _responseTimes = new();
    
    public void RecordRequest(bool success, double responseTime)
    {
        Interlocked.Increment(ref _totalRequests);
        
        if (success)
            Interlocked.Increment(ref _successfulRequests);
        else
            Interlocked.Increment(ref _failedRequests);
        
        lock (_responseTimes)
        {
            _responseTimes.Add(responseTime);
            
            // 只保留最近 1000 条记录
            if (_responseTimes.Count > 1000)
                _responseTimes.RemoveAt(0);
        }
    }
    
    public Metrics GetMetrics()
    {
        lock (_responseTimes)
        {
            return new Metrics
            {
                TotalRequests = _totalRequests,
                SuccessfulRequests = _successfulRequests,
                FailedRequests = _failedRequests,
                SuccessRate = _totalRequests > 0 ? (double)_successfulRequests / _totalRequests : 0,
                AverageResponseTime = _responseTimes.Any() ? _responseTimes.Average() : 0,
                P95ResponseTime = _responseTimes.Any() ? CalculatePercentile(_responseTimes, 0.95) : 0
            };
        }
    }
    
    private double CalculatePercentile(List<double> values, double percentile)
    {
        var sorted = values.OrderBy(x => x).ToList();
        var index = (int)Math.Ceiling(sorted.Count * percentile) - 1;
        return sorted[Math.Max(0, index)];
    }
}

public class Metrics
{
    public long TotalRequests { get; set; }
    public long SuccessfulRequests { get; set; }
    public long FailedRequests { get; set; }
    public double SuccessRate { get; set; }
    public double AverageResponseTime { get; set; }
    public double P95ResponseTime { get; set; }
}

5. 测试检查

5.1 测试覆盖
  • [ ] 单元测试覆盖率 > 80%

  • [ ] 集成测试覆盖关键场景

  • [ ] 端到端测试通过

  • [ ] 性能测试达标

  • [ ] 压力测试通过

5.2 测试环境
  • [ ] 在类生产环境中测试

  • [ ] 测试了故障场景

  • [ ] 测试了高负载场景

  • [ ] 测试了数据恢复

6. 文档检查

  • [ ] API 文档完整

  • [ ] 部署文档完整

  • [ ] 运维手册完整

  • [ ] 故障排查指南完整

  • [ ] 用户手册完整(如需要)

监控和告警配置

1. 配置 Application Insights

using Microsoft.ApplicationInsights;
using Microsoft.ApplicationInsights.Extensibility;

public class ApplicationInsightsMonitoring
{
    private readonly TelemetryClient _telemetryClient;
    
    public ApplicationInsightsMonitoring(string instrumentationKey)
    {
        var config = TelemetryConfiguration.CreateDefault();
        config.InstrumentationKey = instrumentationKey;
        _telemetryClient = new TelemetryClient(config);
    }
    
    public void TrackRequest(string name, DateTimeOffset startTime, TimeSpan duration, bool success)
    {
        _telemetryClient.TrackRequest(name, startTime, duration, 
            success ? "200" : "500", success);
    }
    
    public void TrackException(Exception ex)
    {
        _telemetryClient.TrackException(ex);
    }
    
    public void TrackMetric(string name, double value)
    {
        _telemetryClient.TrackMetric(name, value);
    }
    
    public void TrackEvent(string name, Dictionary<string, string> properties = null)
    {
        _telemetryClient.TrackEvent(name, properties);
    }
}

2. 配置告警规则

告警配置示例

public class AlertConfiguration
{
    public List<AlertRule> Rules { get; set; } = new()
    {
        new AlertRule
        {
            Name = "高错误率告警",
            Condition = metrics => metrics.SuccessRate < 0.95,
            Message = "错误率超过 5%",
            Severity = AlertSeverity.High
        },
        new AlertRule
        {
            Name = "响应时间告警",
            Condition = metrics => metrics.P95ResponseTime > 5.0,
            Message = "P95 响应时间超过 5 秒",
            Severity = AlertSeverity.Medium
        },
        new AlertRule
        {
            Name = "服务不可用告警",
            Condition = metrics => metrics.TotalRequests == 0,
            Message = "服务可能不可用",
            Severity = AlertSeverity.Critical
        }
    };
}

public class AlertRule
{
    public string Name { get; set; }
    public Func<Metrics, bool> Condition { get; set; }
    public string Message { get; set; }
    public AlertSeverity Severity { get; set; }
}

public enum AlertSeverity
{
    Low,
    Medium,
    High,
    Critical
}

告警检查器

public class AlertChecker
{
    private readonly AlertConfiguration _config;
    private readonly INotificationService _notificationService;
    
    public AlertChecker(AlertConfiguration config, INotificationService notificationService)
    {
        _config = config;
        _notificationService = notificationService;
    }
    
    public async Task CheckAlertsAsync(Metrics metrics)
    {
        foreach (var rule in _config.Rules)
        {
            if (rule.Condition(metrics))
            {
                await _notificationService.SendAlertAsync(new Alert
                {
                    RuleName = rule.Name,
                    Message = rule.Message,
                    Severity = rule.Severity,
                    Timestamp = DateTime.UtcNow,
                    Metrics = metrics
                });
            }
        }
    }
}

public interface INotificationService
{
    Task SendAlertAsync(Alert alert);
}

public class Alert
{
    public string RuleName { get; set; }
    public string Message { get; set; }
    public AlertSeverity Severity { get; set; }
    public DateTime Timestamp { get; set; }
    public Metrics Metrics { get; set; }
}

3. 配置通知渠道

public class EmailNotificationService : INotificationService
{
    private readonly string _smtpServer;
    private readonly string _fromEmail;
    private readonly List<string> _toEmails;
    
    public EmailNotificationService(string smtpServer, string fromEmail, List<string> toEmails)
    {
        _smtpServer = smtpServer;
        _fromEmail = fromEmail;
        _toEmails = toEmails;
    }
    
    public async Task SendAlertAsync(Alert alert)
    {
        var subject = $"[{alert.Severity}] {alert.RuleName}";
        var body = $@"
告警时间: {alert.Timestamp:yyyy-MM-dd HH:mm:ss}
告警规则: {alert.RuleName}
告警消息: {alert.Message}

当前指标:
- 总请求数: {alert.Metrics.TotalRequests}
- 成功率: {alert.Metrics.SuccessRate:P}
- 平均响应时间: {alert.Metrics.AverageResponseTime:F2} 秒
- P95 响应时间: {alert.Metrics.P95ResponseTime:F2} 秒
";
        
        // 实际发送邮件的代码
        Console.WriteLine($"发送告警邮件: {subject}");
        await Task.CompletedTask;
    }
}

运维最佳实践

1. 部署策略

1.1 蓝绿部署
  • 维护两个相同的生产环境(蓝和绿)

  • 新版本部署到非活动环境

  • 测试通过后切换流量

  • 出问题可以快速回滚

1.2 金丝雀发布
  • 先将新版本部署到一小部分服务器

  • 观察指标,逐步扩大范围

  • 发现问题及时回滚

2. 备份和恢复

public class BackupService
{
    private readonly string _backupPath;
    
    public BackupService(string backupPath)
    {
        _backupPath = backupPath;
    }
    
    // 备份配置
    public async Task BackupConfigurationAsync()
    {
        var config = LoadConfiguration();
        var backupFile = Path.Combine(_backupPath, 
            $"config_backup_{DateTime.UtcNow:yyyyMMdd_HHmmss}.json");
        
        await File.WriteAllTextAsync(backupFile, 
            JsonSerializer.Serialize(config, new JsonSerializerOptions { WriteIndented = true }));
        
        Console.WriteLine($"配置已备份到: {backupFile}");
    }
    
    // 备份数据
    public async Task BackupDataAsync()
    {
        // 实现数据备份逻辑
        await Task.CompletedTask;
    }
    
    // 恢复配置
    public async Task RestoreConfigurationAsync(string backupFile)
    {
        var json = await File.ReadAllTextAsync(backupFile);
        var config = JsonSerializer.Deserialize<Configuration>(json);
        
        // 恢复配置
        Console.WriteLine($"配置已从 {backupFile} 恢复");
    }
    
    private Configuration LoadConfiguration()
    {
        // 实际实现
        return new Configuration();
    }
}

public class Configuration
{
    // 配置属性
}

3. 定期维护任务

public class MaintenanceTasks
{
    // 清理过期数据
    public async Task CleanupExpiredDataAsync()
    {
        Console.WriteLine("开始清理过期数据...");
        
        // 清理 30 天前的日志
        var cutoffDate = DateTime.UtcNow.AddDays(-30);
        // 实现清理逻辑
        
        Console.WriteLine("过期数据清理完成");
        await Task.CompletedTask;
    }
    
    // 优化数据库
    public async Task OptimizeDatabaseAsync()
    {
        Console.WriteLine("开始优化数据库...");
        
        // 实现数据库优化逻辑
        
        Console.WriteLine("数据库优化完成");
        await Task.CompletedTask;
    }
    
    // 检查系统健康
    public async Task CheckSystemHealthAsync()
    {
        Console.WriteLine("开始系统健康检查...");
        
        // 检查磁盘空间
        var drives = DriveInfo.GetDrives();
        foreach (var drive in drives.Where(d => d.IsReady))
        {
            var freeSpacePercent = (double)drive.AvailableFreeSpace / drive.TotalSize;
            if (freeSpacePercent < 0.1)
            {
                Console.WriteLine($"警告: 磁盘 {drive.Name} 空间不足 ({freeSpacePercent:P})");
            }
        }
        
        Console.WriteLine("系统健康检查完成");
        await Task.CompletedTask;
    }
}

4. 故障排查流程

故障排查清单

  1. 确认问题

    • 收集错误信息

    • 确定影响范围

    • 记录开始时间

  2. 查看监控

    • 检查错误率

    • 检查响应时间

    • 检查资源使用

  3. 查看日志

    • 查找错误日志

    • 分析错误模式

    • 确定根本原因

  4. 采取行动

    • 如果是配置问题,回滚配置

    • 如果是代码问题,回滚版本

    • 如果是资源问题,扩容资源

  5. 验证修复

    • 确认问题已解决

    • 监控指标恢复正常

    • 通知相关人员

  6. 事后分析

    • 记录故障原因

    • 制定预防措施

    • 更新文档

生产环境检查清单总结

上线前必查项

  • [ ] 安全:密钥管理、数据保护、网络安全

  • [ ] 性能:性能优化、资源配置

  • [ ] 可靠性:错误处理、容错能力

  • [ ] 监控:日志配置、监控指标、告警规则

  • [ ] 测试:测试覆盖、测试环境

  • [ ] 文档:API 文档、部署文档、运维手册

上线后必做项

  • [ ] 监控:持续监控关键指标

  • [ ] 告警:及时响应告警

  • [ ] 备份:定期备份配置和数据

  • [ ] 维护:定期执行维护任务

  • [ ] 优化:根据监控数据持续优化

小结

生产环境部署是一个系统工程,关键要点:

  1. 全面检查:使用清单确保不遗漏

  2. 监控告警:及时发现和响应问题

  3. 备份恢复:做好最坏的打算

  4. 持续优化:根据数据不断改进

  5. 文档完善:让团队都能快速上手

记住:生产环境的稳定性比新功能更重要

更多AIGC文章

RAG技术全解:从原理到实战的简明指南

更多VibeCoding文章

更多Agent文章

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐