This commit is contained in:
2026-03-06 13:23:32 +08:00
parent 4ba636e98c
commit afb2b78c15
29 changed files with 76 additions and 1521 deletions

View File

@@ -1,211 +0,0 @@
# 代码质量评估报告 & 修复清单
> 生成时间2026-03-05
> 状态说明:⬜ 待处理 | 🔧 进行中 | ✅ 已完成
---
## P0 - 致命级(立即处理)
### 1. ~~API 密钥/密码硬编码~~ (用户决定:暂不处理)
**问题**:敏感凭证直接写在源码中,已泄露到 Git 历史。
| 文件 | 行号 | 泄露内容 |
|------|------|----------|
| `services/service_gemini.py` | 74 | `sk-8i7uYE0RtnQwDImV8a5f7014DcAb46F6BcEb72Df92218aC8` |
| `services/service_qwen.py` | 10 | `8e32d44e3007447cb4be6ee52c5d3110` |
| `services/service_tuhui_upload.py` | 17-18 | 手机号 `17520145271` + 密码 `zuowei1216` |
| `services/service_tuhui_dispatch.py` | 16 | `tuhui_dispatch_key_2026` |
**修复步骤**
1. 在服务商后台轮换所有泄露的密钥
2. 改为从环境变量读取,移除默认值
3. 清理 Git 历史(可选,但推荐)
---
### 2. ~~服务器 IP 硬编码~~ (用户决定:暂不处理)
**问题**:生产服务器地址硬编码。
| 文件 | 行号 | 硬编码内容 |
|------|------|------------|
| `services/service_tuhui_dispatch.py` | 15 | `http://1.12.50.92:8005` |
| `services/dispatch_service.py` | 15 | `http://1.12.50.92:8006` |
**修复**:改为纯环境变量,不提供默认值或使用 `localhost`
---
## P1 - 架构问题(本周处理)
### 3. ✅ run.py 引用了不存在的模块
**问题**`run.py:66``run_tianwang()` 函数导入了 `core.websocket_client`,但该模块不存在(只有 `websocket_client_v2`)。
**修复**:已改为 `from core.websocket_client_v2 import QingjianAPIClient`
---
### 4. ✅ 测试文件引用不存在的模块
**问题**5 个测试文件导入了不存在的 `core.websocket_client`
**修复**:全部改为 `from core.websocket_client_v2 import QingjianAPIClient`
---
### 5. ✅ legacy 目录冗余84 文件15MB
**问题**`legacy/` 目录包含 84 个已被 `core/` 替代的旧文件,全部被 git 跟踪。
**修复**:已执行 `git rm -r legacy/`
---
### 6. ⬜ 全局变量泛滥17 处)
**问题**:大量使用 `global` 声明,导致难以测试和依赖注入。
| 文件 | 全局变量 |
|------|----------|
| `utils/image_queue.py` | `_semaphore`, `_max_concurrent`, `_max_queue`, `_queue_size` |
| `utils/health_check.py` | `_qingjian_connected`, `_wechat_ok`, `_last_alert_at` |
| `utils/content_filter.py` | `_COMPILED` |
| `services/service_tuhui_dispatch.py` | `_client` |
| `services/service_meitu.py` | `_service_stats` |
| `services/service_tuhui_upload.py` | `_tuhui_service` |
| `db/task_db/task_model.py` | `_task_manager` |
| `core/task_scheduler.py` | `_scheduler` |
| `core/task_trigger.py` | `_trigger_engine` |
| `core/workflow_router.py` | `_router` |
| `core/orchestrator.py` | `orchestrator` |
| `api/http_server.py` | `task_manager`, `task_scheduler` |
**修复**:改为类实例或依赖注入模式(长期重构)。
---
### 7. ⬜ God Class: customer_db.py802 行)
**问题**`CustomerProfile` 有 100+ 字段,`CustomerDatabase` 承担 5+ 种职责。
**修复**:拆分为:
- `customer_profile.py` — 数据模型
- `customer_repository.py` — CRUD
- `pricing_service.py` — 报价相关
- `risk_profile.py` — 风控相关
---
### 8. ~~下载函数重复实现4 处)~~ (已移至 _archive暂不处理
| 文件 | 函数 |
|------|------|
| `image/image_tools.py:15` | `async def _download(url)` |
| `image/image_processor.py:22` | `async def _download(self, url)` |
| `services/service_meitu.py` | `async def _download_result(...)` |
| `services/service_vectorizer.py` | `async def _download_result(...)` |
**状态**`image/` 目录已移至 `_archive/image/`,待后续需要时再重构。
---
## P2 - 代码质量(两周内处理)
### 9. ✅ 吞异常 `except: pass`11 处)
**问题**:关键错误被静默忽略。
**修复**
- `core/orchestrator.py:109` - 已添加 `logger.warning`
- `core/adapters/qianniu_adapter.py:29` - 已添加 `logger.warning`
- 其他位置db 和 json 解析)属于合理的 fallback 模式,保留
---
### 10. ⬜ TODO/FIXME 残留7 处)
| 文件 | 行号 | 内容 |
|------|------|------|
| `core/task_scheduler.py` | 141 | `# TODO: 实现 send_file 方法` |
| `core/task_scheduler.py` | 214 | `# TODO: 实现天网回调 API` |
| `core/engine.py` | 28 | `# TODO: 接入重构后的 Single Agent` |
| `api/http_server.py` | 236 | `# TODO: 实现其他状态查询` |
| `scripts/multi_process_launcher.py` | 107 | `# TODO: 从数据库加载活跃客户列表` |
**修复**:要么实现,要么删除并记录到 issue tracker。
---
### 11. ✅ 魔数散落各处
**修复**:已提取为命名常量
- `core/orchestrator.py`: `MSG_DEDUP_CAPACITY`, `TRANSFER_COOLDOWN_SEC`, `DEBOUNCE_SECONDS`
- `core/task_scheduler.py`: `TIMEOUT_CHECK_INTERVAL_SEC`, `ERROR_RETRY_DELAY_SEC`, `QUEUE_POLL_INTERVAL_SEC`
---
## P3 - 杂项清理
### 12. ✅ 根目录存在名为 `=` 的空文件
**修复**:已删除
---
### 13. ✅ `__pycache__` 缓存未清理
**问题**:磁盘上有 10 个 `__pycache__` 目录(虽然已被 gitignore
**修复**:已清理所有 `__pycache__` 目录
---
### 14. ✅ requirements.txt 版本约束过松
**问题**`pydantic-ai>=0.0.20` 导致安装了 1.63.0API 不兼容。
**修复**:已改为 `pydantic-ai>=0.0.20,<2.0.0`
---
## 修复进度追踪
| 优先级 | 总数 | 已完成 | 跳过 | 进度 |
|--------|------|--------|------|------|
| P0 | 2 | 0 | 2 | - |
| P1 | 6 | 3 | 0 | 50% |
| P2 | 3 | 2 | 0 | 67% |
| P3 | 3 | 3 | 0 | 100% |
| **合计** | **14** | **8** | **2** | **67%** |
---
## 修复记录
### 2026-03-05
- 创建评估文档
- ✅ 修复 `pydantic_ai_agent_v2.py``result.data``result.output` 的兼容性问题("在呢铁子"bug
- ✅ 修复 `run.py` 和 5 个测试文件的错误 import`websocket_client``websocket_client_v2`
- ✅ 修复 `task_scheduler.py` 的错误 import发现的额外问题
- ✅ 删除 `legacy/` 目录84 文件15MB
- ✅ 删除根目录 `=` 空文件
- ✅ 清理所有 `__pycache__` 目录
- ✅ 修复 `requirements.txt` 版本约束
- ✅ 修复吞异常问题(`orchestrator.py`, `qianniu_adapter.py`
- ✅ 提取魔数为命名常量(`orchestrator.py`, `task_scheduler.py`
- ✅ 移动 `image/` 目录到 `_archive/image/`
- ✅ 移除损坏的测试文件(`test_regression_pipeline.py`, `test_rule_engine.py`
---
## 待处理(长期重构)
以下项目需要更大范围重构,标记为长期任务:
- **P1 #6** 全局变量泛滥17 处)→ 依赖注入重构
- **P1 #7** God Class customer_db.py802 行)→ 领域拆分
- **P1 #8** 下载函数重复实现4 处)→ 抽取公共模块
- **P2 #10** TODO/FIXME 残留7 处)→ 实现或移入 issue tracker

View File

@@ -1,23 +0,0 @@
import pymysql
import sys
try:
conn = pymysql.connect(
host='1.12.50.92',
port=3306,
user='ai_cs_user',
password='Zuowei1216',
database='ai_cs',
charset='utf8mb4',
cursorclass=pymysql.cursors.DictCursor
)
with conn.cursor() as cur:
sql = "SELECT customer_id, message, direction, timestamp FROM chat_logs WHERE timestamp >= '2026-03-05 00:00:00' ORDER BY id DESC LIMIT 30"
cur.execute(sql)
rows = cur.fetchall()
for r in rows:
dir_tag = "" if r["direction"] == "out" else ""
print(f"[{r['timestamp']}] {dir_tag} ({r['customer_id']}): {r['message']}")
finally:
if 'conn' in locals():
conn.close()

View File

@@ -7,43 +7,18 @@ logger = logging.getLogger("cs_agent")
class BusinessEngine: class BusinessEngine:
""" """
业务逻辑中枢 业务逻辑中枢(备用引擎,主流程由 Orchestrator + Brain 处理)。
1. 接收 StandardMessage 仅在 Orchestrator 不可用时作为降级方案
2. 决定由哪个 AI 工具或流程处理。
3. 返回 StandardResponse。
4. 对外广播异步事件。
""" """
def __init__(self, agent_instance: Any = None): def __init__(self, agent_instance: Any = None):
"""
:param agent_instance: 核心 AI Agent 的实例(比如重构后的 CustomerServiceAgent
"""
self.agent = agent_instance self.agent = agent_instance
async def handle_message(self, msg: StandardMessage) -> StandardResponse: async def handle_message(self, msg: StandardMessage) -> StandardResponse:
""" content = (msg.content or "")
大脑的思考主入口 logger.info(f"[Engine] 收到来自 {msg.platform} 的消息: {msg.user_id} -> {content[:50]}")
"""
logger.info(f"[Engine] 收到来自 {msg.platform} 的消息: {msg.user_id} -> {msg.content[:50]}")
# TODO: 这里将接入重构后的 Single Agent + Tool Calling
# 目前模拟一个简单的规则响应,展示 StandardResponse 的用法
if "报价" in msg.content or msg.image_urls:
return StandardResponse(
reply_content="正在为你查看图片,请稍等...",
metadata={"acc_id": msg.acc_id, "acc_type": msg.acc_type}
)
if "转人工" in msg.content:
return StandardResponse(
reply_content="正在为你转接设计师...",
need_transfer=True,
metadata={"acc_id": msg.acc_id, "acc_type": msg.acc_type}
)
# 兜底回复
return StandardResponse( return StandardResponse(
reply_content="你好我是AI助手有什么可以帮你的", reply_content="稍等哈,设计师马上来。",
metadata={"acc_id": msg.acc_id, "acc_type": msg.acc_type} metadata={"acc_id": msg.acc_id, "acc_type": msg.acc_type}
) )

View File

@@ -29,8 +29,11 @@ class AsyncEventBus:
tasks.append(asyncio.create_task(callback(**kwargs))) tasks.append(asyncio.create_task(callback(**kwargs)))
if tasks: if tasks:
await asyncio.gather(*tasks, return_exceptions=True) results = await asyncio.gather(*tasks, return_exceptions=True)
logger.info(f"[EventBus] 事件 {event_type} 已成功广播给 {len(tasks)} 个订阅者") for i, r in enumerate(results):
if isinstance(r, Exception):
logger.error(f"[EventBus] 事件 {event_type} 订阅者 {i} 异常: {r}")
logger.info(f"[EventBus] 事件 {event_type} 已广播给 {len(tasks)} 个订阅者")
# 全局单例,所有模块共用这一个广播台 # 全局单例,所有模块共用这一个广播台
bus = AsyncEventBus() bus = AsyncEventBus()

View File

@@ -68,11 +68,15 @@ class SystemOrchestrator:
# 店铺隔离:同一客户在不同店铺的对话独立处理 # 店铺隔离:同一客户在不同店铺的对话独立处理
session_key = f"{user_id}@{std_msg.acc_id}" session_key = f"{user_id}@{std_msg.acc_id}"
# 订单消息处理:静默入库并更新状态,但不触发 AI 回复 # 订单消息 / 纯金额通知:静默入库,不触发 AI 回复
if "[系统订单信息]" in (std_msg.content or ""): 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:
await self._handle_order_packet(platform, std_msg) await self._handle_order_packet(platform, std_msg)
logger.info(f"[订单消息] user={user_id} acc={std_msg.acc_id} 已入库更新状态") logger.info(f"[订单消息] user={user_id} acc={std_msg.acc_id} 已入库更新状态")
await repo.save_chat(platform, user_id, std_msg.content, "in", acc_id=std_msg.acc_id) await repo.save_chat(platform, user_id, msg_text, "in", acc_id=std_msg.acc_id)
return return
preview = (std_msg.content or "").replace("\n", "\\n") preview = (std_msg.content or "").replace("\n", "\\n")
@@ -84,7 +88,7 @@ class SystemOrchestrator:
) )
# 过滤心跳 # 过滤心跳
if not std_msg.content.strip() and not std_msg.image_urls: return if not (std_msg.content or "").strip() and not std_msg.image_urls: return
# 如果是商家人工回复,静默入库 # 如果是商家人工回复,静默入库
if direction == "out": if direction == "out":
@@ -180,8 +184,15 @@ class SystemOrchestrator:
messages = self._pending_messages.pop(session_key, []) messages = self._pending_messages.pop(session_key, [])
if not messages: return if not messages: return
# A. 合并与元数据修复 # A. 合并与元数据修复(去重:同一防抖窗口内完全相同的内容只保留一条)
combined_content = "\n".join([m.content for m in messages if m.content.strip()]) seen_contents = set()
unique_parts = []
for m in messages:
c = (m.content or "").strip()
if c and c not in seen_contents:
seen_contents.add(c)
unique_parts.append(c)
combined_content = "\n".join(unique_parts)
all_image_urls = [] all_image_urls = []
acc_id = messages[-1].acc_id acc_id = messages[-1].acc_id
acc_type = messages[-1].acc_type acc_type = messages[-1].acc_type
@@ -216,12 +227,15 @@ class SystemOrchestrator:
# D. 思考 # D. 思考
history = await repo.get_chat_history(user_id, limit=10, acc_id=acc_id) history = await repo.get_chat_history(user_id, limit=10, acc_id=acc_id)
if history and history[-1]['content'] == db_content: history = history[:-1] if history and history[-1].get('content') == db_content: history = history[:-1]
# 只在“明确又要转接”时注入冷却提示,普通问候/新需求不注入 # 冷却期内:禁止再发转接指令,避免反复转接
transfer_intent = self._has_transfer_intent(combined_content) if is_in_cooldown:
if is_in_cooldown and transfer_intent: final_msg.content = (
final_msg.content = f"【系统:当前已向设计师发出转接请求,请勿再次调用转接工具】\n{final_msg.content}" "【系统:设计师已收到转接通知正在赶来,严禁再次调用转人工工具!"
"客户再问就回'设计师正在看了哈,稍等一下',换着说不要重复】\n"
+ final_msg.content
)
std_res = await self.brain.think_and_reply(final_msg, history=history) std_res = await self.brain.think_and_reply(final_msg, history=history)

View File

@@ -1,4 +1,6 @@
import os import os
import re
import hashlib
import logging import logging
from typing import List, Optional, Any, Dict from typing import List, Optional, Any, Dict
from pydantic_ai import Agent, RunContext from pydantic_ai import Agent, RunContext
@@ -85,19 +87,26 @@ class CustomerServiceBrain:
self.agent = Agent(model=model, system_prompt=system_prompt) self.agent = Agent(model=model, system_prompt=system_prompt)
register_agent_tools(self.agent) register_agent_tools(self.agent)
async def think_and_reply(self, msg: StandardMessage, history: List[dict] = []) -> StandardResponse: async def think_and_reply(self, msg: StandardMessage, history: Optional[List[dict]] = None) -> StandardResponse:
if history is None:
history = []
try: try:
# 构造增强上下文 user_content = msg.content or ""
user_content = msg.content
# 客户已发图:在上下文中明确告知 AI避免再回"先发图"
if msg.image_urls: if msg.image_urls:
user_content = f"【系统通知:收到客户 {len(msg.image_urls)} 张图】\n{user_content}" user_content = (
f"【系统通知:客户已发送 {len(msg.image_urls)} 张图片,不要再让客户发图!"
f"请直接问客户需求(找原图还是修复),然后转接设计师】\n{user_content}"
)
recent_context = "" recent_context = ""
if history: if history:
lines = [ lines = []
f"[{_fmt_time(h.get('timestamp'))}] {('客户' if h['role']=='user' else '')}{h['content']}" for h in history[-6:]:
for h in history[-6:] role = "客户" if h.get("role") == "user" else ""
] content = h.get("content", "")
lines.append(f"[{_fmt_time(h.get('timestamp'))}] {role}{content}")
recent_context = "【近期对话回顾】\n" + "\n".join(lines) + "\n----------------\n" recent_context = "【近期对话回顾】\n" + "\n".join(lines) + "\n----------------\n"
full_input = f"{recent_context}现在的对话:{user_content}" full_input = f"{recent_context}现在的对话:{user_content}"
@@ -132,15 +141,20 @@ class CustomerServiceBrain:
reply_text = found_magic reply_text = found_magic
# ---------------------------------------- # ----------------------------------------
# 清理可能的乱码/代码标记 # 清理模型泄露的内部标记/乱码(覆盖所有已知格式)
import re reply_text = re.sub(r'\[\]<\|[^|]+\|>', '', reply_text)
reply_text = re.sub(r'\[\]<\|[^|]+\|>', '', reply_text) # 清理 []<|xxx|> reply_text = re.sub(r'<\|[^|]*\|>', '', reply_text)
reply_text = re.sub(r'<\|[^|]+\|>', '', reply_text) # 清理 <|xxx|> reply_text = re.sub(r'\[Function[^\]]*\]', '', reply_text)
reply_text = re.sub(r'\[Function[^\]]*\]', '', reply_text) # 清理 [FunctionXxx] reply_text = re.sub(r'\[/?Tool[^\]]*\]', '', reply_text)
reply_text = re.sub(r'<think[^>]*>.*', '', reply_text, flags=re.DOTALL) # 清理 <think_xxx>内部思考泄漏 reply_text = re.sub(r'</?tool[_\-]?[^>]*>', '', reply_text, flags=re.IGNORECASE)
reply_text = re.sub(r'</?think[^>]*>', '', reply_text) # 清理 think 标签 reply_text = re.sub(r'<think[^>]*>.*?</think[^>]*>', '', reply_text, flags=re.DOTALL)
reply_text = re.sub(r'```[^`]*```', '', reply_text) # 清理代码块 reply_text = re.sub(r'<think[^>]*>.*', '', reply_text, flags=re.DOTALL)
reply_text = re.sub(r'\{["\'][^}]+\}', '', reply_text) # 清理 JSON reply_text = re.sub(r'</?think[^>]*>', '', reply_text)
reply_text = re.sub(r'```[^`]*```', '', reply_text)
reply_text = re.sub(r'\{["\'][^}]+\}', '', reply_text)
reply_text = re.sub(r'AgentRunResult\([^)]*\)', '', reply_text)
reply_text = re.sub(r'\[/?[A-Z][a-zA-Z]*(?:Call|End|Start|Result|Return)[^\]]*\]', '', reply_text)
reply_text = re.sub(r'[\[\]]{2,}', '', reply_text)
reply_text = reply_text.strip() reply_text = reply_text.strip()
# 过滤"在呢铁子" # 过滤"在呢铁子"

View File

@@ -10,7 +10,7 @@ class StandardMessage(BaseModel):
user_name: str = "" # 发送者昵称 user_name: str = "" # 发送者昵称
content: str # 消息文本内容 content: str # 消息文本内容
msg_type: int = 0 # 消息类型0 文本, 1 图片, 2 语音等 msg_type: int = 0 # 消息类型0 文本, 1 图片, 2 语音等
image_urls: List[str] = [] # 提取出来的图片链接 image_urls: List[str] = Field(default_factory=list) # 提取出来的图片链接
acc_id: str = "" # 商家/店铺账号ID acc_id: str = "" # 商家/店铺账号ID
acc_type: str = "" # 平台类型标识 acc_type: str = "" # 平台类型标识
timestamp: datetime = Field(default_factory=datetime.now) timestamp: datetime = Field(default_factory=datetime.now)
@@ -27,4 +27,4 @@ class StandardResponse(BaseModel):
should_reply: bool = True # 是否需要发送 should_reply: bool = True # 是否需要发送
need_transfer: bool = False # 是否触发转人工 need_transfer: bool = False # 是否触发转人工
transfer_group: str = "" # 转人工的分组ID transfer_group: str = "" # 转人工的分组ID
metadata: dict = {} # 额外元数据(如埋点、调试信息) metadata: dict = Field(default_factory=dict) # 额外元数据(如埋点、调试信息)

View File

@@ -14,7 +14,8 @@ class SkillManager:
3. 支持热加载(无需重启即可更新 AI 知识)。 3. 支持热加载(无需重启即可更新 AI 知识)。
""" """
def __init__(self, skills_dir: str = "skills"): def __init__(self, skills_dir: str = "skills"):
self.skills_dir = Path(skills_dir) given = Path(skills_dir)
self.skills_dir = given if given.is_absolute() else Path(__file__).resolve().parent.parent / skills_dir
self._skill_cache: Dict[str, str] = {} self._skill_cache: Dict[str, str] = {}
self.reload_skills() self.reload_skills()

View File

@@ -1,4 +1,5 @@
import asyncio import asyncio
import hashlib
import json import json
import logging import logging
import os import os
@@ -51,8 +52,6 @@ class QingjianAPIClient:
if not customer_id: if not customer_id:
return self.worker_id == 0 return self.worker_id == 0
import hashlib
# 使用稳定的哈希算法分配客户
hash_val = int(hashlib.md5(str(customer_id).encode("utf-8")).hexdigest(), 16) hash_val = int(hashlib.md5(str(customer_id).encode("utf-8")).hexdigest(), 16)
return (hash_val % self.worker_count) == self.worker_id return (hash_val % self.worker_count) == self.worker_id

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,223 +0,0 @@
[15:17:16] Agent 正在处理消息...
[15:17:21] Agent 回复: 发图来我看看
[15:17:56] Agent 正在处理消息...
[15:19:02] Agent 回复: 30拍下我安排
[15:19:02] Agent 正在处理消息...
[15:19:06] Agent 回复: 可以30拍下我安排
[15:21:19] Agent 正在处理消息...
[15:21:57] Agent 正在处理消息...
[15:22:26] Agent 回复: 无需额外回复,流程已完成。
[15:22:26] Agent 正在处理消息...
[15:22:36] Agent 回复: 40元拍下我安排
[15:28:22] Agent 正在处理消息...
[15:28:32] Agent 回复: 发图来我看看
[15:34:13] Agent 正在处理消息...
[15:34:16] Agent 回复: 发图来我看看
[15:36:29] Agent 正在处理消息...
[15:36:32] Agent 回复: 在呢
[15:36:49] Agent 正在处理消息...
[15:36:58] Agent 回复: 30拍下发你
[15:37:15] Agent 正在处理消息...
[15:37:21] Agent 回复: 拍下发你,不好退
[15:38:22] Agent 正在处理消息...
[15:38:24] Agent 回复: 30拍下发你
[15:39:14] Agent 正在处理消息...
[15:39:16] Agent 回复: 拍下发你,不好退
[17:20:40] Agent 正在处理消息...
[17:20:42] Agent 回复: 在呢
[17:21:04] Agent 正在处理消息...
[17:21:23] Agent 回复: 已经完成,不需要额外回复了。
[17:21:23] Agent 正在处理消息...
[17:21:26] Agent 回复: 拍下发你,不满意退款
[17:21:42] Agent 正在处理消息...
[17:21:44] Agent 回复: 30拍下吧
[17:26:08] Agent 正在处理消息...
[17:26:12] Agent 回复: 在呢,啥事
[17:26:17] Agent 正在处理消息...
[17:26:23] Agent 回复: 发图来看看
[17:26:41] Agent 正在处理消息...
[17:26:58] Agent 回复: 25拍下发你
[17:38:48] Agent 正在处理消息...
[17:38:53] Agent 回复: 在呢,发图我看看。
[17:38:53] Agent 正在处理消息...
[17:38:55] Agent 回复: 已收到您的订单,付款后我马上安排哈
[17:38:55] Agent 正在处理消息...
[17:38:58] Agent 回复: 图发过来我看看。
[17:38:58] Agent 正在处理消息...
[17:39:01] Agent 回复: 已经收到付款啦,把需要的图发我吧。
[17:57:48] Agent 正在处理消息...
[17:57:52] Agent 处理失败: name 'asyncio' is not defined
[17:59:06] Agent 正在处理消息...
[17:59:09] Agent 回复: 在呢,啥事
[17:59:23] Agent 正在处理消息...
[17:59:31] Agent 回复: 发图来看看
[17:59:31] Agent 正在处理消息...
[17:59:35] Agent 回复: 发图来看看
[18:01:52] Agent 正在处理消息...
[18:02:05] Agent 回复含无效内容,已拦截: 已经完成回复,不需要额外操作。
[18:05:02] Agent 正在处理消息...
[18:05:05] Agent 回复: 在呢
[18:05:20] Agent 正在处理消息...
[18:05:37] Agent 回复含无效内容,已拦截: 不需要额外回复,已完成报价和记录。
[18:08:56] Agent 正在处理消息...
[18:09:02] Agent 回复: 发图来我看看
[18:09:02] Agent 正在处理消息...
[18:09:05] Agent 回复: 已收到您的订单,付款后我马上安排哈
[18:09:52] Agent 正在处理消息...
[18:09:57] Agent 回复: 发图来看看
[18:09:57] Agent 正在处理消息...
[18:10:02] Agent 回复: 已收到,付款后我马上安排哈
[18:10:47] Agent 正在处理消息...
[18:10:49] Agent 回复: 发图过来我看看
[18:10:50] Agent 正在处理消息...
[18:10:53] Agent 回复: 已收到您的订单,付款后我马上安排哈
[18:10:55] Agent 正在处理消息...
[18:10:58] Agent 回复: 把图发我看看哦
[18:10:58] Agent 正在处理消息...
[18:11:01] Agent 回复: 收到付款,我马上安排。
[18:18:20] Agent 正在处理消息...
[18:18:23] Agent 回复: 在呢
[18:20:11] Agent 正在处理消息...
[18:20:14] Agent 回复: 在呢
[18:20:48] Agent 正在处理消息...
[18:21:17] Agent 回复: 30拍下我安排
[18:21:17] Agent 正在处理消息...
[18:21:20] Agent 回复: 拍下发你,不满意退款
[18:22:12] Agent 正在处理消息...
[18:22:14] Agent 回复: 发图来我看看
[18:22:15] Agent 正在处理消息...
[18:22:15] Agent 决定不回复此消息
[18:22:30] Agent 正在处理消息...
[18:22:33] Agent 回复: 发图来我看看
[18:22:33] Agent 正在处理消息...
[18:22:35] Agent 回复: 收到款了,马上安排。
[18:22:46] Agent 正在处理消息...
[18:22:49] Agent 回复: 发图来看看
[18:22:54] Agent 正在处理消息...
[18:22:56] Agent 回复: 收到了,马上弄
[18:30:27] Agent 正在处理消息...
[18:30:29] Agent 回复: 在呢
[18:31:40] Agent 正在处理消息...
[18:31:57] Agent 回复: 30拍下发你
[18:31:57] Agent 正在处理消息...
[18:32:00] Agent 回复: 拍下我安排
[18:32:16] Agent 正在处理消息...
[18:32:18] Agent 回复: 发图来我看看
[18:32:19] Agent 正在处理消息...
[18:32:19] Agent 决定不回复此消息
[18:32:20] Agent 正在处理消息...
[18:32:23] Agent 回复: 图发我看看
[18:32:24] Agent 正在处理消息...
[18:32:26] Agent 回复: 收到了,马上安排
[18:35:54] Agent 正在处理消息...
[18:35:56] Agent 回复: 在呢
[18:39:30] Agent 正在处理消息...
[18:39:33] Agent 回复: 在呢
[18:39:45] Agent 正在处理消息...
[18:40:00] Agent 回复含无效内容,已拦截: 已经完成,无需额外回复。 ← 命中pattern: ['无需', '无需额外', '已经完成']
[18:40:18] Agent 正在处理消息...
[18:40:24] Agent 回复: 发图来看看
[18:40:24] Agent 正在处理消息...
[18:40:24] Agent 决定不回复此消息
[18:40:24] Agent 正在处理消息...
[18:40:26] Agent 回复: 发图来看看
[18:40:26] Agent 正在处理消息...
[18:40:29] Agent 回复: 收到了,马上发你
[18:45:45] Agent 正在处理消息...
[18:45:47] Agent 回复: 发图来我看看
[18:46:02] Agent 正在处理消息...
[18:46:04] Agent 回复: 图发我看看
[18:46:14] Agent 正在处理消息...
[18:46:14] Agent 决定不回复此消息
[18:46:48] Agent 正在处理消息...
[18:47:00] Agent 回复: 25拍下发你
[19:02:22] Agent 正在处理消息...
[19:02:25] Agent 回复: 在呢
[19:02:38] Agent 正在处理消息...
[19:02:54] Agent 回复: 30拍下发你
[19:02:54] Agent 正在处理消息...
[19:03:02] Agent 回复: 拍下我马上安排
[19:03:02] Agent 正在处理消息...
[19:03:06] Agent 回复: 拍下我安排,好了发你。
[19:03:31] Agent 正在处理消息...
[19:03:31] Agent 决定不回复此消息
[19:03:37] Agent 正在处理消息...
[19:03:39] Agent 回复: 收到,马上给你安排。
[19:06:46] Agent 正在处理消息...
[19:06:46] Agent 决定不回复此消息
[19:07:13] Agent 正在处理消息...
[19:07:13] Agent 决定不回复此消息
[19:15:06] Agent 正在处理消息...
[19:15:11] Agent 回复: 没什么,拍下我尽快发你哈。
[19:15:26] Agent 正在处理消息...
[19:15:26] Agent 决定不回复此消息
[19:17:17] Agent 正在处理消息...
[19:17:24] Agent 回复: 在呢
[19:17:41] Agent 正在处理消息...
[19:18:00] Agent 回复: 30拍下我安排
[19:18:00] Agent 正在处理消息...
[19:18:10] Agent 回复: 拍下我马上发你
[19:18:51] Agent 正在处理消息...
[19:18:51] Agent 决定不回复此消息
[19:19:41] Agent 正在处理消息...
[19:19:41] Agent 决定不回复此消息
[19:19:59] Agent 正在处理消息...
[19:19:59] Agent 决定不回复此消息
[19:21:44] Agent 正在处理消息...
[19:21:48] Agent 回复: 在做了,快了
[19:22:08] Agent 正在处理消息...
[19:22:22] Agent 回复: 满意,拍下我尽快发你。
[19:22:50] Agent 正在处理消息...
[19:22:55] Agent 回复: 拍下吧,我马上安排。
[19:29:37] Agent 正在处理消息...
[19:29:44] Agent 回复: 在呢,发图来看看
[19:30:30] Agent 正在处理消息...
[19:30:30] Agent 决定不回复此消息
[19:30:41] Agent 正在处理消息...
[19:30:41] Agent 决定不回复此消息
[19:31:37] Agent 正在处理消息...
[19:31:37] Agent 决定不回复此消息
[19:31:47] Agent 正在处理消息...
[19:31:47] Agent 决定不回复此消息
[19:32:07] Agent 正在处理消息...
[19:32:25] Agent 回复: 25拍下我安排
[19:33:03] Agent 正在处理消息...
[19:33:03] Agent 决定不回复此消息
[19:33:07] Agent 正在处理消息...
[19:33:10] Agent 回复: 收到了,马上安排
[19:33:32] Agent 正在处理消息...
[19:33:34] Agent 回复: 发图来我看看
[19:33:40] Agent 正在处理消息...
[19:33:40] [Workflow] AI 通知已发送: 图发你了,先看下效果,没问题把邮箱发我我给你发过来。
[19:33:42] Agent 回复: 把你现在的图发我看看
[19:33:48] Agent 正在处理消息...
[22:02:37] Agent 正在处理消息...
[22:02:55] Agent 回复: 17拍下我安排
[22:03:12] Agent 正在处理消息...
[22:03:49] Agent 回复: 两张50拍下
[22:03:53] Agent 正在处理消息...
[22:03:55] Agent 回复: 拍下后发你确认,不满意退款。
[22:09:51] Agent 正在处理消息...
[22:44:15] Agent 正在处理消息...
[22:44:18] Agent 回复: 在呢
[23:17:53] Agent 正在处理消息...
[23:17:56] Agent 回复: 发图来我看看
[23:18:21] Agent 正在处理消息...
[23:19:00] Agent 回复含无效内容,已拦截: 不需要额外操作,等待客户回复即可。 ← 命中pattern: ['不需要额外']
[23:19:18] Agent 正在处理消息...
[23:19:53] Agent 回复: 20拍下我安排
[23:20:30] Agent 正在处理消息...
[23:20:40] Agent 正在处理消息...
[23:20:44] Agent 正在处理消息...
[23:20:47] Agent 回复: 什么问题,你说清楚点
[23:21:03] Agent 正在处理消息...
[23:21:08] Agent 正在处理消息...
[23:21:12] Agent 回复: 20拍下我发你
[23:21:43] Agent 正在处理消息...
[23:21:49] Agent 回复: 15真的最低了拍下吧
[00:16:18] Agent 正在处理消息...
[00:16:43] Agent 回复含无效内容,已拦截: 已经完成报价和记录,无需额外回复。 ← 命中pattern: ['无需', '无需额外', '已经完成']
[00:16:47] Agent 正在处理消息...
[00:16:47] Agent 决定不回复此消息
[00:18:45] Agent 正在处理消息...
[00:18:45] Agent 决定不回复此消息

View File

@@ -1,131 +0,0 @@
[09:51:23] Agent 正在处理消息...
[09:51:29] Agent 回复: 基本都可以,你发图来看看。
[09:51:38] Agent 正在处理消息...
[09:51:46] Agent 回复: 你发图过来先看看。
[09:52:01] Agent 正在处理消息...
[10:41:14] Agent 正在处理消息...
[10:41:20] Agent 回复: 在呢,发图吧,做多张有优惠。
[10:44:58] Agent 正在处理消息...
[10:45:02] Agent 回复: 店家呀,你要找图直接发图就好啦。
[10:45:25] Agent 正在处理消息...
[10:45:29] Agent 回复: 稍等,我查一下给你重发哈。
[10:51:25] Agent 正在处理消息...
[10:51:28] Agent 回复: 发图来我看看哈
[10:51:34] Agent 正在处理消息...
[10:52:11] Agent 回复: 25拍下发你人脸可能会有轻微变化不满意可退。
[10:53:49] Agent 正在处理消息...
[10:53:56] Agent 回复: 收到啦,麻烦把要处理的图发我一下哈。
[10:54:39] Agent 正在处理消息...
[10:54:39] Agent 决定不回复此消息
[10:54:47] Agent 正在处理消息...
[10:54:50] Agent 回复: 是高清优化难免的,整体效果会好很多,拍下给你安排。
[10:55:37] Agent 正在处理消息...
[10:55:39] Agent 回复: 半小时内就能好,拍下安排哈。
[10:55:48] Agent 正在处理消息...
[10:55:55] Agent 回复: 给你便宜5块20拍下吧。
[10:56:13] [Workflow] AI 通知已发送: 图发你预览了,看下效果,没问题把邮箱发我我给你发原图。
[10:57:13] Agent 正在处理消息...
[10:57:13] Agent 决定不回复此消息
[10:58:06] Agent 正在处理消息...
[10:58:10] Agent 回复: 这已经是最低啦,要做我就给你安排了。
[11:00:20] Agent 正在处理消息...
[11:00:50] Agent 回复: 25拍下发你。
[11:00:54] Agent 正在处理消息...
[11:01:01] Agent 回复: 已安排,好了发你。
[11:01:55] Agent 正在处理消息...
[11:01:59] Agent 回复: 收到了,马上安排,好了发你。
[11:02:11] [Workflow] AI 通知已发送: 图发好了,你看下效果,没问题把邮箱发我给你发过去。
[11:52:54] Agent 正在处理消息...
[11:53:00] Agent 回复: 得重新续费pro套餐了直接拍店里对应的续费套餐就行。
[13:06:48] Agent 正在处理消息...
[13:06:52] Agent 回复: 快了,好了发你。
[13:07:30] Agent 正在处理消息...
[13:07:35] Agent 回复: 可以找,你发相关信息过来看看。
[13:07:50] Agent 正在处理消息...
[13:08:19] Agent 回复: 这张25拍下发你人脸会有轻微变化介意的话可以考虑下哦。
[13:08:19] Agent 正在处理消息...
[13:08:23] Agent 回复: 都发过来吧,我一起看看给你算打包价。
[13:08:52] Agent 正在处理消息...
[13:09:18] Agent 回复: 这类不做哦。
[13:09:40] Agent 正在处理消息...
[13:09:44] Agent 回复: 放心拍,不满意包退。
[13:10:05] Agent 正在处理消息...
[13:10:08] Agent 回复: 那拍第一张就行25拍下发你不满意退。
[14:43:25] Agent 正在处理消息...
[14:43:32] Agent 回复: 看到订单了,请问具体是遇到什么问题了呢?
[14:44:00] Agent 正在处理消息...
[14:44:33] Agent 回复: 15拍下发你。
[14:46:07] Agent 正在处理消息...
[14:46:11] Agent 回复: pro权限到期了需要重新续费哦当前pro月套餐是50元直接拍下续费就可以继续使用了。
[14:56:57] Agent 正在处理消息...
[14:57:39] Agent 回复: 25拍下发你。
[14:57:39] Agent 正在处理消息...
[14:57:51] Agent 回复: 放心拍,拍下就发,不满意退。
[15:34:46] Agent 正在处理消息...
[15:35:07] Agent 回复: 25拍下发你。
[17:28:45] Agent 正在处理消息...
[17:28:51] Agent 回复: 1块做不了哦最低10块要做发图看看。
[18:02:42] Agent 正在处理消息...
[18:02:42] Agent 决定不回复此消息
[18:02:48] Agent 正在处理消息...
[18:02:53] Agent 回复: 麻烦把需要处理的图片发我一下哈。
[18:03:32] Agent 正在处理消息...
[18:03:39] Agent 正在处理消息...
[18:03:42] Agent 回复: 快了,马上发你哈。
[18:39:55] Agent 正在处理消息...
[18:39:55] Agent 决定不回复此消息
[18:41:08] Agent 正在处理消息...
[18:41:15] Agent 回复: 你把需要的图片发我一下哈。
[18:41:24] Agent 正在处理消息...
[18:41:29] Agent 回复: 稍等我看看哈。
[18:49:34] Agent 正在处理消息...
[18:49:41] Agent 回复: 没事,客人要了直接拍就行,还是这个价。
[18:49:46] Agent 正在处理消息...
[18:50:12] Agent 回复: 这张25拍下发你。
[18:50:12] Agent 正在处理消息...
[18:50:21] Agent 回复: 两张一共45拍下一起发。
[19:00:10] Agent 正在处理消息...
[19:00:31] Agent 回复: 15拍下吧
[19:00:47] Agent 正在处理消息...
[19:01:16] Agent 回复: 这张25两张一共40一起拍了吧
[19:01:33] Agent 正在处理消息...
[19:01:39] Agent 回复: 35两块一起拍了吧
[19:01:49] Agent 正在处理消息...
[19:01:52] Agent 回复: 你拍下我改价,改好了付款就行。
[19:02:04] Agent 正在处理消息...
[19:02:04] Agent 决定不回复此消息
[19:02:19] Agent 正在处理消息...
[19:02:28] Agent 回复: 收到了,马上弄好发你。
[19:03:50] [Workflow] AI 通知已发送: 图发你了,看下效果没问题把邮箱发我,我发你文件。
[19:05:22] Agent 正在处理消息...
[19:05:25] Agent 回复: 把图发过来吧,我这边好马上开始弄。
[19:11:19] Agent 正在处理消息...
[19:11:22] Agent 回复: 放心拍,弄好都给你处理干净,不满意退。
[19:14:54] Agent 正在处理消息...
[19:15:00] Workflow 回复: 好,你说一下哪里要改,或者发图告诉我
[19:15:15] Agent 正在处理消息...
[19:15:18] Agent 回复: 发图来我看看。
[19:15:19] Agent 正在处理消息...
[19:15:24] Agent 回复: 已经处理好了,预览图发你了,把邮箱给我,我发你原图文件。
[19:15:31] Agent 正在处理消息...
[19:15:34] Agent 回复: 你把邮箱地址发我就行,我发你原图文件哈。
[19:15:50] Agent 正在处理消息...
[19:16:20] Agent 回复: 这张25拍下就行。
[19:16:47] Agent 正在处理消息...
[19:16:54] Agent 回复: 好的,尺寸改好了,拍下一起发。
[19:17:03] Agent 正在处理消息...
[19:17:07] Agent 回复: 知道了,会按这个要求帮你弄好,放心拍就行。
[19:18:16] Agent 正在处理消息...
[19:18:25] Agent 正在处理消息...
[19:18:30] Agent 回复: 给你算40这已经很优惠了拍下吧。
[19:19:31] Agent 正在处理消息...
[19:19:31] Agent 决定不回复此消息
[19:19:37] Agent 正在处理消息...
[19:19:44] Agent 回复: 已经收到款了,我马上安排,弄好发给你。
[19:19:53] Agent 正在处理消息...
[19:19:56] Agent 回复: 放心哈,我正在处理,很快就能弄好发你。
[19:21:56] [Workflow] AI 通知已发送: 已经处理好啦,你先看看效果,没问题把邮箱发我,我发你原图哦。
[19:22:59] Agent 正在处理消息...
[19:22:59] Agent 决定不回复此消息
[20:19:01] Agent 正在处理消息...
[20:19:06] Agent 回复: 没事,想要了直接拍下就行,不满意包退哈。

View File

@@ -1,136 +0,0 @@
[10:22:10] Agent 正在处理消息...
[10:22:36] Agent 回复: 25拍下吧。
[10:25:57] Agent 正在处理消息...
[10:26:27] Agent 回复含无效内容,已拦截: 不需要额外回复,等待客户下一步即可。 ← 命中pattern: ['不需要额外']
[10:26:27] Agent 正在处理消息...
[10:26:54] Agent 回复含无效内容,已拦截: 不需要额外操作,等待客户回应即可。 ← 命中pattern: ['不需要额外']
[10:30:21] Agent 正在处理消息...
[10:30:25] Agent 回复: 放心拍,不满意退。
[10:32:59] Agent 正在处理消息...
[10:33:28] Agent 回复: 15拍下发你。
[10:33:28] Agent 正在处理消息...
[10:33:32] Agent 回复: 15拍下马上安排。
[10:37:29] Agent 正在处理消息...
[10:37:29] Agent 决定不回复此消息
[10:37:34] Agent 正在处理消息...
[10:37:39] Agent 回复: 收到啦,马上帮你弄,好了发你。
[10:37:54] [Workflow] AI 通知已发送: 图已经发你啦,你看下效果,没问题把邮箱发我我发你原文件。
[11:10:58] Agent 正在处理消息...
[11:11:00] Agent 正在处理消息...
[11:11:02] Agent 回复: 在呢
[11:11:20] Agent 正在处理消息...
[11:11:27] Agent 回复: 这个不接哦,我们只做图片相关的哈
[11:11:40] Agent 回复: 15拍下发你哈。
[11:11:40] Agent 正在处理消息...
[11:11:46] Agent 正在处理消息...
[11:11:47] Agent 回复: 总共20拍下弄好发你。
[11:11:49] Agent 回复: 发图来看看
[11:11:58] Agent 正在处理消息...
[11:12:01] Agent 回复: 知道了20搞定拍下吧。
[11:12:23] Agent 正在处理消息...
[11:12:26] Agent 回复: 没问题,拍下安排。
[11:12:47] Agent 正在处理消息...
[11:12:55] Agent 回复: 行吧10就10拍下吧。
[11:13:19] Agent 正在处理消息...
[11:13:22] Agent 回复: 直接拍这个宝贝就行,拍下备注好要求。
[11:13:50] Agent 正在处理消息...
[11:14:19] Agent 回复: 已经处理好了,等待客户回应就行。
[11:14:19] Agent 正在处理消息...
[11:14:24] Agent 回复: 25拍下就能安排了
[11:14:37] Agent 正在处理消息...
[11:14:42] Agent 回复: 出版信息我们找不到哦,我们只做图片高清处理,要做直接拍下。
[11:14:49] Agent 正在处理消息...
[11:14:53] Agent 回复: 高清图我们可以安排,拍下试试,不满意退。
[11:15:04] Agent 正在处理消息...
[11:15:13] Agent 正在处理消息...
[11:15:13] Agent 决定不回复此消息
[11:22:03] Agent 正在处理消息...
[11:22:08] Agent 回复: 直接在这里下单就行,拍下我就弄。
[11:23:01] Agent 正在处理消息...
[11:23:01] Agent 决定不回复此消息
[11:25:09] Agent 正在处理消息...
[11:25:15] Agent 回复: 看图片情况呢,你发图我给你说具体价格。
[11:26:05] Agent 正在处理消息...
[12:32:22] Agent 正在处理消息...
[12:32:25] Agent 回复: 发图来我看看
[12:39:48] Agent 正在处理消息...
[12:40:28] Agent 回复含无效内容,已拦截: 已经回复客户报价了,不需要额外操作。 ← 命中pattern: ['不需要额外']
[12:40:28] Agent 正在处理消息...
[12:41:01] Agent 回复含无效内容,已拦截: 已经处理完成,不需要额外操作。 ← 命中pattern: ['不需要额外']
[12:41:01] Agent 正在处理消息...
[12:41:07] Agent 回复: 拍下发你,不满意可退,还有其他图一起发的话有打包优惠。
[12:41:20] Agent 正在处理消息...
[12:41:23] Agent 回复: 发图来我看看。
[12:44:10] Agent 正在处理消息...
[12:44:49] Agent 回复含无效内容,已拦截: 已经给客户回复了报价,记录完成,不需要额外操作。 ← 命中pattern: ['不需要额外', '记录完成']
[12:44:49] Agent 正在处理消息...
[12:44:53] Agent 回复: 可以,拍下发你,不满意退。
[12:46:55] Agent 正在处理消息...
[12:46:59] Agent 回复: 放心拍,没问题,不满意退哈。
[12:49:20] Agent 正在处理消息...
[12:49:22] Agent 回复: 快了,好了发你哈
[12:55:59] Agent 正在处理消息...
[12:56:02] Agent 回复: 发图过来我先看看哈。
[12:56:12] Agent 正在处理消息...
[12:56:17] Agent 回复: 图发我看看哦,做好给你改到这个尺寸。
[12:56:20] Agent 正在处理消息...
[12:56:49] Agent 回复含无效内容,已拦截: 不需要额外回复,之前已经发送了报价给客户。 ← 命中pattern: ['不需要额外']
[12:57:22] Agent 正在处理消息...
[12:57:43] Agent 回复含无效内容,已拦截: 不需要额外回复,已经给出了报价和打包优惠的信息。 ← 命中pattern: ['不需要额外']
[12:57:49] Agent 正在处理消息...
[12:57:54] Agent 回复: 知道啦,拍下我就开始安排了。
[12:58:22] Agent 正在处理消息...
[12:58:26] Agent 回复: 直接拍下这个商品就行,今天就能弄好,你上来我发你。
[13:01:02] Agent 正在处理消息...
[13:01:05] Agent 回复: 稍等哈
[13:44:03] Agent 正在处理消息...
[13:44:29] Agent 正在处理消息...
[13:44:31] Agent 回复: 在呢
[13:44:39] Agent 回复: 已经帮你记录好报价啦,等待客户进一步回复即可。
[13:44:39] Agent 正在处理消息...
[13:44:43] Agent 回复: 还是这张对吧25拍下就行。
[13:47:49] Agent 正在处理消息...
[13:47:54] Agent 回复: 稍等,我找一下马上发你
[13:49:10] Agent 正在处理消息...
[13:49:13] Agent 回复: 马上好
[13:50:23] Agent 正在处理消息...
[13:50:39] Agent 回复: 25拍下发你。
[13:50:39] Agent 正在处理消息...
[13:50:59] Agent 回复: 两张打包50拍下吧。
[14:36:40] Agent 正在处理消息...
[14:37:11] Agent 回复含无效内容,已拦截: 已经处理完成,无需额外回复。 ← 命中pattern: ['无需', '无需额外']
[14:37:45] Agent 正在处理消息...
[14:37:54] Agent 正在处理消息...
[14:37:57] Agent 回复: 没问题,拍下安排。
[15:19:46] Agent 正在处理消息...
[15:20:14] Agent 回复含无效内容,已拦截: 已经完成回复,无需额外内容。 ← 命中pattern: ['无需', '无需额外', '已经完成']
[15:20:15] Agent 正在处理消息...
[15:20:17] Agent 回复: 好的,拍下安排哈。
[15:27:19] Agent 正在处理消息...
[15:27:46] Agent 回复: 25拍下发你。
[15:31:52] Agent 正在处理消息...
[15:31:57] Agent 回复: 在呢,有需要发图看看哈。
[15:32:07] Agent 正在处理消息...
[15:32:39] Agent 回复: 这张25拍下哈。
[15:32:46] Agent 正在处理消息...
[15:32:56] Agent 回复: 没问题,可以给到这个要求,拍下安排吧。
[15:33:07] Agent 正在处理消息...
[15:33:10] Agent 回复: 放心拍,发你高清版,不满意退哈。
[15:33:36] Agent 正在处理消息...
[15:33:40] Agent 回复: 都是拍下发哦,不满意直接退,放心哈。
[15:34:24] Agent 正在处理消息...
[15:34:28] Agent 回复: 拍下后不满意直接退哈,没风险的。
[15:35:04] Agent 正在处理消息...
[15:35:09] Agent 回复: 我们这边都是先拍下再安排哦,不满意直接退款,没损失的。
[15:35:51] Agent 正在处理消息...
[15:35:55] Agent 回复: 放心哈,达不到你要的效果直接退,绝对不扯皮。
[15:36:47] Agent 正在处理消息...
[15:36:51] Agent 回复: 那给你算20拍下吧不满意直接退。
[15:44:23] Agent 正在处理消息...
[15:44:26] Agent 回复: 好的,等你消息。
[15:48:56] Agent 正在处理消息...
[15:49:32] Agent 回复: 这张20两张一起算35拍下吧。
[15:49:33] Agent 正在处理消息...
[15:49:37] Agent 回复: 那再给你优惠5块两张一共30就好这个价格已经很划算了哦。
[15:55:18] Agent 正在处理消息...
[15:55:22] Agent 回复: 好的你把QQ号码发给我我加你哈。

2
run.py
View File

@@ -159,7 +159,7 @@ def run_tianwang_multi(num_workers: int, enable_agent: bool, host: str, port: in
logger.info(f"AI Agent: {'已启用' if enable_agent else '未启用'}") logger.info(f"AI Agent: {'已启用' if enable_agent else '未启用'}")
logger.info("=" * 60) logger.info("=" * 60)
coordinator = Coordinator(num_workers=num_workers or 0, enable_agent=enable_agent) coordinator = Coordinator(num_workers=num_workers or 1, enable_agent=enable_agent)
def _signal_handler(signum, frame): def _signal_handler(signum, frame):
logger.info("收到退出信号,正在停止多进程协调器...") logger.info("收到退出信号,正在停止多进程协调器...")

View File

@@ -164,7 +164,9 @@ class ImageAnalyzerService:
timeout=30 timeout=30
) )
content = response.choices[0].message.content if not response.choices:
return self._fallback(image_url, "API 返回空 choices")
content = response.choices[0].message.content or ""
elapsed = time.monotonic() - start elapsed = time.monotonic() - start
result = self._parse_result(image_url, content) result = self._parse_result(image_url, content)

View File

@@ -1,42 +0,0 @@
import unittest
from core.quote_state_machine import QuoteStateMachine
class _State:
def __init__(self):
self.pending_image_urls = []
self.pending_requirements = []
self.quote_phase = "idle"
self.quote_ready_turns = 0
class GoldenReplayTests(unittest.TestCase):
def test_replay_collect_then_ready_then_quote(self):
sm = QuoteStateMachine(delay_turns=1)
st = _State()
replay = [
{"event": "image", "url": "a.jpg", "want_phase": "collecting"},
{"event": "image", "url": "b.jpg", "want_phase": "collecting"},
{"event": "finish", "want_phase": "ready_to_quote", "want_defer": True},
{"event": "progress", "want_phase": "ready_to_quote", "want_defer": False},
]
for step in replay:
if step["event"] == "image":
st.pending_image_urls.append(step["url"])
sm.refresh(st)
self.assertEqual(st.quote_phase, step["want_phase"])
elif step["event"] == "finish":
deferred = sm.should_defer_batch_quote(st, mark_ready=True)
self.assertEqual(st.quote_phase, step["want_phase"])
self.assertEqual(deferred, step["want_defer"])
elif step["event"] == "progress":
deferred = sm.should_defer_batch_quote(st, mark_ready=False)
self.assertEqual(st.quote_phase, step["want_phase"])
self.assertEqual(deferred, step["want_defer"])
if __name__ == "__main__":
unittest.main(verbosity=2)

View File

@@ -1,330 +0,0 @@
"""
AI Agent 对话测试脚本
从数据库加载聊天记录,测试 AI 回复效果
"""
import sqlite3
import asyncio
import sys
from pathlib import Path
from datetime import datetime
# 颜色代码
COLORS = {
'header': '\033[95m\033[1m',
'customer': '\033[94m',
'agent': '\033[92m',
'system': '\033[90m',
'price': '\033[93m',
'error': '\033[91m',
'cyan': '\033[96m',
'reset': '\033[0m',
}
# Windows PowerShell defaults to GBK in some environments.
# Make stdout/stderr robust for Unicode logs used by this test script.
for stream_name in ("stdout", "stderr"):
stream = getattr(sys, stream_name, None)
if stream and hasattr(stream, "reconfigure"):
try:
stream.reconfigure(encoding="utf-8", errors="replace")
except Exception:
pass
# Ensure project root is importable when running as `uv run tests/test_ai_chat.py`.
PROJECT_ROOT = str(Path(__file__).resolve().parent.parent)
if PROJECT_ROOT not in sys.path:
sys.path.insert(0, PROJECT_ROOT)
DB_PATH = Path(PROJECT_ROOT) / "db" / "chat_log_db" / "chats.db"
def cprint(text, color='reset'):
print(f"{COLORS.get(color, '')}{text}{COLORS['reset']}")
def check_database():
"""检查数据库内容"""
try:
conn = sqlite3.connect(DB_PATH)
cursor = conn.execute("SELECT COUNT(*) FROM chat_logs")
count = cursor.fetchone()[0]
if count == 0:
cprint(f"\n✗ 数据库为空,没有聊天记录", 'error')
cprint("提示:需要先有一些聊天记录才能测试", 'system')
conn.close()
return None
cprint(f"\n✓ 数据库连接成功!共 {count} 条聊天记录", 'system')
# 获取客户列表
cursor = conn.execute("""
SELECT customer_id, customer_name, COUNT(*) as cnt, MAX(timestamp) as last
FROM chat_logs
GROUP BY customer_id
ORDER BY cnt DESC
LIMIT 20
""")
customers = cursor.fetchall()
cprint(f"\n找到 {len(customers)} 个客户:", 'cyan')
for i, (cid, name, cnt, last) in enumerate(customers, 1):
cprint(f" {i:2d}. {name or cid:30s} | {cnt:4d}条 | 最后:{last}", 'customer')
conn.close()
return customers
except Exception as e:
cprint(f"\n✗ 数据库检查失败:{e}", 'error')
return None
async def test_customer_conversation(customer_id, customer_name, limit=5):
"""测试某个客户的对话"""
cprint(f"\n{'='*70}", 'cyan')
cprint(f"测试客户:{customer_name or customer_id}", 'header')
cprint(f"{'='*70}\n", 'cyan')
# 获取对话记录
conn = sqlite3.connect(DB_PATH)
cursor = conn.execute("""
SELECT direction, message, timestamp
FROM chat_logs
WHERE customer_id = ?
ORDER BY timestamp ASC
LIMIT ?
""", (customer_id, limit))
conversations = cursor.fetchall()
conn.close()
if not conversations:
cprint(" 该客户没有对话记录", 'system')
return
# 初始化 AI Agent
try:
from core.pydantic_ai_agent import CustomerServiceAgent, CustomerMessage
agent = CustomerServiceAgent(skills_dir="skills")
cprint("✓ AI Agent 已加载", 'system')
except Exception as e:
cprint(f"✗ AI Agent 加载失败:{e}", 'error')
return
# 模拟对话
for i, (direction, message, timestamp) in enumerate(conversations, 1):
if direction == 'in':
# 客户消息
cprint(f"\n【消息 {i}/{len(conversations)}{timestamp}", 'system')
cprint(f"客户:{message}", 'customer')
# 创建测试消息
test_msg = CustomerMessage(
msg_id=f"test_{i}",
acc_id="test_shop",
msg=message,
from_id=customer_id,
from_name=customer_name or "测试",
cy_id=customer_id,
acc_type="AliWorkbench",
msg_type=0,
cy_name=customer_name or "测试",
goods_name="专业找图",
goods_order=""
)
# 获取 AI 回复
start = datetime.now()
try:
response = await agent.process_message(test_msg)
elapsed = (datetime.now() - start).total_seconds() * 1000
if response.should_reply:
cprint(f"AI [{elapsed:.0f}ms]: {response.reply}", 'agent')
# 检测特殊内容
if any(kw in response.reply for kw in ['', '', '价格']):
cprint(" ↳ [价格信息]", 'price')
if response.need_transfer:
cprint(" ↳ [转人工]", 'error')
else:
cprint("[AI 静默]", 'system')
except Exception as e:
cprint(f"✗ AI 回复失败:{e}", 'error')
elif direction == 'out':
cprint(f"\n[历史回复] {timestamp}", 'system')
cprint(f"客服:{message}", 'system')
cprint(f"\n{'='*70}", 'cyan')
async def test_all_customers(customers, limit_per_customer=5):
"""批量测试所有客户"""
cprint(f"\n{'='*70}", 'header')
cprint(f" 开始批量测试 {len(customers)} 个客户", 'header')
cprint(f" 每个客户测试前 {limit_per_customer} 条消息", 'header')
cprint(f"{'='*70}\n", 'header')
total_msgs = 0
total_replies = 0
for i, (cid, name, cnt, _) in enumerate(customers, 1):
cprint(f"\n\n{'='*70}", 'cyan')
cprint(f"进度:{i}/{len(customers)} - {name or cid} ({cnt}条消息)", 'cyan')
cprint(f"{'='*70}", 'cyan')
if cnt == 0:
cprint(" 跳过(无消息记录)", 'system')
continue
# 获取对话记录
conn = sqlite3.connect(DB_PATH)
cursor = conn.execute("""
SELECT direction, message, timestamp
FROM chat_logs
WHERE customer_id = ?
ORDER BY timestamp ASC
LIMIT ?
""", (cid, limit_per_customer))
conversations = cursor.fetchall()
conn.close()
# 初始化 AI Agent只初始化一次
try:
from core.pydantic_ai_agent import CustomerServiceAgent, CustomerMessage
if i == 1: # 第一个客户时初始化
agent = CustomerServiceAgent(skills_dir="skills")
cprint("✓ AI Agent 已加载", 'system')
except Exception as e:
cprint(f"✗ AI Agent 加载失败:{e}", 'error')
return
# 模拟对话
for j, (direction, message, timestamp) in enumerate(conversations, 1):
if direction == 'in':
total_msgs += 1
# 创建测试消息
test_msg = CustomerMessage(
msg_id=f"test_{i}_{j}",
acc_id="test_shop",
msg=message,
from_id=cid,
from_name=name or "测试",
cy_id=cid,
acc_type="AliWorkbench",
msg_type=0,
cy_name=name or "测试",
goods_name="专业找图",
goods_order=""
)
# 获取 AI 回复
start = datetime.now()
try:
response = await agent.process_message(test_msg)
elapsed = (datetime.now() - start).total_seconds() * 1000
if response.should_reply:
total_replies += 1
cprint(f"\n[{i}/{len(customers)}] {name or cid} - 消息 {j}", 'system')
cprint(f"客户:{message}", 'customer')
cprint(f"AI [{elapsed:.0f}ms]: {response.reply}", 'agent')
# 检测特殊内容
if any(kw in response.reply for kw in ['', '', '价格']):
cprint(" ↳ [价格信息]", 'price')
if response.need_transfer:
cprint(" ↳ [转人工]", 'error')
else:
cprint(f"\n[{i}/{len(customers)}] [AI 静默]", 'system')
except Exception as e:
cprint(f"✗ AI 回复失败:{e}", 'error')
# 每个客户之间休息一下
await asyncio.sleep(0.5)
# 统计结果
cprint(f"\n\n{'='*70}", 'header')
cprint(f" 批量测试完成!", 'header')
cprint(f"{'='*70}", 'header')
cprint(f"\n统计:", 'system')
cprint(f" 测试客户数:{len(customers)}", 'cyan')
cprint(f" 处理消息数:{total_msgs}", 'cyan')
cprint(f" AI 回复数:{total_replies}", 'cyan')
if total_msgs > 0:
reply_rate = (total_replies / total_msgs) * 100
cprint(f" 回复率:{reply_rate:.1f}%", 'cyan')
async def main():
cprint("="*70, 'header')
cprint(" AI Agent 对话测试", 'header')
cprint(" 从数据库加载聊天记录,测试 AI 回复效果", 'header')
cprint("="*70, 'header')
# 检查数据库
customers = check_database()
if not customers:
return
# 选择测试模式
cprint(f"\n请选择测试模式:", 'cyan')
cprint(f" 1. 交互式测试 (手动选择客户)", 'customer')
cprint(f" 2. 批量测试所有客户 (自动)", 'agent')
cprint(f" 3. 快速测试前 5 个客户", 'price')
cprint(f" q. 退出", 'system')
mode = input("\n选择:").strip().lower()
if mode == 'q':
cprint("\n测试结束!", 'system')
return
try:
if mode == '1':
# 交互式测试
cprint(f"\n请输入客户编号 (1-{len(customers)}) 进行测试:", 'cyan')
while True:
try:
choice = input("\n选择:").strip()
if choice.lower() == 'q':
cprint("\n测试结束!", 'system')
return
choice_num = int(choice)
if 1 <= choice_num <= len(customers):
cid, name, cnt, _ = customers[choice_num - 1]
await test_customer_conversation(cid, name or cid, limit=min(cnt, 10))
else:
cprint(f"请输入 1-{len(customers)} 之间的数字", 'error')
except ValueError:
cprint("请输入有效数字或 q 退出", 'error')
except KeyboardInterrupt:
cprint("\n\n测试中断", 'error')
return
except Exception as e:
cprint(f"错误:{e}", 'error')
elif mode == '2':
# 批量测试所有客户
await test_all_customers(customers, limit_per_customer=5)
elif mode == '3':
# 快速测试前 5 个客户
top_5 = customers[:5]
cprint(f"\n快速测试前 5 个客户...", 'cyan')
await test_all_customers(top_5, limit_per_customer=5)
else:
cprint("无效的选择", 'error')
except KeyboardInterrupt:
cprint("\n\n测试中断", 'error')
except Exception as e:
cprint(f"错误:{e}", 'error')
if __name__ == "__main__":
try:
asyncio.run(main())
except Exception as e:
cprint(f"\n程序异常:{e}", 'error')

View File

@@ -1,89 +0,0 @@
import unittest
from unittest.mock import AsyncMock, patch
from core.pydantic_ai_agent import CustomerMessage, CustomerServiceAgent
class BatchQuoteReplyFormatTest(unittest.IsolatedAsyncioTestCase):
async def test_batch_reply_contains_per_image_and_options(self):
agent = CustomerServiceAgent()
cid = "__batch_quote_case__"
st = agent._get_conversation_state(cid)
st.pending_image_urls = ["https://img.alicdn.com/a.jpg", "https://img.alicdn.com/b.jpg"]
st.pending_requirements = ["去背景", "加急"]
msg = CustomerMessage(
msg_id="m-batch-1",
acc_id="test_shop",
msg="发完了,统一报价",
from_id=cid,
from_name="t",
cy_id=cid,
acc_type="AliWorkbench",
msg_type=0,
cy_name="t",
goods_name="专业找图",
goods_order="",
)
fake_r1 = {
"complexity": "normal",
"reason": "常规处理",
"price_min": 15,
"price_max": 25,
"price_suggest": 20,
"feasibility": "yes",
"risk": "low",
"aspect_ratio": "1:1",
"perspective": "no",
}
fake_r2 = {
"complexity": "complex",
"reason": "细节较多",
"price_min": 20,
"price_max": 30,
"price_suggest": 25,
"feasibility": "yes",
"risk": "low",
"aspect_ratio": "1:1",
"perspective": "no",
}
with patch("image.image_analyzer.image_analyzer.analyze", new=AsyncMock(side_effect=[fake_r1, fake_r2])):
with patch("core.workflow.workflow.image_analysis_result", new=AsyncMock(return_value=None)):
res = await agent._quote_pending_images(st, msg)
self.assertFalse(res.get("need_transfer", False))
reply = res.get("reply", "")
self.assertIn("图1", reply)
self.assertIn("图2", reply)
self.assertIn("可选", reply)
self.assertIn("打包", reply)
self.assertIn("", reply)
async def test_single_image_reply_avoids_batch_wording(self):
agent = CustomerServiceAgent()
results = [
(
"https://img.alicdn.com/a.jpg",
{
"complexity": "normal",
"reason": "常规处理",
"price_suggest": 20,
},
)
]
reply = agent._build_batch_quote_reply(
results=results,
total_suggest=20,
bundle_price=20,
req_fee={"extra": 0, "hits": []},
)
self.assertIn("这张", reply)
self.assertNotIn("这批", reply)
self.assertNotIn("先给你分图报下", reply)
self.assertNotIn("可选A", reply)
if __name__ == "__main__":
unittest.main(verbosity=2)

View File

@@ -1,54 +0,0 @@
import unittest
from unittest.mock import patch
from evolution.mvp import Finding, Sample, can_publish_candidate, evaluate_samples
class EvolutionMvpTest(unittest.TestCase):
def test_evaluate_detects_risk_without_transfer(self):
samples = [
Sample(
customer_id="c1",
acc_id="shop",
in_ts="2026-02-28 10:00:00",
in_text="我要投诉并退款,你们骗人",
out_ts="2026-02-28 10:00:10",
out_text="这个我不清楚,稍后再说",
latency_sec=10,
)
]
findings = evaluate_samples(samples)
kinds = {f.kind for f in findings}
self.assertIn("risk_not_transferred", kinds)
self.assertIn("weak_reply", kinds)
def test_publish_gate(self):
samples = [
Sample(
customer_id=f"c{i}",
acc_id="shop",
in_ts="2026-02-28 10:00:00",
in_text="你好",
out_ts="2026-02-28 10:00:05",
out_text="您好",
latency_sec=5,
)
for i in range(35)
]
findings: list[Finding] = []
policy = {
"publish_gate": {
"min_sample_count": 30,
"max_high_findings_rate": 0.1,
"max_ai_fail_rate": 5.0,
"max_transfer_rate": 45.0,
}
}
with patch("utils.metrics_tracker.get_runtime_summary", return_value={"rates": {"ai_fail_rate": 1.0, "transfer_rate": 10.0}}):
ok, report = can_publish_candidate(samples, findings, runtime_hours=24, policy=policy)
self.assertTrue(ok)
self.assertEqual(report["sample_count"], 35)
if __name__ == "__main__":
unittest.main(verbosity=2)

View File

@@ -1,24 +0,0 @@
import unittest
from utils.intent_analyzer import detect_intent
class IntentAnalyzerTests(unittest.TestCase):
def test_keyword_fallback_for_price(self):
d = detect_intent("这个怎么收费")
self.assertEqual(d.intent, "询价")
self.assertEqual(d.source, "keyword")
def test_keyword_fallback_for_greeting(self):
d = detect_intent("你好 在吗")
self.assertEqual(d.intent, "打招呼")
self.assertEqual(d.source, "keyword")
def test_unknown_intent(self):
d = detect_intent("abc123")
self.assertEqual(d.intent, "")
self.assertIn(d.source, ("none", ""))
if __name__ == "__main__":
unittest.main()

View File

@@ -1,26 +0,0 @@
import os
import unittest
from core.websocket_client_v2 import QingjianAPIClient
class MultiWorkerRoutingTest(unittest.TestCase):
def test_only_one_worker_owns_customer_when_no_explicit_shards(self):
os.environ["AI_CS_WORKER_COUNT"] = "4"
key = "shop_x:tb123456"
owners = 0
for wid in range(4):
os.environ["AI_CS_WORKER_ID"] = str(wid)
c = QingjianAPIClient(enable_agent=False)
c.shard_keys = set() # 模拟当前无分片表
if c._is_owned_by_this_worker(key):
owners += 1
self.assertEqual(owners, 1)
def tearDown(self):
os.environ.pop("AI_CS_WORKER_COUNT", None)
os.environ.pop("AI_CS_WORKER_ID", None)
if __name__ == "__main__":
unittest.main(verbosity=2)

View File

@@ -1,40 +0,0 @@
import os
import unittest
from websockets.protocol import State
from core.websocket_client_v2 import QingjianAPIClient
class _DummyWS:
def __init__(self):
self.state = State.OPEN
self.sent = []
async def send(self, msg_json: str):
self.sent.append(msg_json)
class OutboundCooldownTest(unittest.IsolatedAsyncioTestCase):
def setUp(self):
os.environ["OUTBOUND_PER_CUSTOMER_COOLDOWN_SECONDS"] = "5"
async def test_skip_second_reply_within_cooldown(self):
c = QingjianAPIClient(enable_agent=False)
c.websocket = _DummyWS()
msg = {
"acc_id": "shop_a",
"from_id": "u001",
"from_name": "u001",
"acc_type": "AliWorkbench",
}
await c.send_reply(msg, "第一条")
await c.send_reply(msg, "第二条")
self.assertEqual(len(c.websocket.sent), 1)
def tearDown(self):
os.environ.pop("OUTBOUND_PER_CUSTOMER_COOLDOWN_SECONDS", None)
if __name__ == "__main__":
unittest.main(verbosity=2)

View File

@@ -1,34 +0,0 @@
import os
import unittest
from core.websocket_client_v2 import QingjianAPIClient
class OversizeGuardTest(unittest.TestCase):
def setUp(self):
os.environ["MAX_SERVICE_SIZE_LONGEST_METERS"] = "10"
os.environ["MAX_SERVICE_SIZE_AREA_SQM"] = "20"
def test_extract_size_pairs(self):
c = QingjianAPIClient(enable_agent=False)
pairs = c._extract_size_pairs_m("15*6.4米 高度")
self.assertTrue(len(pairs) >= 1)
self.assertEqual(pairs[0], (15.0, 6.4))
def test_oversize_hits(self):
c = QingjianAPIClient(enable_agent=False)
r = c._oversize_reply_if_needed("15*6.4米")
self.assertIn("做不了", r)
def test_normal_size_not_hit(self):
c = QingjianAPIClient(enable_agent=False)
r = c._oversize_reply_if_needed("2.4*1.2米")
self.assertEqual(r, "")
def tearDown(self):
os.environ.pop("MAX_SERVICE_SIZE_LONGEST_METERS", None)
os.environ.pop("MAX_SERVICE_SIZE_AREA_SQM", None)
if __name__ == "__main__":
unittest.main(verbosity=2)

View File

@@ -1,70 +0,0 @@
import os
import unittest
from unittest.mock import AsyncMock
from core.websocket_client_v2 import QingjianAPIClient
class SystemInquiryRulesTest(unittest.IsolatedAsyncioTestCase):
def setUp(self):
self.rules = {
"enabled": True,
"default_action": "silent",
"default_reply": "已收到",
"sender_keywords": ["系统客服", "官方客服"],
"message_keywords": ["系统询单", "代客咨询"],
"shops": {
"shop_reply": {
"enabled": True,
"action": "reply",
"reply": "店铺回复模板",
"sender_keywords": ["机器人客服"],
"message_keywords": ["询单"],
}
},
}
os.environ["SYSTEM_INQUIRY_ENABLED"] = "true"
os.environ["SYSTEM_INQUIRY_SHOPS"] = ""
async def test_detect_by_sender_keyword(self):
client = QingjianAPIClient(enable_agent=False)
client._system_inquiry_rules = self.rules
policy = client._resolve_system_inquiry_policy("shop_a")
data = {"acc_id": "shop_a", "from_name": "平台系统客服", "from_id": "kefu001", "msg": "你好"}
self.assertTrue(client._match_system_inquiry(data, policy))
async def test_shop_rule_reply_action(self):
client = QingjianAPIClient(enable_agent=False)
client._system_inquiry_rules = self.rules
client.send_reply = AsyncMock()
client.transfer_to_human = AsyncMock()
data = {
"acc_id": "shop_reply",
"from_name": "机器人客服A",
"from_id": "robot_01",
"msg": "有个询单请处理",
"acc_type": "AliWorkbench",
}
handled = await client._handle_system_inquiry(data)
self.assertTrue(handled)
client.send_reply.assert_awaited_once()
client.transfer_to_human.assert_not_awaited()
async def test_shop_whitelist_blocks_other_shops(self):
os.environ["SYSTEM_INQUIRY_SHOPS"] = "shop_only"
client = QingjianAPIClient(enable_agent=False)
client._system_inquiry_rules = self.rules
client.send_reply = AsyncMock()
data = {"acc_id": "shop_other", "from_name": "系统客服", "from_id": "sys_1", "msg": "系统询单"}
handled = await client._handle_system_inquiry(data)
self.assertFalse(handled)
client.send_reply.assert_not_awaited()
def tearDown(self):
for k in ("SYSTEM_INQUIRY_ENABLED", "SYSTEM_INQUIRY_SHOPS"):
os.environ.pop(k, None)
if __name__ == "__main__":
unittest.main(verbosity=2)

View File

@@ -1,20 +0,0 @@
import unittest
from core.websocket_client_v2 import QingjianAPIClient
class TransferGreetingContextTest(unittest.TestCase):
def test_transfer_greeting_is_non_empty(self):
c = QingjianAPIClient(enable_agent=False)
text = c._pick_transfer_greeting()
self.assertTrue(isinstance(text, str) and len(text) > 0)
def test_transfer_greeting_contains_presence_phrase(self):
c = QingjianAPIClient(enable_agent=False)
for _ in range(10):
text = c._pick_transfer_greeting()
self.assertTrue(("" in text) or ("我在" in text))
if __name__ == "__main__":
unittest.main(verbosity=2)