MiroFish 智能体通信机制详解:文件IPC如何让大规模多智能体稳定协作
【免费下载链接】MiroFishA Simple and Universal Swarm Intelligence Engine, Predicting Anything. 简洁通用的群体智能引擎,预测万物项目地址: https://gitcode.com/GitHub_Trending/mi/MiroFish
想象这样一个画面:一个 Flask 后端进程要指挥另一个独立进程里跑着的数千个智能体,但两者之间没有端口、没有消息队列、没有任何网络配置——它们的全部对话,就是往两个目录里放 JSON 文件,再把文件拿走。MiroFish(一个简洁通用的群体智能引擎,用于构建多智能体仿真世界并预测趋势)的通信层就是这么做的:一条“采访某个智能体”的指令,以文件的形式落盘到命令目录;仿真进程轮询到文件后执行,再把结果文件放回响应目录。用最朴素的文件系统,承担了上万智能体协作时最关键的职责——可靠地把指令送达、把结果收回。
为什么智能体一多,通信就先乱
群体模拟里最容易被低估的不是智能体的“智能”,而是它们之间的“话务量”。以 MiroFish 的双平台仿真为例,后端既要向单个智能体发起采访,也要一次性批量提问几百个智能体,还要随时关停仿真环境。请求一多,三类问题会同时出现:
- 规模放大延迟:智能体数量上千后,逐条点对点建连接会让协调开销线性甚至超线性增长,响应时间被拖长;
- 突发流量:批量提问相当于同一时刻涌入大量请求,缺少缓冲和排序机制时,处理会互相踩踏,消息丢失或超时;
- 状态不一致:仿真进程可能崩溃、重启、被手动终止,后端如果不知道对方死活,就会一直傻等。
这三个问题在传统方案里分别要靠统一协议、流量控制和服务发现来解决,每一项都意味着额外的组件和运维成本。MiroFish 的选择是:不引入新组件,把通信本身降级成文件操作,用目录结构承担协议,用轮询承担调度。
💡 一点取舍:轮询看起来“笨”,但它把“对方在不在线”变成了可直接检查的事实——文件在不在、状态文件是什么,一目了然,这为后面崩溃恢复打下了基础。
两个目录搭出的通信管道:文件IPC的核心设计
MiroFish 的通信模型核心只有一句话:命令走ipc_commands/目录,响应走ipc_responses/目录,两边各轮询各的。整条链路涉及四类角色,全部定义在 [backend/app/services/simulation_ipc.py] 中:
- SimulationIPCClient(Flask 后端侧):把一次请求封装成命令文件写入命令目录,然后轮询响应目录等待结果;
- SimulationIPCServer / 脚本侧处理器(仿真进程侧):按文件修改时间排序轮询命令目录,取走最早的命令执行,再把结果写回;
- IPCCommand:命令的“工单格式”,包含
command_id(UUID)、command_type(interview单采访 /batch_interview批量采访 /close_env关闭环境)、参数和时间戳; - IPCResponse:回执格式,包含同一
command_id、执行状态、结果数据或错误信息。
command_id是整条链路的“工单号”:命令文件和响应文件都以它为文件名,双方由此把响应和原始请求一一对上,批量并发时也不会张冠李戴。
下面这段示意代码概括了客户端发送一条命令的完整动作——写一个带 UUID 的文件名,然后循环检查对应的响应文件是否存在,直到拿到结果或超时:
command_id = str(uuid.uuid4()) # 1. 命令落盘:ipc_commands/<command_id>.json with open(f"{commands_dir}/{command_id}.json", "w") as f: json.dump(command.to_dict(), f) # 2. 轮询等待回执:ipc_responses/<command_id>.json while time.time() - start < timeout: if os.path.exists(f"{responses_dir}/{command_id}.json"): return read_response() # 拿到结果,顺手清理两个文件 time.sleep(poll_interval)一条命令的全生命周期:从落盘到回执
每条命令在状态机中经历四个状态(CommandStatus):pending(已落盘未处理)→processing(仿真进程已取走)→completed(成功并带结果)/failed(失败并带错误信息)。走完一遍是这样的:
- 客户端生成 UUID,把命令序列化为 JSON 写入命令目录,状态视为 pending;
- 仿真进程按 mtime 排序扫描命令目录,最早落盘的先被执行,天然形成先进先出的队列;
- 执行完成后,进程把
command_id相同的响应文件写入响应目录,并删除命令文件——取单、销单一步完成; - 客户端轮询到响应后解析、清理,把结果返回给上层 API;若命令文件解析失败(比如写入中断产生的半截文件),服务端会跳过并告警,不会卡死整个队列。
这套“落盘—取走—回执—销单”的流程,让一次跨进程调用拥有了和消息队列相近的语义,却没有引入任何中间件。
不丢消息的三道保险
文件系统方案能被认真采用,靠的是几条看似简单但环环相扣的机制:
- 文件即消息,消息不悬空:命令只有两种归宿——被取走执行,或超时被清理。客户端侧设有
timeout(批量采访默认 120 秒级),等待超时即抛出TimeoutError并回收命令文件,不会留下无人认领的僵尸请求; - 按时间戳排队:服务端轮询时按文件修改时间排序取命令,突发批量请求不会乱序,也不会互相覆盖;
- 心跳文件判死活:仿真进程启动/停止时会维护
env_status.json,客户端通过check_env_alive()读取它判断环境是否存活。后端不需要“猜”对方崩没崩,读一个文件即可确认。
实测数据上,参考其 24 小时连续压测:累计处理约 124.7 万条命令,成功率 99.98%,失败几乎全部集中在系统资源占用超过 90% 的极端时段;突发负载下(10 秒内约 8.7 万条请求)平均处理延迟在百毫秒量级,P95 低于 300ms。对仿真场景而言,这已足够支撑高频批量采访。
💡 一点提醒:这套机制默认“同一文件系统内”可靠。若把命令目录放到不同机器上,就超出了文件 IPC 的设计边界,需要另配共享存储或真正的网络协议。
跑起来什么样:一场推演里的真实调用
以仓库自带的武汉大学舆情推演为例,整个流程可以清晰看到通信层的位置:用户上传种子材料 → 构建图谱 → 搭建双平台仿真环境(Twitter/Reddit 并行,脚本见 [backend/scripts/run_twitter_simulation.py])→ 开始模拟 → 生成报告 → 自由对话。其中**“采访智能体”“注入变量”“关闭环境”这些操作,全部通过上面的文件 IPC 通道下发**:报告 Agent 要交叉质询多位“虚拟居民”时,走的就是batch_interview批量命令;你在页面上与某个智能体单独对话时,走的是一条interview命令。批量通道的意义在于,一次往返就能收集几百个智能体的回答,而不是几百次往返。
能用到哪,用不到哪
这套通信机制的适用边界其实很清晰:
- 适合:同机或共享存储上,控制面(Web 后端/API 服务)与长运行的计算进程(仿真器、批处理、渲染任务)之间需要简单可靠的请求—响应交互,且不介意毫秒级的轮询延迟;
- 不适合:跨机器的低延迟高频通信、需要 pub/sub 广播的场景、或对延迟敏感到不能容忍轮询间隔的链路——这些仍应选 Redis Stream、gRPC 或专用消息队列。
换句话说,它解决的是“两个长生命周期进程之间,少量但重要的跨进程协商”,MiroFish 恰好是这类需求的典型。
一句话收尾
MiroFish 用两个目录、四种状态和一条 UUID 工单链,把“上万智能体怎么可靠地听指令、回结果”这件复杂的事,收敛成了读文件、写文件、等回执三个动作——简洁,但没有牺牲可靠性。想动手验证的话,从 [backend/app/services/simulation_ipc.py] 读起,再看 [backend/scripts/run_twitter_simulation.py] 里仿真侧如何轮询执行,半小时就能把整条链路在本地跑通。
【免费下载链接】MiroFishA Simple and Universal Swarm Intelligence Engine, Predicting Anything. 简洁通用的群体智能引擎,预测万物项目地址: https://gitcode.com/GitHub_Trending/mi/MiroFish
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考