最近在帮公司折腾内部智能客服系统,之前用的老系统问题一大堆:响应慢得像蜗牛,新政策出来知识库半个月都更新不上,客服同事天天忙得脚不沾地。痛定思痛,我们决定用Coze平台来一次彻底的升级改造。折腾了几个月,系统终于稳定上线,响应速度直接提升了3倍多,今天就来分享一下我们的实战经验和踩过的那些坑。

智能客服系统架构示意图

一、 老系统到底“痛”在哪?

在动手之前,我们仔细盘点了旧系统的三大核心痛点,这也是很多企业内部的通病。

  1. 响应延迟严重:员工在内部IM(比如企业微信)里提问,问题要先到客服人员那里,客服再去翻文档、问同事,平均响应时间超过5分钟。遇到复杂问题,来回沟通能拖上半天,严重影响工作效率。
  2. 知识碎片化严重:公司制度、操作手册、常见问题解答(FAQ)散落在不同的Confluence页面、共享盘文件夹甚至员工的个人笔记里。没有统一入口,信息更新不同步,客服自己找答案都费劲,更别说快速响应了。
  3. 人力成本高企:随着公司规模扩大,客服团队人数不断增加,但大部分时间都在处理重复、简单的咨询上,人力成本成了不小的负担,而且客服人员工作成就感低,流动性也大。

二、 为什么最终选择了Coze?

在技术选型阶段,我们重点对比了Coze、Rasa和Dialogflow。

  • Rasa:开源,定制能力最强,可以完全私有化部署,数据安全性好。但缺点也很明显:需要较强的NLP和机器学习背景,开发和维护成本高,迭代速度慢,中文社区的支持相对较弱。
  • Dialogflow:谷歌出品,NLU能力强大,开发上手快。但它的强项在英文,中文处理有时不够地道;更重要的是,它的核心服务在海外,对于国内企业,数据合规性和API访问延迟是需要慎重考虑的问题。
  • Coze:最终我们选择了它,主要基于以下几点考虑:
    • 快速落地:拖拽式的Bot创建工作流,意图和技能配置直观,让我们在两周内就做出了可演示的原型,极大地提振了项目信心。
    • 出色的中文理解:基于国内大模型优化,对中文口语、行业术语的理解更准确,减少了大量额外的训练工作。
    • 便捷的企业集成:原生支持Webhook,与企业微信、飞书、钉钉等国内主流IM的对接文档清晰,提供了开箱即用的适配方案。
    • 灵活的部署选项:虽然我们目前使用的是云端版,但其也提供了满足更高安全要求的私有化部署方案,为未来留出了扩展空间。

综合来看,Coze在 “开发效率”“本土化适配” 之间找到了一个非常好的平衡点,特别适合我们这种追求快速见效、同时又要对接复杂内部系统的团队。

三、 核心实现:三步搭建智能核心

1. Coze Bot的意图识别与实体抽取配置

在Coze里,意图(Intent)就是用户想干什么,实体(Entity)就是意图里涉及的具体对象。配置好这两样,Bot才能准确理解问题。

比如,员工常问:“怎么申请年假?” 和 “申请年假的流程是什么?”。这都属于 申请年假 这个意图。而“年假”本身,可以作为一个实体被抽取出来,用于后续的流程判断或知识检索。

在Coze工作台,我们主要配置了“用户说法”和“槽位”(Slots,即实体)。以下是一个简化的意图配置JSON结构示例,它可以帮助你理解背后的逻辑:

{
  "intent_name": "ask_annual_leave",
  "training_phrases": [
    "如何申请年假",
    "年假怎么请",
    "申请年假的步骤",
    "想休年假,怎么操作"
  ],
  "slots": [
    {
      "slot_name": "leave_type",
      "entity_type": "预定义实体",
      "value": "年假"
    }
  ],
  "response": "我将为您查询年假申请流程。您也可以直接访问OA系统,在‘人事流程’->‘休假申请’中提交。"
}

经验之谈:初期不要贪多,抓住最高频的10-20个意图做精做透。用户说法(Training Phrases)要尽可能多样化,涵盖口语化表达。

2. 知识库动态更新API设计

公司的知识是活的,每天都在变。我们不可能手动去Coze后台一条条改。所以,我们开发了一个知识库同步API,让Confluence或内部Wiki的更新能自动同步到Coze的知识库。

这个API的核心是调用Coze提供的知识库管理接口。我们设计了一个Python服务,定时(比如每天凌晨)或触发式(检测到Confluence页面更新)地执行同步。

import requests
import hashlib
import time
from typing import List, Dict
from pydantic import BaseModel

class KnowledgeItem(BaseModel):
    """知识条目数据模型"""
    title: str
    content: str
    source_url: str
    tags: List[str] = []

