This commit is contained in:
2026-02-27 16:03:04 +08:00
commit 5aedf1665d
137 changed files with 17604 additions and 0 deletions

12
archive/README.md Normal file
View 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
View 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
View 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
View 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
View File

@@ -0,0 +1,24 @@

────────────────────────────────────────────────────────────
 对话记录 test_user_001 4 条)
────────────────────────────────────────────────────────────
──────────────────── 2026-02-25 ────────────────────
17:44 买家
 你好,想问下图片处理多少钱 
17:44 客服
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