数字员工的活人感,一半是知道什么时候闭嘴

Half of Liveliness Is Knowing When to Stay Silent

我在一个业务群里看到过这样的三条消息,时间戳原样抄下来:

05:01  天刚亮,打个照面。
10:00  上午好,开工了,随时招呼一声。
10:00:55  本次执行已结束,但有消息是否送达尚未确认,请查看运行记录。
(群里没有任何活人发言)
# generated by hugo AI

发消息的是我们自己的数字员工。三条里没有一条错——问候很客气,状态提示很负责,措辞也没毛病。可我在屏幕这头看着,只觉得尴尬。

尴尬在哪,我想了几天才想清楚:05:01 那一刻,群里没有一个活人在。它不是在跟谁打招呼,它是在跟一个空房间打招呼。 而不到一分钟之后那条运行状态提示,等于它转过身,把后台日志贴到了业务群的公告栏上。

我在 数字员工的活人感,不是设计出来的,是长出来的 里写过:活人感不是皮肤,是行为,是「可验证的一致性」。那篇给了六条行为特征——记忆、主动性、边界、一致性、成长、节奏,又给了六个把它做出来的动作。但这三条消息让我发现那篇漏了一整维:特征和动作全是加法,没有一条讲什么时候该做减法。

这篇补上那一维。我的判断是:数字员工的活人感,一半来自它知道什么时候不说话。

Half of Liveliness Is Knowing When to Stay Silent

一、活人感与现实感,是两件不同的事

先把两个词分开,因为它们的药方完全不同。

活人感 说的是「它像个同事」——有节制、看得懂场合、说话有分寸。它的反面是客服腔和机器人腔。

现实感 说的是「它说的每件事都指向现实」——带得出一条数据、一个待办、一个进展。它的反面是客套话。

那三条消息同时丢了这两样。05:01 和 10:00 两条丢的是活人感(没人说话的时候自己开口,不像同事,像定时器);「随时招呼一声」丢的是现实感(这句话没法接——你回什么?回「好的」?然后呢?)。而 10:00:55 那条同时丢了两个:既是运维噪音(不该在这个群),又是零信息量(没人知道该做什么)。

活人感是「该不该说」,现实感是「说的有没有东西」。前者靠克制,后者靠内容。

这个区分很重要,因为大多数团队只补第二个(改 prompt、加人设、调语气),而第一个是 结构问题——不是措辞能修的,是触发机制错了。你把「天刚亮打个照面」改成任何更自然的话,只要它还是 05:01 由定时器发到一个空群里,尴尬一分不减。

二、开口前的四道闸

我把该问的问题收成四道闸。任何一条过不去,就不发。

       ┌──────────────────────────────────────────────┐
消息 →  │ 闸 1 触发源:谁让我开口的?                    │
       │   事件/被@/有产出 → 过     纯时间点 → 拦        │
       ├──────────────────────────────────────────────┤
       │ 闸 2 信息量:这句话里有几件具体的事?            │
       │   ≥1 件(数据/待办/进展) → 过   0 件 → 拦      │
       ├──────────────────────────────────────────────┤
       │ 闸 3 场子:最近 N 条都是我自己发的吗?           │
       │   有活人在说 → 过         全是自己 → 拦         │
       ├──────────────────────────────────────────────┤
       │ 闸 4 渠道:这是给人看的,还是给运维看的?        │
       │   业务结论 → 群           运行状态 → 监控/私聊  │
       └──────────────────────────────────────────────┘
                          ↓
                     过四道闸才发
# generated by hugo AI

拿这几句话过一遍,每处都死得很清楚:

消息闸 1闸 2闸 3闸 4
05:01「天刚亮打个照面」✗ 纯时间点✗ 零件事✗ 全是自己✓
10:00「上午好,开工了」✗ 纯时间点✗ 零件事✗ 全是自己✓
「随时招呼一声」(10:00 那条的后半句)—✗ 没法接的话—✓
10:00:55「送达尚未确认」✓ 执行结束触发✗ 无行动项✗ 全是自己✗ 运维状态

