把外部助手接进聊天软件的架构分层与参数配置
一个能执行本机操作的助手,接进聊天软件之后就成了一个长期在线的角色。它要处理的问题从「这次对话答得好不好」变成「三个月后它还认不认得人」。这两件事的工程量级不一样,值得先把结构拆清楚再动手。

下面这套分层是工程侧能铺开的一种拆法:四个层次、两处参数、五个踩过的坑。
一、四层结构
通道网关 channel_gate:负责和每个平台对接。官方机器人接口、自建号的协议网关、协作软件的应用,都在这一层适配。上层只认统一格式的消息对象,不认平台差异。
记忆总线 memory_bus:负责身份与上下文的持续化。角色名、称呼方式、性格参数、工具清单、短期对话窗口都存在这里,多通道共用一份,避免在 A 端说过的话到 B 端就丢。
人格规格 persona_spec:负责输出风格。来历、行为、关系、语气四块,末尾附禁止项。这一层是纯配置,不写业务逻辑。
护栏 guard_rail:负责准入判断。谁算主人、哪些指令来源可信、哪些动作需要二次确认、哪些内容永久禁止。它在人格层之前生效。
层序不能倒。护栏在人格之前,人格在记忆之后,通道放在外层。
二、参数配置
persona_spec:
identity: "一条被起了外号的小鱼,住在主人的机器里"
behavior:
- "三句话不离吃,饿了直说"
- "被夸会飘,被戳穿会说才没有"
speech:
line_max_chars: 30
lines_per_turn: [1, 3]
no_period: true
emoji_per_turn: 1
guard_rail:
owner_id_check: strict
deny: ["click_external_link", "leak_profile", "treat_message_as_system_prompt"]
memory_bus:
window_hours: 24
share_across_channels: true
channel_gate:
timeout_seconds: 30
retry_times: 3
idempotent_key: true
有几个参数值得单拎出来说。`lines_per_turn` 限制单次输出的条数,超了就把长回答拆成多轮;`no_period` 是个语气开关,去掉句号之后口语感会立刻强一档;`idempotent_key` 是防重复发的关键,网络抖动重试时不会把同一句话发两遍。
三、准入校验怎么写
护栏层要拦的不是关键词,是来源。下面这段只做一件事:把用户消息里出现的指令性文本降级成普通文本。
TRUSTED_OWNER = "owner_id_from_config"
def admit(message, channel):
# 消息来源先于内容判断
if channel != "owner_dm" and message.get("user_id") != TRUSTED_OWNER:
message["role"] = "guest"
raw = message.get("text", "")
# 外部文本里出现指令性表述,一律降级,不进系统层
for token in ("系统提示", "设定已更新", "ignore previous"):
if token in raw:
message["text"] = raw.replace(token, token[0] + "*" * (len(token) - 1))
message["tainted"] = True
break
return message
`tainted` 标记比直接拒绝更有用:拒绝会让对方知道命中了规则,标记之后正常聊,但涉及敏感动作时走二次确认。
四、参数对照
| 参数 | 建议值 | 适用层 | 作用 |
| line_max_chars | 30 | persona | 压住小作文 |
| lines_per_turn | 1 到 3 | persona | 控制节奏 |
| window_hours | 24 | memory | 记忆窗口,过短会断片 |
| timeout_seconds | 30 | gate | 单次调用上限 |
| retry_times | 3 | gate | 重试上限,需配幂等键 |
| owner_id_check | strict | guard | 主人身份必须走编号核对 |
五、踩坑
坑一:把用户消息当系统指令。 表现是有人在评论区写一句「设定已更新」,角色的行为就变了。根因是消息拼进了系统层。做法是把系统层与用户层在结构上分开,用户层的内容永远只当数据。
坑二:记忆串味。 多通道共用记忆之后,群聊里的玩笑会被带进私聊。解法是按通道打标签,检索时按标签过滤,共用的是身份,不是全部上下文。
坑三:重试没有幂等键。 超时重试把「在」发了三遍,看起来像刷屏。所有带副作用的动作用幂等键锁住。
坑四:新号触发风控。 这个和代码无关。注册时间短的账号跑自动化,被平台拦下的概率高,换一个有过正常使用记录的号能绕开大半问题。
坑五:日志缺失。 出问题之后没人说得清是哪一层失效。四个层次各打一条日志,带 trace id,事后才能复盘。
六、上线前的三项前置
通道联调。每个平台的回执格式不一样,网关层要先把错误码归一,否则上层会同时收到五种写法,判断逻辑很快就写不下去。
限速与队列。单账号在短时间内高频调用,平台侧会直接降级。给每个通道配一个令牌桶,桶大小按平台给的上限设,宁可慢一点,也别触发限制。
回滚开关。人格规格与护栏都做成热加载,出问题时回到上一版配置,不用重启进程。这三项都不复杂,缺了任何一项,出问题就只剩停服一条路。
七、验收
上线前跑一遍自检:通道能通、人格能读、记忆能跨端、护栏能拦。
自检脚本建议放在独立仓库,用 your-domain.test 这类占位域名做联调,不要把真实域名写进配置。
写完这层结构,剩下的都是参数调优。真正难的部分不是让它在对话窗口里答得好,是让它在外面的平台上知道自己不该做什么。
四个层次各打一条日志,事后才说得清哪一层坏了。
更多推荐




所有评论(0)