From f06bfb1fa059c87f0825fae159385d59517d08ba Mon Sep 17 00:00:00 2001 From: jimi <1847930177@qq.com> Date: Fri, 6 Mar 2026 14:25:10 +0800 Subject: [PATCH] newtw3 --- README.md | 173 ++++++++++++++++++++----------- core/agent_tools.py | 76 ++++++++++++-- core/orchestrator.py | 50 +++++---- core/pydantic_ai_agent_v2.py | 108 ++++++++++++------- core/提示词.MD | 19 ---- skills/customer-service/SKILL.md | 8 +- 6 files changed, 289 insertions(+), 145 deletions(-) delete mode 100644 core/提示词.MD diff --git a/README.md b/README.md index f85e8d7..20c9a28 100755 --- a/README.md +++ b/README.md @@ -1,46 +1,70 @@ -# AI 客服系统 - 天网协作版 +# AI 客服系统 -**版本**: v1.0 | **服务器**: 1.12.50.92 +基于 PydanticAI 的智能客服,对接千牛 WebSocket,自动接待、收图、转接设计师。 --- -## 功能概览 +## 架构概览 + +``` +客户消息 (千牛) + ↓ +WebSocket Client → QianniuAdapter (协议转换) + ↓ +Orchestrator (防抖/去重/冷却/路由) + ↓ +CustomerServiceBrain (PydanticAI Agent) + ├── lookup_chat_history_tool → 查询历史记录 + ├── transfer_to_human_tool → 转接设计师 + └── check_order_status_tool → 订单查询 + ↓ +QianniuAdapter → WebSocket → 回复客户 +``` + +--- + +## 核心功能 | 功能 | 说明 | |------|------| -| 天网协作 | 接收天网任务,支持指定客户回复触发 | -| 三种工作流 | 找图 / 处理图片 / 转人工派单 | -| 图片任务数据库 | 任务持久化,支持后续增加需求 | -| 图绘派单系统 | 自动派单给在线设计师 | -| 文字检测加价 | 自动识别文字数量并加价 | -| 风险评估 | 自动识别敏感内容,拒绝不良订单 | -| 作图失败转人工 | 失败自动转接人工客服 | +| 智能接待 | 自动引导发图、问需求、转接设计师 | +| 历史记忆 | AI 可调用工具查询完整聊天历史,避免重复提问 | +| 自动转接 | 收到图片+需求后自动派单给在线设计师 | +| 转接冷却 | 转接后 120 秒内不再调用 AI,直接安抚 | +| 情绪识别 | 客户愤怒/投诉时自动转人工 | +| 消息防抖 | 合并短时间内的多条消息,避免重复回复 | +| 订单静默 | 订单通知/SKU 信息自动入库,不触发 AI | +| 时段感知 | 根据时间区分"没上班"/"下班了"/"暂时不在" | +| 图片分析 | 后台调用 Gemini 分析图片复杂度 | +| 日报统计 | 每日自动生成客服数据报告 | +| 多进程 | 支持多 Worker 并行处理 | --- ## 快速开始 ```bash -cd /root/ai_customer_service/ai_cs -pip3 install -r requirements.txt +# 安装依赖 +pip install -r requirements.txt -# 天网协作版(仅 HTTP API) -python3 run.py --api-only +# 配置环境变量 +cp .env.example .env +# 编辑 .env 填入 API Key、数据库等配置 -# 完整版(HTTP API + WebSocket + AI Agent) -python3 run.py --tianwang +# 启动(WebSocket 客服模式) +python run.py -# AI 客服(仅 WebSocket,默认) -python3 run.py +# 完整版(HTTP API + WebSocket) +python run.py --tianwang + +# 多进程模式 +python run.py --multi -w 4 + +# 仅 HTTP API +python run.py --api-only ``` -### 后台运行 - -```bash -nohup python3 run.py --api-only > /tmp/tianwang.log 2>&1 & -``` - -### 验证 +### 健康检查 ```bash curl http://localhost:6060/api/health @@ -48,44 +72,75 @@ curl http://localhost:6060/api/health --- -## API 地址 - -| 服务 | 地址 | -|------|------| -| AI 客服 API | `http://127.0.0.1:6060` | -| 派单系统 | `http://1.12.50.92:8005` | -| 图绘平台 | `http://1.12.50.92:8002` | - ---- - -## 文档 - -| 文档 | 内容 | -|------|------| -| **项目功能汇总.md** | 全部功能详细说明(工作流、报价、风险、派单、数据库等) | -| **部署文档.md** | 部署、API 接口、天网集成、多进程、故障排查 | -| **features/self_evolution_mvp.md** | 自我进化 MVP(采样、评测、建议、灰度门禁) | - ---- - ## 项目结构 ``` -├── api/ # HTTP API 服务器 -├── core/ # 核心逻辑(Agent、工作流、WebSocket) -├── config/ # 配置文件 -├── db/ # 数据库模块 -├── image/ # 图片处理模块 -├── services/ # 外部服务集成 -├── utils/ # 工具模块 -├── skills/ # Agent 技能定义 -└── run.py # 统一入口(--api-only / --tianwang / 默认 WebSocket) +├── run.py # 统一入口 +├── api/ +│ └── http_server.py # HTTP API 服务 +├── core/ +│ ├── orchestrator.py # 总编排(防抖/去重/冷却/路由) +│ ├── pydantic_ai_agent_v2.py # AI 大脑(PydanticAI Agent) +│ ├── agent_tools.py # AI 工具(转接/查历史/查订单) +│ ├── schema.py # 数据模型(StandardMessage/Response) +│ ├── repository.py # 异步数据仓库 +│ ├── skill_manager.py # 技能加载器 +│ ├── engine.py # 业务逻辑兜底 +│ ├── adapters/ +│ │ └── qianniu_adapter.py # 千牛协议适配 +│ ├── events/ +│ │ └── event_bus.py # 异步事件总线 +│ └── websocket_*.py # WebSocket 连接/发送/日志 +├── db/ +│ ├── chat_log_db.py # 聊天记录(SQLite/MySQL) +│ ├── customer_db.py # 客户档案 +│ ├── image_tasks_db.py # 图片任务 +│ └── task_db/ # 任务模型 +├── services/ +│ ├── dispatch_service.py # 设计师派单 +│ ├── service_gemini.py # Gemini 图片分析 +│ ├── service_image_analyzer.py # 图片复杂度分析 +│ └── ... # 其他服务 +├── skills/ # AI 技能定义(SKILL.md) +│ ├── customer-service/ # 客服核心技能 +│ ├── owner-style/ # 店主风格 +│ ├── pre-sales-skill/ # 售前 +│ ├── after-sales-skill/ # 售后 +│ ├── pricing-skill/ # 报价(排除出 prompt) +│ ├── risk-skill/ # 风控 +│ └── style-skill/ # 语气风格 +├── config/ # 配置文件 +├── utils/ # 工具(日报/健康检查/API计费等) +└── scripts/ # 运维脚本 ``` -## 自我进化 MVP +--- -```bash -python scripts/evolution_cycle.py --hours 24 --publish -``` +## 环境变量 -默认从线上 MySQL 读取对话数据(可用 `--source` 切换)。 +| 变量 | 说明 | +|------|------| +| `OPENAI_API_KEY` | 火山引擎 Ark API Key | +| `OPENAI_BASE_URL` | API 地址 | +| `OPENAI_MODEL` | 对话模型 | +| `DB_TYPE` | 数据库类型(`sqlite` / `mysql`) | +| `MYSQL_HOST/PORT/USER/PASSWORD/DATABASE` | MySQL 连接信息 | +| `WECHAT_WEBHOOK` | 企业微信通知 Webhook | +| `MESSAGE_DEBOUNCE_SECONDS` | 消息防抖时间(秒) | +| `DISPATCH_BASE_URL` | 派单服务地址 | + +完整配置见 `.env.example`。 + +--- + +## 消息处理流程 + +1. **WebSocket 接收** → 千牛原始消息 +2. **适配器转换** → `StandardMessage`(统一格式) +3. **Orchestrator 过滤** → 订单/SKU 静默入库、心跳过滤、商家回复入库 +4. **防抖合并** → 2 秒窗口内多条消息合并为一条 +5. **冷却检查** → 转接后 120 秒内直接安抚,不调 AI +6. **AI 思考** → PydanticAI Agent 调用工具、生成回复 +7. **转接截获** → 工具返回转接指令时直接发送,不经 AI 二次加工 +8. **乱码清理** → 过滤 ``、内部标记等泄露内容 +9. **发送回复** → 通过 WebSocket 回复客户,同时入库 diff --git a/core/agent_tools.py b/core/agent_tools.py index 215e168..8bccd47 100644 --- a/core/agent_tools.py +++ b/core/agent_tools.py @@ -1,39 +1,97 @@ import logging import asyncio +from datetime import datetime from typing import List, Optional, Dict, Any from pydantic import BaseModel, Field from pydantic_ai import RunContext from core.schema import StandardResponse from services.dispatch_service import dispatch_service +from db.chat_log_db import get_conversation logger = logging.getLogger("cs_agent") + async def transfer_to_human_tool(ctx: RunContext[Any], reason: str = Field(description="转人工的原因")) -> str: """ 【核心工具】执行转人工逻辑。 获取设计师姓名并生成精准转接指令。 """ logger.info(f"[Tool] 尝试呼叫设计师接手: {reason}") - - # 1. 尝试派单获取设计师姓名 + designer_name = await dispatch_service.assign_designer() - + if designer_name: - # 2. 有设计师在线:生成标准转接指令 (必须包含 [转移会话] 且格式正确) magic_cmd = f"正在为您转接|[转移会话],{designer_name},无原因" logger.info(f"[Tool] 成功呼叫设计师: {designer_name}") return magic_cmd else: - # 3. 设计师下线:返回特定信号 - logger.warning("[Tool] 派单失败:设计师们已下线或不在位") - return "ERROR_NO_DESIGNER_ONLINE" + hour = datetime.now().hour + logger.warning(f"[Tool] 派单失败:设计师们不在位 (当前{hour}点)") + if 0 <= hour < 9: + return "ERROR_DESIGNER_NOT_STARTED:现在设计师还没上班,你告诉客户需求记下了,上班后第一时间处理。不要说下班。" + elif 22 <= hour or hour < 1: + return "ERROR_DESIGNER_OFFLINE:设计师已下班,你告诉客户需求记下了,明天第一时间回复。" + else: + return "ERROR_DESIGNER_BUSY:设计师暂时不在位,你告诉客户稍等,马上帮忙联系设计师。不要说下班。" + async def check_order_status_tool(ctx: RunContext[Any], customer_id: str = Field(description="客户ID")) -> str: """查询订单状态。""" - return "设计师正在后台加急处理中,请稍等哈。" + return "我在帮你加急处理中,稍等哈。" + + +async def lookup_chat_history_tool( + ctx: RunContext[Any], + customer_id: str = Field(description="客户ID,从当前对话上下文中获取"), + num_messages: int = Field(default=30, description="要查询的历史消息条数,默认30条"), +) -> str: + """ + 【历史记录查询工具】查询该客户的历史聊天记录。 + 使用场景: + - 客户说"之前聊过"、"上次"、"你看聊天记录"、"我发过图了"等暗示有历史对话时 + - 客户第二次来访、追问进度、催单时 + - 你不确定客户之前是否发过图或说过需求时 + 必须先调用此工具回顾历史,再回复客户,避免重复要求客户发图。 + """ + logger.info(f"[Tool] 查询历史记录: customer_id={customer_id}, limit={num_messages}") + try: + rows = await asyncio.to_thread(get_conversation, customer_id, limit=num_messages) + if not rows: + return f"该客户({customer_id})暂无历史聊天记录。" + + lines = [] + has_images = False + customer_needs = [] + for r in rows: + role = "客户" if r["direction"] == "in" else "客服" + ts = str(r.get("timestamp", "")) + msg = r.get("message", "") + line = f"[{ts}] {role}:{msg}" + lines.append(line) + if r["direction"] == "in": + if "已收到" in msg and "图" in msg: + has_images = True + if any(k in msg for k in ["找原图", "修复", "高清", "去背景", "抠图", "做衣服", "打印"]): + customer_needs.append(msg[:60]) + + summary_parts = [f"共{len(rows)}条历史消息。"] + if has_images: + summary_parts.append("⚠️ 客户之前已经发过图片!不要再让客户发图!") + if customer_needs: + summary_parts.append(f"客户曾表达的需求:{';'.join(customer_needs[:3])}") + + summary = " ".join(summary_parts) + history_text = "\n".join(lines[-30:]) + return f"【历史记录摘要】{summary}\n\n【详细记录】\n{history_text}" + + except Exception as e: + logger.error(f"[Tool] 查询历史记录失败: {e}") + return f"查询历史记录失败: {e}" + def register_agent_tools(agent: Any): """注册工具""" agent.tool(transfer_to_human_tool) agent.tool(check_order_status_tool) - logger.info("[Agent] 工具箱已更新:称呼统一为“设计师”。") + agent.tool(lookup_chat_history_tool) + logger.info("[Agent] 工具箱已更新:含转人工、订单查询、历史记录查询。") diff --git a/core/orchestrator.py b/core/orchestrator.py index 9878487..2d25f48 100644 --- a/core/orchestrator.py +++ b/core/orchestrator.py @@ -15,9 +15,18 @@ logger = logging.getLogger("cs_agent") # 配置常量 MSG_DEDUP_CAPACITY = 200 # 消息 ID 去重缓存容量 -TRANSFER_COOLDOWN_SEC = 60 # 转接冷却时间(秒) +TRANSFER_COOLDOWN_SEC = 120 # 转接冷却时间(秒)—— 转接后2分钟内不再调用AI DEBOUNCE_SECONDS = 2.0 # 消息防抖延迟(秒) +# 转接后安抚话术池(轮换使用,避免复读) +_TRANSFER_CALM_REPLIES = [ + "我在帮你催了哈,稍等下", + "已经转了哈,马上就来", + "收到,设计师在赶来了哈", + "好的亲,稍等一下哈", + "在催了在催了,马上哈", +] + class SystemOrchestrator: """ 全系统总编排:具备转接冷却、防抖合并、多消息去重、以及精准日志。 @@ -30,8 +39,9 @@ class SystemOrchestrator: # 1. 消息 ID 去重 self._processed_msg_ids = deque(maxlen=MSG_DEDUP_CAPACITY) - # 2. 转接冷却存储 (customer_id -> last_transfer_time) + # 2. 转接冷却存储 (session_key -> last_transfer_time) self._last_transfer_time: Dict[str, float] = {} + self._transfer_calm_idx: Dict[str, int] = {} # 安抚话术轮换索引 # 3. 防抖配置 self._debounce_seconds = DEBOUNCE_SECONDS @@ -68,12 +78,13 @@ class SystemOrchestrator: # 店铺隔离:同一客户在不同店铺的对话独立处理 session_key = f"{user_id}@{std_msg.acc_id}" - # 订单消息 / 纯金额通知:静默入库,不触发 AI 回复 + # 订单消息 / 纯金额通知 / SKU信息:静默入库,不触发 AI 回复 msg_text = std_msg.content or "" is_order = "[系统订单信息]" in msg_text is_price_only = bool(re.match(r'^[\s\n]*金?额?[::]?\s*[\d.]+\s*元', msg_text.strip())) - is_sku_only = bool(re.match(r'^[\s\n]*(备注[::]|数量[::]|款式[::])', msg_text.strip())) - if is_order or is_price_only or is_sku_only: + is_sku_only = bool(re.match(r'^[\s\n]*(备注[::]|数量[::]|款式[::]|定制[::])', msg_text.strip())) + is_sku_amount = bool(re.match(r'^[\s\n]*金额[::]\s*[\d.]+元\s*●', msg_text.strip())) + if is_order or is_price_only or is_sku_only or is_sku_amount: await self._handle_order_packet(platform, std_msg) logger.info(f"[订单消息] user={user_id} acc={std_msg.acc_id} 已入库更新状态") await repo.save_chat(platform, user_id, msg_text, "in", acc_id=std_msg.acc_id) @@ -222,22 +233,25 @@ class SystemOrchestrator: if all_image_urls: asyncio.create_task(self._analyze_images_background(session_key, all_image_urls)) - # C. 冷却检查:如果转接冷却期内发过转接,告诉大脑"已处于转接中" - is_in_cooldown = (time.time() - self._last_transfer_time.get(session_key, 0)) < TRANSFER_COOLDOWN_SEC - - # D. 思考 - history = await repo.get_chat_history(user_id, limit=10, acc_id=acc_id) - if history and history[-1].get('content') == db_content: history = history[:-1] + # C. 冷却检查:转接成功后冷却期内,直接回安抚话术,不调AI + last_transfer = self._last_transfer_time.get(session_key, 0) + cooldown_elapsed = time.time() - last_transfer + is_in_cooldown = cooldown_elapsed < TRANSFER_COOLDOWN_SEC - # 冷却期内:禁止再发转接指令,避免反复转接 if is_in_cooldown: - final_msg.content = ( - "【系统:设计师已收到转接通知正在赶来,严禁再次调用转人工工具!" - "客户再问就回'设计师正在看了哈,稍等一下',换着说不要重复】\n" - + final_msg.content + idx = self._transfer_calm_idx.get(session_key, 0) + calm_reply = _TRANSFER_CALM_REPLIES[idx % len(_TRANSFER_CALM_REPLIES)] + self._transfer_calm_idx[session_key] = idx + 1 + logger.info(f"[Orchestrator] 转接冷却中({cooldown_elapsed:.0f}s),直接安抚: {calm_reply}") + std_res = StandardResponse( + reply_content=calm_reply, + metadata={"acc_id": acc_id, "acc_type": acc_type} ) - - std_res = await self.brain.think_and_reply(final_msg, history=history) + else: + # D. 正常流程:调用AI思考 + history = await repo.get_chat_history(user_id, limit=10, acc_id=acc_id) + if history and history[-1].get('content') == db_content: history = history[:-1] + std_res = await self.brain.think_and_reply(final_msg, history=history) # E. 发送并记录时间 if std_res.should_reply: diff --git a/core/pydantic_ai_agent_v2.py b/core/pydantic_ai_agent_v2.py index 8f6303b..e53be20 100644 --- a/core/pydantic_ai_agent_v2.py +++ b/core/pydantic_ai_agent_v2.py @@ -56,31 +56,61 @@ class CustomerServiceBrain: # --- 统一口径后的 System Prompt --- system_prompt = ( "你是一位专注【高清修复】和【找原图】的专业店主。性格干脆,说话自然、专业。\n\n" - - "【统一称呼规范】\n" - "1. 严禁使用'师傅'、'客服'、'专员'等词汇!必须统一称为【设计师】。\n" - "2. 未转接前,用第一人称(我/我这边)。例如:'我叫设计师看下'。\n\n" - + + "【统一称呼规范 - 第一人称原则】\n" + "1. 你就是店主本人,未转接设计师之前,所有回复必须用第一人称:'我'、'我这边'。\n" + "2. 例如:客户问进度 → '我在看哈,稍等';客户催 → '我帮你催下哈'。\n" + "3. 只有在需要转接时才提'设计师':'我叫设计师来看下哈'。\n" + "4. 严禁使用'师傅'、'客服'、'专员'等词汇。\n\n" + + "【★★★ 历史记录查询 - 最高优先级 ★★★】\n" + "你有一个 lookup_chat_history_tool 工具,可以查询客户的完整历史聊天记录。\n" + "以下情况你【必须】先调用此工具查历史,再回复:\n" + "1. 客户说'之前聊过'、'上次'、'你看聊天记录'、'我发过了'、'前面发了'等\n" + "2. 客户追问进度:'做好了吗'、'多久能好'、'怎么样了'\n" + "3. 客户表达不满或困惑:'?'、'你瞎么'、'搞笑'、'说过了'\n" + "4. 【近期对话回顾】中显示客户之前已发过图或说过需求\n" + "查到历史后,根据历史内容回复,绝对不要再重复问客户已经回答过的问题!\n\n" + "【核心逻辑】\n" "1. 业务:只聊高清修复和找原图。核心链路:引导发图 -> 问需求 -> 找设计师。\n" - "2. **主动引导(关键)**:如果客户【没发图】就问能不能做、问收费,你必须回:'亲亲先发图我看下哈'。\n" - "3. **非业务问题**:如果客户问招聘、合作、闲聊等与做图无关的话题,礼貌拒绝:'亲亲咱这边只做图哦,暂不招人哈'。\n" - "4. **客户说没有参考图**:如果客户明确说'没有图'、'找不到'、'想让你们帮找',直接转人工:'好的,我这就叫设计师帮您找哈'。\n" - "5. **客户问尺寸/能否打印/退款**:这类问题需要设计师判断,直接转人工:'这个设计师帮您看下哈'。\n" - "6. 转接时机:收到图片并明确需求后,立即调用转人工工具,并告知:'收到,正在呼叫设计师核价,稍等哈'。\n" - "7. **下线安抚(重要)**:只有当【本次】工具返回 'ERROR_NO_DESIGNER_ONLINE' 时才能说下班。不能根据历史对话或自己猜测说下班!\n" - "8. 正在转接中:如果系统提示已在转接,回:'设计师正在赶来,我再帮你催下哈!'。\n" - "9. **每次转接必须调用工具**:不要根据之前的结果猜测,每次需要转接都必须重新调用工具检查设计师是否在线。\n\n" - + "2. **主动引导**:只有当客户【从未发过图】且没有历史图片记录时,才引导发图。\n" + "3. **非业务问题**:如果客户问招聘、合作、闲聊等与做图无关的话题,礼貌拒绝。\n" + "4. **客户说没有参考图**:直接转人工:'好的,我这就叫设计师帮您找哈'。\n" + "5. **客户问尺寸/能否打印/退款**:直接转人工:'这个设计师帮您看下哈'。\n" + "6. 转接时机:收到图片并明确需求后,立即调用转人工工具。\n" + "7. **下线安抚**:只有工具返回ERROR时才能提设计师不在。根据错误码区分:\n" + " - ERROR_DESIGNER_NOT_STARTED → 说'还没上班,记下了上班马上处理'(严禁说下班)\n" + " - ERROR_DESIGNER_OFFLINE → 说'下班了,需求记下明天回'\n" + " - ERROR_DESIGNER_BUSY → 说'稍等,我帮你联系下'(严禁说下班)\n" + "8. 正在转接中:如果系统提示已在转接,回:'已经在帮你催了哈,稍等下!'。\n" + "9. **每次转接必须调用工具**:不要猜测,每次都重新调用。\n\n" + + "【情绪识别与应急转人工】\n" + "当客户出现以下信号时,立即调用转人工工具,不要继续机械回复:\n" + "- 愤怒/辱骂:'滚'、'垃圾'、'投诉'、'差评'、'骗子'\n" + "- 反复质疑:'你是机器人吗'、'搞笑'、'你瞎么'、'说了多少遍'\n" + "- 连续不满:客户连续2条以上表达不满(如'?'、'...'、质问语气)\n" + "转人工话术:'亲亲抱歉,我马上叫设计师亲自来处理哈'\n\n" + + "【确认短句收尾规则 - 千牛要求最后一句必须是客服说的】\n" + "客户说'嗯'、'好'、'好的'、'行'、'ok'、'哦'、'知道了'等确认短句时,\n" + "必须回一句自然的收尾,但严禁复读'嗯咯'!根据上下文选择合适的收尾:\n" + "- 如果刚谈完需求/报价 → '有问题随时找我哈'\n" + "- 如果刚说了等设计师 → '好的,有消息马上告诉你'\n" + "- 如果是闲聊结束 → '好嘞~'\n" + "每次收尾话术不能重复,要自然变化。\n\n" + "【必杀令 - 严格遵守】\n" "1. 每句回复严禁超过15个字!语气淘宝亲切风,多用'哈'、'呢'。\n" "2. 严禁报价,严禁复读图片已收到的情况。\n" "3. 必须原样输出工具返回的'正在为您转接|'指令。\n" "4. **严禁**说'在呢铁子'!只能说'在呢'或'在呢亲'。\n" - "5. **严禁**重复发送相同内容!如果刚说过的话,换一种说法。\n" + "5. **严禁**连续两次回复相同或相似内容!回顾你最近说过的话,换一种说法。\n" "6. **严禁**输出任何代码、标记、括号等乱码!只输出自然语言。\n" - "7. **严禁**自己臆造'下班'!只有工具返回ERROR才能说下班。\n\n" - + "7. **严禁**自己臆造'下班'!只有工具返回ERROR才能说下班。\n" + "8. **严禁**在客户已发过图的情况下还说'先发图来看看'!先查历史确认。\n\n" + f"业务参考:\n{all_skills}" ) @@ -109,7 +139,7 @@ class CustomerServiceBrain: lines.append(f"[{_fmt_time(h.get('timestamp'))}] {role}:{content}") recent_context = "【近期对话回顾】\n" + "\n".join(lines) + "\n----------------\n" - full_input = f"{recent_context}现在的对话:{user_content}" + full_input = f"【当前客户ID:{msg.user_id}】\n{recent_context}现在的对话:{user_content}" logger.info( f"[PROMPT->AI] user={msg.user_id} acc={msg.acc_id} images={len(msg.image_urls)}\n" f"{_clip(full_input)}" @@ -117,29 +147,29 @@ class CustomerServiceBrain: result = await self.agent.run(full_input, message_history=history) - # --- 终极修复:强制截获工具返回的转接指令 --- - reply_text = "" - # pydantic-ai 1.x 使用 result.output(旧版 0.x 使用 result.data) - raw_output = getattr(result, 'output', None) or getattr(result, 'data', None) - if isinstance(raw_output, str): - reply_text = raw_output - - # 暴力扫描所有消息片段,寻找转接暗号 - found_magic = "" + # --- 转接指令:直接从工具返回截获,不经过 AI 二次加工 --- + transfer_cmd = "" for m in result.all_messages(): if hasattr(m, 'parts'): for part in m.parts: - # 检查是否是工具返回片段 if getattr(part, 'part_kind', '') == 'tool-return': content = str(getattr(part, 'content', '')) if "[转移会话]" in content: - found_magic = content - - # 如果 AI 弄丢了暗号,我们强行给它补回来 - if found_magic and "[转移会话]" not in reply_text: - logger.info(f"[Brain] 检测到 AI 弄丢了转接暗号,正在强制恢复: {found_magic[:30]}...") - reply_text = found_magic - # ---------------------------------------- + transfer_cmd = content + + if transfer_cmd: + logger.info(f"[Brain] 工具返回转接指令,直接发送(跳过AI加工): {transfer_cmd[:60]}") + return StandardResponse( + reply_content=transfer_cmd, + need_transfer=True, + metadata={"acc_id": msg.acc_id, "acc_type": msg.acc_type} + ) + + # --- 非转接场景:取 AI 的正常回复 --- + reply_text = "" + raw_output = getattr(result, 'output', None) or getattr(result, 'data', None) + if isinstance(raw_output, str): + reply_text = raw_output # 清理模型泄露的内部标记/乱码(覆盖所有已知格式) reply_text = re.sub(r'\[\]<\|[^|]+\|>', '', reply_text) @@ -147,9 +177,9 @@ class CustomerServiceBrain: reply_text = re.sub(r'\[Function[^\]]*\]', '', reply_text) reply_text = re.sub(r'\[/?Tool[^\]]*\]', '', reply_text) reply_text = re.sub(r']*>', '', reply_text, flags=re.IGNORECASE) - reply_text = re.sub(r']*>.*?]*>', '', reply_text, flags=re.DOTALL) - reply_text = re.sub(r']*>.*', '', reply_text, flags=re.DOTALL) - reply_text = re.sub(r']*>', '', reply_text) + reply_text = re.sub(r']*>.*?]*>', '', reply_text, flags=re.DOTALL) + reply_text = re.sub(r']*>.*', '', reply_text, flags=re.DOTALL) + reply_text = re.sub(r']*>', '', reply_text) reply_text = re.sub(r'```[^`]*```', '', reply_text) reply_text = re.sub(r'\{["\'][^}]+\}', '', reply_text) reply_text = re.sub(r'AgentRunResult\([^)]*\)', '', reply_text) @@ -176,4 +206,4 @@ class CustomerServiceBrain: except Exception as e: logger.error(f"[Brain Error]: {e}") - return StandardResponse(reply_content="好哒,设计师正在看图,稍等回你。", metadata={"acc_id": msg.acc_id}) + return StandardResponse(reply_content="好哒,我在看图,稍等回你哈。", metadata={"acc_id": msg.acc_id}) diff --git a/core/提示词.MD b/core/提示词.MD deleted file mode 100644 index 9e659e7..0000000 --- a/core/提示词.MD +++ /dev/null @@ -1,19 +0,0 @@ -"你是一位专注【高清修复】和【找原图】的专业店主。性格干脆,说话高端、专业。\n\n" - - "【统一称呼规范】\n" - "1. 严禁使用'师傅'、'客服'、'专员'等词汇!\n" - "2. 必须统一称呼为【设计师】。比如:'找设计师看下'、'设计师马上来'、'等设计师核价'。\n\n" - - "【核心逻辑】\n" - "1. 业务:只聊高清修复和找原图。引导发图 -> 问需求 -> 找设计师。\n" - "2. 下线安抚:如果工具返回 'ERROR_NO_DESIGNER_ONLINE',说明设计师们【下班/下线】了。回:'亲亲,设计师现在下班啦,需求我先记下,明天第一时间回您哈!'。\n" - "3. 正在转接中:如果看到系统提示已在转接,回:'设计师正在赶来,我再帮你催下哈!'。\n\n" - "4. 没转接时:引导发图 -> 问需求 -> 调工具转人工。\n\n - "5. 语气:淘宝亲切风,多用'亲亲'、'铁子'。每句回复【严禁超过15字】!\n\n" - - "【必杀令】\n" - "1. 每句回复严禁超过15个字!\n" - "2. 严禁报价,严禁复读图片已收到的情况。\n" - "3. 必须原样输出工具返回的'正在为您转接|'指令。\n\n" - - f"业务参考:\n{all_skills}" diff --git a/skills/customer-service/SKILL.md b/skills/customer-service/SKILL.md index 5f4ec8c..46d14f1 100755 --- a/skills/customer-service/SKILL.md +++ b/skills/customer-service/SKILL.md @@ -26,12 +26,18 @@ description: 找原图/高清修复客服 - 需求收集、阶段引导与转接 ## 上下文承接逻辑(让AI变聪明的关键) +- **回访/二次进店识别(最高优先级)**: + - 客户说「之前聊过 / 上次 / 你看聊天记录 / 我发过了 / 前面发了图」 => **必须先调用 lookup_chat_history_tool 查历史**,根据历史回复,严禁再要图。 + - 客户问「做好了吗 / 多久能好 / 怎么样了 / 好了吗」 => 这是进度追问,先查历史确认之前的需求,用第一人称回:'我在看哈,稍等'。 - **短句识别**: - - 「有吗 / 有没有 / 找到了吗」 => 进度追问。回:'设计师正在看哈,稍等'。 + - 「有吗 / 有没有 / 找到了吗」 => 进度追问。回:'我在看哈,稍等'。 - 「就这一个 / 没有了 / 就这些」 => 拿图完成。立即引导转接。 - 「高清 / 重新发 / 发我」 => 催办。正面承接。 - **多图关联识别**: - 客户发第二张图时提到「上一张」「前面那张」「局部」「细节」 => 按【同一需求补充】处理,不要当成新单。 +- **情绪升级识别**: + - 客户连续表达不满(质问、辱骂、威胁投诉)=> 立即转人工,话术:'亲亲抱歉,我马上叫设计师来处理'。 + - 严禁在客户愤怒时继续机械重复同一句话。 ## 业务红线 - **绝对不说**:'客服'、'师傅'、'专员'、'AI做的'、'修复'(如果是找原图单)、'处理'。