后端架构(Unified Pipeline 当前状态)

Phase 1 进行中(2026-06-06 verify)。Design 见 unified-pipeline-final.md

怎么读:

  • 没 context:看 Layer 1(30 秒)
  • 后端 / debug pipeline:Layer 1 + Layer 2(5 分钟)
  • 要改代码 / 写 prompt:Layer 3 各 zoom-in(按需)

图例:✅ Phase 1 已收口 · ⚠️ 部分收口 · ❌ 未做


Layer 1 — Mental Model(30 秒)

整个系统在干什么?

Mental Model — 4 类外部事件 → AI 提案层 → Policy Guard + 3 个 Writer → 7 张 Neon 表

Layer 2 — 后端 Pipeline 拓扑(5 分钟)

后端 Lambda + SQS + 表怎么连?Phase 1 哪里 done 哪里没 done?

后端拓扑 — 4 类外部触发 → 5 Lambda + studio-api → 共享写入层 → Neon 表。颜色编码 ✅⚠️❌ Phase 1 接入状态

file:line 速查:

路径状态位置
contacts-analyzer → Task Orchestratorlambda/contacts-analyzer/src/infrastructure/neon-repository.ts:29
lead-processor → Task Orchestratorlambda/lead-processor/src/core/persist-downstream.ts:38
message-processor → Contact Writer + Task Orchestratorlambda/message-processor/src/infrastructure/neon-repository.ts:25
ai-analysis-processor → Contact Writer⚠️NAME_TRUST 常量,但没用 upsertIdentity()
studio-api → Task Orchestratorapps/api/src/routes/tasks/{close,reopen,postpone}.ts 仍是 raw SQL

Layer 3a — AI Prompt 全景

所有 AI 决策点 / 触发 / 写哪。每个 prompt 节点 clickable 跳全文。

Prompt 全景 — 7 prompt + 1 代码 stage + 1 brain,按 Lambda 分组,标 file:line / 类型 / 写哪

file:line 速查:

#Surface文件:行
Pre-triageai-analysis-processor/.../stages/pre-triage.ts:36 preTriageByRules
Triageai-analysis-processor/.../stages/prompts.ts:23 TRIAGE_SYSTEM_PROMPT
Classifyai-analysis-processor/.../stages/prompts.ts:80 CLASSIFY_SYSTEM_PROMPT
Verifyai-analysis-processor/.../stages/verify.ts:39 VERIFY_SYSTEM_PROMPT
Coachingai-analysis-processor/.../stages/prompts.ts:225 COACHING_SYSTEM_PROMPT
⑤bCoaching Brain🆕 待新建 ai-analysis-processor/.../stages/coaching-brain.ts
Contact Analyzercontacts-analyzer/src/core/prompt-builder.ts:406 buildSystemPrompt
Task Playbook🆕 代码无 — 待新建 lambda + studio-api Playbook tab

Layer 3b — Prompt Before / After 对比

2026-06-02 用户交付 6 份 prompt 文件(02-classification / 03-coaching / 03b-coaching-brain / 04-contact-profile / 05-task-decision / 05-task-playbook)。全部还没接入代码。这一节回答:接入后哪些 prompt 被替换 / 新增 / 不动?

Prompt Before/After 对比图 — 左列当前 5 prompt + 1 代码 stage(live);右列接入后 7 prompt + 1 brain + 1 代码 stage。REPLACE/拆出/新增/不动 按行颜色编码,每个节点 clickable 跳到 prompt 全文

6 份新文件 → 接入位置(精简表)

新文件替代 / 新增接哪个代码文件文档站对应
02-classification-system-prompt.txtREPLACE 老 Classify(prompts.ts:80),改 enum 结构 + 新增 topic_type standard/cancellation 互斥ai-analysis-processor/.../stages/prompts.ts:80 替换 CLASSIFY_SYSTEM_PROMPT 字符串prompts/02-classify.md(覆盖)
03-coaching-system-prompt.txtREPLACE 老 Coaching(prompts.ts:225),只留框架同 prompts.ts 替换 COACHING_SYSTEM_PROMPTprompts/04-coaching.md(覆盖)
03b-coaching-playbook-brain.txt🆕 新增 read-only Brain 层(老代码 0 hit)新建 stages/coaching-brain.ts + 改 coaching.ts 把 brain 拼到 user messageprompts/04b-coaching-brain.md(新增)
04-contact-profile-system-prompt.txt拆出 老 Contact Analyzer 的画像段contacts-analyzer/.../prompt-builder.ts:406 改成 2 个内部函数,这是其一prompts/05a-contact-profile.md(新增,填 planned 占位)
05-task-decision-system-prompt.txt拆出 老 Contact Analyzer 的 task 段同上,这是其二;两段拼成一个 system prompt 一次调用prompts/06-task-decision.md(新增,填 planned 占位)
05-task-playbook-system-prompt.txt🆕 全新增(老代码 grep TASK_PLAYBOOK 0 hit)待新建 lambda + UI Playbook tab 引用prompts/07-task-playbook.md(已存在,2026-06-01 用户提供 V1)