四道闸里,闸 1 是根,其余三条是它的并发症。因为一旦触发源是「时间到了」而不是「有事发生了」,它必然凑不出信息量(没事发生,哪来的数据)、必然撞上闸 3(没人因为定时器而说话,所以最近几条一定是它自己)、必然要拿客套话填空——定时问候不是措辞问题,是它在结构上无话可说,只能编话说。

三、最强反方:不刷脸,谁会记得它?

这个方案有一个很硬的反方,我得正面回答:数字员工是要被用起来的。它不主动出现,没人记得它存在,没人给它派活,最后变成一个装了没多久就没人用的功能。定时问候至少是一种运营手段——曝光量摆在那儿。

这个担心不是没道理,尤其是刚上线那阵子。但我认为它算错了一笔账:负曝光比没曝光更糟,而且它是有利息的。

在群里刷客套话,活人的反应只有三种:沉默、静音、把它移出群。沉默是最常见的——你没法接「随时招呼一声」,于是没人回,于是它下一条还是没人回。连续几次之后,群成员对这个机器人的印象就从「有个数字员工」变成「那个总在群里刷早安的机器人」。而一旦有人点了静音,它真正有用的产出也一起被静音了——这才是最贵的部分:你为了几次无效曝光,把未来所有有效触达一起卖掉了。

更麻烦的是这笔账是复利的。曝光的收益是一次性的(看到一次,多一次印象),而厌烦是累积的(每刷一次,加一分「这玩意很烦」)。用一次性收益换累积成本,是亏的。

那存在感靠什么?靠 有用的产出被看见。我们的另一个 harness 里根本没有定时问候任务,发言全部由入站消息或事件触发;兜底提示只在真的失败时发一句人话,任务统计默认只走单聊、不进群。它从不刷脸,但没人觉得它不在岗——因为它每次开口都带着一件具体的事。存在感不是发言次数给的,是发言内容给的。

(这里有个边界要说清:冷启动期确实需要让人知道它存在。但正确的做法是 上岗仪式一次讲清——它叫什么、负责什么、怎么召唤它、什么情况下它会主动说话——而不是每天提醒一次。一次说清楚是介绍,天天说是骚扰。)

四、四道闸怎么落到工程上

四道闸不是靠 prompt 里写「请克制」能实现的——模型不会知道自己上一条是什么时候发的,也不知道群里有没有活人说话。这四条全是 运行时状态判断,必须在 harness 里做。

from dataclasses import dataclass
from enum import Enum
from datetime import datetime


class TriggerKind(Enum):
    """谁让我开口的。"""
    EVENT = "event"        # 告警/数据变化/任务完成 —— 有现实发生
    MENTION = "mention"    # 被 @ 或被直接问 —— 有人在等
    SCHEDULE = "schedule"  # 纯时间点 —— 什么都没发生


@dataclass
class SpeechGate:
    """开口前的四道闸。任何一道 False,这条消息就不该发出去。"""

    recent_window: int = 10           # 闸 3:回看最近多少条
    max_self_streak: int = 2          # 闸 3:自己连续发言上限

    def gate1_trigger(self, kind: TriggerKind) -> bool:
        """闸 1 触发源:纯时间点触发一律拦。"""
        return kind is not TriggerKind.SCHEDULE

    def gate2_substance(self, message: str, facts: list[str]) -> bool:
        """闸 2 信息量:必须带至少一件具体的事(数据/待办/进展)。"""
        return len(facts) >= 1 and len(message.strip()) > 0

    def gate3_room(self, history: list[tuple[str, datetime]]) -> bool:
        """闸 3 场子:最近 N 条全是我自己发的,就闭嘴。"""
        recent = history[-self.recent_window:]
        streak = 0
        for speaker, _ in reversed(recent):
            if speaker == "self":
                streak += 1
            else:
                break
        return streak < self.max_self_streak

    def gate4_channel(self, is_ops_status: bool, target: str) -> bool:
        """闸 4 渠道:运行状态不进业务群,走监控或私聊负责人。"""
        if is_ops_status:
            return target in {"monitor", "dm_owner"}
        return True

    def should_speak(self, kind, message, facts, history, is_ops_status, target) -> bool:
        return all([
            self.gate1_trigger(kind),
            self.gate2_substance(message, facts),
            self.gate3_room(history),
            self.gate4_channel(is_ops_status, target),
        ])
