现状(多决策者,易不同步)
回复、写库、回执三条链;飞鸽可先「说对了」但库/回执未跟上。
flowchart TB
U([用户发话]) --> G[prepare_turn_labeling / 门控]
G --> GW{门控直写?}
GW -->|是| DB1[(数据库)]
GW -->|否| R1[Router 只读/写意图]
R1 --> LLM1[主 LLM 第 1 轮]
LLM1 --> T1{自选 tool?}
T1 -->|调| DB2[(数据库)]
T1 -->|不调| X1[无写库]
DB1 --> RC0[回执收集器]
DB2 --> RC0
X1 --> RC0
RC0 --> LLM1b[主 LLM 回复 / 或模板覆盖]
LLM1b --> GUARD[空诺校验]
GUARD --> UI([用户可见: 正文 + 回执行])
style GW fill:#fde8e8
style LLM1b fill:#fff3e0
style RC0 fill:#e3f2fd
侧路/门控直写(与 LLM 不同步)
回复可能先于或未依据回执
回执需各路径手工接线
目标(单入口写库,回执驱动说话)
已实现方向:小模型 tool 阶段 → 回执 → 主 LLM speak;门控不直写;无空诺兜底。
flowchart TB
U([用户发话]) --> R2[Router: 只读 vs 可能写库]
R2 -->|只读| LLM_RO[主 LLM 直接回复]
R2 -->|可能写库| LLM_T[主 LLM · 仅 tool 阶段]
LLM_T --> T2[写库 tool 0~1 个]
T2 --> DB3[(数据库)]
DB3 --> V{库内验证}
V --> RC[TurnReceiptCollector]
RC --> INJ[注入 system: 本回合回执块]
INJ --> LLM_S[主 LLM · speak 阶段]
LLM_S --> GUARD2[空诺兜底]
GUARD2 --> UI2([正文 + UI回执行])
LLM_RO --> UI2
style T2 fill:#e8f5e9
style RC fill:#e3f2fd
style LLM_S fill:#e8f5e9
style GW fill:#fde8e8
唯一写库入口 + 回执后再说话
回执 = 事实;LLM = 人话
目标架构 · 单回合总览
门控保留「分类 / pending / 开放哪些 tool」,但不在 gate 内 commit。
flowchart TD
START([POST /api/chat]) --> SAVE_U[落库 user 消息]
SAVE_U --> PRE[prepare_turn_labeling]
PRE --> GATE[classify_labeling_gate]
GATE --> GOUT{输出}
GOUT -->|dialogue / await_*| PEND[写入 pending 状态
不直写业务表]
GOUT -->|suspect_*| PEND
PEND --> POL[resolve_turn_write_policy]
POL --> RO{is_readonly?}
RO -->|是| SYS_RO[组装 system
库内快照 + 对话]
SYS_RO --> GEN_RO[LLM 生成回复]
GEN_RO --> OUT
RO -->|否| SYS_W[组装 system + 开放写库 tools]
SYS_W --> PHASE1[LLM 阶段 A: tool-only
禁止完成态口头承诺]
PHASE1 --> CALL{调了写库 tool?}
CALL -->|是| EXEC[执行 tool handler]
CALL -->|否| NOREC[回执: skipped / 无 ok]
EXEC --> VERIFY[verified_*_from_db]
VERIFY --> REC[record_ok / pending / 无 UI 失败行]
NOREC --> REC
REC --> PERSIST[persist_turn_receipts]
PERSIST --> SYS_S[system += 回执块 + speak 指令]
SYS_S --> PHASE2[LLM 阶段 B: 无 tool
仅据回执组织回复]
PHASE2 --> SAN[sanitize 空诺兜底]
SAN --> ATT[assistant 消息 + action_receipts_json]
ATT --> OUT([响应 + UI 回执])
style GATE fill:#fff8e1
style EXEC fill:#e8f5e9
style PHASE2 fill:#e8f5e9
style REC fill:#e3f2fd
写库回合 · 两阶段 LLM(详图)
与你的要求一致:不是「回执覆盖回复」,而是「无回执不说话、有回执再生成」。
sequenceDiagram autonumber participant U as 用户 participant S as 服务端 participant L as 主 LLM participant T as 写库 Tool participant D as 数据库 participant R as 回执收集器 U->>S: 用户消息 S->>S: Router 判定非只读,开放 tools S->>L: 阶段 A messages(含 pending / 快照) Note over L: 只允许 function call
不输出用户可见完成态 L->>T: confirm_* / batch_cancel / relay_* … T->>D: 事务写库 D-->>T: 行存在 / 失败 T-->>S: tool JSON S->>R: record_* + 库内验证 R-->>S: ok | pending | 无 ok S->>L: 阶段 B messages + 【本回合回执】 Note over L: 规则:
有 ok → 自然口语确认
无 ok → 说明未落实 + 引导
禁止编造回执外细节 L-->>S: 最终 reply 正文 S->>S: 空诺校验(兜底) S-->>U: reply + turn_receipts / action_receipts
| 阶段 | LLM 能做什么 | 不能做什么 |
|---|---|---|
| A · tool | 选择并调用 0~1 个写库 tool;可输出极短思考(可选,不展示) | 不对用户说「已登记/已传达/已改名单」等完成态 |
| B · speak | 根据回执块用飞鸽口吻回复;可引用库内快照细节 | 声称回执中不存在的动作;与 UI 回执行矛盾 |
只读回合(查进度 / 闲聊 / 问名单)
不强制 tool;无写库回执;仍以库内快照为事实来源。
flowchart LR
U([用户]) --> H{heuristic / LLM
readonly?}
H -->|是| SNAP[注入账户快照 / 进度块]
SNAP --> LLM[主 LLM 单轮回复]
LLM --> OUT([无写库回执])
H -->|否| W[进入写库回合流程]
Pending 与异步(传话 fulfill)
不是每轮都有 ok;回执块须区分 ok / pending,LLM 话术随状态变化。
flowchart TD
RELAY[传话 tool 或承诺轮] --> RM[relay_messages / 承诺 mes_type]
RM --> F{fulfill 后台}
F -->|本回合完成| OK[回执 ok: 已入库/已传达]
F -->|排队中| PEND[回执 pending: 处理中]
OK --> SPEAK_OK[LLM: 已记下要点,对方会话可见]
PEND --> SPEAK_P[LLM: 已记下,送达以下一轮回执为准]
SPEAK_OK --> UI3[UI: 三态行 尚未传达→未读→已读]
SPEAK_P --> UI3
各动作 · 写库入口对照(改造清单)
实现时可按表逐项:去掉左侧「现状侧路」,统一为右侧 tool + 回执 + speak。
| 用户意图 | 现状写库入口 | 目标 tool | UI 回执示例 | Speak 依据 |
|---|---|---|---|---|
| 话题黑/白名单 | 门控直写 gate + 可选 tool | confirm_save_help_topics | 增加1条黑名单 | 回执 summary → LLM |
| 登记需求 | 侧路 auto(未接线)+ tool | confirm_register_collecting_need | 已登记 N 条需求 | verified need 行 |
| 取消需求 | tool | batch_cancel_collecting_needs | 已取消 N 条需求 | verified cancel ids |
| 日常传话 | tool + 后台 fulfill | confirm_daily_relay_to_buddy | pending → 三态行 | ok/pending 分话术 |
| 发征询 | tool | relay_match_invite | 已发出征询 | invite ok 回执 |
| 绑定 collecting | 侧路 + tool | bind_collecting_need | 已绑定:… | need 行摘要 |