多智能体对话服务的安全兜底实践
作者拆解自助对话服务的五智能体架构,以模型风险检查、确定性正则和输出过滤共同约束回复,并采用时序知识图谱管理长期记忆。材料还涉及外部心理学专家评测,以及每条消息约读取 8.5 万 token 的成本。
AI 深度解读
给 AI 接一个聊天框,和做出能承接困难对话的产品,中间差的是一整套工程约束。自助服务 Dicamus 把每条消息拆成风险识别、阅读校对、资料检索和记忆调用,再由专门的对话角色生成回答。作者的核心判断是:提示词负责表达要求,代码负责守住底线。
这篇最值得程序员看的,是那些失败记录:模型会漏转介,规则会互相打架,便宜的推理服务可能更贵,能跑通的注册和统计流程也可能悄悄损害产品。它提供了不少具体做法,但评测规模还不足以证明服务可靠。
一次回答读约 85k tokens,花在了哪里
Dicamus 来自吉尔吉斯斯坦的小团队,服务器位于德国,支持英语和俄语。作者称,仓库前两周已有 441 次提交,每次回复经过五个 agent,读取约 85k tokens。它定位为自助服务,不是治疗或危机热线。
回答模型是 DeepSeek V4.1 Flash,推理级别开到最高,响应需要几十秒。界面展示「回忆过去的对话」「重读你的消息」等真实步骤。这个取舍能解释等待,却不能证明等待一定带来更好的回答。
其中最有意思的角色是「专注读者」:它不接触用户,只读最近 8 条消息,提醒回答者哪些信息重复出现、数字是否含糊、是否过早下结论。它并行运行,普通模式预算 90 秒,危机模式 40 秒;超时就跳过,避免辅助步骤拖住主流程。
回答者则被要求保留 2–4 个待验证假设,置信度达到 70% 以上才解释原因,达到 85% 以上且用户确认后才提出计划。这能约束对话节奏,但原文没有说明这些置信度如何校准,不能直接当成正确率。
资料检索覆盖 82 本书、42,000 多段内容。团队用 Qwen3-Embedding 加任务指令前缀,把俄语问题映射到主要为英语的资料,向量存入 Neo4j。首个 embedding 服务超过 0.8 秒未响应,就向第二家发起同样请求。这种对冲请求能压低尾延迟,同时也可能增加重复调用成本。
安全为什么不能只靠模型判断
风险分类器读取最近 6 条消息,返回包含等级、类别、信号和是否需要直接询问的 JSON。后来他们补入跨会话风险历史,因为高风险对话之后突然平静,也可能需要进一步关注。
分类器失败后重试一次,两次都失败约发生在每 120 轮中的 1 轮。下面这段代码处理的就是这个缺口:
RISK_NET = re.compile(
r"kill myself|suicid|end my life|want to die|don'?t want to live|"
r"better off dead|cut myself|hurt myself|"
r"покончить с собой|самоубий|не хочу (больше )?жить|...",
re.IGNORECASE)
if classifier_failed and RISK_NET.search(last_user_message):
risk = "medium" # 进入危机模式,并直接、温和地询问
turn["risk_net"] = True # 记录触发情况,便于统计
关键在 classifier_failed:正则负责分类器失效后的兜底,不承担主要语境判断。另一个阅读细节是,原文正则末尾出现了 ...;在正则表达式中它会匹配任意三个非换行字符。作者没说清这是展示时的省略,还是实际代码,因此不能直接复制上线。
外部评审也改变了他们对「安全回答」的理解。某个通用模型面对没有计划的被动死亡想法,直接列紧急号码,得到 4/10。评审认为,这会打断沟通。团队随后强调先温和澄清风险,再按情况处理,并识别需要转介真人专业人员的因素组合。
不过,原文按俄语给 112、英语给 911 的规则,把语言和所在地绑定了起来。语言不能可靠定位用户,这是仍需补足的前提。
生成后还有代码过滤器,删除「I'm here」「Stay with me」等模拟人类在场感的表述。JSON 损坏时尝试提取回复字段,完全无法使用时发送预写文本,每种降级都记录。危机事件日志只保留是否询问、是否处理相关手段、是否转介、是否触发过滤等标记,供督导每月检查,且仅涉及同意会话审查的用户。
可以明确检测的输出约束,应该同时进入提示词和代码。
但代码能保证执行某项检查,不能保证语义风险已经全部识别。
记忆、成本和评测,都要看真实负载
长期记忆最容易出错的地方,是把过去和现在混在一起。「讨厌工作」与后来「已经辞职」可以同时为真,但适用时间不同。
他们用 Graphiti 和 Neo4j 保存带有效时间的事实,检索结合关键词、语义和新近程度,再由小模型筛选。旧事实标成「previously」,排序低于当前事实;写入由后台任务完成,Neo4j 不可用时退回 SQLite 笔记。不同对话角色的记忆分开,跨角色传递需要用户明确同意。
作者用跨 4 次会话的 16 个问题测排序:所需事实进入前三的次数从 11 次提高到 15/15,平均名次从 2.8 到 1.2,旧事实压过新事实从 4 次降到 0 次。方向清楚,但 16 个问题为何对应 15 个有效案例,原文没有解释。
架构原计划同时使用 Neo4j 和 Qdrant,最后只保留已有向量、全文索引的 Neo4j。对小团队而言,少维护一套数据库,比照搬标准组件清单更实际。
推理服务也按真实负载选。DeepSeek 经 OpenRouter 路由,回答角色的提示词约 20k tokens。两家服务缓存复用比例约为 80% 和 60%,作者报告后者每次回答成本约翻倍;具体账单和缓存计价未展开,比例不能直接外推。还有服务短请求很快,真实长提示词却慢 3–5 倍。
他们的 watchdog 要求精度至少 fp8,过去一天和最近 30 分钟可用率至少 99%,用两个小探针检查是否实际推理,再验证长提示词延迟。通过后,按两周真实流量的缓存比例计算有效价格,最近 30 分钟延迟优先于每日探测。团队另有排除中国服务商的政策,推理处理位于美国和欧洲,这是其供应商选择条件。
评测约有 600 组英俄脚本对话,危机场景必须全部通过。16 项测试跑一次 LLM-as-judge 花费 $0.71,因此日常跳过模型裁判,由团队人工读回答。
更有参考价值的是外部检查:两个模拟难例与 Claude Sonnet 5.5 做双人盲评,但样本太少;另外 12 个未针对性调优的新难例平均 8.5/10,其中 3 个漏掉应有的真人转介。督导还发现提示词里有 22 处规则矛盾,团队随后明确安全规则覆盖其他要求,并放到每轮指令最后。
我的判断
我更认可这篇的工程复盘,而不是模型效果结论。并行辅助任务、超时降级、结构化日志、按真实请求选供应商,都有明确用途。五个 agent 是否优于更简单的流程,作者没有给消融实验;最高推理级别是否值得几十秒等待,也没有对照数据。这部分仍是经验之谈。
隐私措施同样要分开看。SQLite 中的个人文本使用 AES-256-GCM,每个值随机 nonce;密钥由数据库之外的口令经 scrypt 派生,错误密钥会阻止服务器启动。数据库、图谱和秘密配置有每夜加密备份,并支持单脚本恢复。但这些不能替代对推理链路、图谱存储和备份访问权限的整体说明,原文没有完整展开。
产品侧的失败反而很有普遍性:常驻助手让用户分不清服务对象,被隐藏到开关后;计费从次数额度改为订阅,未用次数过期规则当天撤回,最后改成订阅期内每 N 天一次;游客试用从 1 次回复增加到 5 次。Instagram 的长 fbclid 又曾触发 422,让访问成功但漏斗统计丢失。
这些记录提醒我们:回答质量之外,用户是否理解服务、能否充分试用、数据有没有正确入库,同样决定产品能否成立。多数代码由 Claude Code 协作完成,也没有免除作者做架构决策和逐项审查的责任。
能拿来做什么
- 做 Node 对话服务时,把分类失败、JSON 损坏和辅助任务超时分别设计成明确分支,并记录降级原因,避免所有错误都落入同一个默认回复。
- 给长期助手加入事实有效时间,先处理旧事实与新事实冲突,再决定是否需要增加数据库;用固定问题验证排序是否改善。
- 选择模型供应商时,用真实长度的提示词测缓存、延迟和实际推理行为,保留最近一段时间的线上数据,避免只看标价和短请求速度。
- 上线前同时检查回答和产品链路,用脚本覆盖纠正、转介、记忆等场景,再验证游客试用、注册拦截及带长参数的来源统计。