chore: 从汽车AI客服项目拷贝骨架并清理(blog页/redis)
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
"""批次0.5 基线统计:上线前存档,上线后同口径对比。只读。"""
|
||||
import re
|
||||
import sqlite3
|
||||
import sys
|
||||
from itertools import groupby
|
||||
from pathlib import Path
|
||||
|
||||
DB = sys.argv[1] if len(sys.argv) > 1 else "./data/cc_ai_kefu.db"
|
||||
|
||||
EMAIL_RE = re.compile(r"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}")
|
||||
INTL_RE = re.compile(r"\+\d{6,15}")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
# mode=ro:路径写错时直接报错,而不是新建空库
|
||||
uri = f"file:{Path(DB).resolve().as_posix()}?mode=ro"
|
||||
try:
|
||||
conn = sqlite3.connect(uri, uri=True)
|
||||
except sqlite3.OperationalError as exc:
|
||||
print(f"无法只读打开数据库 {DB}: {exc}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
cur = conn.cursor()
|
||||
|
||||
print("# 注意: 本文件为开发库占位数据, 生产基线需在部署机覆盖重跑")
|
||||
|
||||
# 1) 历史已发邮件中无联系方式占比(按 source)
|
||||
rows = cur.execute(
|
||||
"""SELECT c.source,
|
||||
COUNT(*) AS sent,
|
||||
SUM(CASE WHEN COALESCE(e.contact_info, '{}') IN ('{}', 'null', '')
|
||||
OR e.contact_info IS NULL THEN 1 ELSE 0 END) AS no_contact
|
||||
FROM evaluations e JOIN conversations c ON c.id = e.conversation_id
|
||||
WHERE e.email_sent = 1 GROUP BY c.source"""
|
||||
).fetchall()
|
||||
print("== 已发邮件联系方式缺失(按来源)==")
|
||||
if not rows:
|
||||
print(" (无记录)")
|
||||
for source, sent, no_contact in rows:
|
||||
pct = (no_contact / sent * 100) if sent else 0
|
||||
print(f" {source}: 发送 {sent}, 缺失 {no_contact} ({pct:.0f}%)")
|
||||
|
||||
# 2) 用户消息中含联系方式样式的会话占比(新策略邮件量预估)
|
||||
# messages.conversation_id 无索引:单次全表扫描 + groupby,避免逐会话 N+1 查询
|
||||
with_contact = early_contact = total = 0
|
||||
msgs = cur.execute(
|
||||
"""SELECT conversation_id, role, content FROM messages
|
||||
ORDER BY conversation_id, created_at, id"""
|
||||
)
|
||||
for _cid, convo_msgs in groupby(msgs, key=lambda m: m[0]):
|
||||
user_msgs = [m[2] for m in convo_msgs if m[1] == "user"]
|
||||
if not user_msgs:
|
||||
continue
|
||||
total += 1
|
||||
hit_idx = next(
|
||||
(i for i, content in enumerate(user_msgs)
|
||||
if EMAIL_RE.search(content) or INTL_RE.search(content)),
|
||||
None,
|
||||
)
|
||||
if hit_idx is not None:
|
||||
with_contact += 1
|
||||
if hit_idx < 2:
|
||||
early_contact += 1
|
||||
print("== 新策略邮件量预估 ==")
|
||||
print(f" 有用户消息的会话: {total}")
|
||||
print(f" 消息中含联系方式样式: {with_contact}"
|
||||
f" ({with_contact / total * 100 if total else 0:.0f}%) ← 样式上界")
|
||||
print(f" 联系方式出现在前2条消息内: {early_contact}"
|
||||
f" ({early_contact / total * 100 if total else 0:.0f}%) ← 即时触发增量")
|
||||
|
||||
conn.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,41 @@
|
||||
"""职能边界破防率观测(只读): AI 回复中出现"价格形态数字"的轮次占比。
|
||||
方案B(输出过滤)的启动依据: >10% 即砌墙。锚定式匹配,年份/续航数字天然不命中。"""
|
||||
import re
|
||||
import sqlite3
|
||||
import sys
|
||||
|
||||
DB = sys.argv[1] if len(sys.argv) > 1 else "./data/cc_ai_kefu.db"
|
||||
|
||||
PATTERNS = [
|
||||
(r"\$\s?\d", "美元符"),
|
||||
(r"\d[\d,.]*\s*(usd|美元|万块?|块钱)", "货币单位"),
|
||||
(r"(price|cost|quote)[^.\d]{0,20}(?<![a-z])(?!(?:19|20)\d\d(?!\d))\d",
|
||||
"价格动词邻接(年份/车型编号排除)"),
|
||||
(r"\d[\d,.]*\s*k\b(?!\w)", "25k形式"),
|
||||
]
|
||||
COMPILED = [(re.compile(p, re.I), name) for p, name in PATTERNS]
|
||||
|
||||
|
||||
def main() -> None:
|
||||
conn = sqlite3.connect(f"file:{DB}?mode=ro", uri=True)
|
||||
rows = conn.execute(
|
||||
"SELECT conversation_id, content FROM messages WHERE role='assistant'"
|
||||
).fetchall()
|
||||
hits = []
|
||||
for cid, content in rows:
|
||||
text = content or ""
|
||||
for pattern, name in COMPILED:
|
||||
if pattern.search(text):
|
||||
hits.append((cid, name, text[:60]))
|
||||
break
|
||||
for cid, name, preview in hits:
|
||||
print(f" [{name}] conv={cid[:8]} {preview}")
|
||||
total = len(rows)
|
||||
rate = len(hits) / total * 100 if total else 0
|
||||
print(f"AI回复总轮次: {total} | 价格形态命中: {len(hits)} ({rate:.0f}%)")
|
||||
print("← 破防率。>10% 启动方案B(输出过滤)")
|
||||
conn.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,39 @@
|
||||
"""本地联调假 UniService 接收端(仅测试用,不进生产)。
|
||||
|
||||
用法: python scripts/mock_uniservice.py [端口,默认9099]
|
||||
预期请求头 X-Api-Key: test-key-123;收到推送打印前 2000 字符,回 accepted。
|
||||
"""
|
||||
import json
|
||||
import sys
|
||||
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||
|
||||
KEY = "test-key-123"
|
||||
|
||||
|
||||
class Handler(BaseHTTPRequestHandler):
|
||||
def do_POST(self):
|
||||
n = int(self.headers.get("Content-Length", 0))
|
||||
body = json.loads(self.rfile.read(n) or b"{}")
|
||||
msgs = body.get("messages", [])
|
||||
print(f"\n=== 收到推送 {len(msgs)} 条 (X-Api-Key={self.headers.get('X-Api-Key')}) ===")
|
||||
print(json.dumps(msgs, ensure_ascii=False, indent=2)[:2000])
|
||||
if self.headers.get("X-Api-Key") != KEY:
|
||||
code, resp = 401, {"success": False, "error": "unauthorized"}
|
||||
else:
|
||||
code, resp = 200, {"success": True, "data": {
|
||||
"accepted": len(msgs), "duplicated": 0, "failed": []}}
|
||||
data = json.dumps(resp).encode()
|
||||
self.send_response(code)
|
||||
self.send_header("Content-Type", "application/json")
|
||||
self.send_header("Content-Length", str(len(data)))
|
||||
self.end_headers()
|
||||
self.wfile.write(data)
|
||||
|
||||
def log_message(self, *a): # 静默默认访问日志,只留推送打印
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
port = int(sys.argv[1]) if len(sys.argv) > 1 else 9099
|
||||
print(f"mock UniService on http://127.0.0.1:{port} (期待 X-Api-Key: {KEY})")
|
||||
HTTPServer(("127.0.0.1", port), Handler).serve_forever()
|
||||
Reference in New Issue
Block a user