如何打造高效的企业客服智能助手实战案例?

摘要:企业客服智能助手实战 前言 在前八篇文章中,我们已经系统学习了Agent Framework的核心概念和关键技术。现在,是时候将这些知识综合运用到一个完整的实战项目中了。 本文将带领大家从零开始构建一个企业级客服智能助手。这个项目将涵盖:需
企业客服智能助手实战 前言 在前八篇文章中,我们已经系统学习了Agent Framework的核心概念和关键技术。现在,是时候将这些知识综合运用到一个完整的实战项目中了。 本文将带领大家从零开始构建一个企业级客服智能助手。这个项目将涵盖:需求分析、架构设计、核心功能实现、多渠道接入、知识库集成,以及完整的部署方案。通过这个实战项目,你将学会如何将理论转化为生产级的应用。 一、项目概述 1.1 需求分析 我们的企业客服智能助手需要满足以下核心需求: 多渠道接入:支持网站在线客服、微信公众号、企业微信、电话IVR等多个渠道的统一接入。 智能对话:能够理解用户意图,进行自然流畅的多轮对话,准确回答用户问题。 知识库支持:集成企业产品知识库、FAQ文档、业务流程等,为用户提供准确的信息。 转人工策略:当智能客服无法解决问题时,能够智能识别并平滑转接到人工客服。 数据分析:提供对话统计、用户满意度、问题分类等数据分析功能。 可扩展性:支持快速接入新的业务场景和功能模块。 1.2 技术架构 整体架构采用微服务设计,主要包含以下组件: 接入层:负责处理来自不同渠道的请求,进行统一的格式转换和路由。 Agent核心层:基于Agent Framework构建,负责对话逻辑处理、意图识别、实体抽取等。 知识层:管理企业知识库,包括FAQ、产品信息、业务流程等文档。 数据层:存储对话记录、用户信息、业务数据等。 集成层:对接企业现有系统,如CRM、订单系统、ERP等。 二、核心实现 2.1 客服Agent定义 首先,定义客服Agent的核心能力: // CustomerServiceAgent.cs public class CustomerServiceAgent { private readonly IAIAgent _agent; private readonly IIntentClassifier _intentClassifier; private readonly IKnowledgeBase _knowledgeBase; private readonly IToolRegistry _toolRegistry; private readonly IConversationManager _conversationManager; private readonly ILogger<CustomerServiceAgent> _logger; public CustomerServiceAgent( IAIAgent agent, IIntentClassifier intentClassifier, IKnowledgeBase knowledgeBase, IToolRegistry toolRegistry, IConversationManager conversationManager, ILogger<CustomerServiceAgent> logger) { _agent = agent; _intentClassifier = intentClassifier; _knowledgeBase = knowledgeBase; _toolRegistry = toolRegistry; _conversationManager = conversationManager; _logger = logger; } public async Task<AgentResponse> ProcessMessageAsync( string conversationId, string userId, string message, ChannelType channel) { // 1. 获取对话上下文 var context = await _conversationManager.GetContextAsync(conversationId); // 2. 意图识别 var intent = await _intentClassifier.ClassifyAsync(message, context); // 3. 根据意图选择处理策略 return intent.Name switch { "查询订单" => await HandleOrderInquiryAsync(context, message), "售后服务" => await HandleAfterSalesAsync(context, message), "产品咨询" => await HandleProductConsultAsync(context, message), "转人工" => await HandleTransferToHumanAsync(context), _ => await HandleGeneralQuestionAsync(context, message) }; } private async Task<AgentResponse> HandleOrderInquiryAsync( ConversationContext context, string message) { // 提取订单相关信息 var orderInfo = ExtractOrderInfo(message); if (orderInfo.OrderId != null) { // 通过工具查询订单 var tool = _toolRegistry.Get("query_order"); var result = await tool.ExecuteAsync(new Dictionary<string, object> { ["orderId"] = orderInfo.OrderId }); return new AgentResponse { Message = FormatOrderInfo(result.Output), Intent = "查询订单", Success = true }; } // 需要更多信息 return new AgentResponse { Message = "请提供您的订单号,我可以为您查询订单状态。", Intent = "查询订单", NeedMoreInfo = true, RequiredInfo = new List<string> { "orderId" } }; } private async Task<AgentResponse> HandleProductConsultAsync( ConversationContext context, string message) { // 搜索知识库 var searchResults = await _knowledgeBase.SearchAsync(message, topK: 3); if (searchResults.Any()) { var answer = searchResults.First(); var related = searchResults.Skip(1).Select(r => r.Title).ToList(); return new AgentResponse { Message = answer.Content, Intent = "产品咨询", KnowledgeMatch = true, RelatedQuestions = related }; } // 未找到答案,转接人工 return new AgentResponse { Message = "抱歉,我暂时没有找到相关信息。我为您转接人工客服,请稍候。", Intent = "产品咨询", ShouldTransfer = true, TransferReason = "知识库无匹配" }; } private OrderInfo ExtractOrderInfo(string message) { // 使用正则或NLP提取订单信息 var orderInfo = new OrderInfo(); // 匹配订单号格式 var orderMatch = Regex.Match(message, @"(?:订单号|order)[::\s]*([A-Z0-9]{10,})", RegexOptions.IgnoreCase); if (orderMatch.Success) { orderInfo.OrderId = orderMatch.Groups[1].Value; } // 匹配手机号 var phoneMatch = Regex.Match(message, @"(?:手机号|电话)[::\s]*(1[3-9]\d{9})", RegexOptions.IgnoreCase); if (phoneMatch.Success) { orderInfo.Phone = phoneMatch.Groups[1].Value; } return orderInfo; } private string FormatOrderInfo(string? orderJson) { if (string.IsNullOrEmpty(orderJson)) { return "未查询到订单信息"; } var order = JsonSerializer.Deserialize<OrderInfo>(orderJson); return $""" 订单信息: - 订单号:{order?.OrderId} - 订单状态:{order?.Status} - 商品名称:{order?.ProductName} - 金额:{order?.Amount}元 - 下单时间:{order?.CreatedAt} """; } } 2.2 意图识别器 // IntentClassifier.cs public class IntentClassifier : IIntentClassifier { private readonly IAIAgent _agent; private readonly ILogger<IntentClassifier> _logger; private readonly List<IntentDefinition> _intents; public IntentClassifier(IAIAgent agent, ILogger<IntentClassifier> logger) { _agent = agent; _logger = logger; // 定义支持的意图 _intents = new List<IntentDefinition> { new IntentDefinition { Name = "查询订单", Examples = new[] { "查订单", "订单在哪", "我的订单", "物流信息" }, Keywords = new[] { "订单", "物流", "快递", "发货" } }, new IntentDefinition { Name = "售后服务", Examples = new[] { "退货", "换货", "退款", "售后" }, Keywords = new[] { "退货", "退款", "售后", "质量问题" } }, new IntentDefinition { Name = "产品咨询", Examples = new[] { "这个产品怎么样", "有什么功能", "价格多少" }, Keywords = new[] { "产品", "功能", "价格", "规格" } }, new IntentDefinition { Name = "账户问题", Examples = new[] { "登录不上", "修改密码", "账户异常" }, Keywords = new[] { "登录", "密码", "账户", "注册" } }, new IntentDefinition { Name = "转人工", Examples = new[] { "找人工", "转客服", "真人" }, Keywords = new[] { "人工", "客服", "转接" } } }; } public async Task<IntentResult> ClassifyAsync( string message, ConversationContext context) { // 1. 关键词快速匹配 var keywordMatch = MatchByKeywords(message); if (keywordMatch.Confidence > 0.9) { return keywordMatch; } // 2. 使用LLM进行意图识别 var llmResult = await ClassifyByLLMAsync(message, context); // 3. 综合判断 var finalResult = CombineResults(keywordMatch, llmResult); _logger.LogDebug("意图识别结果: Intent={Intent}, Confidence={Confidence}", finalResult.Name, finalResult.Confidence); return finalResult; } private IntentResult MatchByKeywords(string message) { var bestMatch = new IntentResult { Name = "未知", Confidence = 0 }; foreach (var intent in _intents) { var matchCount = intent.Keywords.Count(k => message.Contains(k, StringComparison.OrdinalIgnoreCase)); if (matchCount > 0) { var confidence = (double)matchCount / intent.Keywords.Count; if (confidence > bestMatch.Confidence) { bestMatch = new IntentResult { Name = intent.Name, Confidence = confidence, Method = "keyword" }; } } } return bestMatch; } private async Task<IntentResult> ClassifyByLLMAsync( string message, ConversationContext context) { var prompt = $""" 请根据用户消息识别用户意图。 用户消息:{message} 支持的意图: - 查询订单:询问订单状态、物流信息 - 售后服务:退货、换货、退款 - 产品咨询:产品功能、价格咨询 - 账户问题:登录、密码、账户异常 - 转人工:明确要求转接人工客服 请只返回意图名称,不要其他内容。如果不确定,返回"未知"。 """; try { var result = await _agent.CompleteAsync(prompt); var intentName = result.Trim(); var intent = _intents.FirstOrDefault(i => i.Name.Contains(intentName, StringComparison.OrdinalIgnoreCase)); return new IntentResult { Name = intent?.Name ?? "未知", Confidence = 0.7, Method = "llm" }; } catch (Exception ex) { _logger.LogWarning(ex, "LLM意图识别失败"); return new IntentResult { Name = "未知", Confidence = 0, Method = "llm_failed" }; } } private IntentResult CombineResults(IntentResult keywordResult, IntentResult llmResult) { // 如果关键词匹配度很高,直接返回 if (keywordResult.Confidence > 0.8) { return keywordResult; } // 如果LLM结果更可靠,返回LLM结果 if (llmResult.Confidence > keywordResult.Confidence) { return llmResult; } // 取置信度较高的结果 return keywordResult.Confidence > llmResult.Confidence ? keywordResult : llmResult; } } public class IntentDefinition { public string Name { get; set; } = string.Empty; public string[] Examples { get; set; } = Array.Empty<string>(); public string[] Keywords { get; set; } = Array.Empty<string>(); } public class IntentResult { public string Name { get; set; } = string.Empty; public double Confidence { get; set; } public string Method { get; set; } = string.Empty; } 2.3 知识库集成 // KnowledgeBaseService.cs public class KnowledgeBaseService : IKnowledgeBase { private readonly ISemanticSearch _semanticSearch; private readonly IDocumentProcessor _documentProcessor; private readonly ICache _cache; private readonly ILogger<KnowledgeBaseService> _logger; public KnowledgeBaseService( ISemanticSearch semanticSearch, IDocumentProcessor documentProcessor, ICache cache, ILogger<KnowledgeBaseService> logger) { _semanticSearch = semanticSearch; _documentProcessor = documentProcessor; _cache = cache; _logger = logger; } public async Task<List<KnowledgeItem>> SearchAsync( string query, int topK = 5, string? category = null) { // 1. 尝试从缓存获取 var cacheKey = $"kb:search:{Hash(query)}:{topK}:{category}"; var cached = await _cache.GetAsync<List<KnowledgeItem>>(cacheKey); if (cached != null) { _logger.LogDebug("知识库缓存命中: Query={Query}", query); return cached; } // 2. 执行语义搜索 var results = await _semanticSearch.SearchAsync(query, topK * 2); // 3. 过滤和排序 var filtered = results .Where(r => category == null || r.Category == category) .OrderByDescending(r => r.Score) .Take(topK) .Select(r => new KnowledgeItem { Id = r.Id, Title = r.Title, Content = r.Content, Category = r.Category, Score = r.Score, Url = r.Url }) .ToList(); // 4. 缓存结果 await _cache.SetAsync(cacheKey, filtered, TimeSpan.FromMinutes(30)); _logger.LogInformation("知识库搜索: Query={Query}, Results={Count}", query, filtered.Count); return filtered; } public async Task IndexDocumentAsync(KnowledgeDocument document) { // 1. 处理文档 var processed = await _documentProcessor.ProcessAsync(document); // 2. 分块处理 var chunks = await _documentProcessor.ChunkAsync(processed.Content); // 3. 生成向量并索引 foreach (var chunk in chunks) { await _semanticSearch.IndexAsync(new SearchableChunk { Id = $"{document.Id}:{chunk.Index}", Content = chunk.Text, Metadata = new Dictionary<string, object> { ["documentId"] = document.Id, ["title"] = document.Title, ["category"] = document.Category, ["tags"] = document.Tags } }); } _logger.LogInformation("文档已索引: DocumentId={DocumentId}, Chunks={Count}", document.Id, chunks.Count); } } 2.4 转人工策略 // TransferToHumanService.cs public class TransferToHumanService { private readonly IConversationManager _conversationManager; private readonly IAgentMetrics _metrics; private readonly ILogger<TransferToHumanService> _logger; public TransferToHumanService( IConversationManager conversationManager, IAgentMetrics metrics, ILogger<TransferToHumanService> logger) { _conversationManager = conversationManager; _metrics = metrics; _logger = logger; } public async Task<TransferResult> EvaluateTransferAsync( string conversationId, AgentResponse lastResponse) { var context = await _conversationManager.GetContextAsync(conversationId); // 检查是否满足转人工条件 var reasons = new List<string>(); // 1. 用户明确要求转人工 if (lastResponse.Intent == "转人工") { reasons.Add("用户明确要求"); } // 2. 连续多次无法理解用户意图 if (context.FailedAttempts >= 3) { reasons.Add("连续理解失败"); } // 3. 知识库无匹配 if (lastResponse.KnowledgeMatch == false && context.TurnCount > 2) { reasons.Add("知识库无匹配"); } // 4. 用户满意度低 if (context.RecentSatisfactionScores.Count >= 3 && context.RecentSatisfactionScores.Average() < 2) { reasons.Add("满意度持续低"); } // 5. 涉及敏感操作 if (ContainsSensitiveOperation(lastResponse.Message)) { reasons.Add("敏感操作需要人工确认"); } if (reasons.Any()) { return new TransferResult { ShouldTransfer = true, Reasons = reasons, Priority = EvaluatePriority(reasons) }; } return new TransferResult { ShouldTransfer = false }; } public async Task ExecuteTransferAsync( string conversationId, TransferResult transferResult) { var context = await _conversationManager.GetContextAsync(conversationId); // 1. 更新对话状态 context.Status = ConversationStatus.Transferred; context.TransferReasons = transferResult.Reasons; context.TransferredAt = DateTime.UtcNow; await _conversationManager.SaveContextAsync(conversationId, context); // 2. 记录指标 _metrics.RecordTransfer(transferResult.Reasons); // 3. 通知人工客服 await NotifyHumanAgentAsync(conversationId, context, transferResult); _logger.LogInformation( "转接人工客服: ConversationId={ConversationId}, Reasons={Reasons}", conversationId, string.Join(", ", transferResult.Reasons)); } private bool ContainsSensitiveOperation(string message) { var sensitiveKeywords = new[] { "退款", "投诉", "法律", "发票", "大额" }; return sensitiveKeywords.Any(k => message.Contains(k)); } private int EvaluatePriority(List<string> reasons) { if (reasons.Contains("用户明确要求") || reasons.Contains("投诉")) return 1; if (reasons.Contains("敏感操作需要人工确认")) return 2; return 3; } private async Task NotifyHumanAgentAsync( string conversationId, ConversationContext context, TransferResult transferResult) { // 构建转接信息 var transferInfo = new { conversationId = conversationId, userId = context.UserId, reasons = transferResult.Reasons, priority = transferResult.Priority, summary = GenerateConversationSummary(context), history = context.History.TakeLast(5).ToList() }; // 通知可用的客服 // 这里可以对接企业的客服系统 _logger.LogInformation("准备转接: {TransferInfo}", JsonSerializer.Serialize(transferInfo)); } private string GenerateConversationSummary(ConversationContext context) { var userMessages = context.History .Where(h => h.Role == "user") .TakeLast(3) .Select(h => h.Content) .ToList(); return string.Join(" | ", userMessages); } } 三、多渠道接入 3.1 统一消息格式 定义跨渠道的统一消息格式: // UnifiedMessage.cs public class UnifiedMessage { public string MessageId { get; set; } = Guid.NewGuid().ToString(); public string ConversationId { get; set; } = string.Empty; public string UserId { get; set; } = string.Empty; public ChannelType Channel { get; set; } public MessageType Type { get; set; } public string Content { get; set; } = string.Empty; public Dictionary<string, string> Metadata { get; set; } = new(); public DateTime Timestamp { get; set; } = DateTime.UtcNow; } public enum ChannelType { Web, // 网页 WeChat, // 微信公众号 WeCom, // 企业微信 App, // App内嵌 IVR // 电话IVR } public enum MessageType { Text, Image, Voice, Video, File, Location } 3.2 渠道适配器 // ChannelAdapter.cs public interface IChannelAdapter { ChannelType Channel { get; } Task<UnifiedMessage> ReceiveAsync(dynamic rawMessage); Task<dynamic> SendAsync(UnifiedMessage message); } public class WeChatAdapter : IChannelAdapter { private readonly string _appId; private readonly string _token; private readonly ILogger<WeChatAdapter> _logger; public ChannelType Channel => ChannelType.WeChat; public WeChatAdapter( string appId, string token, ILogger<WeChatAdapter> logger) { _appId = appId; _token = token; _logger = logger; } public async Task<UnifiedMessage> ReceiveAsync(dynamic rawMessage) { var message = new UnifiedMessage { Channel = ChannelType.WeChat, Type = MapMessageType(rawMessage.MsgType), Content = rawMessage.Content?.ToString() ?? "", Metadata = new Dictionary<string, string> { ["FromUserName"] = rawMessage.FromUserName?.ToString() ?? "", ["CreateTime"] = rawMessage.CreateTime?.ToString() ?? "", ["MsgId"] = rawMessage.MsgId?.ToString() ?? "" } }; // 生成用户ID(微信公众号使用OpenID) message.UserId = rawMessage.FromUserName?.ToString() ?? ""; message.ConversationId = $"wechat:{message.UserId}"; _logger.LogDebug("接收微信消息: UserId={UserId}, Type={Type}", message.UserId, message.Type); return await Task.FromResult(message); } public async Task<dynamic> SendAsync(UnifiedMessage message) { // 构建微信消息格式 var response = new { ToUserName = message.Metadata.GetValueOrDefault("FromUserName"), FromUserName = _appId, CreateTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds(), MsgType = "text", Content = message.Content }; return await Task.FromResult(response); } private MessageType MapMessageType(string msgType) { return msgType?.ToLower() switch { "text" => MessageType.Text, "image" => MessageType.Image, "voice" => MessageType.Voice, "video" => MessageType.Video, "location" => MessageType.Location, _ => MessageType.Text }; } } 四、数据分析 4.1 对话统计 // ConversationAnalytics.cs public class ConversationAnalytics { private readonly IConversationRepository _repository; private readonly ILogger<ConversationAnalytics> _logger; public ConversationAnalytics( IConversationRepository repository, ILogger<ConversationAnalytics> logger) { _repository = repository; _logger = logger; } public async Task<ConversationStatistics> GetStatisticsAsync( DateTime startDate, DateTime endDate) { var conversations = await _repository.GetConversationsAsync(startDate, endDate); var stats = new ConversationStatistics { Period = new DateRange { Start = startDate, End = endDate }, TotalConversations = conversations.Count, CompletedConversations = conversations.Count(c => c.Status == ConversationStatus.Completed), TransferredConversations = conversations.Count(c => c.Status == ConversationStatus.Transferred), // 计算平均对话时长 AverageDuration = TimeSpan.FromTicks( (long)conversations .Where(c => c.EndedAt.HasValue && c.StartedAt.HasValue) .Average(c => (c.EndedAt!.Value - c.StartedAt!.Value).Ticks)), // 计算解决率 ResolutionRate = (double)conversations.Count(c => c.Status == ConversationStatus.Resolved) / conversations.Count, // 转人工率 TransferRate = (double)conversations.Count(c => c.Status == ConversationStatus.Transferred) / conversations.Count, // 平均消息数 AverageMessagesPerConversation = conversations.Average(c => c.MessageCount), // 意图分布 IntentDistribution = conversations .GroupBy(c => c.Intent) .ToDictionary(g => g.Key, g => g.Count()), // 渠道分布 ChannelDistribution = conversations .GroupBy(c => c.Channel) .ToDictionary(g => g.Key, g => g.Count()) }; return stats; } public async Task<List<DailyTrend>> GetDailyTrendsAsync( DateTime startDate, DateTime endDate) { var conversations = await _repository.GetConversationsAsync(startDate, endDate); var trends = conversations .GroupBy(c => c.StartedAt.Date) .Select(g => new DailyTrend { Date = g.Key, ConversationCount = g.Count(), CompletedCount = g.Count(c => c.Status == ConversationStatus.Completed), TransferredCount = g.Count(c => c.Status == ConversationStatus.Transferred), AverageDuration = TimeSpan.FromTicks( (long)g.Where(c => c.EndedAt.HasValue) .Average(c => (c.EndedAt!.Value - c.StartedAt!.Value).Ticks)), UserSatisfaction = g.Where(c => c.Rating.HasValue) .Average(c => c.Rating!.Value) }) .OrderBy(t => t.Date) .ToList(); return trends; } } public class ConversationStatistics { public DateRange Period { get; set; } = new(); public int TotalConversations { get; set; } public int CompletedConversations { get; set; } public int TransferredConversations { get; set; } public TimeSpan AverageDuration { get; set; } public double ResolutionRate { get; set; } public double TransferRate { get; set; } public double AverageMessagesPerConversation { get; set; } public Dictionary<string, int> IntentDistribution { get; set; } = new(); public Dictionary<ChannelType, int> ChannelDistribution { get; set; } = new(); } public class DailyTrend { public DateTime Date { get; set; } public int ConversationCount { get; set; } public int CompletedCount { get; set; } public int TransferredCount { get; set; } public TimeSpan AverageDuration { get; set; } public double UserSatisfaction { get; set; } } 五、部署与运维 5.1 Docker部署 # Dockerfile FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base WORKDIR /app EXPOSE 80 EXPOSE 443 FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build WORKDIR /src COPY ["CustomerServiceAgent/CustomerServiceAgent.csproj", "CustomerServiceAgent/"] RUN dotnet restore "CustomerServiceAgent/CustomerServiceAgent.csproj" COPY . . WORKDIR "/src/CustomerServiceAgent" RUN dotnet build "CustomerServiceAgent.csproj" -c Release -o /app/build FROM build AS publish RUN dotnet publish "CustomerServiceAgent.csproj" -c Release -o /app/publish FROM base AS final WORKDIR /app COPY --from=publish /app/publish . ENTRYPOINT ["dotnet", "CustomerServiceAgent.dll"] 5.2 Kubernetes配置 # deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: customer-service-agent labels: app: customer-service-agent spec: replicas: 3 selector: matchLabels: app: customer-service-agent template: metadata: labels: app: customer-service-agent spec: containers: - name: agent image: customer-service-agent:latest ports: - containerPort: 80 env: - name: ASPNETCORE_ENVIRONMENT value: "Production" - name: ConnectionStrings__DefaultConnection valueFrom: secretKeyRef: name: customer-service-secrets key: database-connection - name: OpenTelemetry__Endpoint value: "http://otel-collector:4317" resources: requests: memory: "512Mi" cpu: "500m" limits: memory: "1Gi" cpu: "1000m" livenessProbe: httpGet: path: /health port: 80 initialDelaySeconds: 30 periodSeconds: 10 readinessProbe: httpGet: path: /ready port: 80 initialDelaySeconds: 10 periodSeconds: 5 六、总结 通过本文的学习,我们已经完成了企业客服智能助手的完整实现: ✅ 需求分析:明确客服系统的核心需求 ✅ 架构设计:采用分层架构,支持多渠道接入 ✅ 核心Agent:实现意图识别、多轮对话、知识库集成 ✅ 转人工策略:智能识别转人工场景 ✅ 多渠道支持:统一消息格式,适配不同渠道 ✅ 数据分析:提供完整的统计和分析功能 这个实战项目综合运用了前几篇文章学到的知识,包括Agent开发、工具集成、对话管理、记忆系统、工作流编排、可观测性等。通过这个项目,你应该对如何构建生产级的Agent应用有了更深入的理解。 下一篇文章预告: 在最后一篇文章中,我们将继续实战项目,构建一个数据分析智能助手,实现自然语言查询数据库、自动生成报表等功能。 实践建议: 从简单场景开始,逐步增加复杂度 重视用户体验,设计友好的对话流程 建立完善的监控告警体系 持续优化知识库,提升回答准确率 收集用户反馈,不断改进系统 相关资源: 客服系统设计模式 微信公众号开发文档 企业微信开发文档 "好的客服系统不仅能回答问题,更要理解用户的需求。"