Skip to content

拟人化自动发言 Agent 架构理解

🕒 Published at:

拟人化自动发言 Agent 架构理解 ​

1. 项目目标 ​

目标不是构造一个一问一答的聊天机器人,而是构造一个会在真实聊天流中自动出现、沉默、插话、连发、跳话题的拟人化 agent。

传统聊天机器人逻辑是:

text
用户输入 -> 模型回复

本项目需要的是:

text
消息流 + 时间流 + agent 内部状态 -> 是否发言 -> 何时发言 -> 发几条 -> 每条说什么

因此,agent 的输出不一定是回复,也可能是沉默。沉默本身就是拟人化行为的一部分。

2. 核心判断 ​

拟人化能力不能全部依赖模型内在能力,尤其不能指望小模型仅靠微调学会完整的人类聊天行为。

合理分工是:

text
外部 Agent / 统计行为模型:控制什么时候说、说几条、沉默多久
LoRA 内容模型:控制这一条消息具体怎么说
RAG / 记忆系统:提供历史场景和相关记忆

也就是说:

text
行为节奏靠 Agent
语言风格靠 LoRA
历史记忆靠 RAG

模型可以学习局部表达风格,但不适合作为第一版的全权行为调度器。

3. 数据建模原则 ​

最小单位必须是单条消息,而不是一轮回复。

真实聊天不是严格的一问一答,两个人可能随机离散发言,也可能各说各的。不能假设一条消息天然是在回复上一条消息。

因此,初级数据结构应保持原子消息:

json
{
  "msg_id": 12345,
  "platform": "platform_a",
  "time": "2024-01-01 12:00:00",
  "sender": "target",
  "type": "text",
  "text": "...",
  "gap_prev_sec": 8,
  "gap_next_sec": 42
}

所有会话切分都只判断相邻两条消息之间是否应该断开:

text
message_i | message_i+1

不做以下假设:

text
用户消息 -> 目标人格回复
连续同一发送者消息 -> 必须合并
两个平台消息 -> 天然属于同一条时间线

4. 会话断点策略 ​

第一层使用时间断点,按平台分别处理,不跨平台混合。

推荐断点层级:

text
gap > 60min:强断点
gap > 30min:候选断点
gap > 15min:弱候选断点

第一步先用 60 分钟切出高置信自然大段。随后在大段内部标记 30 分钟和 15 分钟候选断点。

后续再借助 LLM 判断候选断点附近是否发生了自然聊天场景切换。

LLM 不应判断“谁回复谁”,只判断:

text
这两条相邻消息之间,是否像一个聊天场景的结束与另一个聊天场景的开始。

推荐给 LLM 的上下文形式:

text
左侧若干条消息
---
候选断点:间隔 N 分钟
---
右侧若干条消息

输出结构:

json
{
  "is_boundary": true,
  "confidence": 0.82,
  "reason": "前后话题和聊天场景明显变化"
}

5. 行为调度模型 ​

agent 运行时不应该每次收到用户消息就回复。用户消息只是事件,不是命令。

事件来源包括:

text
用户发出一条消息
用户连续发出多条消息
当前会话已经沉默一段时间
当前话题与目标人格高兴趣主题相关
agent 刚刚发过一条消息
当前处于连续发言状态
当前处于低活跃时间段
RAG 检索到相似历史场景

调度器周期性计算发言概率:

text
speak_score =
  话题兴趣
+ 是否被明显 cue 到
+ 当前聊天热度
+ 目标人格自身表达冲动
+ 随机扰动
- 冷却时间
- 离线/低活跃状态

超过阈值或抽样命中后,agent 才发言。否则保持沉默。

6. 输出不是回复,而是事件序列 ​

agent 每次决策的输出可以是空,也可以是一组带延迟的原子消息:

json
[]

表示沉默。

json
[
  {"delay_sec": 18, "text": "确实"},
  {"delay_sec": 6, "text": "这波有点离谱"},
  {"delay_sec": 42, "text": "不过也正常"}
]

表示延迟后连续发出多条消息。

因此运行时结构应是:

text
Message Stream
  ↓
Session State
  ↓
Behavior Scheduler
  ↓
是否发言
  ↓
LoRA Generator
  ↓
Single Message
  ↓
Burst Controller
  ↓
是否继续发下一条

7. LoRA 训练策略 ​

不建议把多年数据按年份顺序覆盖式继续训练成一个 LoRA:

text
year_1 -> year_2 -> year_3 -> year_4

这种方式容易导致后面的年份覆盖前面的年份,产生灾难性遗忘。最终 adapter 更像近期数据,而不是稳定人格。

更推荐将 LoRA 组件化:

text
base model
  + global_lora
  + recent_lora

其中:

text
global_lora:使用全部数据或均衡采样数据,学习长期稳定语言风格
recent_lora:使用最近半年到两年的数据,学习近期话题和当前表达状态

推理时可用不同 scale 混合:

text
global_lora scale = 0.6 ~ 0.8
recent_lora scale = 0.2 ~ 0.5

如果需要研究时间变化,可以单独训练年度 LoRA:

text
lora_year_1
lora_year_2
lora_year_3
lora_year_4

但年度 LoRA 更适合分析和对比,不建议作为第一版主路径。

8. 训练样本构造方向 ​

LoRA 内容训练不应构造成传统问答对,而应构造成:

text
最近 N 条原子消息 + 时间间隔信息 -> 目标人格下一条原子消息

对每一条目标人格真实发出的消息,可构造一个正样本:

text
context = 这条消息之前的若干条消息
target = 目标人格这一条消息

注意 target 仍然是单条消息,不合并连续消息。

行为调度器则从全量消息流中统计:

text
目标人格在他人发言后多久出现
目标人格发一条后继续发下一条的概率
连续发言长度分布
不同时间段活跃概率
session 开始后活跃度变化
沉默多久后基本视为自然结束
哪些话题提高发言概率

9. 推荐阶段路线 ​

第一阶段:

text
按平台保留原子消息
计算 gap_prev / gap_next
60min 强切自然 session
30/15min 标记候选断点
统计基础行为分布

第二阶段:

text
用 LLM 辅助判断候选断点
生成最终自然 message segment
构造 LoRA 内容样本
构造行为统计样本

第三阶段:

text
训练 global_lora
训练 recent_lora
实现 Behavior Scheduler
实现单条消息生成
实现连续发言控制

第四阶段:

text
加入 RAG 记忆
加入话题兴趣判断
加入长期状态和短期情绪
调试发言频率、沉默概率和连发概率

10. 最终系统形态 ​

最终系统应类似一个常驻在线人格进程:

text
监听消息流
维护 session 状态
维护短期上下文
维护长期记忆
周期性 tick
统计调度器决定是否发言
LoRA 生成单条消息
burst controller 决定是否继续发
必要时调用 RAG 或工具

这不是一个被动回答器,而是一个由统计行为模型驱动、由 LoRA 表达风格、由 RAG 提供记忆的拟人化自动发言 agent。