关键约束(防止接入时搞错)

  • 拆开但仍是 1 次 LLM 调用 — 04 / 05 拆开不是 2 次 API 请求,只是把 588 行 prompt 拆成 2 段文本然后在 buildSystemPrompt() 里拼回去(详见 拆 prompt 的方式)
  • 05a 不输出 taskDecisions / 06 不输出 contact 画像字段 — 两个 prompt header 都写明 hard boundary
  • 06 可以读 05a 的输出当输入(单向依赖:先画像后 task 决策)
  • Playbook 三处不要混:(a) Coaching Brain(coaching 读 / 不输出)(b) Store Playbook(06 Task Decision 可选 input)(c) Task Playbook(给 UI Playbook tab 的输出)

Prompt 架构细节

Prompt 的职责边界、standard template、schema 注入方式、registry definition render 规则、DeepSeek / OpenRouter structured-output 边界,统一放在 3-prompt-architecture.md。本页只保留 backend prompt surfaces 的位置图。


Layer 3c — Contacts-Analyzer 节点内部(最复杂)

唯一同时写多张共享表的 AI 节点 / 4 个触发源 / 内部 stage 拆解

                    contacts-analyzer Lambda 内部
                    ════════════════════════════

  触发源(4 种,共享 dailyBatchQueue FIFO):

  ┌────────────────────────────────────┐
  │ per_call_analysis                  │ ← ai-analysis-processor 每通电话后
  │ cron       (EventBridge 06:00 UTC) │ ← daily fan-out 全 active contacts
  │ on_demand  (studio-api manual)     │ ← UI 触发 manual reanalysis
  │ reprocess  (manual replay)         │ ← cooldown gate (24h)
  └─────────────────┬──────────────────┘

                    ▼ SQS body: { phone, storeId, source, ... }
  ┌────────────────────────────────────────────────────────────────┐
  │  handler.ts                                                    │
  │                                                                │
  │  1. resolvePhoneIdentity()  ← 查 store_id / franchise_id        │
  │  2. Context Builder         ← 聚合 calls + messages + leads    │
  │                               + contacts + open tasks          │
  │                                                                │
  │  3. ⑥ Contact Analyzer AI 调用                                  │
  │     ┌──────────────────────────────────────────────────────┐   │
  │     │ system prompt (588 行,Target State 拆成 04 + 05)     │   │
  │     │   ├─ [Target] 模板1: Contact Profile (画像)           │   │
  │     │   └─ [Target] 模板2: Task Decision                    │   │
  │     │                                                       │   │
  │     │ user message: 聚合的 calls/messages/leads/tasks 数据  │   │
  │     └──────────────────────────────────────────────────────┘   │
  │                          │ 1 个 JSON output                    │
  │                          ▼                                      │
  │     {                                                          │
  │       contact 字段 (18 个画像 + DNC + lifecycle...),            │
  │       taskDecisions[] (6 种 action)                             │
  │     }                                                          │
  │                                                                │
  │  4. neon-repository.ts                                         │
  │                                                                │
  │     ├─→ ContactWriter.upsertIdentity()  ✅ 已接入               │
  │     │                                                          │
  │     └─→ taskDecisions.map(d =>                                  │
  │            applyTaskAction(client, d, context))  ✅ 已接入      │
  │            (6 处调用,line 702 / 744 / 773 /                    │
  │             805 / 838 / 871)                                   │
  │                                                                │
  │     全部 statement 放进 db.batch() 原子提交                     │
  └──────────────────────┬─────────────────────────────────────────┘


                   ┌─────────────────────────────────┐
                   │ contacts                        │
                   │ tasks                           │
                   │ task_progress_events            │
                   │ contact_timeline                │
                   └─────────────────────────────────┘