class CozeKnowledgeManager:
    """Coze知识库管理客户端"""
    
    def __init__(self, api_base: str, bot_id: str, api_key: str):
        self.api_base = api_base.rstrip('/')
        self.bot_id = bot_id
        self.api_key = api_key
        self.session = requests.Session()
        self.session.headers.update({
            'Authorization': f'Bearer {self.api_key}',
            'Content-Type': 'application/json'
        })
    
    def _make_request(self, method: str, endpoint: str, **kwargs):
        """发起请求并处理异常"""
        url = f'{self.api_base}/{endpoint}'
        try:
            resp = self.session.request(method, url, **kwargs)
            resp.raise_for_status()
            return resp.json()
        except requests.exceptions.RequestException as e:
            print(f"请求Coze API失败: {e}")
            # 这里可以加入重试逻辑或告警
            raise
    
    def upload_knowledge(self, items: List[KnowledgeItem]) -> Dict:
        """
        批量上传或更新知识条目。
        实现思路:计算内容哈希作为唯一ID,存在则更新,不存在则新增。
        """
        payload = []
        for item in items:
            # 生成唯一ID,避免重复上传
            content_hash = hashlib.md5(item.content.encode()).hexdigest()
            payload.append({
                "id": f"doc_{content_hash}",
                "title": item.title,
                "content": item.content,
                "metadata": {
                    "source": item.source_url,
                    "tags": item.tags,
                    "update_time": int(time.time())
                }
            })
        
        endpoint = f"bots/{self.bot_id}/knowledge/documents/batch"
        return self._make_request('POST', endpoint, json={"documents": payload})
    
    def delete_obsolete_knowledge(self, valid_hash_list: List[str]):
        """
        删除已不在源知识库中的条目。
        valid_hash_list: 当前有效的所有内容哈希列表。
        """
        # 1. 先获取Coze知识库现有条目ID列表
        endpoint = f"bots/{self.bot_id}/knowledge/documents"
        current_docs = self._make_request('GET', endpoint)
        
        # 2. 找出需要删除的ID(哈希不在有效列表中的)
        to_delete_ids = []
        for doc in current_docs.get('documents', []):
            doc_id = doc.get('id', '')
            # 假设我们ID格式是 doc_{hash}
            if doc_id.startswith('doc_'):
                hash_part = doc_id[4:]
                if hash_part not in valid_hash_list:
                    to_delete_ids.append(doc_id)
        
        # 3. 批量删除
        if to_delete_ids:
            delete_endpoint = f"bots/{self.bot_id}/knowledge/documents/batch_delete"
            self._make_request('POST', delete_endpoint, json={"document_ids": to_delete_ids})
            print(f"已删除 {len(to_delete_ids)} 条过时知识。")

# 使用示例
if __name__ == '__main__':
    manager = CozeKnowledgeManager(
        api_base='https://api.coze.cn/v1',
        bot_id='your_bot_id_here',
        api_key='your_api_key_here'
    )
    
    # 假设从Confluence解析出了新知识
    new_items = [
        KnowledgeItem(
            title='2024年差旅报销新规',
            content='自2024年1月1日起,经济舱机票报销标准上调至2000元...',
            source_url='https://confluence.company.com/pages/123',
            tags=['财务', '报销', '制度']
        )
    ]
    
    # 上传新知识
    result = manager.upload_knowledge(new_items)
    print(f"知识上传结果: {result}")

关键点

  • 幂等性设计:通过内容哈希生成唯一ID,避免重复上传。
  • 增量更新:定期清理Coze知识库中已不存在的条目,保持知识库清洁。
  • 错误处理与重试:网络请求必须做好异常捕获,对于同步失败的重要知识,要有重试或人工审核机制。

3. 与企业微信对接的Webhook实现

Bot建好了,知识库也有了,下一步就是让员工能在企业微信里直接@它提问。这需要我们在自己的服务器上实现一个Webhook,接收企业微信转发过来的用户消息,处理后调用Coze Bot的API,再把回复传回企业微信。

企业微信(或飞书)出于安全考虑,要求对接收到的消息进行签名验证。以下是核心的Webhook处理逻辑:

from flask import Flask, request, jsonify
import hashlib
import hmac
import json
import time
from typing import Optional

app = Flask(__name__)

# 配置信息,应从环境变量或配置中心读取
CORP_ID = 'your_corp_id'
AGENT_SECRET = 'your_agent_secret'
TOKEN = 'your_webhook_token'  # 在企微后台配置的Token
ENCODING_AES_KEY = 'your_encoding_aes_key'  # 在企微后台配置的EncodingAESKey

