Loading

企业客服智能助手实战

企业客服智能助手实战

前言

在前八篇文章中,我们已经系统学习了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

六、总结

通过本文的学习,我们已经完成了企业客服智能助手的完整实现:

  1. ✅ 需求分析:明确客服系统的核心需求
  2. ✅ 架构设计:采用分层架构,支持多渠道接入
  3. ✅ 核心Agent:实现意图识别、多轮对话、知识库集成
  4. ✅ 转人工策略:智能识别转人工场景
  5. ✅ 多渠道支持:统一消息格式,适配不同渠道
  6. ✅ 数据分析:提供完整的统计和分析功能

这个实战项目综合运用了前几篇文章学到的知识,包括Agent开发、工具集成、对话管理、记忆系统、工作流编排、可观测性等。通过这个项目,你应该对如何构建生产级的Agent应用有了更深入的理解。

下一篇文章预告:

在最后一篇文章中,我们将继续实战项目,构建一个数据分析智能助手,实现自然语言查询数据库、自动生成报表等功能。


实践建议:

  1. 从简单场景开始,逐步增加复杂度
  2. 重视用户体验,设计友好的对话流程
  3. 建立完善的监控告警体系
  4. 持续优化知识库,提升回答准确率
  5. 收集用户反馈,不断改进系统

相关资源:


"好的客服系统不仅能回答问题,更要理解用户的需求。"

posted @ 2026-03-13 21:44  饭勺oO  阅读(3172)  评论(0)    收藏  举报