MiroFish:基于大语言模型的多智能体群体推演引擎

📅 发布时间:2026/9/18 3:49:40
MiroFish:基于大语言模型的多智能体群体推演引擎
这次新品发出去之后大家的反应会往哪边走上个月一个做消费电子的朋友这么问我。他手上有两套预热节奏预算只够投一套想提前知道哪套更容易把讨论热度托起来。以前碰到这种问题要么拍脑袋要么花几万块做小样本调研要么干脆等结果出来再复盘。我给的建议是先别急着下单用MiroFish跑几轮多智能体推演把两种节奏各模拟一遍再决定。MiroFish 是我这两个月一直在折腾的一套群体行为推演引擎它的核心思路很朴素——不指望单个模型给出大家会怎么想而是养一群各自有立场、有记忆、有关系网的智能体让它们互相影响最后看鱼群整体游向哪里。这篇文章我会把 MiroFish 的构件拆到字段级别讲清楚为什么这么设计、跑一次完整推演要经过哪些环节、成本大概多少、以及我在实操里反复踩到的坑。不管你是想拿它做产品口碑预演、做内容选题验证还是纯粹想搞明白多智能体仿真是怎么回事下面的内容都能直接抄。1. MiroFish 到底是什么一群鱼替你做群体判断先说清楚定位否则很容易把它和普通的AI 预测工具混在一起。MiroFish 不是在提示词里塞一段背景、然后让模型吐出一句预计正面情绪占比 65%的那种东西。它是一套基于大语言模型的多智能体群体仿真框架你把一个初始事件丢进去系统里成百上千个被赋予身份、性格、信息渠道和社交关系的智能体开始按时间步演化每一轮里它们会感知、表达、互相影响、改变立场最后系统输出的是一整条情绪与传播的演化曲线而不是一个孤零零的数字。1.1 单模型为什么推不出群体结果这里有个很多人一开始想不通的点现在的大模型知识面这么广直接问它不就完了为什么要费劲养一群 Agent我的实测结论是单模型给出的答案在方向上经常是对的但它在过程和分布上是失真的。你问它这款耳机定价 1299 会不会被吐槽贵它大概会说会有部分用户觉得偏贵但音质和降噪会形成对冲。这句话没错但它没有告诉你吐槽是从第几天开始集中的、先由哪一类人群带起来、会不会在第 3 天被另一波真香内容压回去、以及最终有多少比例的潜在用户会因为价格而流失。原因在于群体反应从来不是个体意见的简单加权平均。它包含阈值效应沉默的大多数需要一个足够响的领跑者才敢发声、从众效应看到持相反意见的人变多了一部分人会悄悄改变表述、回声室效应同质人群互相强化把温和态度推向极端。这些机制在单次推理里根本无处安放因为单模型没有时间这个概念也没有别人说了什么这个概念。MiroFish 把这两样东西补上时间步给了演化过程智能体之间的可见性给了互相影响。所以它输出的不是结论而是结论是怎么长出来的。我一般会跟人说用单模型做判断像看一张终点照片用 MiroFish 做推演像看一段行车记录仪。照片有时候够用但当你要做的是决策尤其是要在两个方案之间选一个的时候过程比终点重要得多。1.2 MiroFish 的三根支柱角色、记忆、关系剥掉所有工程细节MiroFish 的运转就靠三样东西缺一个这套仿真就退化成让模型轮流出声。角色Persona每个智能体的初始设定决定它是谁、关心什么、说话什么调性、对这件事的天然立场偏向在哪里。角色决定了整个仿真的人口结构。记忆Memory智能体在推演过程中读到过什么、说过什么、被别人怎么回应过。记忆决定了它的立场会不会漂移、漂移得多快。关系Relation谁看得见谁、谁的话对谁更有分量。关系决定了信息在网络里怎么传、传多快、在哪个节点被放大。这三样里我个人认为最容易做砸的是关系。角色可以靠人工精修几十张卡然后用模板扩量记忆可以靠标准的时间窗口检索策略但关系网络一旦给错整个推演的方向就会偏离现实——比如你把所有 KOL 类型角色的影响权重都给成一样那么推演结果就永远无法呈现某个腰部博主先起量、头部后跟进这种真实节奏出来的曲线会平滑得不像话看着挺美但没有决策价值。1.3 MiroFish 不负责的事顺便把边界划清楚免得抱错期待。MiroFish 不做实时数据抓取它不联网、不爬数据现实世界的输入得你自己准备好再喂给它它也不做因果推断它能告诉你按这套规则推演情绪在第 4 天会有一个明显的负面拐点但不能告诉你是因为某个具体原因导致的因果关系还是要靠人看细节日志。还有一点MiroFish 的输出不是概率意义上的预测。它更像一个情景沙盘——你在里面试不同的初始条件观察哪些结果是稳健的、哪些是脆弱的。把它的输出当成有 70% 概率会发生 X那是误用。2. 拆开鱼群MiroFish 核心构件的字段级设计这一节讲工程细节也是整套东西真正花时间的地方。我用一个偏通用的结构来展开你可以按自己的业务调整字段名。需要说明的是这套字段设计有一部分来自我自己的实践另一部分是按同类多智能体仿真引擎的通行做法补全的具体到你的项目字段数量可以砍但这三类信息一个都不能省。2.1 角色卡信息密度比数量更重要角色卡的核心矛盾是写太少所有 Agent 长一个样推演结果会坍缩成一个人格的回声写太多Token 成本爆炸而且模型会抓着无关细节不放输出一堆我作为一个住在城南、养了两只猫、周末喜欢骑行的人……这种跑题废话。我的经验是控制在 6 个字段以内每个字段一句话剩下的靠立场这一个字段来锚定行为。下面这张表是我现在在用的版本字段作用常见踩坑身份职业、年龄段、大致消费层级写得太细模型开始编造生活细节立场倾向对这类产品的天然态度-2 到 2全部给 0导致没有人愿意先发声表达风格短句/长文、理性/情绪化、爱不爱反问全给理性客观输出高度雷同信息渠道从哪类来源获取信息全部同源网络传播失去层次影响权重它的话被别人看到的概率倍数全设 1.0等于没有关系网络活跃阈值什么条件下它才开口全设很低前几轮就吵成一团这张表里最关键的是立场倾向和活跃阈值这两个字段它们共同决定了整个仿真的点火速度。我做过一组对照把立场分布设成全部中立、活跃阈值统一设低结果第 1 轮就出现了超过 40% 的 Agent 发言讨论在三个时间步内就完成了曲线是一根陡峭的直上直下——完全不像真实的讨论扩散。后来把活跃阈值改成只有当至少 2 个它关注的角色已经发言、且话题与它的立场冲突时才有 30% 概率开口扩散过程才呈现出了我预期的那种先慢后快、然后进入长尾的形状。2.2 记忆流忘掉什么比记住什么更难记忆流的实现方式通常是一份带时间戳的条目列表每一轮结束前智能体把自己说的话、看到的高权重发言、以及自己立场的当前值写进去下一轮开始时系统按相关度 新近度检索若干条注入上下文。这里最容易被忽略的是遗忘策略。我一开始给了很宽松的检索窗口结果出现了典型的记忆污染某个 Agent 在第 5 轮被一个错误信息带偏之后每一轮它都能检索到自己曾经这么认为的记录于是立场被自己的历史输出锚死了再也没有回来过。整个仿真跑到第 12 轮负面情绪占比虚高了一大截。修正办法有三个我都在用立场用数值字段存不用自然语言存。上下文中只注入当前立场值不注入历史立场表述避免模型从自己的旧输出里读回立场。对被别人说服和自己坚持设不同的记忆衰减率。别人说的内容衰减快自己的判断衰减慢这更接近真实人的认知。每轮结束时做一次硬截断只保留最近 N 条与当前话题相关的记录把闲聊性质的输出直接丢掉。提示记忆流一定要有单条长度上限。我见过一次因为某条记忆长度失控单轮上下文从 3k Token 涨到 40k跑一轮的钱够跑一整组实验。2.3 关系网络与影响权重谁看得见谁关系网络决定了信息怎么流。我在 MiroFish 里用的是两层结构一层是静态关注关系推演开始前就固定好比如普通用户关注 3 到 8 个账号垂直博主关注同行 5 个这层保证网络有基本的连接度另一层是动态可见性每一轮只有活跃的 Agent 的输出会被传播出去传播范围由它的影响权重决定。影响权重我建议用分布而不是定值。实测下来幂律分布少数节点权重很高、大量节点权重接近 0模拟出来的扩散曲线最接近真实讨论的形态。而你如果用均匀分布得到的是每个人都在说、但没人说得响的扁平场景传播速度会快得不真实且完全看不到关键节点带节奏的效果。2.4 时间步与事件总线让推演有节奏感时间步的粒度是个很实际的工程选择。粒度太细比如一分钟一步跑一百轮也才一百分钟除了烧钱没有别的收获粒度太粗一天一步你会把上午发布、中午发酵、晚上反转这种关键节奏整个抹平。我的默认值是一小时一步跑 48 到 72 步覆盖三到四天的完整周期。如果推演对象是快节奏的内容比如剧集更新、游戏版本上线会切成半小时一步如果是长周期的品牌活动会用半天一步。事件总线则负责在指定时间步向指定人群投递外部扰动量比如第 10 步投放第一波达人内容第 24 步竞品发布对比测评。这个机制是 MiroFish 里我最喜欢的设计因为它让方案 A 和方案 B的对比变得非常干净——除了注入的事件不同其他一切完全相同。3. 从零跑一次完整推演种子事件到收敛结果讲完构件说链路。下面这套流程是我跑了几十组实验之后固下来的你可以直接按这个顺序来。3.1 环境、模型与目录约定MiroFish 这一类引擎基本上都是 Python 生态因为并发调度、异步 IO、数据处理这几件事在 Python 里最省事。我用的组合是 Python 3.11 asyncio httpx 做并发请求pydantic 做角色卡和记忆条目的结构校验pandas 做结果分析matplotlib 出曲线。模型接入方面建议准备两档一档是便宜快速的模型用来跑大量普通 Agent 的日常输出一档是能力更强的模型只用来跑高影响权重的关键角色和最后的总结。这个双档模型策略能把整体成本压到三分之一左右而推演质量几乎看不出差别因为普通 Agent 的输出本来就是碎片化的短句。目录结构我固定成这样方便复用和回放mirofish_run/ ├── config/ │ ├── personas.json # 角色卡 │ └── relations.json # 关注关系与影响权重 ├── events/ │ └── timeline.json # 时间步与外部事件注入 ├── prompts/ # 各类提示词模板 ├── runs/ # 每次运行的快照与日志 └── analyze/ └── report.py每次跑之前把runs/下的输出目录命名为run_日期_序号里面存一份完整的 config 副本。这个习惯救过我很多次——当某个结果看起来特别可疑时能精确复现当时的全部初始条件。3.2 事件注入初始感知分布比事件本身更重要同一个事件交给不同的初始人群结果完全不同。所以这一步的重点不是事件写得多煽情而是初始感知分布怎么设。我的做法是把人群切成四类给不同的初始感知概率人群类型占比初始正面概率初始负面概率中立核心粉丝15%0.700.050.25泛兴趣用户45%0.250.250.50价格敏感型25%0.100.550.35旁观型15%0.200.200.60这张表最重要的意义是它逼你把你的用户结构想清楚。很多人卡在这一步是因为他从来没认真想过自己的受众里价格敏感型到底占多大比例。而这个比例一旦从 25% 调到 40%推演结果可能就从整体平稳直接翻成负面占优。所以我现在的习惯是占比这类参数不设单一值而是设成一个区间跑三组低/中/高三档看结论稳不稳。3.3 一个最小可跑的推演循环下面是我从项目里抽出来的最小骨架把并发、记忆、传播三件事都保留了下来。真实项目里会拆成多个模块但核心逻辑就是这一段import asyncio, json, random async def step(agents, step_id, event_bus): # 1. 收集本轮所有活跃发言 outputs await asyncio.gather(*[ a.act(step_id, event_bus.get(step_id)) for a in agents if a.should_speak(step_id) ], return_exceptionsTrue) # 2. 过滤异常按影响权重排序决定谁能被看见 valid [o for o in outputs if not isinstance(o, Exception)] valid.sort(keylambda o: o.influence, reverseTrue) # 3. 广播只有权重靠前的发言进入他人的可见集 visible valid[: max(1, int(len(valid) * 0.35))] # 4. 每个 agent 更新记忆与立场 for a in agents: seen [v for v in visible if v.author in a.following] a.remember(seen, step_id) a.update_stance(seen) return valid async def run(persons_path, events_path, steps48): agents load_personas(persons_path) event_bus load_events(events_path) trajectory [] for s in range(steps): out await step(agents, s, event_bus) trajectory.append(snapshot(agents, s, out)) return trajectory这段代码里有三个参数值得单独说。0.35这个可见比例决定了一轮里有多少内容会被传播太小信息传不出去太大就变成所有人听见所有人退化回究极嘈杂的状态should_speak里要同时考虑活跃阈值和本轮是否有它关心的事件否则会出现没人提这件事但大家突然开始讨论的诡异现象update_stance一定要限制单轮的立场最大变化量比如单轮最多变 0.3否则会出现看一眼就掉头的极端个体把整体曲线带得毛毛躁躁。3.4 结果怎么读看分布和拐点别盯结论跑完之后trajectory里会有几十份快照。我的阅读顺序固定是三步第一步看总曲线也就是每轮正面/中立/负面的占比变化。这条线告诉你整体走向但信息量有限。第二步找拐点。拐点是推演里最有价值的东西第几轮出现了负面加速、那一轮谁在说话、他说了什么。我一般会把拐点前后各两轮的日志全部导出来人工过一遍。很多时候答案非常直白——某个高权重角色说了一句特别绝对的话然后被大量中等权重角色复述情绪就翻过去了。第三步看分组差异。把人群按类型拆开看曲线经常能发现整体看起来平稳但价格敏感人群中负面已经到 70%这种结构性风险而这种风险在总曲线上是完全被平均掉的。4. 把几十个 Agent 撑到几百个成本、并发与可复现Demo 阶段最爽二十个 Agent 跑二十轮几分钟出结果感觉马上就能用。真到业务场景里二十个 Agent 根本代表不了任何人群结构上到三五百个之后三个新问题会同时冒出来钱、并发、以及这次结果和上次不一样怎么办。4.1 Token 预算先算账再开跑我现在的估算方法是先算单轮单 Agent 的上下文和输出再乘上去。单个 Agent 一轮大致是这样组成大致 Token系统提示与角色卡200 - 350检索到的记忆条目5 - 8 条400 - 900本轮事件与可见发言摘要300 - 800输出60 - 150合计约 1000 - 2200按 200 个 Agent、48 轮算总调用量在 9600 次左右Token 总量大致落在 1000 万到 2000 万这个区间。这个数字不算小所以有两个非常实用的优化我基本每次都用一是让绝大多数 Agent 走便宜模型只有影响权重前 15% 的角色走强模型二是让 Agent 不必每轮都发言实际活跃率控制在 25% 到 40% 就足够形成传播这样调用量能直接砍掉一多半。注意不要为了省钱把记忆条目的检索数量砍到 2 条以下。少于 3 条时Agent 基本只能看到刚刚发生了什么立场会变得极度墙头草推演结果会呈现无意义的高频震荡。4.2 并发、限流与失败重试三五百个 Agent 同时发请求必然撞限流。我在这一层的做法是用asyncio.Semaphore把并发控制在服务商允许的七成左右留出余量所有请求走统一的调用封装里面做指数退避重试最多三次超过三次的请求不直接失败而是标记为沉默让这个 Agent 本轮不发言。这个沉默处理很关键。我早期版本碰到限流直接抛异常整个时间步就崩了只能从头重跑后来改成沉默单轮的失败率就算到 5%对整体曲线的影响也几乎看不出来因为传播本来就是靠少数高权重节点驱动的。另外一个细节是同一步内的请求顺序要打乱。如果按 Agent 编号顺序发请求同一批请求会集中打到同一个节点上限流命中率会明显升高打乱之后配合信号量实测失败率能降一半以上。4.3 让推演可复现种子、快照与回放两次跑结果差很多这件事在业务方那里是致命的。他会直接问你这到底是模型的问题还是我的方案真的不一样我的解决办法是三件事一起上。第一所有随机选择都走同一个种子包括 Agent 的发言判定、检索条目的采样、初始可见顺序全部用random.Random(seed)的实例不用全局随机。第二每个时间步结束存一份快照包含所有 Agent 的立场值、记忆条目的哈希、以及本轮被选中传播的发言 ID。第三提供回放功能给定某个快照和后续的种子能够从中间重新跑起。有了这三样方案 A 和方案 B 的对比才有意义同样的种子、同样的人群、同样的网络结构只有注入事件不同那么曲线的差异就大概率来自方案本身。我一般会跑五组不同种子然后看结论是不是一致——如果五组里三组说 A 好、两组说 B 好那说明这个差异在模型噪声范围内不该拿来做决策。5. 六个最容易翻车的点这一节是我踩过的坑里最花时间、也最值得提前知道的六个。前四个是技术性的后两个一半是技术、一半是心态。5.1 回声室把温和立场推向极端多智能体仿真最常见的结果就是群体极化一堆本来偏中立的人聚在一起讨论最后整体态度比任何一个人的初始态度都更极端。这个问题在 MiroFish 里同样存在因为它的传播机制天然会强化内部一致性。我的处理方式是在传播环节加跨圈层曝光每一轮强制让 10% 到 15% 的 Agent 看到圈层外的内容哪怕它并没有关注对方。这个比例不能高高了就变成全民大讨论、失去圈层结构也不能是 0否则极化会一路到底。我实测 12% 左右是个不错的平衡点既能保留圈层差异又能让整体情绪有一个自然的回调。5.2 角色同质化导致结论坍缩第二个坑更隐蔽你的角色卡看起来很丰富但实质上所有人的表达风格和思考方式都一样于是推演变成了同一个声音在自我对话。判断方法很简单——把某个时间步所有发言打乱顺序看你能不能凭文字风格猜出发言人。如果猜不出来说明角色同质化了。解决办法是给表达风格加硬约束比如必须用 20 字以内的短句并且结尾带一个反问必须引用一个具体数字必须用我觉得吧开头。这些约束看起来有点做作但实测能显著拉开角色之间的差异度也能让后续的分组分析真正有意义。5.3 记忆污染与错误级联一个 Agent 记错了一件事然后另一个 Agent 看到了这个错误再复述一遍第三个 Agent 又看到了第二个人说的版本。三轮之后这个错误就变成了大家的共识。这种级联在多智能体系统里极其普遍。我在实践中的应对是在传播环节做来源降权当某条信息在最近两轮内已经被复述过两次以上它的影响权重自动下调。同时Agent 的记忆条目里存一个source_confidence字段二手信息和一手发言分开记检索时优先注入高置信度条目。这两个小改动把错误级联的传播深度从四五轮压到了一到两轮。5.4 时间步设计不合理导致推演失真前面提到过时间步的粒度问题这里说的是另一个维度的失真所有 Agent 都在同一时刻做决定。真实世界里人们的反应是错开的有人秒回、有人隔了半天才看到。同步更新的仿真会人为制造出整点爆发的假象。我在每个 Agent 上加了一个reaction_delay字段用泊松分布采样一个延迟值延迟期间它对当前事件还没看到。就这么一个改动扩散曲线立刻从阶梯状变成了平滑上升和真实讨论的形态接近了很多。5.5 提示词里的隐性引导这个坑我个人觉得最危险因为它会悄悄毁掉整个实验的可信度。如果你在提示词里写请判断这个定价是否合理模型会倾向于给出一个判断如果你写请说说你看到这个价格时的第一反应输出就会自然得多。类似的还有在事件描述里带评价性词汇比如这款备受争议的产品——这一个词就能把负面概率整体抬高。我的自查方法是把注入给 Agent 的所有文本单独打印出来读一遍问自己这段话有没有暗示我希望它往哪边倒。这个检查花不了五分钟但能挡掉很多脏数据。5.6 把推演当成预言最后一个坑是心态问题。我见过有人跑完 MiroFish看到第 36 小时负面到达峰值就按这个时间点排好了全部应对动作。然后现实里峰值出现在第 96 小时所有准备都错位了。正确的用法是把它当敏感性测试工具不是问会发生什么而是问什么条件下会变坏、坏到什么程度、我对哪些变量最敏感。这个视角一转推演结果的用法就完全不同了——你不再是照着曲线排日程而是去找那些一调就翻盘的关键参数然后针对它们准备预案。6. 什么时候该信这套推演这一节是我最想说的部分因为多智能体仿真最大的风险不是技术做不出来而是做出来了却用错地方。6.1 高可信的场景相对比较、结构性风险、敏感变量有三类问题上我对 MiroFish 的输出是有信心的。第一类是相对比较比如方案 A 和方案 B 哪个更容易引发争议——绝对值可能不准但相对排序在多次不同种子下的稳定性相当高。第二类是结构性风险提示比如价格敏感人群的负面占比明显高于整体这类结论来自人群分组不依赖具体数值精度。第三类是敏感变量识别就是找出哪个参数一动结论就翻。这三类的共同点是它们都不要求仿真输出绝对准确只要求内部一致性和相对稳定性。这也是为什么我一直强调要多跑几组种子——看的是稳定性而不是某一组的具体数字。6.2 低可信的场景精确时间点、精确占比、突发热点反过来有三类问题我不建议依赖推演结果。精确的时间点第几小时达到峰值受初始参数影响极大换个种子可能差一整天。精确的占比数字同理推演里的负面占比 43%和现实里的 43% 之间没有直接的数值对应关系。由外部突发热点导致的变化更没法推因为这类事件的触发条件根本不在仿真范围内引擎里没有这个输入输出里自然也不会有。我给自己定的规则是推演结果只用来做排序和方向判断任何需要精确数值的地方都要用真实数据或者小规模灰度测试去校准。6.3 拿真实数据对齐让仿真越跑越像真正让 MiroFish 变得有用的是事后对齐。每次真实事件发生后我会做一件很枯燥但很值钱的事把现实中的讨论量、情绪走向按小时整理出来和当时的推演曲线并排画在一起然后逐段比对——哪一段形状接近哪一段完全对不上。对不上的地方往往能定位到具体原因要么是初始人群结构设错了要么是漏掉了一个关键的外部扰动要么是某个高权重角色的行为模式设得不合理。把这些问题一条条改回去下一次推演就会更贴近现实。我前后做了大概六七轮这样的对齐现在对于同一品类的项目推演曲线的形态已经能对得上七成左右——这个程度不足以拿来做唯一决策依据但用来快速筛掉明显不靠谱的方案效率非常高。我个人在实际操作中的体会是这套东西的价值不在算得准而在想得全。在养鱼群、调参数、看拐点的过程里那些你原本没考虑到的变量会自己跳出来——比如你会突然意识到原来我的用户里有四成是价格敏感型或者原来某个腰部账号的影响权重被我严重低估了。这些认知上的收获比那条曲线本身值钱得多。最后分享一个小技巧如果你的场景比较复杂、Agent 数量上不去可以先只跑单一人群的高精度子仿真——比如只模拟 80 个核心粉丝把他们的行为打磨准然后再用这批参数去扩量到全人群。我在预算紧张的时候经常这么干比一上来就铺几百个粗糙角色的效果好不少。