Skill: xianji520/auto-customer-support (version 1.0.0)

--- name: auto-customer-support description: 自动化客服（基于 FAQ 的轻量检索回复 + 简单转人工/工单接口；支持 Webhook 接入、邮件/企业微信/工单系统集成与可选升级到 LLM 混合模式）。 --- # 自动化客服 Skill 本 Skill 提供一个可运行的客服自动化 scaffold，适合快速搭建轻量客服机器人并接入外部渠道。默认实现是基于 FAQ 的检索+模板回复，并提供转人工/工单的接口。 适用触发语（示例） - “帮我自动回复客服消息” - “把这个接入到微信/邮箱，遇不到答案就转人工” - “给我一份客服 FAQ 的示例和测试对话” 快速功能概览 - 加载 CSV 格式的 FAQ（intent, question_variants, answer） - 基于简单关键词/模糊匹配选择最佳答案并渲染模板 - 可配置信心阈值，低信心时自动标记为需人工或创建工单 - 提供 /webhook 接口用于接收消息并返回回复（JSON），以及 /escalate 用于转人工 目录结构（已创建） - scripts/server.py — Flask webhook 服务（主运行脚本） - data/faq.csv — 示例问答对 - assets/templates/auto-reply.txt — 回复模板示例（变量支持） - references/integration-guides.md — 渠道/集成与凭证说明 - examples/sample_conversation.md — 示例对话与测试用例 运行（快速上手） 1) 安装依赖：uv pip install flask 2) 启动服务（开发）： uv run python skills/auto-customer-support/scripts/server.py --port 5005 3) 测试请求： curl -X POST http://localhost:5005/webhook -H "Content-Type: application/json" -d '{"message":"如何退款","sender":"user-123"}' 输出（示例） - 成功匹配时返回：{ "reply": "...", "confidence": 0.95, "escalate": false } - 未匹配或低置信时返回：{ "reply": "抱歉，我不确定……我们将转人工处理。", "confidence": 0.35, "escalate": true } 扩展与升级点 - 将检索层替换成向量搜索（FAISS/Chroma）并接入 LLM 以生成更自然的回答 - 增加对话上下文管理与会话状态 - 集成渠道（企业微信/微信客服/邮箱/Zendesk）并实现双向消息同步 隐私与安全 - 不要把生产 API Key、密码粘贴到对话中；在部署时使用环境变量或受限凭证文件 - 自动回复前在测试环境做 100% 验证，避免误发敏感信息 下一步 - 我可以立刻在 workspace 中创建并启动服务供你本地测试；或根据你要接入的渠道（微信/邮箱/Zendesk）帮你生成对接示例。

path: assets/templates/auto-reply.txt
content: 您好 {{name}}，

感谢您的咨询！我们已收到您的问题：

{{message}}

我们的客服将尽快处理（通常在1个工作日内）。如需立即人工帮助，请回复“人工”。

此邮件为系统自动回复。如需退订或隐私说明，请访问: https://example.com/privacy


path: data/faq.csv
content: intent,question_variants,answer
refund,"如何退款|我要退货|退款流程","您好，退款流程为：1) 请提供订单号；2) 我们核实后将在7个工作日内完成退款。如需帮助请回复订单号。"
shipment,"我的快递在哪里|订单物流查询|包裹进度","您好，请提供运单号或订单号，我将帮您查询最新物流状态。"
pricing,"你们的价格是多少|报价|收费标准","您好，请问您关注的是哪一款产品/服务？我可以根据具体需求给出报价。"


path: examples/sample_conversation.md
content: User: 我的订单什么时候到？
Bot: 您好，请提供订单号，我将帮您查询最新物流状态。
User: 订单号 123456
Bot: 感谢，正在查询...（转人工或调用物流API）

User: 我想退款
Bot: 您好，退款流程为：1) 请提供订单号；2) 我们核实后将在7个工作日内完成退款。如需帮助请回复订单号。


path: references/integration-guides.md
content: # 渠道集成指南

支持的集成方式：
- Webhook（任何能 POST JSON 的平台）
- 微信 / 企业微信：使用企业微信消息 API 或第三方接入（需要 CorpID/Secret）
- 邮件：使用 SMTP/IMAP 拉取未读邮件并调用 /webhook 接口处理
- 工单系统：Zendesk/Freshdesk/Feishu – 使用其 REST API 创建工单（示例见下）

凭证配置建议：
- 使用环境变量或受限凭证文件：不要在代码中写入明文 Key
- 权限分离：机器人仅需消息读取/回复与创建工单权限

速率与错误处理：
- 对于外部渠道的 webhook，确保幂等与重试逻辑（重复消息可能发生）
- 日志与告警：将错误写入监控并在连续失败时通知运维


path: scripts/server.py
content: #!/usr/bin/env python
"""
Simple Flask server for FAQ-based auto customer support.
- /webhook: receive {message, sender} and return {reply, confidence, escalate}
- /escalate: mark conversation for human follow-up (stub)
"""
from flask import Flask, request, jsonify
import csv
import os
import argparse
from difflib import SequenceMatcher

app = Flask(__name__)
FAQ = []


def load_faq(path):
    faqs = []
    with open(path, newline='', encoding='utf-8') as f:
        r = csv.DictReader(f)
        for row in r:
            # question_variants stored as | separated text
            variants = [v.strip() for v in row.get('question_variants','').split('|') if v.strip()]
            faqs.append({'intent': row.get('intent',''), 'variants': variants, 'answer': row.get('answer','')})
    return faqs


def best_match(message):
    best = None
    best_score = 0.0
    for item in FAQ:
        for v in item['variants']:
            score = SequenceMatcher(None, message.lower(), v.lower()).ratio()
            if score > best_score:
                best_score = score
                best = item
    return best, best_score


@app.route('/webhook', methods=['POST'])
def webhook():
    data = request.get_json() or {}
    message = data.get('message','')
    sender = data.get('sender')
    if not message:
        return jsonify({'error':'missing message'}), 400
    item, score = best_match(message)
    threshold = float(os.environ.get('CONFIDENCE_THRESHOLD', '0.6'))
    if item and score >= threshold:
        reply = item['answer']
        return jsonify({'reply': reply, 'confidence': score, 'escalate': False})
    else:
        # low confidence -> escalate
        return jsonify({'reply':'抱歉，我不确定如何回答。我们会尽快将您的问题转人工处理。', 'confidence': score, 'escalate': True})


@app.route('/escalate', methods=['POST'])
def escalate():
    data = request.get_json() or {}
    # stub: in real integration, create ticket in Zendesk/Feishu/etc.
    return jsonify({'status':'escalated','data':data})


if __name__ == '__main__':
    p = argparse.ArgumentParser()
    p.add_argument('--faq', default='skills/auto-customer-support/data/faq.csv')
    p.add_argument('--port', type=int, default=5005)
    args = p.parse_args()
    FAQ = load_faq(args.faq)
    app.run(host='0.0.0.0', port=args.port, debug=True)


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: clean (LOW)