From f0af1cfe8ed8067cf64bf2743341bbefdcf07493 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B5=85=E5=BF=B5?= Date: Tue, 29 Sep 2026 18:29:07 +0800 Subject: [PATCH] =?UTF-8?q?chore:=20=E6=9C=8D=E5=8A=A1=E7=AB=AF=E5=93=81?= =?UTF-8?q?=E7=89=8C=E4=B8=8E=E8=A1=8C=E4=B8=9A=E6=AE=8B=E7=95=99=E6=9B=B4?= =?UTF-8?q?=E5=90=8D=E6=B8=85=E9=9B=B6(HESUPIPE/DB=E5=90=8D/redis=E5=AD=97?= =?UTF-8?q?=E6=AE=B5)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Code --- .env.example | 4 ++-- app/api/routes/chat_rest.py | 2 +- app/config.py | 6 +----- app/main.py | 2 +- app/services/email_service.py | 6 +++--- app/services/rate_limit.py | 2 +- .../plans/2026-09-29-building-materials-ai-kefu.md | 10 +++++----- scripts/baseline_stats.py | 2 +- scripts/breach_rate.py | 2 +- tests/test_boundary_filter.py | 6 +++--- tests/test_contact_pipeline.py | 4 ++-- tests/test_lead_email_trigger.py | 6 +++--- 12 files changed, 24 insertions(+), 28 deletions(-) diff --git a/.env.example b/.env.example index 5884bd3..7fe27f3 100644 --- a/.env.example +++ b/.env.example @@ -6,7 +6,7 @@ AI_MODEL=glm-4-flash ZHIPU_BASE_URL=https://open.bigmodel.cn/api/coding/paas/v4 # 数据库 -DATABASE_URL=sqlite+aiosqlite:///./data/cc_ai_kefu.db +DATABASE_URL=sqlite+aiosqlite:///./data/hesupipe_ai_kefu.db # 邮件推送 SMTP_HOST=smtp.example.com @@ -23,5 +23,5 @@ PUSH_EXTERNAL_KEY_PREFIX=ai-kefu # 应用配置 APP_HOST=0.0.0.0 -APP_PORT=8016 +APP_PORT=8017 APP_DEBUG=true diff --git a/app/api/routes/chat_rest.py b/app/api/routes/chat_rest.py index 221700d..7d7b059 100644 --- a/app/api/routes/chat_rest.py +++ b/app/api/routes/chat_rest.py @@ -1,4 +1,4 @@ -"""REST shell for the HESU app (non-streaming, JSON-in/JSON-out). +"""REST shell for the HESUPIPE app (non-streaming, JSON-in/JSON-out). These routes wrap `ConversationManager`, shared with the existing `/ws/chat` widget path. The app uses HTTP because it lacks a WebSocket client and is fine diff --git a/app/config.py b/app/config.py index 041db6d..35c8013 100644 --- a/app/config.py +++ b/app/config.py @@ -14,11 +14,7 @@ class Settings(BaseSettings): ZHIPU_BASE_URL: str = "" AI_MODEL: str = "glm-4-flash" - DATABASE_URL: str = "sqlite+aiosqlite:///./data/cc_ai_kefu.db" - - REDIS_URL: str = "redis://localhost:6379/0" - REDIS_PASSWORD: str = "" - SESSION_TTL: int = 86400 # 24 hours + DATABASE_URL: str = "sqlite+aiosqlite:///./data/hesupipe_ai_kefu.db" SMTP_HOST: str = "" SMTP_PORT: int = 465 diff --git a/app/main.py b/app/main.py index 1f76809..5d0c926 100644 --- a/app/main.py +++ b/app/main.py @@ -46,7 +46,7 @@ async def lifespan(app: FastAPI): yield -app = FastAPI(title="AI汽车客服", lifespan=lifespan) +app = FastAPI(title="HESUPIPE AI 客服", lifespan=lifespan) from app.api.routes.chat import router as chat_router from app.api.routes.admin import router as admin_router diff --git a/app/services/email_service.py b/app/services/email_service.py index f7ee35d..2c86df4 100644 --- a/app/services/email_service.py +++ b/app/services/email_service.py @@ -71,7 +71,7 @@ async def send_lead_email( if contact_parts: subject += " | " + ", ".join(contact_parts) msg["Subject"] = subject - msg["From"] = f"【XYH】 <{settings.SMTP_USER}>" + msg["From"] = f"【HESUPIPE】 <{settings.SMTP_USER}>" msg["To"] = settings.NOTIFY_EMAIL info_rows = "".join( @@ -108,7 +108,7 @@ async def send_returning_lead_email(contact_info: dict) -> bool: ] msg = MIMEMultipart("alternative") msg["Subject"] = f"[线索回访] {' | '.join(contact_parts) or '已知客户'}再次来访" - msg["From"] = f"【XYH】 <{settings.SMTP_USER}>" + msg["From"] = f"【HESUPIPE】 <{settings.SMTP_USER}>" msg["To"] = settings.NOTIFY_EMAIL msg.attach(MIMEText( f"

