init
This commit is contained in:
12
archive/README.md
Normal file
12
archive/README.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# 归档文件
|
||||
|
||||
此目录存放暂未使用或已替代的旧文件,主流程不依赖。
|
||||
|
||||
| 文件 | 说明 |
|
||||
|------|------|
|
||||
| service_meitu.py | 美图服务(未接入) |
|
||||
| service_vectorizer.py | 矢量化服务(未接入) |
|
||||
| view_chats.py | 旧版聊天查看(已由 chat_log_viewer 替代) |
|
||||
| test_import.py | 导入测试 |
|
||||
| test_battle.py | 压价话术测试 |
|
||||
| viewer_out.txt | 临时输出 |
|
||||
298
archive/test_battle.py
Normal file
298
archive/test_battle.py
Normal file
@@ -0,0 +1,298 @@
|
||||
"""
|
||||
AI 左右互搏测试工具
|
||||
|
||||
客服AI(真实) vs 买家AI(模拟)
|
||||
用来调试客服AI的回复质量,不需要真实客户
|
||||
"""
|
||||
import asyncio
|
||||
import os
|
||||
import random
|
||||
import httpx
|
||||
from openai import AsyncOpenAI
|
||||
from dotenv import load_dotenv
|
||||
from pydantic_ai_agent import CustomerServiceAgent, CustomerMessage
|
||||
|
||||
load_dotenv()
|
||||
|
||||
WECHAT_WEBHOOK = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=cc88bdef-a13f-4d7e-bdb6-ee51b68b8205"
|
||||
|
||||
|
||||
async def notify_wechat(content: str):
|
||||
"""发送企业微信机器人通知"""
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=10) as client:
|
||||
await client.post(WECHAT_WEBHOOK, json={
|
||||
"msgtype": "text",
|
||||
"text": {"content": content}
|
||||
})
|
||||
except Exception as e:
|
||||
print(f"[通知] 企业微信发送失败: {e}")
|
||||
|
||||
# ========== 颜色输出 ==========
|
||||
RESET = "\033[0m"
|
||||
BLUE = "\033[94m" # 买家
|
||||
GREEN = "\033[92m" # 客服
|
||||
YELLOW = "\033[93m" # 系统提示
|
||||
GRAY = "\033[90m" # 分隔线
|
||||
|
||||
|
||||
def print_buyer(msg):
|
||||
print(f"{BLUE}【买家】{msg}{RESET}")
|
||||
|
||||
def print_shop(msg):
|
||||
print(f"{GREEN}【客服】{msg}{RESET}")
|
||||
|
||||
def print_system(msg):
|
||||
print(f"{YELLOW}{msg}{RESET}")
|
||||
|
||||
def print_sep():
|
||||
print(f"{GRAY}{'─' * 50}{RESET}")
|
||||
|
||||
|
||||
# ========== 买家人设配置 ==========
|
||||
BUYER_PERSONAS = {
|
||||
"普通买家": """你是一个普通淘宝买家,想找一张图的高清版本。
|
||||
行为:先打招呼,发图问价,可能小砍一次价,觉得合适就说要拍了。
|
||||
说话简短自然,像手机上发消息,1-2句话。不要太正式。""",
|
||||
|
||||
"砍价客": """你是个爱砍价的淘宝买家,总想便宜一点。
|
||||
行为:问价后嫌贵,砍价2-3次,最后可能接受也可能走人。
|
||||
说话直接,喜欢说"能不能便宜点""太贵了""别家更便宜"。""",
|
||||
|
||||
"爱问细节": """你是个很谨慎的买家,买东西前喜欢问清楚。
|
||||
行为:先问在不在,问能不能找到,问格式,问效果,问不满意能退吗,最后才考虑下单。
|
||||
说话有点啰嗦,喜欢多问几个问题。""",
|
||||
|
||||
"快节奏": """你是个很忙的买家,说话简短,不废话。
|
||||
行为:直接发图问多少钱,价格合适立刻说拍了,不合适直接走。
|
||||
每次只说3-5个字,极度简短。""",
|
||||
|
||||
"售后投诉": """你是个已经付款的买家,但对收到的图片不满意,想要退款或重做。
|
||||
行为:先说拿到图了但效果不行,描述哪里不满意(模糊/颜色不对/尺寸不够),
|
||||
要求重做或者退款,态度有点不耐烦,反复追问进度。
|
||||
说话带点情绪,但不至于骂人,就是那种"花钱了结果不行"的委屈感。""",
|
||||
|
||||
"多图打包": """你是个需要批量处理图片的买家,手头有好几张图要找高清版。
|
||||
行为:先问在不在,然后说有多张图要处理,询问能不能打包便宜点,
|
||||
逐步发图或询问价格,跟客服商量总价,最终决定下单还是再想想。
|
||||
说话随意,像在商量事情,不太在意每张的价格,更关注总价合不合适。""",
|
||||
|
||||
"大批量砍价": """你是个手头有十几张图要处理的买家,量大,觉得自己有底气砍价。
|
||||
行为:上来就说"我有很多图,你们量大能便宜吗",
|
||||
告知大概数量(10-20张),用量大作为筹码反复压价,
|
||||
问"做完这批还有下一批,能不能给个长期价""别家给我打6折了",
|
||||
如果客服给的总价还算合理就下单,否则拉锯几个来回。
|
||||
说话有点强势,觉得自己是大客户,应该享受优惠。""",
|
||||
|
||||
"要分层PSD": """你是个做设计的买家,需要带分层的PSD格式,不是普通jpg。
|
||||
行为:先问在不在,发图后问"这个能出PSD吗""要分层的那种",
|
||||
追问能不能保留图层、格式是不是真的PSD,听到价格后可能嫌贵问能不能便宜,
|
||||
最终决定下单或者放弃。
|
||||
说话带点专业感,会说"图层""分层""PSD""源文件"之类的词。""",
|
||||
|
||||
"问尺寸分辨率": """你是个有印刷需求的买家,非常在意图片的尺寸和分辨率。
|
||||
行为:发图后先不问价,反复问"这个能做多大""分辨率能到300dpi吗""印出来会不会模糊",
|
||||
问完尺寸再问价格,如果客服说能满足需求就考虑下单,否则犹豫很久。
|
||||
说话里经常出现"厘米""像素""dpi""印刷""大图"。""",
|
||||
|
||||
"问颜色效果": """你是个对颜色很挑剔的买家,担心高清版颜色和原图有色差。
|
||||
行为:发图后问"颜色会变吗""饱和度能保持吗""跟原图一样的色调吧",
|
||||
让客服保证颜色效果,追问不满意能不能改,反复确认才肯下单。
|
||||
说话谨慎,总要客服给"保证",但说话不凶,就是很在意品质。""",
|
||||
|
||||
"来源质疑": """你是个对店铺服务有疑虑的买家,总觉得有猫腻,想搞清楚原理。
|
||||
行为:问"你们是怎么找的""原图是从哪里来的""是AI弄的吗还是真的原图",
|
||||
追问来源和方法,对客服的回答将信将疑,继续追问,
|
||||
最终可能被说服下单,也可能觉得不靠谱走人。
|
||||
说话带点怀疑,喜欢反问,但不是来找茬的,是真的想搞清楚。""",
|
||||
}
|
||||
|
||||
# 用于测试的图片URL
|
||||
TEST_IMAGE_URLS = [
|
||||
"https://img.alicdn.com/imgextra/i1/O1CN01OilxfD1kr8tM4Ugg2_!!4611686018427387680-0-amp.jpg",
|
||||
]
|
||||
|
||||
|
||||
class BuyerAgent:
|
||||
"""模拟买家的AI"""
|
||||
|
||||
def __init__(self, persona_name: str = "普通买家"):
|
||||
self.client = AsyncOpenAI(
|
||||
api_key=os.getenv("OPENAI_API_KEY"),
|
||||
base_url=os.getenv("OPENAI_BASE_URL"),
|
||||
)
|
||||
self.model = os.getenv("OPENAI_MODEL", "gpt-4o-mini")
|
||||
self.persona_name = persona_name
|
||||
self.persona = BUYER_PERSONAS.get(persona_name, BUYER_PERSONAS["普通买家"])
|
||||
self.history = []
|
||||
self.image_sent = False
|
||||
self.rounds = 0
|
||||
|
||||
async def next_message(self, shop_reply: str = None) -> str:
|
||||
"""根据客服回复,生成下一条买家消息"""
|
||||
self.rounds += 1
|
||||
|
||||
if shop_reply:
|
||||
self.history.append({"role": "assistant", "content": f"客服说:{shop_reply}"})
|
||||
|
||||
# 第一轮:打招呼或直接发问
|
||||
if self.rounds == 1:
|
||||
first_msgs = ["在不在", "在吗", "你好", "有人吗", "亲在吗"]
|
||||
msg = random.choice(first_msgs)
|
||||
self.history.append({"role": "user", "content": msg})
|
||||
return msg
|
||||
|
||||
# 第二轮:发图+问有没有
|
||||
if self.rounds == 2 and not self.image_sent:
|
||||
self.image_sent = True
|
||||
url = random.choice(TEST_IMAGE_URLS)
|
||||
msg = f"有吗#*#{url}"
|
||||
self.history.append({"role": "user", "content": msg})
|
||||
return msg
|
||||
|
||||
# 后续:让AI根据对话历史自由生成
|
||||
system = f"""{self.persona}
|
||||
|
||||
你正在和一家找图店的客服聊天,对话历史如下。
|
||||
请根据你的人设生成下一条消息,简短自然,像真人发消息。
|
||||
如果对话已经快结束(超过10轮),可以说"好的拍了"或"算了不要了"结束对话。
|
||||
只输出你要发的消息内容,不要加任何前缀说明。"""
|
||||
|
||||
resp = await self.client.chat.completions.create(
|
||||
model=self.model,
|
||||
messages=[
|
||||
{"role": "system", "content": system},
|
||||
*self.history,
|
||||
{"role": "user", "content": "(请生成你的下一条消息)"}
|
||||
],
|
||||
max_tokens=80,
|
||||
temperature=0.9,
|
||||
)
|
||||
msg = resp.choices[0].message.content.strip()
|
||||
self.history.append({"role": "user", "content": msg})
|
||||
return msg
|
||||
|
||||
|
||||
async def run_battle(persona_name: str = "普通买家", max_rounds: int = 8):
|
||||
"""运行一场对话模拟"""
|
||||
|
||||
print_sep()
|
||||
print_system(f" 开始模拟 | 买家人设:{persona_name} | 最多 {max_rounds} 轮")
|
||||
print_sep()
|
||||
|
||||
# 初始化双方
|
||||
buyer = BuyerAgent(persona_name)
|
||||
shop = CustomerServiceAgent()
|
||||
|
||||
buyer_id = "test_buyer_001"
|
||||
shop_id = "test_shop_001"
|
||||
|
||||
shop_reply = None
|
||||
|
||||
for i in range(max_rounds):
|
||||
# 买家发消息
|
||||
buyer_msg = await buyer.next_message(shop_reply)
|
||||
print_buyer(buyer_msg)
|
||||
|
||||
# 判断对话是否结束
|
||||
end_words = ["算了", "不要了", "不买了", "拍了", "好的拍了", "下单了", "谢谢"]
|
||||
if any(w in buyer_msg for w in end_words):
|
||||
print_system(f"\n 对话结束(第 {i+1} 轮)")
|
||||
break
|
||||
|
||||
# 构建消息对象
|
||||
customer_msg = CustomerMessage(
|
||||
msg_id=f"test_{i}",
|
||||
acc_id=shop_id,
|
||||
msg=buyer_msg,
|
||||
from_id=buyer_id,
|
||||
from_name="测试买家",
|
||||
cy_id=buyer_id,
|
||||
acc_type="AliWorkbench",
|
||||
msg_type=0,
|
||||
cy_name="测试买家",
|
||||
goods_name="模糊图清晰处理专业代找原图素材淘宝图片找图修复服务",
|
||||
)
|
||||
|
||||
# 含图片URL时先打印等待语(模拟真实客服的即时回应)
|
||||
if "#*#" in buyer_msg or (buyer_msg.startswith(("http://", "https://")) and any(
|
||||
h in buyer_msg for h in ("alicdn.com", "imgextra", ".jpg", ".jpeg", ".png")
|
||||
)):
|
||||
print_shop("我找一下看看")
|
||||
print()
|
||||
|
||||
# 客服AI处理
|
||||
response = await shop.process_message(customer_msg)
|
||||
|
||||
if response.need_transfer:
|
||||
print_shop("[转人工]")
|
||||
print_system("\n 客服决定转人工,对话结束")
|
||||
break
|
||||
|
||||
shop_reply = response.reply if response.should_reply else None
|
||||
|
||||
# 过滤 AI 误输出的内部独白(与生产环境保持一致)
|
||||
nonsense_patterns = [
|
||||
"无需", "流程已完成", "不需要回复", "无需额外", "已完成",
|
||||
"无需回复", "不需要额外", "已经完成", "无需再", "操作已完成",
|
||||
"任务完成", "流程完成", "记录完成", "报价已",
|
||||
]
|
||||
if shop_reply and any(p in shop_reply for p in nonsense_patterns):
|
||||
print_system(f" [已拦截无效回复: {shop_reply}]")
|
||||
shop_reply = None
|
||||
|
||||
if shop_reply:
|
||||
print_shop(shop_reply)
|
||||
else:
|
||||
print_system(" (客服决定不回复)")
|
||||
shop_reply = ""
|
||||
|
||||
print()
|
||||
await asyncio.sleep(0.3)
|
||||
|
||||
print_sep()
|
||||
print_system(" 模拟结束")
|
||||
print_sep()
|
||||
|
||||
|
||||
async def main():
|
||||
import sys
|
||||
|
||||
personas = list(BUYER_PERSONAS.keys())
|
||||
|
||||
print(f"\n{YELLOW}{'=' * 50}")
|
||||
print(" AI 左右互搏测试工具")
|
||||
print(f"{'=' * 50}{RESET}")
|
||||
print(f"{GRAY}用法: python test_battle.py [人设编号|all]")
|
||||
print(f" 1=普通买家 2=砍价客 3=爱问细节 4=快节奏")
|
||||
print(f" 5=售后投诉 6=多图打包 7=大批量砍价 8=要分层PSD")
|
||||
print(f" 9=问尺寸分辨率 10=问颜色效果 11=来源质疑 0=全部{RESET}\n")
|
||||
|
||||
arg = sys.argv[1] if len(sys.argv) > 1 else "1"
|
||||
|
||||
if arg == "0" or arg == "all":
|
||||
for persona in personas:
|
||||
await run_battle(persona, max_rounds=14)
|
||||
print()
|
||||
await asyncio.sleep(1)
|
||||
else:
|
||||
try:
|
||||
idx = int(arg) - 1
|
||||
persona = personas[idx] if 0 <= idx < len(personas) else personas[0]
|
||||
except ValueError:
|
||||
persona = personas[0]
|
||||
await run_battle(persona, max_rounds=14)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
asyncio.run(main())
|
||||
except Exception as e:
|
||||
err_str = str(e)
|
||||
if "AccountOverdueError" in err_str or "overdue" in err_str.lower():
|
||||
msg = "⚠️ 火山引擎 API 欠费,请立即充值!\n地址:https://console.volcengine.com/ark"
|
||||
else:
|
||||
msg = f"⚠️ 测试脚本异常退出:{err_str[:200]}"
|
||||
print(f"\n[通知] {msg}")
|
||||
asyncio.run(notify_wechat(msg))
|
||||
raise
|
||||
13
archive/test_import.py
Normal file
13
archive/test_import.py
Normal file
@@ -0,0 +1,13 @@
|
||||
import traceback
|
||||
|
||||
try:
|
||||
from pydantic_ai_agent import CustomerServiceAgent, CustomerMessage
|
||||
print("✓ Agent 模块导入成功")
|
||||
|
||||
# 尝试初始化
|
||||
agent = CustomerServiceAgent()
|
||||
print("✓ Agent 初始化成功")
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ 导入或初始化失败: {e}")
|
||||
traceback.print_exc()
|
||||
119
archive/view_chats.py
Normal file
119
archive/view_chats.py
Normal file
@@ -0,0 +1,119 @@
|
||||
"""
|
||||
聊天记录查看工具
|
||||
|
||||
用法:
|
||||
python view_chats.py # 列出所有客户
|
||||
python view_chats.py <客户ID> # 查看某客户的全部对话
|
||||
python view_chats.py <客户ID> --today # 只看今天
|
||||
python view_chats.py --search <关键词> # 全文搜索
|
||||
"""
|
||||
|
||||
import sys
|
||||
import argparse
|
||||
from chat_log_db import get_customers, get_conversation, get_conversation_today, search_messages
|
||||
|
||||
# ANSI 颜色
|
||||
GREEN = "\033[92m"
|
||||
BLUE = "\033[94m"
|
||||
YELLOW = "\033[93m"
|
||||
GRAY = "\033[90m"
|
||||
RESET = "\033[0m"
|
||||
BOLD = "\033[1m"
|
||||
|
||||
|
||||
def list_customers():
|
||||
customers = get_customers(limit=200)
|
||||
if not customers:
|
||||
print("暂无聊天记录")
|
||||
return
|
||||
|
||||
print(f"\n{BOLD}{'='*60}{RESET}")
|
||||
print(f"{BOLD} 客户聊天记录总览 共 {len(customers)} 位客户{RESET}")
|
||||
print(f"{BOLD}{'='*60}{RESET}")
|
||||
print(f" {'客户ID':<22} {'昵称':<12} {'平台':<15} {'消息数':>6} {'最后联系'}")
|
||||
print(f" {'-'*22} {'-'*12} {'-'*15} {'-'*6} {'-'*19}")
|
||||
|
||||
for c in customers:
|
||||
cid = c["customer_id"][:20]
|
||||
name = (c["customer_name"] or "未知")[:10]
|
||||
plat = (c["platform"] or "未知")[:13]
|
||||
total = c["total_msgs"]
|
||||
last = c["last_time"][:16]
|
||||
print(f" {YELLOW}{cid:<22}{RESET} {name:<12} {GRAY}{plat:<15}{RESET} {total:>6}条 {last}")
|
||||
|
||||
print(f"\n{GRAY}查看某客户对话:python view_chats.py <客户ID>{RESET}\n")
|
||||
|
||||
|
||||
def show_conversation(customer_id: str, today_only: bool = False):
|
||||
if today_only:
|
||||
records = get_conversation_today(customer_id)
|
||||
label = "今日对话"
|
||||
else:
|
||||
records = get_conversation(customer_id, limit=300)
|
||||
label = "全部对话"
|
||||
|
||||
if not records:
|
||||
print(f" {GRAY}暂无记录{RESET}")
|
||||
return
|
||||
|
||||
print(f"\n{BOLD}{'='*60}{RESET}")
|
||||
print(f"{BOLD} 客户:{customer_id} {label} 共 {len(records)} 条{RESET}")
|
||||
print(f"{BOLD}{'='*60}{RESET}\n")
|
||||
|
||||
last_date = ""
|
||||
for r in records:
|
||||
ts = r["timestamp"]
|
||||
date = ts[:10]
|
||||
time = ts[11:16]
|
||||
msg = r["message"]
|
||||
direction = r["direction"]
|
||||
|
||||
if date != last_date:
|
||||
print(f" {GRAY}── {date} ──────────────────────────────{RESET}")
|
||||
last_date = date
|
||||
|
||||
if direction == "in":
|
||||
# 客户消息,左对齐蓝色
|
||||
print(f" {GRAY}{time}{RESET} {BLUE}【客户】{RESET} {msg}")
|
||||
else:
|
||||
# 客服回复,右对齐绿色
|
||||
print(f" {GRAY}{time}{RESET} {GREEN}【客服】{RESET} {msg}")
|
||||
|
||||
print()
|
||||
|
||||
|
||||
def show_search(keyword: str):
|
||||
results = search_messages(keyword, limit=50)
|
||||
if not results:
|
||||
print(f" 未找到包含"{keyword}"的消息")
|
||||
return
|
||||
|
||||
print(f"\n{BOLD}搜索:"{keyword}" 共 {len(results)} 条结果{RESET}\n")
|
||||
for r in results:
|
||||
direction = "客户" if r["direction"] == "in" else "客服"
|
||||
color = BLUE if r["direction"] == "in" else GREEN
|
||||
print(f" {GRAY}{r['timestamp'][:16]}{RESET} {YELLOW}{r['customer_id'][:20]}{RESET} {color}[{direction}]{RESET} {r['message']}")
|
||||
print()
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="查看按用户分开的聊天记录",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog=__doc__,
|
||||
)
|
||||
parser.add_argument("customer_id", nargs="?", help="客户ID(不填则列出所有客户)")
|
||||
parser.add_argument("--today", action="store_true", help="只显示今天的对话")
|
||||
parser.add_argument("--search", metavar="关键词", help="全文搜索所有消息")
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.search:
|
||||
show_search(args.search)
|
||||
elif args.customer_id:
|
||||
show_conversation(args.customer_id, today_only=args.today)
|
||||
else:
|
||||
list_customers()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
24
archive/viewer_out.txt
Normal file
24
archive/viewer_out.txt
Normal file
@@ -0,0 +1,24 @@
|
||||
|
||||
[1m[36m────────────────────────────────────────────────────────────[0m
|
||||
[1m[36m 对话记录 test_user_001 (4 条)[0m
|
||||
[1m[36m────────────────────────────────────────────────────────────[0m
|
||||
|
||||
[2m──────────────────── 2026-02-25 ────────────────────[0m
|
||||
[2m17:44[0m [97m买家[0m
|
||||
[48;5;236m 你好,想问下图片处理多少钱 [0m
|
||||
|
||||
[2m17:44[0m [32m客服[0m
|
||||
python : Traceback (most recent call last):
|
||||
所在位置 C:\Users\jimi\AppData\Local\Temp\ps-script-7c6a5466-18ca-4772-ac44-a07a3d10df05.ps1:75 字符: 19
|
||||
+ ... d d:\Terminator; python chat_log_viewer.py test_user_001 2>&1 | Out-F ...
|
||||
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
+ CategoryInfo : NotSpecified: (Traceback (most recent call last)::String) [], RemoteException
|
||||
+ FullyQualifiedErrorId : NativeCommandError
|
||||
|
||||
File "D:\Terminator\chat_log_viewer.py", line 233, in <module>
|
||||
cmd_show_conversation(args[0])
|
||||
File "D:\Terminator\chat_log_viewer.py", line 127, in cmd_show_conversation
|
||||
print_bubble(m["direction"], m["message"], ts)
|
||||
File "D:\Terminator\chat_log_viewer.py", line 80, in print_bubble
|
||||
print(f" {GREEN}\u25b6 {line}{RESET}")
|
||||
UnicodeEncodeError: 'gbk' codec can't encode character '\u25b6' in position 9: illegal multibyte sequence
|
||||
Reference in New Issue
Block a user