如何从Hello-World智能体发展到实际对话智能体?

摘要:第一个智能体:从Hello World到实际对话 前言 在上一篇文章中,我们成功搭建了Agent Framework的开发环境,并创建了第一个简单的智能体。如果你完成了上篇文章的所有步骤,现在应该已经有了一个能够回答问题的&
第一个智能体:从Hello World到实际对话 前言 在上一篇文章中,我们成功搭建了Agent Framework的开发环境,并创建了第一个简单的智能体。如果你完成了上篇文章的所有步骤,现在应该已经有了一个能够回答问题的"HelloBot"。 但是,真正的智能体不仅仅是回答一个问题那么简单。一个优秀的智能体应该能够: 理解上下文:记住对话历史,理解用户意图 处理复杂任务:分解复杂问题,分步解决 保持对话状态:在多轮对话中保持一致性 处理异常情况:优雅地处理错误和边界情况 今天,我们将从基础的Hello World升级到创建一个真正有用的智能体。通过本文的学习,你将能够创建一个可以处理实际业务场景的AI助手。 一、智能体的核心概念深入 1.1 智能体的组成要素 在深入编码之前,我们需要理解智能体的几个关键组成部分: 1. 系统指令(System Instructions) 这是智能体的"人格设定",决定了它如何思考和回答。好的系统指令应该: 明确智能体的角色和职责 定义回答风格和格式 设置行为边界和限制 2. 上下文管理(Context Management) 智能体如何记忆和利用对话历史。这包括: 短期记忆:当前对话的上下文 长期记忆:跨会话的重要信息 上下文窗口管理:处理token限制 3. 响应处理(Response Handling) 如何解析和处理智能体的输出: 流式响应:实时显示回答过程 结构化输出:获取JSON等结构化数据 错误处理:处理各种异常情况 1.2 Agent Framework的智能体模型 Microsoft Agent Framework采用了统一的智能体模型,无论使用哪种LLM提供商,都使用相同的接口: // 基本智能体创建 var agent = client .AsAIAgent( name: "AgentName", // 智能体名称 instructions: "Instructions", // 系统指令 modelConfiguration: config // 模型配置 ); 这种统一的设计让代码在不同提供商之间迁移变得非常容易。 二、创建业务导向的智能体 2.1 场景设计:技术支持助手 让我们以一个实际场景为例:创建一个技术支持助手。这个助手需要能够: 回答常见技术问题 收集故障信息 提供解决方案步骤 必要时创建工单 2.2 系统指令设计 好的系统指令是智能体成功的关键。对于技术支持助手,我们的系统指令应该: const string supportInstructions = """ 你是一个专业的技术支持助手,专门帮助用户解决软件技术问题。 你的职责包括: 1. 耐心倾听用户的问题描述 2. 询问必要的故障信息(错误代码、操作步骤、环境信息等) 3. 提供清晰、分步的解决方案 4. 如果问题复杂,建议创建正式的技术支持工单 5. 始终保持专业和友好的态度 回答规范: - 使用中文回答,技术术语保持英文 - 复杂解决方案使用编号列表 - 重要的技术细节使用**粗体**强调 - 如果涉及敏感信息,提醒用户通过安全渠道提供 限制: - 不要提供未经证实的安全建议 - 不要代替用户执行危险操作 - 涉及账号安全问题时,引导用户联系官方支持 """; 2.3 智能体实现代码 基于上面的设计,我们来创建完整的技术支持助手: // TechSupportAgent.cs using Microsoft.Agents.AI; using OpenAI; public class TechSupportAgent { private readonly IAIAgent _agent; // 系统指令 private const string Instructions = """ 你是一个专业的技术支持助手,专门帮助用户解决软件技术问题。 你的职责包括: 1. 耐心倾听用户的问题描述 2. 询问必要的故障信息(错误代码、操作步骤、环境信息等) 3. 提供清晰、分步的解决方案 4. 如果问题复杂,建议创建正式的技术支持工单 5. 始终保持专业和友好的态度 回答规范: - 使用中文回答,技术术语保持英文 - 复杂解决方案使用编号列表 - 重要的技术细节使用**粗体**强调 - 如果涉及敏感信息,提醒用户通过安全渠道提供 限制: - 不要提供未经证实的安全建议 - 不要代替用户执行危险操作 - 涉及账号安全问题时,引导用户联系官方支持 """; public TechSupportAgent(string apiKey, string model = "gpt-4o-mini") { var openAIClient = new OpenAIClient(apiKey); _agent = openAIClient .GetResponsesClient(model) .AsAIAgent( name: "TechSupportAssistant", instructions: Instructions ); } public async Task<string> GetSupportAsync(string userQuestion) { try { Console.WriteLine($"用户问题: {userQuestion}"); Console.WriteLine("正在分析问题..."); var response = await _agent.RunAsync(userQuestion); return response; } catch (Exception ex) { return $"抱歉,处理您的请求时出现错误: {ex.Message}。请稍后重试或联系人工支持。"; } } } 2.4 添加对话历史管理 真实的对话需要记住历史。让我们改进智能体,让它能够记住对话内容: // EnhancedTechSupportAgent.cs using Microsoft.Agents.AI; using OpenAI; using System.Text.Json; public class EnhancedTechSupportAgent { private readonly IAIAgent _agent; private readonly List<ConversationTurn> _conversationHistory; // 对话回合记录 private class ConversationTurn { public string Role { get; set; } = string.Empty; // "user" 或 "assistant" public string Content { get; set; } = string.Empty; public DateTime Timestamp { get; set; } = DateTime.UtcNow; } public EnhancedTechSupportAgent(string apiKey, string model = "gpt-4o-mini") { var openAIClient = new OpenAIClient(apiKey); _agent = openAIClient .GetResponsesClient(model) .AsAIAgent( name: "TechSupportAssistant", instructions: Instructions ); _conversationHistory = new List<ConversationTurn>(); } public async Task<string> ProcessQueryAsync(string userQuery) { // 添加用户查询到历史 _conversationHistory.Add(new ConversationTurn { Role = "user", Content = userQuery }); try { // 构建包含历史的上下文 var context = BuildConversationContext(); // 这里简化处理,实际应该使用框架的对话管理功能 var response = await _agent.RunAsync(context + userQuery); // 添加助手回答到历史 _conversationHistory.Add(new ConversationTurn { Role = "assistant", Content = response }); // 如果历史太长,清理早期记录 ManageHistorySize(); return response; } catch (Exception ex) { return $"处理错误: {ex.Message}"; } } private string BuildConversationContext() { if (_conversationHistory.Count == 0) return string.Empty; // 只保留最近5轮对话作为上下文 var recentHistory = _conversationHistory .TakeLast(5) .Select(turn => $"{turn.Role}: {turn.Content}") .ToList(); return "对话历史:\n" + string.Join("\n", recentHistory) + "\n\n当前问题: "; } private void ManageHistorySize() { // 保持最多20轮对话历史 if (_conversationHistory.Count > 20) { _conversationHistory.RemoveRange(0, _conversationHistory.Count - 20); } } public string GetConversationSummary() { return JsonSerializer.Serialize(_conversationHistory, new JsonSerializerOptions { WriteIndented = true }); } } 三、实际对话场景示例 3.1 场景1:简单技术问题 让我们看看智能体如何处理一个简单问题: 用户输入: 我的程序报错:NullReferenceException,怎么办? 智能体应该: 理解这是.NET的常见异常 询问更多信息(哪行代码、什么操作时发生) 提供排查步骤 预期回答示例: NullReferenceException是.NET中常见的运行时错误,表示尝试访问null对象的成员。 为了帮您准确解决问题,请提供以下信息: 1. **错误发生的具体代码行**(如果有堆栈跟踪) 2. **相关的对象是什么**(哪个变量为null) 3. **操作步骤**:错误发生前您执行了什么操作 常见原因和解决方案: 1. **未初始化的对象**:确保对象在使用前已经实例化 2. **方法返回null**:检查方法的返回值处理 3. **异步操作问题**:检查async/await调用链 请提供更多细节,我可以给出更具体的建议。 3.2 场景2:复杂故障排查 用户输入: 我们的网站突然变慢,CPU使用率很高,但不知道原因。 智能体应该: 引导用户收集系统信息 提供诊断步骤 建议监控工具 预期回答: 网站性能问题通常需要系统性的排查。让我们分步骤进行: **第一步:基本信息收集** 1. 服务器规格(CPU核心数、内存大小) 2. 当前并发用户数 3. 数据库负载情况 **第二步:初步诊断** 1. 使用任务管理器或top命令查看具体哪个进程占用CPU 2. 检查应用程序日志中的错误或警告 3. 监控网络流量和响应时间 **第三步:深度分析** 1. 如果是ASP.NET应用,启用性能计数器 2. 使用dotnet-counters或dotnet-trace收集运行时信息 3. 分析数据库查询性能 **建议工具:** - **Application Insights**:Azure的APM工具 - **dotnet-counters**:.NET性能计数器 - **SQL Server Profiler**:数据库性能分析 请先执行第一步的信息收集,然后我们可以进一步分析。 3.3 场景3:创建工单 当问题需要人工介入时: 用户输入: 按照你的建议检查了,还是找不到问题。我们需要紧急支持。 智能体应该: 确认需要人工支持 收集必要信息创建工单 提供后续流程 预期回答: 理解您的情况,这个问题可能需要我们的技术专家介入。 为了创建有效的技术支持工单,请提供以下信息: 1. **问题描述**:详细的问题现象和发生时间 2. **已尝试的解决步骤**:您已经尝试过哪些方法 3. **系统环境**: - 操作系统版本 - .NET运行时版本 - 应用程序版本 4. **错误日志**:相关的应用程序日志或事件查看器记录 5. **影响范围**:有多少用户受影响,业务影响程度 **紧急联系方式**: - 电话支持:400-xxx-xxxx(24小时) - 在线工单系统:https://support.example.com 请尽快提交以上信息,我们的技术支持团队会优先处理您的请求。 四、高级特性:流式响应和结构化输出 4.1 流式响应实现 对于长回答,流式响应能提供更好的用户体验: public async Task StreamResponseAsync(string userQuery) { Console.WriteLine("技术支持助手:"); // 注意:实际API可能有所不同,这里展示概念 var responseStream = await _agent.RunStreamingAsync(userQuery); await foreach (var chunk in responseStream) { Console.Write(chunk); await Task.Delay(50); // 模拟实时显示效果 } Console.WriteLine(); } 4.2 结构化输出 有时我们需要智能体返回结构化数据: public async Task<TroubleTicket> CreateTroubleTicketAsync(string issueDescription) { const string ticketInstructions = """ 请根据用户的问题描述创建一个标准的技术支持工单。 返回JSON格式,包含以下字段: - title: 工单标题 - severity: 严重程度(Low/Medium/High/Critical) - category: 问题分类 - description: 详细描述 - stepsToReproduce: 重现步骤 - expectedBehavior: 期望行为 - actualBehavior: 实际行为 """; var jsonAgent = _client .GetResponsesClient("gpt-4o") .AsAIAgent( name: "TicketCreator", instructions: ticketInstructions ); var response = await jsonAgent.RunAsync(issueDescription); try { return JsonSerializer.Deserialize<TroubleTicket>(response) ?? throw new InvalidOperationException("无法解析工单信息"); } catch { // 如果JSON解析失败,创建默认工单 return new TroubleTicket { Title = "技术支持请求", Severity = "Medium", Description = issueDescription, CreatedAt = DateTime.UtcNow }; } } public class TroubleTicket { public string Title { get; set; } = string.Empty; public string Severity { get; set; } = "Medium"; // Low/Medium/High/Critical public string Category { get; set; } = string.Empty; public string Description { get; set; } = string.Empty; public List<string> StepsToReproduce { get; set; } = new(); public string ExpectedBehavior { get; set; } = string.Empty; public string ActualBehavior { get; set; } = string.Empty; public DateTime CreatedAt { get; set; } } 五、错误处理与边缘情况 5.1 网络错误处理 public async Task<string> GetResponseWithRetryAsync(string query, int maxRetries = 3) { for (int attempt = 1; attempt <= maxRetries; attempt++) { try { return await _agent.RunAsync(query); } catch (HttpRequestException ex) when (ex.StatusCode == System.Net.HttpStatusCode.TooManyRequests) { // 429 Too Many Requests var delay = TimeSpan.FromSeconds(Math.Pow(2, attempt)); // 指数退避 Console.WriteLine($"速率限制,等待 {delay.TotalSeconds} 秒后重试..."); await Task.Delay(delay); } catch (HttpRequestException ex) when ((int)ex.StatusCode >= 500) { // 服务器错误 if (attempt == maxRetries) throw; await Task.Delay(TimeSpan.FromSeconds(attempt)); } catch (Exception ex) when (ex.Message.Contains("timeout", StringComparison.OrdinalIgnoreCase)) { // 超时错误 Console.WriteLine($"请求超时,尝试 {attempt}/{maxRetries}"); if (attempt == maxRetries) throw; } } throw new InvalidOperationException($"在{maxRetries}次尝试后仍失败"); } 5.2 输入验证和清理 public string SanitizeInput(string userInput) { if (string.IsNullOrWhiteSpace(userInput)) throw new ArgumentException("输入不能为空"); // 限制输入长度 if (userInput.Length > 2000) { userInput = userInput[..2000] + "... [内容过长已截断]"; } // 移除可能的恶意内容(简单示例) var maliciousPatterns = new[] { "<script>", "javascript:", "onload=", "onerror=" }; foreach (var pattern in maliciousPatterns) { if (userInput.Contains(pattern, StringComparison.OrdinalIgnoreCase)) { userInput = userInput.Replace(pattern, "[已过滤]", StringComparison.OrdinalIgnoreCase); } } return userInput; } 六、性能优化技巧 6.1 响应缓存 public class CachedAgent { private readonly IAIAgent _agent; private readonly MemoryCache _cache; public CachedAgent(IAIAgent agent) { _agent = agent; _cache = new MemoryCache(new MemoryCacheOptions { SizeLimit = 100 // 最多缓存100个回答 }); } public async Task<string> GetCachedResponseAsync(string query) { var cacheKey = $"response_{query.GetHashCode()}"; if (_cache.TryGetValue(cacheKey, out string cachedResponse)) { Console.WriteLine("使用缓存回答"); return cachedResponse; } var response = await _agent.RunAsync(query); // 缓存回答(设置合适的过期时间) var cacheOptions = new MemoryCacheEntryOptions { Size = 1, AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30) }; _cache.Set(cacheKey, response, cacheOptions); return response; } } 6.2 并发处理 public async Task<List<string>> ProcessBatchAsync(List<string> queries, int maxConcurrency = 5) { var semaphore = new SemaphoreSlim(maxConcurrency); var tasks = new List<Task<string>>(); foreach (var query in queries) { await semaphore.WaitAsync(); tasks.Add(Task.Run(async () => { try { return await _agent.RunAsync(query); } finally { semaphore.Release(); } })); } return (await Task.WhenAll(tasks)).ToList(); } 七、完整示例程序 7.1 主程序 // Program.cs using System.Text.Json; class Program { static async Task Main(string[] args) { Console.WriteLine("=== 技术支持助手启动 ==="); // 配置 var apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY"); if (string.IsNullOrEmpty(apiKey)) { Console.WriteLine("请设置 OPENAI_API_KEY 环境变量"); return; } // 创建智能体 var agent = new EnhancedTechSupportAgent(apiKey, "gpt-4o-mini"); Console.WriteLine("助手已就绪!输入您的问题(输入 'exit' 退出)"); Console.WriteLine(); while (true) { Console.Write("> "); var input = Console.ReadLine(); if (string.IsNullOrWhiteSpace(input)) continue; if (input.Equals("exit", StringComparison.OrdinalIgnoreCase)) break; if (input.Equals("history", StringComparison.OrdinalIgnoreCase)) { Console.WriteLine("对话历史:"); Console.WriteLine(agent.GetConversationSummary()); continue; } try { var response = await agent.ProcessQueryAsync(input); Console.WriteLine(); Console.WriteLine($"助手: {response}"); Console.WriteLine(); } catch (Exception ex) { Console.WriteLine($"错误: {ex.Message}"); } } Console.WriteLine("感谢使用技术支持助手!"); } } 7.2 项目结构 TechSupportDemo/ ├── TechSupportDemo.csproj ├── Program.cs ├── TechSupportAgent.cs ├── EnhancedTechSupportAgent.cs ├── CachedAgent.cs ├── Models/ │ ├── TroubleTicket.cs │ └── ConversationTurn.cs └── README.md 八、测试与验证 8.1 单元测试示例 // TechSupportAgentTests.cs using Xunit; public class TechSupportAgentTests { [Fact] public void SanitizeInput_RemovesMaliciousContent() { var agent = new TechSupportAgent("test-key"); var input = "问题描述<script>alert('xss')</script>"; var sanitized = agent.SanitizeInput(input); Assert.DoesNotContain("<script>", sanitized); Assert.Contains("[已过滤]", sanitized); } [Theory] [InlineData("", true)] [InlineData(" ", true)] [InlineData("正常输入", false)] public void SanitizeInput_ValidatesEmptyInput(string input, bool shouldThrow) { var agent = new TechSupportAgent("test-key"); if (shouldThrow) { Assert.Throws<ArgumentException>(() => agent.SanitizeInput(input)); } else { var result = agent.SanitizeInput(input); Assert.NotNull(result); } } } 8.2 集成测试 // IntegrationTests.cs public class IntegrationTests : IAsyncLifetime { private TechSupportAgent _agent; public async Task InitializeAsync() { // 使用测试API密钥或模拟 var apiKey = Environment.GetEnvironmentVariable("TEST_OPENAI_API_KEY"); _agent = new TechSupportAgent(apiKey ?? "test-key"); } [Fact] public async Task Agent_RespondsToCommonIssue() { var response = await _agent.GetSupportAsync("程序报错 NullReferenceException"); Assert.NotNull(response); Assert.NotEmpty(response); Assert.Contains("NullReferenceException", response); } public Task DisposeAsync() { _agent = null; return Task.CompletedTask; } } 九、总结与下一步 通过本文的学习,我们已经创建了一个功能完整的技术支持智能体。我们实现了: ✅ 智能系统指令设计:定义了专业的技术支持角色 ✅ 对话历史管理:让智能体记住上下文 ✅ 实际业务场景处理:处理从简单到复杂的技术问题 ✅ 高级特性实现:流式响应、结构化输出 ✅ 健壮性保障:错误处理、输入验证、性能优化 ✅ 完整测试覆盖:单元测试和集成测试 关键收获: 系统指令的质量直接影响智能体的表现 对话历史管理是实现自然对话的关键 错误处理是生产环境应用的必备功能 性能优化能显著提升用户体验 下一篇文章预告: 在第三篇文章中,我们将探索如何让智能体具备真正的"执行能力"。通过工具集成,智能体将能够: 调用外部API获取实时信息 操作数据库进行数据查询 执行系统命令和文件操作 集成现有的业务系统 这将让我们的智能体从一个"知识库"升级为真正的"智能助手"。 实践建议: 尝试修改系统指令,观察智能体行为的变化 为你的业务场景定制专门的智能体 在实际项目中逐步引入Agent技术 持续关注框架更新和最佳实践 相关资源: Agent Framework对话管理文档 系统指令设计最佳实践 .NET性能优化指南 "好的智能体不是知道所有答案,而是知道如何找到答案并帮助用户解决问题。"