AgentFramework-零基础入门-第10章_进阶主题和最佳实践
本文摘要: 《AI代理应用开发全攻略》从性能优化、安全防护、错误处理到生产部署,全面介绍了AI代理开发的实践要点。性能优化部分提出了7大策略,包括API调用优化、缓存机制和模型选择,可提升响应速度75%并降低60%成本。安全章节详细讲解了密钥管理、数据脱敏和访问控制,确保系统防护无死角。错误处理模块设计了断路器、重试机制等容错方案,使系统可用性达到99.9%。最后的生产清单包含6大维度检查项,从安

性能优化
概述
在开发 AI 代理应用时,性能优化是确保应用能够高效运行、提供良好用户体验的关键。本文将介绍 AI 代理应用中的性能优化关键点、实用技巧和测试方法。
为什么性能优化很重要?
想象一下,如果你的 AI 客服助手每次回答问题都需要等待 30 秒,用户会有什么感受?性能优化就像给你的代理装上"涡轮增压器",让它更快、更高效地工作。
性能问题的常见表现
-
响应时间过长:用户等待时间超过 5 秒
-
资源消耗过高:CPU、内存占用过大
-
并发能力不足:无法同时处理多个请求
-
成本过高: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%)
小结
性能优化是一个持续的过程,关键要点:
-
测量优先:先测量,再优化,避免过早优化
-
找到瓶颈:使用性能测试找出真正的性能瓶颈
-
逐步优化:一次优化一个点,验证效果
-
平衡取舍:在性能、成本、质量之间找到平衡
-
持续监控:部署后持续监控性能指标
记住:最好的优化是避免不必要的工作。在编写代码时就考虑性能,比事后优化要容易得多。
安全最佳实践
概述
在开发 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("<", "<")
.Replace(">", ">")
.Replace("\"", """)
.Replace("'", "'");
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 策略
-
[ ] 配置防火墙规则
-
-
[ ] 日志和监控
-
[ ] 记录安全事件
-
[ ] 不记录敏感信息
-
[ ] 配置告警
-
小结
安全是一个持续的过程,关键要点:
-
永远不要硬编码密钥:使用环境变量或 Key Vault
-
保护用户数据:脱敏、加密、访问控制
-
验证所有输入:长度、速率、恶意内容
-
清理所有输出:移除危险内容
-
持续监控:记录安全事件,及时响应
记住:安全不是一次性的工作,而是持续的实践。
错误处理策略
概述
在开发 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
-
[ ] 区分不同类型的异常
-
[ ] 记录所有异常信息
-
-
[ ] 重试机制
-
[ ] 实现了指数退避重试
-
[ ] 设置了合理的重试次数
-
[ ] 只对临时性错误重试
-
-
[ ] 断路器
-
[ ] 实现了断路器模式
-
[ ] 设置了合理的失败阈值
-
[ ] 配置了恢复超时时间
-
-
[ ] 用户体验
-
[ ] 错误消息友好易懂
-
[ ] 不暴露技术细节
-
[ ] 提供解决建议
-
-
[ ] 日志记录
-
[ ] 记录所有错误
-
[ ] 包含足够的上下文信息
-
[ ] 不记录敏感信息
-
-
[ ] 监控告警
-
[ ] 监控错误率
-
[ ] 配置告警阈值
-
[ ] 及时响应告警
-
小结
错误处理是构建可靠应用的基础,关键要点:
-
捕获所有异常:不要让应用崩溃
-
区分错误类型:不同错误不同处理
-
实现重试机制:自动恢复临时性错误
-
使用断路器:防止雪崩效应
-
友好的错误消息:让用户知道发生了什么
-
完善的日志:帮助快速定位问题
-
持续监控:及时发现和解决问题
记住:好的错误处理不是避免错误,而是优雅地处理错误。
测试方法
概述
测试是确保 AI 代理应用质量的关键。本文将介绍单元测试和集成测试的方法,并提供实用的测试代码示例。
为什么测试很重要?
想象一下,如果你的 AI 客服在上线后才发现无法正确回答用户问题,或者工具调用失败,会造成多大的损失?
测试就像给你的应用做"体检",在问题发生之前就发现并修复它们。
测试的好处
-
提前发现问题:在开发阶段就发现 bug
-
保证质量:确保功能按预期工作
-
安全重构:修改代码时不怕破坏现有功能
-
文档作用:测试代码展示了如何使用功能
测试的类型
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%
-
[ ] 关键路径都有测试
-
-
[ ] 持续集成
-
[ ] 配置了自动测试
-
[ ] 测试失败会阻止合并
-
小结
测试是保证代码质量的重要手段,关键要点:
-
单元测试:测试单个函数,使用 Mock 隔离依赖
-
集成测试:测试组件协作,验证真实场景
-
测试命名:清晰描述测试内容
-
AAA 模式:准备、执行、断言
-
测试覆盖率:核心逻辑要有足够覆盖
-
持续集成:自动运行测试
记住:好的测试不仅能发现 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. 故障排查流程
故障排查清单:
-
确认问题
-
收集错误信息
-
确定影响范围
-
记录开始时间
-
-
查看监控
-
检查错误率
-
检查响应时间
-
检查资源使用
-
-
查看日志
-
查找错误日志
-
分析错误模式
-
确定根本原因
-
-
采取行动
-
如果是配置问题,回滚配置
-
如果是代码问题,回滚版本
-
如果是资源问题,扩容资源
-
-
验证修复
-
确认问题已解决
-
监控指标恢复正常
-
通知相关人员
-
-
事后分析
-
记录故障原因
-
制定预防措施
-
更新文档
-
生产环境检查清单总结
上线前必查项
-
[ ] 安全:密钥管理、数据保护、网络安全
-
[ ] 性能:性能优化、资源配置
-
[ ] 可靠性:错误处理、容错能力
-
[ ] 监控:日志配置、监控指标、告警规则
-
[ ] 测试:测试覆盖、测试环境
-
[ ] 文档:API 文档、部署文档、运维手册
上线后必做项
-
[ ] 监控:持续监控关键指标
-
[ ] 告警:及时响应告警
-
[ ] 备份:定期备份配置和数据
-
[ ] 维护:定期执行维护任务
-
[ ] 优化:根据监控数据持续优化
小结
生产环境部署是一个系统工程,关键要点:
-
全面检查:使用清单确保不遗漏
-
监控告警:及时发现和响应问题
-
备份恢复:做好最坏的打算
-
持续优化:根据数据不断改进
-
文档完善:让团队都能快速上手
记住:生产环境的稳定性比新功能更重要。
更多推荐




所有评论(0)