# generated by hugo AI

几个实现上的细节,是「想过才知道」的:

  • 闸 3 的 history 必须是真实的群消息序列,不是自己的发送日志。 很多机器人只记「我发过什么」,不记「别人发过什么」,于是永远不知道自己在空房间里说了多久。这条依赖 IM 侧能读到群历史——拿不到群历史的场景,退一步用自己的连续发言计数,效果差一些但聊胜于无。
  • 闸 2 的 facts 要由任务侧结构化传入,不要让模型自己判断「我这句话有没有信息量」。 让模型自评信息量,它会觉得自己每句话都挺有信息量。正确的做法是:任务完成时产出一个结构化结果(指标、待办、进展),有结果才允许触发发言——没有 facts 就没有发言权,这在代码层面就是一行 len(facts) >= 1。
  • 闸 4 是渠道分流,不是内容过滤。 运行状态本身有价值,只是它的读者不是业务群——应该进监控渠道,或者私聊负责人。所以实现上不是「丢掉」,是「改道」。
  • 四道闸要有旁路,且旁路必须显式。 告警、被 @、明确的任务产出,应该直接过闸(TriggerKind.EVENT / MENTION 天然满足闸 1)。危险的实现是把闸门做成可配置的开关然后默认关掉——那样过一阵子就没人记得它存在。

顺便说一个对照。我在 谁停掉了数字员工的早读? 里写过,我的数字员工有个「今日早读」任务:早上 8 点整由定时投递,读公众号后台、读 GA、digest 小红书、更新 token 消耗、读 aibase——五步,预算一小时。那也是定时器触发的,但它过得了闸 2:跑完有实打实的产出,值得开口。同样是 cron,一个触发的是活儿,一个触发的是寒暄。

定时器解决的是「别忘了做」,不是「该不该说」。 这两件事被混在一起,就产生了 05:01 的那句「天刚亮打个照面」——它把「别忘了打招呼」当成了「该打招呼」。

五、把活人感的定义补完整

回到开头那篇旧文。我在那里写:活人感 = 可验证的一致性。 现在我得给它加一条:

活人感 = 可验证的一致性 + 有节制的发言权。

一致性说的是「它是不是同一个『人』」,节制说的是「它是不是一个懂场合的人」。真同事让你舒服,一半是因为他记得你说过什么,另一半是因为 他不废话——他知道什么事该在群里说、什么事该私聊、什么事压根不用播报。

这一半恰恰是当下最被忽略的。整个行业的注意力都在给数字员工加东西:加形象、加音色、加人设、加记忆、加工具、加主动性。加到最后,它在群里一天说上十几二十句话,每句都客气,每句都没法接。而「加主动性」这条尤其危险——没有节制的主动性,比没有主动性更糟:前者让人想把它静音,后者只是让人忘了它。

至于去模板化用语(不用「打个照面」「随时招呼」这种固定客套),我认为它是四道闸的 结果而不是前提。闸 2 一旦生效——每句话必须带一件具体的事——客套话自然就没了,因为客套话的定义就是「不带事的话」。反过来,如果只改措辞不设闸,你会得到一批措辞更自然、但依然没人想回的定时消息。

所以整套规范其实只有一句:先问该不该说,再问怎么说。 大多数团队直接从第二个问题开始,于是做出了一个说话很好听、但没人愿意它说话的同事。


最后留一个我自己也没想清楚的问题:四道闸拦住的是「无话可说时的发言」,但还有一种更难的情况——它有话可说,可那些话此刻不值得打断别人。 比如它半夜跑完了一个分析,结论很重要,但没人在等;比如它发现了一个小异常,够不上告警,但攒三天会变成大事。

前者考验的是它对「紧急度」的判断,后者考验的是它对「累积效应」的判断。这两个都超出了四道闸的能力——四道闸只管「该不该现在说」,不管「该攒到什么时候说」。

你在实际落地里遇到过数字员工「话太多」或者「话太少」的问题吗?特别是那个更难的第二类——它是怎么决定什么时候开口的?欢迎留言聊聊。


See also