Layer 3d — 共享写入层内部

Phase 1 新建的 4 个 module:接受什么 / 输出什么 / 谁调

                                共享写入层(@retaintive/common/domain)
                                ════════════════════════════════════

                  调用方                                Module                          表
                  ──────                                ──────                          ──

  contacts-analyzer  ──┐
                      │       ┌──────────────────────────┐
  lead-processor    ──┼─────→ │  Policy Guard            │
                      │       │  (in-process,不是      │
  message-processor ──┤       │   独立 Lambda)           │
                      │       │                          │
  studio-api (Phase 2) ┘       │  - DNC sticky guard      │
                              │  - storeId guard         │
                              │  - duplicate guard       │
                              │  - 状态机                │
                              │  - 幂等                  │
                              │  - AI hallucination      │
                              │  - human authority       │
                              └────────┬─────────────────┘
                                       │ 返回: allow / reject / needs_review*

                       ┌───────────────┼───────────────┐
                       ▼               ▼               ▼
              ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
              │ Task         │ │ Contact      │ │ Timeline     │
              │ Orchestrator │ │ Writer       │ │ Writer       │
              │              │ │              │ │              │
              │ applyTask    │ │ upsert       │ │ writeTime    │
              │   Action():  │ │   Identity   │ │   lineEvent  │
              │              │ │ setDNC       │ │ event catalog│
              │ 6 个 action: │ │ touchActivity│ │ Zod schema   │
              │ - create_open│ │              │ │              │
              │ - create_    │ │ NAME_TRUST   │ │              │
              │   closed     │ │ 共享常量     │ │              │
              │ - close      │ │              │ │              │
              │ - update     │ │              │ │              │
              │ - record_    │ │              │ │              │
              │   progress   │ │              │ │              │
              │ - reopen     │ │              │ │              │
              │              │ │              │ │              │
              │ + closeAll   │ │              │ │              │
              │   OpenFor    │ │              │ │              │
              │   Contact()  │ │              │ │              │
              └──────┬───────┘ └──────┬───────┘ └──────┬───────┘
                     │                │                │
                     ▼                ▼                ▼
              ┌──────────┐    ┌──────────┐    ┌──────────────────┐
              │  tasks   │    │ contacts │    │ contact_timeline │
              │          │    │          │    │                  │
              │  task_   │    └──────────┘    └──────────────────┘
              │  progress│
              │  _events │
              └──────────┘

  * needs_review: type 第三态,Phase 1 暂不走该分支(低置信度直接 reject)

  Module 文件位置: callytics-common/src/domain/
    ├─ index.ts             (统一 export)
    ├─ types.ts             (Contract types)
    ├─ policy-guard.ts      + .test.ts
    ├─ task-orchestrator.ts + .test.ts
    ├─ contact-writer.ts    + .test.ts
    ├─ timeline-writer.ts   + .test.ts
    └─ idempotency.ts       + .test.ts

Phase 1 进度速查

✅ 已完成(可以直接用)
   ├─ Schema: tasks.status ['open','closed'] / task_progress_events 表 / attempt_count / source_call_id
   ├─ Shared modules: Task Orchestrator / Contact Writer / Timeline Writer / Policy Guard (含 unit test)
   ├─ contacts-analyzer: 6 处 applyTaskAction 调用
   ├─ lead-processor: applyTaskAction({ action: 'create_open' })
   └─ message-processor: setDNC + closeAllOpenForContact

⚠️ 部分收口(用了共享但没全用)
   └─ ai-analysis-processor: 用 NAME_TRUST 常量 + Drizzle,但没用 ContactWriter.upsertIdentity()

❌ 未做(Phase 1 计划但未完成)
   ├─ studio-api close/reopen/postpone: 仍是 raw SQL,未接入 Task Orchestrator
   ├─ Prompt 拆分: prompt-builder.ts 仍是 single 588 行 prompt(并行外包,文件已交付 — 04 + 05)
   ├─ Coaching brain 拆分: COACHING_SYSTEM_PROMPT 仍含 domain knowledge(文件已交付 — 03 + 03b)
   └─ Task Playbook(⑦): 还没实现

相关文档