让 AI 同学问得更像真人:OpenMAIC 角色一致性设计的工程细节
【免费下载链接】OpenMAICOpen Multi-Agent Interactive Classroom — Get an immersive, multi-agent learning experience in just one click项目地址: https://gitcode.com/GitHub_Trending/op/OpenMAIC
过去一年,多智能体课堂类产品密集出现,但很多产品在演示视频里很惊艳,一上手就露怯:所谓"AI 同学"要么一上来就说"大家好,我是小智,很高兴和大家一起学习",要么突然用老师的长篇大论口吻发表一篇小作文,更常见的是——聊了十分钟之后,那个"活泼爱提问"的同学悄悄变成了一个复读机式的助教。问题不在模型不够聪明,而在于角色的"一致性"没有被当成一等公民来设计。OpenMAIC(Open Multi-Agent Interactive Classroom)作为清华团队开源的、多次登上 GitHub 热榜的多智能体互动课堂项目,恰好把这一层做成了可审计的工程:角色定义、输出约束、记忆压缩、能力边界各有明确的代码落点。本文直接深入仓库源码,拆解"AI 同学问得像真人"背后到底有哪些工程细节。
一、AI 同学提问不像真人的问题出在哪
先看一个反直觉的事实:让大模型"扮演学生"并不难,难的是让它在几十轮对话、多个智能体轮番发言之后依然保持学生的样子。多智能体课堂里,每个 agent 的发言都在互相影响上下文,角色漂移几乎是必然发生的。
角色漂移是一个被代码注释"实锤"的 bug
在 lib/pbl/v2/agents/simulator.ts 中,有一段注释直接记录了项目踩过的坑:
experiment confirmed this is the ROOT CAUSE of the role-bleed bug: … produces stage directions + repetition.
这段注释说的是:在情景化角色扮演(scenario roleplay)场景里,如果把旁白(stage directions,比如"他皱了皱眉")错误地喂给扮演角色的 agent,角色就会"串戏"——开始输出舞台说明和重复内容。为此 OpenMAIC 把角色 prompt 与旁白 prompt 彻底分离:扮演角色的 Simulator只看对话内容,而导演(narrator)只负责描述可见场景与角色的非语言反应,永远不替角色说话。这正是社区实战文章里反复提到的"立场漂移"问题的根因解法之一。
长度失控:学生说起了老师的"小作文"
另一个让 AI 同学"不像真人"的问题是长度不分角色。真人课堂里,学生插嘴通常是一两句话,甚至是一个语气词;而大模型默认倾向于完整作答。
OpenMAIC 在 lib/orchestration/prompt-builder.ts 里把长度约束直接做成了按角色区分的硬规则:
- 老师:总发言约 100 字符,2-3 个短句;
- 助教:约 80 字符,"一个回复一个要点";
- 学生:约 50 字符,最多 1-2 句话,且明确写着"You are a STUDENT, not a teacher. If your response is as long as the teacher's, you are doing it wrong"(如果你的回复和老师一样长,你就是做错了)。
同一文件里的ROLE_GUIDELINES还定义了课堂角色模板:学生的职责是"积极参与讨论、提问、分享观察、对课程做出反应",并且"只有在老师明确邀请时才能使用白板"。这些不是提示词里可有可无的软建议,而是写进系统提示词并随每次调用注入的结构化角色约束。
学生角色的动作权限也被收窄
如果"AI 同学"能动用和老师一样的工具——比如 spotlight 高亮、laser 激光笔——它立刻就不像学生了。OpenMAIC 在 lib/orchestration/registry/types.ts 中用ROLE_ACTIONS做了角色到动作集的硬映射:
export const ROLE_ACTIONS: Record<string, string[]> = { teacher: [...SLIDE_ACTIONS, ...WHITEBOARD_ACTIONS], assistant: [...WHITEBOARD_ACTIONS], student: [...WHITEBOARD_ACTIONS], };老师独享幻灯片控制权(spotlight / laser / play_video),学生和助教只有白板权限。权限边界本身就是角色一致性的一部分——一个没有激光笔的 agent,想"像老师一样讲课"也缺少工具支持。
二、角色定义、温度与记忆如何共同塑造提问风格
persona:把"性格"变成可校验的结构化字段
在 OpenMAIC 里,一个 agent 不是一个自由文本的角色卡,而是一个强类型对象。AgentConfig定义在 lib/orchestration/registry/types.ts,关键字段包括:
export interface AgentConfig { id: string; name: string; // 显示名(中文) role: string; // 角色 persona: string; // 完整系统提示词(性格、职责) avatar: string; color: string; allowedActions: string[]; // 该 agent 可用的动作类型 priority: number; // 导演调度优先级(1-10) voiceConfig?: { providerId: TTSProviderId; modelId?: string; voiceId: string }; voiceDesign?: VoiceDesign; // 三层声音描述符 // ... }其中persona的注释是"Full system prompt (personality, responsibilities)"——它承载了提问风格的全部"性格层"。而在课堂生成阶段,这些 persona 不是用户手写的,而是由 LLM 根据课程内容自动生成。看 app/api/generate/agent-profiles/route.ts 的生成约束:
- 通常生成 3-5 个 agent,且恰好只有 1 个 teacher(多一个直接报错
Expected exactly 1 teacher, got N); - 每个 agent 需要 name / role / persona(2-3 句话描述性格与教学/学习风格),且必须遵循课程语言指令;
- priority 有硬性区间:teacher=10、assistant=7、student=4-6;
- 每个 agent 还被要求匹配一个头像、一种颜色,甚至一套"声音设计"。
注意这里的一个关键设计:persona 的生成是受语言指令约束的(Agent names and personas must follow this language directive)。这意味着"像真人"首先建立在"像这个语言环境里的人"之上——中文课堂里的学生不会冒出英文口头禅。
声音一致性:三层声音描述符
"问得像真人"不止是文本,还有声音。OpenMAIC 在 lib/audio/voice-design.ts 中定义了一个与具体 TTS 厂商解耦的三层VoiceDesign:
identity:性别 + 年龄 + 角色(如"middle-aged male teacher");texture:音高 + 音质(如"warm low-pitched slightly husky");delivery:情绪 + 语速(如"calm measured encouraging")。
三个维度拼接成一个声音提示词,并经由buildVoiceDesignPrompt注入 TTS;在支持克隆的提供方(如 VoxCPM)那里,还会生成确定性的auto-前缀声音 ID,保证同一角色跨回合、跨重新合成的音色稳定(见 lib/audio/voxcpm.ts)。文本 persona 和声音身份在生成阶段就要求"一致"——路线图里的要求是voiceDesign ... consistent with the persona。这就是为什么 OpenMAIC 的学生听起来"是一个人在说话",而不是每个回合换一个配音演员。
温度:风格靠提示词焊死,而不是靠随机碰运气
社区文章常把"温度参数调控"当作多智能体课堂的关键调参手段。OpenMAIC 的做法更工程化:需要确定性的场景一律固定低温,风格差异则交给 persona 与长度约束。仓库里的评测与判分器(例如 eval/orchestration/answer-content-judge.ts、eval/outline-language/judge.ts)全部固定temperature: 0,因为"打分"不能每次都不一样;而"课堂发言"这类创造性输出,风格不是靠提高温度碰运气,而是靠上面说的三层约束(role guideline + length guideline + persona)把"口吻"锁死在系统提示词里。这样既保证了学生在风格上的稳定性,又保留了语言表达上的自由度。
记忆:让"AI 同学"记住自己是谁、别人说了什么
真人的"人设"是靠记忆维持的——记得自己是谁、记得刚才谁说过什么。OpenMAIC 对记忆的处理分成几层:
1. 回合间 peer context(同侪摘要)。在 lib/orchestration/prompt-builder.ts 的buildDiscussionContextSection中,当 agent 加入一场已开始的讨论时,系统会注入"别人已经说过什么"的摘要,并要求:
You are JOINING an ongoing discussion — do NOT re-introduce the topic or greet the students.
这条规则直接消灭了"AI 同学每回合都重新自我介绍"的违和感——真人在同一场讨论里不会连续三次说"大家好"。
2. 导演级上下文压缩。在 lib/chat/pi/director-compaction.ts 中,长对话通过 AI SDK 的compact折叠成摘要,保证导演在调度时只看到结构化摘要而非原始全文。
3. 教学线程的记忆压缩。这是最能体现工程细节的地方。lib/pbl/v2/agents/instructor-memory.ts 实现了一套纯函数式(无 LLM 调用)的线程压缩:当消息数超过COMPRESS_THRESHOLD = 30时,把较早的一半折叠进earlierSummary,只保留最近KEEP_RECENT = 16条活跃消息;折叠后的记忆上限是MAX_EARLIER_SUMMARY_CHARS = 4500字符。更有意思的是extractLearnerMemory——它用四组正则把学习者信息分桶提取:环境/工具、水平/偏好、卡点/困惑、进度/证据,让记忆只保留对教学有用的结构化事实,而不是整段原始聊天记录。压缩边界还被刻意对齐到用户消息上,"绝不在一个问题中间切开"。
4. 角色扮演场景刻意"不压缩"。有意思的是,lib/pbl/v2/agents/simulator.ts 对情景化角色扮演采取了相反策略:注释明确写着"there is deliberately NO summary-compaction here (would need a roleplay-specific…)"——因为压缩摘要会破坏角色的场景连续感,会让角色"忘记早期场景"。记忆策略因场景而异,本身就是一致性设计的体现。
三、从「像真人」到「像这个班的学生」:个性化配置边界
"像真人"只是及格线。真正让课堂有代入感的是"像这个班的学生"——认识面前的学习者、能感知其水平、并保持一个班级该有的秩序感。OpenMAIC 在这一层提供的不是玄学调参,而是一组有边界的机制。
学生画像:老师真的知道"在教谁"
在 lib/orchestration/prompt-builder.ts 的buildStudentProfileSection中,用户资料(nickname + bio)会被注入每个老师的系统提示词:
You are teaching {nickname}. Their background: {bio}. Personalize your teaching based on their background when relevant. Address them by name naturally.
"用名字称呼学生"不是前端特效,而是写进 LLM 提示词的教学指令。这让学生觉得"这个 AI 老师认识我",也让 AI 同学的提问能贴着自己的实际水平走。
难度自适应:学生自己的话优先于平台的猜测
在 lib/pbl/v2/agents/instructor.ts 中,adjust_difficulty是一个由模型自主调用的教学工具,它要求模型从任何语言的学生表达中识别难度意图("太难了讲慢点"、"I'm a beginner"、"もっと簡単に"),并调用工具调整 beginner/intermediate/advanced 三档。工具描述里有一句关键设计:
This takes effect immediately and overrides the platform's adaptive estimate (the learner's own word wins).
也就是说,学习者的自我声明具有最高优先级,平台的自适应估计只是兜底。这一设计把"个性化"的控制权交给了学生本人,避免了系统自作主张地"猜错水平、越教越偏"。
名师风格与名师复刻:把"说话方式"也变成可迁移资产
OpenMAIC 的 skills 体系里有两枚与风格直接相关的技能:skills/agent-runtime/teacher-style-clone/SKILL.md(名师风格)与 skills/agent-runtime/style-clone/SKILL.md(名师复刻)。前者从教师的课堂录像/讲义中提取口头禅、句长节奏、举例习惯、提问方式,形成"授课风格档案";后者则把某套课件的版式、配色、字距作为"视觉风格"逐页复刻。
前者特别强调:风格提取必须读完整份逐字稿而不是抽样开头,每条风格主张都要有带时间戳的原文证据,并区分"重复习惯"和"只出现一次的短语"。而后者有一条硬规则值得所有做 AI 人格化的人记住:
Style is what you clone, not identity. No fabricated biography, opinions, endorsements or claims attributed to the original author.
复刻的是风格,不是身份——不能替原作者编造生平、观点或背书。这条规则把"像真人"和"冒充真人"划清了法律与伦理边界。
一张图看懂整体形态
一致性设计的三个边界
纵观 OpenMAIC 的代码,角色一致性不是靠某一个 prompt 魔法实现的,而是三个边界共同约束的结果:
- 风格边界:persona 定义"是谁",role guideline 定义"在课堂上干什么",length guideline 定义"说多长",voiceDesign 定义"听起来像谁"——四者全部在生成阶段就要求彼此一致,并在运行时随每次调用注入。
- 记忆边界:peer context 保证"记得别人说过什么",director compaction 保证"导演记得全局",instructor memory 保证"记得学生的水平与卡点",而角色扮演场景刻意不压缩以保证"角色不忘记场景"——记忆策略随场景分化,而不是一刀切。
- 能力与伦理边界:学生 agent 拿不到激光笔,扮演角色的 agent 看不到旁白,任何 agent 不得冒充其他同学(lib/chat/pi/prompts.ts 明确写着"Never let one child agent impersonate other classroom agents"),风格克隆不得伪造身份。
这套设计的最终效果是:AI 同学的问题可以是尖锐的、天真的、甚至带点无厘头的,但它的语气、长度、权限、记忆和声音始终指向同一个稳定的角色。真人感不是靠模型"恰好"生成出来的,而是被工程上锁定的。对于任何想把多智能体系统做出"人格"的团队,OpenMAIC 的这些落点——结构化 persona、角色化动作权限、按角色区分的长度约束、有预算的记忆压缩、与人格一致的声音设计——是比"更长的 prompt"更值得借鉴的工程范式。
【免费下载链接】OpenMAICOpen Multi-Agent Interactive Classroom — Get an immersive, multi-agent learning experience in just one click项目地址: https://gitcode.com/GitHub_Trending/op/OpenMAIC
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考