A 區 · 全廠空照圖(Context + Container)
客戶 LINE 訊息進來 → 客戶來電總機簽收 → 進待處理倉 → 訂單處理員按順序處理 → 分流調度員(AI 大腦)查工具書回客戶;遇到 escalate tag 就敲對講機叫青哥來。
flowchart LR
classDef client fill:#0e2547,stroke:#3aa0ff,color:#3aa0ff
classDef gate fill:#0e2547,stroke:#4cc38a,color:#4cc38a
classDef core fill:#0e2547,stroke:#ff8a3d,color:#ff8a3d
classDef data fill:#0e2547,stroke:#b07cff,color:#b07cff
classDef esc fill:#0e2547,stroke:#ff5e7e,color:#ff5e7e
C[🟦 客戶 LINE 訊息]:::client
W[🟩 客戶來電總機
webhook :8000]:::gate Q[🟪 待處理倉
webhook_queue]:::data WK[🟧 訂單處理員
worker]:::core AI[🟧 分流調度員
ai_router LLM]:::core TB[🟧 工具書架 6 本工具]:::core H[🟪 對話流水帳
history]:::data CU[🟪 客戶檔案櫃
customers]:::data EB[🟥 對講機調度站
escalate_bridge]:::esc EQ[🟪 轉人類隊伍
escalate_queue]:::data TG1[🟩 青哥 TG
@ching_factory_bot]:::gate OA[🟩 青哥 LINE OA 後台
chat.line.biz]:::gate ED[🟦 青哥 / 小編]:::client C --> W --> Q --> WK WK --> AI AI --> TB AI --> H WK --> CU AI -- 含 TRIGGER_QUOTE / TRANSFER_HUMAN tag --> EB EB --> EQ EB -- inline button:跳 LINE OA 後台 --> TG1 TG1 --> ED ED --> OA --> C
webhook :8000]:::gate Q[🟪 待處理倉
webhook_queue]:::data WK[🟧 訂單處理員
worker]:::core AI[🟧 分流調度員
ai_router LLM]:::core TB[🟧 工具書架 6 本工具]:::core H[🟪 對話流水帳
history]:::data CU[🟪 客戶檔案櫃
customers]:::data EB[🟥 對講機調度站
escalate_bridge]:::esc EQ[🟪 轉人類隊伍
escalate_queue]:::data TG1[🟩 青哥 TG
@ching_factory_bot]:::gate OA[🟩 青哥 LINE OA 後台
chat.line.biz]:::gate ED[🟦 青哥 / 小編]:::client C --> W --> Q --> WK WK --> AI AI --> TB AI --> H WK --> CU AI -- 含 TRIGGER_QUOTE / TRANSFER_HUMAN tag --> EB EB --> EQ EB -- inline button:跳 LINE OA 後台 --> TG1 TG1 --> ED ED --> OA --> C
業務閉環:B 方案(5/2 PM 拍板)— TG 推 brief 給青哥,青哥用 LINE OA 後台直接回客戶 / TG 不收回流。
B 區 · 一通客戶訊息的旅程(Sequence)
從客戶按下送出,到青哥收到 TG 報價請求,工廠裡發生的事。
sequenceDiagram
autonumber
participant 客戶
participant 總機 as 客戶來電總機
participant 倉 as 待處理倉
participant 處理員 as 訂單處理員
participant 大腦 as 分流調度員
participant 工具書 as 工具書架
participant 對講機 as 對講機調度站
participant 青哥 as 青哥 TG / LINE OA
客戶->>總機: 「曼巴想改避震 預算一萬」
總機->>總機: 簽章驗證 (HMAC-SHA256)
總機->>倉: 入庫 message_id idempotency
總機-->>客戶: 200 OK (2 秒內)
Note over 倉,處理員: 訂單處理員背景 polling
處理員->>大腦: run(user_id, msg)
大腦->>工具書: search_motorcycles / search_prices ...
工具書-->>大腦: matched data
大腦-->>處理員: reply + tags
處理員->>客戶: line_api.push_text (LINE 回客戶)
alt reply 含 [TRIGGER_QUOTE] 或 [TRANSFER_HUMAN]
處理員->>對講機: notify_editor(...)
對講機->>青哥: TG 報價 brief #N + inline button「💬 跳 LINE OA 後台」
青哥->>客戶: 在 LINE OA 後台直接回客戶
end
C 區 · 6 本工具書(白名單情報來源)
分流調度員不能腦補;要回什麼都要先翻工具書(OpenAI tool-use loop / fuzzy match threshold = 80)。
| 業務名稱 | code 名 / 用途 | 對應工具書 | source |
|---|---|---|---|
| 車款黑話翻譯機 | search_motorcycles 查台灣機車車款、別稱、黑話 | motorcycles.yaml | tools.py:60 |
| 品牌情報員 | search_brands 查改裝品牌(避震 / 排氣 / CNC / 燈具) | brands_categories.yaml | tools.py:137 |
| 商品清單員 | search_products 查青工廠商品 / 服務清單 | products.yaml | tools.py:235 |
| 報價白名單員 | search_prices 查報價白名單 + 規則(白名單外要 push 青哥) | prices_rules.md | tools.py:282 |
| 業務規則查詢員 | search_business_rules 查業務規則 / 紅線 / 細項 | business_rules.md | tools.py:345 |
| 對講機叫人來 | escalate_to_human 轉真人(青哥 / 老徐) | —(直接觸發 escalate_bridge) | tools.py:402 |
FUZZY_THRESHOLD = 80 / tools.py:33
D 區 · 分流調度員(AI 大腦)· 內部裝配
OpenRouter Gemini 2.5 Flash 跑 OpenAI tool-use loop,最多 5 輪,每輪 timeout 8 秒。System prompt 分 3 layer 利用 prefix-cache。
| 模型 | google/gemini-2.5-flash(env: OPENROUTER_MODEL) |
|---|---|
| Tool Use 上限 | 5 輪 / ai_router.py:55 |
| 每輪 timeout | 8 秒(env: OPENROUTER_TIMEOUT_S)/ ai_router.py:56 |
| Retry backoff | 1.0 秒 / ai_router.py:57 |
| 3 層 System Prompt (prefix-cache 設計) |
① 小青人格 persona(穩定 / long-term cache) ② 業務規則 + 報價白名單(中等 / 教學通道改才動) ③ 客戶個人 active_session + summary(不 cache) ai_router.py:98-156 |
| Error Fallback | system_error / no_data / multi_turn_limit / error_fallback.py |
| 主入口 | async def run(user_id, user_msg, message_id) -> str / ai_router.py:204 |
E 區 · 對講機調度站(escalate_bridge)· 轉人類
LLM reply 含 [TRIGGER_QUOTE] 或 [TRANSFER_HUMAN] tag → 訂單處理員拍對講機 → 推 brief 給青哥 TG,brief 內附 inline button「💬 跳 LINE OA 後台」。
| 設計原則 | push only / 不做 reply 回流(B 方案 5/2 PM 拍板) |
|---|---|
| 使用 TG bot | @ching_factory_bot(CHING_FACTORY_BOT_TOKEN + CHING_FACTORY_CHAT_ID) |
| 業務閉環 | brief → 青哥點 inline button → 跳 chat.line.biz LINE OA 後台 → 對話列表找未讀第一筆 → 直接回客戶 |
| 核心員工 |
_line_chat_link(v9.2 件 3 final / Fallback A:跳 OA chat 主頁,不拼 user_id)/ escalate_bridge.py:42 notify_editor(TRIGGER_QUOTE → 報價請求 brief)/ escalate_bridge.py:98 notify_editor_transfer(TRANSFER_HUMAN → 轉真人 brief)/ escalate_bridge.py:152 remind_overdue(cron 30 min / warn 2h / critical 24h / expire 7d)/ escalate_bridge.py:206 |
| 佐料 |
電話遮罩(09xx-xxx-XXX 遮最後 3 碼)/ escalate_state.py:59 Rate limit 60s(同 user × 同類型 60s 內不重推)/ escalate_state.py:72 |
| 觸發點 | _maybe_push_escalate_brief in worker.py:159 / called from worker.py:129 |
F 區 · 通道清單(誰跟外面講話)
| 通道 | 方向 | 用 bot / endpoint | source |
|---|---|---|---|
| 🟩 LINE webhook | 客戶 → 工廠 | POST /webhook 或 /line/webhook(uvicorn :8000)/ HMAC-SHA256 簽章驗證 / 必 2 秒內回 200 | webhook.py:35 |
| 🟩 LINE push | 工廠 → 客戶 | line_api.push_text(async / 不用 reply token) | line_api.py:53 |
| 🟥 TG escalate brief | 工廠 → 青哥 | @ching_factory_bot(push only / inline button) | escalate_bridge.py:59 |
| 🟩 TG 教學通道 | Strider ↔ 工廠 | @qing_factory_v7_teach_bot(long-polling / 30s) | teach_bot.py |
| 🟩 SSH 部署通道 | M4 Max ↔ M1 Ultra | ssh ultra-out Tailscale 100.109.236.18 / ssh ultra-home 192.168.0.168 (infra / 不在 v7 code base) | — |
守門員:HMAC 簽章驗證 / 群組事件直推青哥(補4)/ redelivery skip(補2)/ message_id idempotency(補2)。
G 區 · 倉庫(SQLite 6 表)
runtime DB:~/qing_factory_data/qing.db(WAL 模式)
| 業務名稱 | code 表名 | 用途 |
|---|---|---|
| 客戶檔案櫃 | customers | 每位客戶一張卡:display_name / active_session / 對話總結 summary_md / last_active |
| 對話流水帳 | history | 每則訊息一行:user_msg / reply / tool_calls_json / latency / cost |
| 待處理倉 | webhook_queue | webhook 進來先入庫排隊 → worker 背景拉出來處理(idempotency by message_id) |
| 轉人類隊伍 | escalate_queue | TRIGGER_QUOTE / TRANSFER_HUMAN brief 排隊 / position / status / tg_msg_id migrations/001:6-21 |
| 品檢隊伍 | review_queue | V8_REVIEW_MODE 開時 LLM reply 先排這 / Strider 審核才 push 客戶 migrations/002:9-24 |
| 品檢學習語料庫 | review_corpus | Strider 改寫 / 拒絕 / 通過的 reply 全留下 / 未來訓練語料 migrations/002:31-40 |
H 區 · 廠內常駐員工(launchd 4 個 service)
| 業務崗位 | label | 排班 | 狀態 |
|---|---|---|---|
| 主生產線(LINE bot) uvicorn :8000 / 接 webhook + 跑 worker | com.qgc.v7-line-bot | 常駐 KeepAlive | 🟢 running (audit 當下 PID 38900) |
| 教學通道值班員 Strider TG 改 persona / 工具書 / 重啟 | com.qgc.teach-bot | 常駐 KeepAlive | 🟢 running (audit 當下 PID 9747) |
| 逾時提醒員 escalate_queue 還 pending → 提醒青哥(warn 2h / critical 24h / expire 7d) | com.qgc.overdue-reminder | 每 30 分鐘(StartInterval=1800) | cron / loaded |
| API 額度警報員 OpenRouter credits 快沒 → 預警 / 對應 API 4 護欄 | com.qgc.openrouter-alarm | 每 30 分鐘 | cron / loaded |
I 區 · 環境變數(key 用途,不 dump value)
| 類別 | 變數 | 用途 |
|---|---|---|
| LINE | LINE_CHANNEL_ACCESS_TOKEN | push / profile API 認證 |
LINE_CHANNEL_SECRET | webhook HMAC 簽章驗證 | |
LINE_OA_CHAT_ID | LINE OA 後台 chat.line.biz 路徑(v9.2 件 3 inline button URL 用) | |
| TG | TG_BOT_TOKEN / TG_EDITOR_CHAT_ID | 原 ThAutoPost 共用 bot(escalate brief / 5/2 拍板不開新 bot) |
CHING_FACTORY_BOT_TOKEN / CHING_FACTORY_CHAT_ID | 青哥 TG @ching_factory_bot 推 escalate brief 用 | |
TEACH_BOT_TOKEN | @qing_factory_v7_teach_bot 教學通道 | |
V8_REVIEW_BOT_TOKEN / V8_REVIEW_MODE / REVIEW_TG_CHAT_ID | 品檢站開關(Stage A → B cutover) | |
| OpenRouter | OPENROUTER_API_KEY | LLM 認證 |
OPENROUTER_MODEL | 預設 google/gemini-2.5-flash | |
OPENROUTER_BASE_URL | 預設 https://openrouter.ai/api/v1 | |
OPENROUTER_TIMEOUT_S | 每輪 LLM call timeout,預設 8 秒 | |
OPENROUTER_REFERER / OPENROUTER_TITLE | OpenRouter 流量來源 metadata | |
| 資料 / 路徑 | QING_DB_PATH | SQLite 路徑(預設 ~/qing_factory_data/qing.db) |
QING_TOOLS_DIR | 工具書資料夾(預設 ~/qing_factory_tools/) | |
SYSTEM_PROMPT_PATH | persona_xiaoqing.md 路徑 | |
| Worker / KPI | WORKER_BATCH_SIZE / WORKER_POLL_INTERVAL | worker 拉佇列頻率 |
JUDGE_LOOKBACK_HOURS / JUDGE_SAMPLE_RATE | classify_batch 自動分類抽樣 | |
HUMAN_RATE_TWD_PER_HOUR / MIN_PER_TICKET | KPI 試算(節省人工成本) |
J 區 · 觸發機制 + 配套(Trigger → Reaction)
| 觸發事件 | 反應 |
|---|---|
| 客戶送 LINE 訊息 | HMAC 簽章驗證 → 入待處理倉 → 200 OK 給 LINE → worker 背景拉 → AI 大腦 run → push 客戶 |
LLM reply 含 [TRIGGER_QUOTE] | 對講機 notify_editor → escalate_queue 入隊 quote → @ching_factory_bot push brief(inline button「💬 跳 LINE OA 後台」) |
LLM reply 含 [TRANSFER_HUMAN] | 對講機 notify_editor_transfer → escalate_queue 入隊 transfer → @ching_factory_bot push 轉真人 brief |
LLM reply 含 [BRIEF_SUMMARY:...] | worker 抽 tag 內容當 escalate brief 的「需求摘要」(v9.1 件 1 / 4 行 label 格式) |
| LINE 群組訊息(補4) | 不寫客戶資料夾 / 直接 push 青哥 |
| 同 message_id 重來(補2) | history / queue idempotency check → skip |
| 每 30 分鐘 cron | 逾時提醒員掃 escalate_queue:warn 2h / critical 24h / 7 天 expired |
| 每 30 分鐘 cron | OpenRouter credits 警報員(API 4 護欄) |
| customers.display_name 沒 | v9.2 件 3:worker 即時 call line_api.fetch_display_name → 寫回 cache(brief 顯示真名) |
K 區 · 已知設計選擇 / 拍板(PM 看的決策史)
- B 方案 escalate brief(5/2 PM 拍):TG 推 brief / 青哥用 LINE OA 後台直接回客戶 / TG 不收 reply 回流。
- 教學通道 TG only:Strider 改 persona / 工具書一律走 @qing_factory_v7_teach_bot / 不混 LINE。
- v9.2 件 3 deep link Fallback A(5/5 search-confirmed):LINE Messaging API userId ≠ OAM userId,LINE 沒公開 mapping API → button URL 砍 user_id 後綴 / 跳 OA chat 主頁 / 小編在未讀第一筆找剛 escalate 的客戶。
- v9.3 worker sanity check trigger 改 escalate-tag-driven(5/5 round 5 audit 修 3 紅):worker 偵測 `[TRIGGER_QUOTE]` / `[TRANSFER_HUMAN]` 任一 → 必含 `[BRIEF_SUMMARY:]`;舊版用「push 字眼」trigger 改成 escalate tag 自身 trigger。
- v9.4 鐵律 8 資訊量底線(5/6 修 brief #23 真實踩雷):brief 至少 1 件具體(車款 / 想改部位 / 預算)才能 push escalate;模糊意圖(「我要改車 / 想改」)→ 純釐清不 push 空 brief。
- 3 層 system prompt cache:persona / business_rules / customer-specific 分離,吃 OpenRouter prefix-cache。
- API 4 護欄:prepaid only / 月 hard cap / 80%-100% 預警 / API key 隔離(對應 SHARED_RULES)。