def verify_signature(token: str, timestamp: str, nonce: str, signature: str) -> bool:
    """验证企业微信/飞书等平台发来的消息签名"""
    try:
        # 1. 将token、timestamp、nonce按字典序排序
        tmp_list = sorted([token, timestamp, nonce])
        # 2. 拼接成一个字符串
        tmp_str = ''.join(tmp_list)
        # 3. 进行sha1加密
        tmp_str = hashlib.sha1(tmp_str.encode()).hexdigest()
        # 4. 与收到的signature对比
        return hmac.compare_digest(tmp_str, signature)
    except Exception:
        return False

@app.route('/webhook/wecom', methods=['POST'])
def wecom_webhook():
    """处理企业微信机器人Webhook回调"""
    # 1. 获取URL参数和签名
    msg_signature = request.args.get('msg_signature', '')
    timestamp = request.args.get('timestamp', '')
    nonce = request.args.get('nonce', '')
    
    # 2. 验证签名(此处简化,实际需解密)
    if not verify_signature(TOKEN, timestamp, nonce, msg_signature):
        return jsonify({'error': 'Invalid signature'}), 403
    
    # 3. 解析企业微信加密消息体(此处为简化示例,实际需用官方SDK解密)
    encrypted_data = request.get_json()
    # 假设此处已解密,得到明文消息内容
    user_message = encrypted_data.get('text', {}).get('content', '').strip()
    user_id = encrypted_data.get('from_user_id', '')
    
    if not user_message:
        return jsonify({'error': 'Empty message'}), 400
    
    # 4. 调用Coze Bot API获取回复
    coze_reply = call_coze_bot(user_message, user_id)
    
    # 5. 将回复加密后返回给企业微信(此处为简化,返回明文)
    response_text = coze_reply.get('text', '抱歉,我暂时无法处理这个问题。')
    
    # 6. 构造返回给企业微信的JSON
    # 注意:实际需按企微要求加密回复消息
    return jsonify({
        'msgtype': 'text',
        'text': {
            'content': response_text
        }
    })

def call_coze_bot(message: str, user_id: str) -> dict:
    """调用Coze Bot对话API"""
    # 这里调用Coze的对话API
    # 注意携带用户ID以支持多轮对话上下文
    coze_api_url = 'https://api.coze.cn/v1/chat'
    headers = {
        'Authorization': 'Bearer YOUR_COZE_BOT_API_KEY',
        'Content-Type': 'application/json'
    }
    payload = {
        'bot_id': 'YOUR_BOT_ID',
        'user_id': user_id,  # 重要:用于区分不同用户的对话上下文
        'query': message,
        'stream': False
    }
    
    try:
        resp = requests.post(coze_api_url, json=payload, headers=headers, timeout=10)
        resp.raise_for_status()
        return resp.json()
    except requests.exceptions.RequestException as e:
        print(f"调用Coze API失败: {e}")
        return {'text': '服务暂时不可用,请稍后再试。'}

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000, debug=False)

对接避坑指南

  • 签名验证:一定要做,这是安全底线。企业微信和飞书的签名算法略有不同,务必仔细阅读官方文档。
  • 消息加解密:企业微信默认要求对消息体进行加密传输,需要使用官方提供的加解密库或SDK,不能直接传输明文。
  • 超时处理:设置合理的超时时间(如5-8秒),避免用户长时间等待。如果Coze API调用超时,应返回友好的降级提示。
  • 重试机制:对于网络波动导致的失败,可以考虑加入一次重试。

企业微信与Coze Bot对接流程

四、 性能优化:让响应更快更安全