该联系方式 24h 内已发过完整询盘,客户又开启了新对话," @@ -120,7 +120,7 @@ async def send_correction_email(contact_info: dict, count: int = 1) -> bool: """D4: 联系方式变化(改号/新增/无提示换值)后的轻量更正邮件,每会话上限3封。""" msg = MIMEMultipart("alternative") msg["Subject"] = f"[询盘·更正{count}] 客户联系方式已更新(以本封为准)" - msg["From"] = f"【XYH】 <{settings.SMTP_USER}>" + msg["From"] = f"【HESUPIPE】 <{settings.SMTP_USER}>" msg["To"] = settings.NOTIFY_EMAIL msg.attach(MIMEText( f"

这是本会话第 {count} 次更正(上限 3 次),请以本封为准:

" diff --git a/app/services/rate_limit.py b/app/services/rate_limit.py index 60c0430..9da7289 100644 --- a/app/services/rate_limit.py +++ b/app/services/rate_limit.py @@ -1,6 +1,6 @@ """Minimal in-memory rate limiter (per (ip, device_id), rolling 1h window). -Mirrors hesu's rate_limit.py shape. Only the chat message route uses this; +Kept aligned with the reference implementation's rate_limit.py shape. Only the chat message route uses this; session/history endpoints are cheap and not throttled. State is process-local and resets on restart, which is acceptable for a single-worker ai-kefu deploy. diff --git a/docs/superpowers/plans/2026-09-29-building-materials-ai-kefu.md b/docs/superpowers/plans/2026-09-29-building-materials-ai-kefu.md index abf499f..4732fd3 100644 --- a/docs/superpowers/plans/2026-09-29-building-materials-ai-kefu.md +++ b/docs/superpowers/plans/2026-09-29-building-materials-ai-kefu.md @@ -340,7 +340,7 @@ python -m pytest -q && git add -A && git commit -m "feat: 多语言输出墙(€ - Modify: `app/services/conversation.py`、`app/services/contact_pipeline.py` - Test: `tests/test_contact_pipeline.py`、`tests/test_context_seeding.py` -- [ ] **Step 1: 先写失败的阿语数字测试** +- [x] **Step 1: 先写失败的阿语数字测试** `tests/test_contact_pipeline.py` 追加: @@ -353,14 +353,14 @@ def test_arabic_digits_normalized(): (具体 API 名以参考项目实际函数签名为准——`split_validate`/canon 归一化入口。) -- [ ] **Step 2: 跑测试确认失败** +- [x] **Step 2: 跑测试确认失败** ```bash python -m pytest tests/test_contact_pipeline.py -q -k arabic ``` 预期:FAIL(号码原样保留阿语数字) -- [ ] **Step 3: 实现数字归一化** +- [x] **Step 3: 实现数字归一化** `contact_pipeline.py` 顶部加: @@ -371,7 +371,7 @@ _DIGIT_TRANSLATE = str.maketrans("٠١٢٣٤٥٦٧٨٩۰۱۲۳۴۵۶۷۸۹", "01 在归一化入口(normalize_contacts/digits_only 调用链的最前端)对值做 `v.translate(_DIGIT_TRANSLATE)`。注意:正则 `\d` Unicode 模式本就匹配阿语数字,位数校验能过——本步只解决"存进去的形式"。 -- [ ] **Step 4: conversation.py 建材化** +- [x] **Step 4: conversation.py 建材化** 1. `BOUNDARY_TRIGGER_RE`:确认已含 MOQ/lead time/delivery time/sample/certificate 类词;补 `container loading|装柜|起订`(以实际正则现状为准增补) 2. context 种子白名单:`vehicle_name/vehicle_id` → `product_name/product_id` @@ -379,7 +379,7 @@ _DIGIT_TRANSLATE = str.maketrans("٠١٢٣٤٥٦٧٨٩۰۱۲۳۴۵۶۷۸۹", "01 4. 同步更新 `tests/test_context_seeding.py` 的键名 5. **话术跨层同步(Task 3 质量审查 Minor 4)**:conversation.py 约 592 行的注入提醒与 boundary_filter.py 的 NO_CONTACT_WEB 仍是 "our manager replies fast!",与 system.md 的 "reply within 12 hours" 口径不一致——统一为 12 小时句(或两版并存亦可,但须明确决定) -- [ ] **Step 5: 全量测试 + Commit** +- [x] **Step 5: 全量测试 + Commit** ```bash python -m pytest -q && git add -A && git commit -m "feat: 会话编排建材化+阿语号码转ASCII" diff --git a/scripts/baseline_stats.py b/scripts/baseline_stats.py index e837b0e..e20b119 100644 --- a/scripts/baseline_stats.py +++ b/scripts/baseline_stats.py @@ -5,7 +5,7 @@ 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" +DB = sys.argv[1] if len(sys.argv) > 1 else "./data/hesupipe_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}") diff --git a/scripts/breach_rate.py b/scripts/breach_rate.py index 679d2ae..21ad9ad 100644 --- a/scripts/breach_rate.py +++ b/scripts/breach_rate.py @@ -4,7 +4,7 @@ import re import sqlite3 import sys -DB = sys.argv[1] if len(sys.argv) > 1 else "./data/cc_ai_kefu.db" +DB = sys.argv[1] if len(sys.argv) > 1 else "./data/hesupipe_ai_kefu.db" PATTERNS = [ (r"\$\s?\d", "美元符"), diff --git a/tests/test_boundary_filter.py b/tests/test_boundary_filter.py index f3c65e3..560497f 100644 --- a/tests/test_boundary_filter.py +++ b/tests/test_boundary_filter.py @@ -198,7 +198,7 @@ def test_asks_contact_fragment_followup_still_pass(): def test_warranty_commitment_violates(): # 政策变更(2026-09-10 用户定调: 不清楚的留资转经理): AI 输出的保修期限 # 是编造的公司政策承诺——从"白名单放行"改为墙级违规 - assert violates_boundary("we offer 6 months warranty on every car") + assert violates_boundary("we offer 6 months warranty on every pipe") assert violates_boundary("battery has 8 years warranty") # year 靠定向模式 assert violates_boundary("warranty of 8 years on battery") # EN 倒装 assert violates_boundary("电池保修8个月") @@ -214,9 +214,9 @@ def test_chinese_time_commitment_violates(): def test_age_and_year_talk_still_clean(): - # 误杀防护: 二手车龄/车型年款/无数字保修讨论必须放行(实证 13 条全清) + # 误杀防护: 库货年限/年款/规格数字/无数字保修讨论必须放行(实证 13 条全清) assert not violates_boundary("under 5 years old") - assert not violates_boundary("5 years old car") + assert not violates_boundary("5 years old stock pipe") assert not violates_boundary("2024年款") assert not violates_boundary("5座SUV") assert not violates_boundary("600km续航") diff --git a/tests/test_contact_pipeline.py b/tests/test_contact_pipeline.py index 0565b8b..c3bdc01 100644 --- a/tests/test_contact_pipeline.py +++ b/tests/test_contact_pipeline.py @@ -123,7 +123,7 @@ def test_scan_wa_me_link(): def test_scan_ignores_prices_years_and_ai_messages(): texts = m( - "budget $25,000 for 2024 BYD Tang", # 价格+年份 + "budget $25,000 for 2024 PE100 pipes", # 价格+年份 "50,000 km mileage", # 里程 "I want 5 units", # 数量 ) @@ -221,7 +221,7 @@ def test_recency_kills_extractor_oscillation(): before = {"phone": "+8613800138004", "whatsapp": "+8613800138004"} after = {"phone": "+8613800138000", "whatsapp": "+8613800138004"} # extractor 振荡 msgs = _u("my whatsapp is +8613800138000", "my whatsapp is +8613800138004", - "any car talk") + "any pipe talk") assert resolve_by_recency(before, after, msgs) == before # 无变化→不触发更正 diff --git a/tests/test_lead_email_trigger.py b/tests/test_lead_email_trigger.py index 93b090a..07a9431 100644 --- a/tests/test_lead_email_trigger.py +++ b/tests/test_lead_email_trigger.py @@ -132,7 +132,7 @@ def test_no_contact_never_emails_and_marks_stuck(async_db, monkeypatch): # D8: interested+无联系方式 → 不发邮件,upsert Evaluation 卡单记录 (manager, conv), sent = _setup(async_db, monkeypatch, ai=_mk_ai({}, evaluate=INTERESTED)) - for text in ("hi", "kenya", "20k budget", "byd tang"): + for text in ("hi", "kenya", "20k budget", "pe100 pipe"): asyncio.run(_send(manager, conv.session_id, text)) assert sent == [] @@ -263,7 +263,7 @@ def test_oscillation_does_not_burn_correction_quota(async_db, monkeypatch): ai = FakeAI(extract=seq) (manager, conv), sent = _setup(async_db, monkeypatch, ai=ai) texts = ["my whatsapp is +8613800138000", "my whatsapp is +8613800138004", - "什么车好", "再说", "还有吗", "18325698745"] + "哪种管好", "再说", "还有吗", "18325698745"] for t in texts: asyncio.run(_send(manager, conv.session_id, t)) kinds = [s[0] for s in sent] @@ -464,7 +464,7 @@ def test_evaluation_upsert_keeps_flags_and_intent(async_db, monkeypatch): def test_reminder_boundary_redline_on_price_question(): text = ConversationManager._build_context_reminder( - {}, "how much is the BYD Tang?", []) + {}, "how much is the DN800 pipe?", []) assert "职能边界" in text and "禁止" in text