系统跑起来后,我们开始关注性能和安全性优化。

  1. 对话上下文缓存策略 多轮对话是智能客服的基本功。Coze API本身支持通过 user_idconversation_id 来维持上下文。但频繁调用大模型成本高、延迟也高。我们在Webhook服务层增加了Redis缓存。

    • 缓存什么:将Coze返回的 conversation_id 以及最近几轮的对话摘要(非完整历史,节省空间)缓存起来,键为 coze:conv:{user_id}
    • 缓存时长:设置合理的TTL(如30分钟),超过时间未互动则自动清除,释放资源。
    • 带来的好处:用户连续提问时,无需Coze Bot从头理解,直接携带缓存的上下文ID即可,平均响应时间减少了40%。
  2. 敏感信息过滤 内部聊天难免涉及员工号、手机号、邮箱等敏感信息。虽然Coze Bot不会主动泄露,但为保险起见,我们在消息送入Coze API之前,先做一层过滤。

    • 正则表达式过滤:我们编写了一套正则规则,用于检测和脱敏。
    import re
    
    class SensitiveInfoFilter:
        PATTERNS = {
            'employee_id': re.compile(r'\b[Ee]\d{6}\b'),  # 假设员工号格式 E123456
            'phone': re.compile(r'\b1[3-9]\d{9}\b'),
            'email': re.compile(r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b'),
        }
        
        @classmethod
        def filter_text(cls, text: str) -> str:
            filtered_text = text
            for key, pattern in cls.PATTERNS.items():
                if pattern.search(filtered_text):
                    filtered_text = pattern.sub(f'[已过滤的{key}]', filtered_text)
            return filtered_text
    
    # 使用
    user_input = "我的员工号是E123456,手机是13800138000,帮我查下年假。"
    safe_input = SensitiveInfoFilter.filter_text(user_input)
    # safe_input: "我的员工号是[已过滤的employee_id],手机是[已过滤的phone],帮我查下年假。"
    
    • 日志脱敏:同样,所有写入业务日志或审计日志的对话内容,都必须经过此过滤处理,确保合规。

五、 避坑指南:那些我们踩过的“雷”

  1. 意图冲突与Fallback机制 Bot不可能理解所有问题。当用户问题超出预设意图或置信度很低时,需要一个优雅的降级(Fallback)策略。我们的做法是:

    • 设置置信度阈值:Coze返回的意图识别会有一个置信度分数。我们设定一个阈值(如0.7),低于此分数,则触发Fallback。
    • Fallback流程:首先,尝试从通用知识库进行语义搜索,返回最相关的3个知识片段。如果仍然没有合适答案,则回复:“您的问题我已记录,将转交人工客服处理。同时,您可以尝试这样提问:1. 如何报销? 2. 年假有多少天?”,并自动创建一条低优先级工单。
  2. 多部门知识库的权限隔离 公司不同部门(如HR、财务、IT)的知识可能涉及权限。我们通过以下方式实现:

    • Coze Bot多技能集:为HR、财务等分别创建独立的“技能”(Skill),每个技能关联自己部门的知识库。
    • 用户身份识别:在Webhook层,通过企业微信API获取用户的部门信息。
    • 路由逻辑:根据用户部门,决定调用哪个技能集。例如,IT部门的员工问财务问题,Bot可以回答:“这是财务相关问题,我已为您转接至财务知识库。”然后调用财务技能集。对于跨部门通用问题(如年假),则所有用户都可访问。
  3. 对话日志的脱敏存储 为后续分析模型效果和优化意图,对话日志必须存储,但必须脱敏。

    • 存储内容:用户原始问题(脱敏后)、Bot回复、意图、置信度、用户ID(哈希化)、时间戳、会话ID。
    • 存储方式:使用Elasticsearch进行索引,便于按意图、时间、用户等多维度分析。同时,定期(如每周)将日志冷存储至对象存储(如S3)进行归档。

六、 效果验证:数据说了算

系统上线稳定运行一个月后,我们进行了一次压力测试和数据分析。

  • 压测报告关键数据

    • QPS(每秒查询率):在4核8G的标准服务器上,我们的Webhook服务(含缓存、过滤逻辑)单实例可稳定支撑约120 QPS。
    • 平均响应时间:从用户发送消息到收到Bot回复,端到端平均响应时间 从原来的300秒以上降至95秒以内,提升超过300%。这主要得益于自动化的知识检索和回答。
    • 准确率:对高频意图(Top 20)的识别准确率达到92%,回答满意度(通过后续“是否解决”按钮收集)为87%。
  • 业务指标提升

    • 客服团队关于简单政策咨询的接待量下降了70%。
    • 员工获取信息的平均等待时间从分钟级降至秒级。
    • 知识库的更新从按月周期变为近乎实时。

写在最后

通过Coze搭建内部智能客服,对我们来说是一次成功的“敏捷”实践。它没有追求大而全的复杂AI模型,而是用相对轻量的方式,解决了最迫切的效率问题。技术选型上,Coze的易用性和本土化优势是项目快速成功的关键。

当然,系统还有优化空间。一个始终困扰我们的开放问题是:如何平衡AI应答的准确率与人工介入的阈值? 把阈值设得太高,很多问题AI不敢答,都转人工,效率提升有限;设得太低,AI乱答,又会引起用户不满和信任危机。我们目前采用动态阈值策略,对高风险领域(如薪酬、合规)设置高阈值,对通用信息查询设置低阈值,并持续通过用户反馈来调整。这或许是一个需要长期用数据和算法去优化的问题。

未来,我们计划探索更深入的集成,比如将智能客服与OA审批流打通,实现“问完即办”;或者利用对话数据分析,反向推动知识库的结构化优化。这条路还很长,但第一步的成果,已经让我们看到了巨大的价值。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