[控场AI]
· 9 分钟阅读· 4,763 字

用SwiftUI造多智能体模拟器:零API密钥的AI社会推演

用SwiftUI造多智能体模拟器:零API密钥的AI社会推演

Sorcerer用SwiftUI调用用户已登录的CLI,零密钥实现多智能体社会模拟。

开发者gbrasil720的macOS开源应用Sorcerer,用纯SwiftUI实现了多智能体社会模拟:用户提出一个假设问题并召唤一组角色,即可观察他们逐轮发帖、回复、站队甚至改变立场。项目的核心取巧是不内置模型、不要求API密钥,而是驱动用户已登录的claude、codex或Ollama命令行工具,把推理成本转嫁到既有订阅上。工程上,模拟规则全部实现为纯函数,模型输出必须经apply校验,因而可脱离模型做单元测试;brief中的事实分public、insider、hidden三级实现结构性信息隔离;手写扫描器在JSON流式传输中解析未闭合对象以支撑逐字显示。报告区分模型判断与世界计数,作者也明确表示模拟不能替代现实证据。

用SwiftUI造多智能体模拟器:零API密钥的AI社会推演

Sorcerer应用界面

如果提出一个假设,让一群AI角色在虚拟社区里讨论、争吵、改变立场,会怎样?开发者gbrasil720为此做了一个名为Sorcerer的macOS应用——纯SwiftUI编写、MIT协议开源,用户只需输入一个“what if…?”问题,再召唤一组角色,就能观察他们逐轮发帖、回复、站队,甚至“倒戈”。

这个项目最有趣的地方不在于“多智能体”这个概念本身,而在于它如何用极轻量的方式绕开了大模型应用常见的工程泥潭:不申请API密钥、不自建推理后端、不依赖第三方库。作者在技术博客中拆解了完整实现,本文提炼其中值得借鉴的设计决策。

不碰API密钥:直接驱动用户已登录的CLI

Sorcerer的核心取巧在于:它不内置任何模型,也不要求用户填写API Key,而是调用用户电脑上已经安装并登录的命令行工具——claude(Claude Code)、codex(ChatGPT)或本地Ollama。这意味着推理成本被转嫁到用户已有的订阅上,应用本身零推理开销。

首个工程障碍很典型:从Finder启动的macOS应用拿不到终端里的PATH环境变量,导致claude命令报“command not found”。作者的解法是启动一次交互式zsh(zsh -ilc)把$PATH问出来并缓存,同时加了5秒超时,防止shell被插件或提示符卡死。这个细节看似琐碎,却是所有“调用本地CLI的GUI应用”都会踩的坑。

针对不同提供方,每个后端只是一个switch分支。Claude以无工具、无会话持久化、无用户配置的方式运行,并使用stream-json输出格式——因为该格式同时携带订阅的5小时与每周用量窗口,应用可以在用户触达限额之前主动暂停。Codex走codex exec --ephemeral -s read-only,Ollama则是向localhost:11434发一个流式POST请求。

zsh -ilc 中的 -i 表示交互模式(interactive),-l 表示登录模式(login),-c 则让 shell 执行后面跟随的命令字符串。登录 shell 会依次读取 /etc/zprofile、~/.zprofile、~/.zshrc 等配置文件,用户手动安装的 Node、Homebrew 或 npm 全局包所追加的 PATH 正是在这些文件里生效的。macOS 从 Finder 或 Dock 启动的图形应用继承的是 launchd 提供的精简环境,根本不经过任何 shell 初始化,因此终端里能用的 claude 在 GUI 里就找不到。用登录交互式 shell 跑一次 echo $PATH,等价于“请 shell 按用户真实配置告诉我路径”,再把结果缓存给应用使用,是这类封装 CLI 的 GUI 工具的通用做法。加超时则是因为 .zshrc 里常夹杂版本管理器、提示符框架等耗时逻辑,甚至是等待输入的命令,不设上限可能永久挂起。

角色即数据结构,规则即纯函数

Sorcerer中的角色是一个朴素的Codable结构体Persona,包含handle、role、bio、traits字典、interests、goals、mood(对假设的态度,-1到1)、memories(每轮由模型写一行)、ties(handle到关系类型的映射,如“同事”“竞争对手”)以及insider标记。关系网决定了影响力如何传播,记忆则提供跨轮次的连续性。

真正让这个项目可测试的是Engine.swift的设计哲学——文件头注释直言“提示词与解析。纯函数,因此模拟规则无需模型即可测试”。每轮roundPrompt把整个世界状态交给模型:角色阵容(含角色、情绪、关系、最近4条记忆和上一个动作)、历史事件、本轮注入内容、最近24条帖子(每条带稳定ID如A3表示分支A的第三条),以及每个角色“可以知道什么”。

模型扮演所有角色,且必须只返回JSON,包含posts(作者、回复目标、文本、情绪)、reactions(对某帖的赞同/反对计数)和memories。提示词会引导写实性:不是每个人每轮都发言,观点逐步漂移,回复更常来自有关系的人,安静的角色也会用赞同/反对表态。

关键在于apply函数不信任模型输出,而是做校验:丢弃未知作者、钳制情绪值、动作必须在实验允许列表内、只有离开的人才能“回归”、每轮最多一次邀请。由于是纯代码,swift test可以在不调用模型的情况下覆盖全部规则。

纯函数指输出只由输入决定、不修改外部状态、不产生副作用的函数,同样的世界状态必然得到同样的校验结果。在 Sorcerer 里,apply 接收模型返回的 JSON 和当前世界状态,返回一个全新的、经过清洗的状态对象,而不是就地修改旧对象,这让每次模拟推进都可以被重放和比对。配合 Swift 的 Codable 协议,模型输出的 JSON 能直接解码成强类型结构体,字段类型不匹配会在解码阶段暴露而非悄悄污染状态。swift test 使用 XCTest 框架,可以构造若干手写的模型输出样本,断言非法作者被丢弃、情绪被钳制在 -1 到 1 之间、动作被限制在允许列表内。这种“规则在代码里、模型只提供原材料”的分层,使得更换模型或调整提示词时,模拟的合法性底线不随之漂移,也是把不可靠的生成式组件纳入可测试系统的常见范式。

谁知道什么:三级知识边界

Sorcerer允许用户指向一个GitHub仓库(通过用户自己的gh登录)、文件夹、网站或文件,生成一份brief,其中每条事实被标记为public、insider或hidden。knowledge()函数据此为每个提示词构建知识区:insider事实只送达标记为insider的角色,hidden事实无人可见,并以“角色从不引用他们不可能知道的事实”收尾。

隐私边界同样明确:读取源文件时只读文档,.env和密钥文件永不打开,角色看到的是brief而非用户的代码。这个设计对做企业内部模拟或产品反馈推演的场景尤其重要——它把“信息隔离”变成了提示词工程之外的结构性约束。

流式解析半个JSON

为了在界面上显示“@wren正在输入…”并让文字逐字浮现,Sorcerer在JSON还在流式传输时就进行解析。partialRound函数是一个手写的小型扫描器:定位"posts"、跟踪花括号深度与字符串转义、把每个闭合的{…}解码为完成帖,再从当前未闭合的对象中提取author和部分text。注释写着“永不抛错:半个JSON在这里是常态”。

这是全文最值得玩味的技术点——流式UI的体验瓶颈往往不在模型速度,而在于你敢不敢在数据不完整时就渲染。Claude(配合--include-partial-messages)和Ollama支持流式,Codex暂不支持,已作为issue #21公开征集贡献。

大模型接口的流式输出(streaming)通常按 token 或小块逐步返回文本,调用方拿到的是不断增长的字节流,而非一个完整的响应体。若等全部生成完毕再解析,用户会在数秒内面对空白界面;边收边解析则能实现打字机效果和进度提示。难点在于 JSON 是结构化格式,任意截断点都可能落在字符串中间、转义字符之后或嵌套对象内部,标准解析器会直接报错。因此需要手写增量扫描器:维护花括号深度栈、识别 " 与 \\ 的转义状态、记录当前所在字段名,只在遇到配对的右花括号时才尝试解码一个完整对象。Claude 的 --include-partial-messages 与 Ollama 的流式接口都提供这种增量输出,而 Codex 目前只返回最终结果,这也是该功能尚待补齐的原因。

分支、倒带与“计数而非猜测”

模拟世界支持分叉:fork()复制当前分支(同一批人、同一批记忆),分配新字母名并记录“from B at r3”之类的父系标记。若要回到过去某轮分叉,rewind(_:to:start:)会过滤帖子、事件、动作与投票,剔除后来加入的人,移除标记为后续轮次的记忆,并根据帖子重建每个人的情绪。作者坦承一个权衡:赞同/反对计数是总量,后续轮次的反应会残留在旧帖上。

报告系统刻意区分“模型判断”与“世界计数”。如果六个人回答投票,报告显示“would you pay $14 · no · 3 / 6”来自真实答案;动作统计只计每人最新动作,先“升级”后“取消”只算一次取消。这个区分让模拟输出具备一定的可审计性。

技术栈与参与方式

应用侧是纯Swift + SwiftUI + Swift Package,零第三方依赖,swift run即可跑,swift test即可测。落地页用Bun + Turbo monorepo(React、TanStack Router、Tailwind、Hono、tRPC),部署在Cloudflare Pages。发布流程是推一个vX.Y.Z标签,CI负责测试、构建通用DMG并发布。

作者已开放若干范围明确的issue,包括非默认Ollama主机、VoiceOver无障碍标签、新实验模板、Ollama流解析单测、CI集成Biome与落地页构建、移除残留脚手架模块,以及最受关注的Codex流式回复支持。他还建议用Ollama测试以免消耗Claude或ChatGPT额度。

值得一提的是作者自己的克制表述:模拟揭示的是可能的反应,不能替代现实证据。这句话应该成为所有多智能体社会模拟产品的默认免责声明——它的价值在于探索反应空间,而非预测真相。

Swift Package Manager 是苹果官方的依赖管理与构建工具,通过 Package.swift 声明目标和依赖,swift run 直接编译运行可执行目标,swift test 运行测试目标,无需打开 Xcode 工程即可完成日常开发与 CI 构建。零第三方依赖意味着供应链面积极小,也避免了 SPM 解析远程包时的网络与版本冲突问题。落地页侧的 Bun 是兼作运行时、包管理器与打包器的工具,Turbo 负责 monorepo 中的任务编排与缓存,TanStack Router 提供类型安全的路由,Hono 是轻量 Web 框架,tRPC 让前后端共享类型定义而免去手写接口契约,Cloudflare Pages 则承担静态与边缘部署。两侧技术栈的共同取向是“少依赖、强类型、构建快”,与桌面端保持一致的工程哲学。

核心要点

  • 通过调用用户已登录的CLI(claude、codex、Ollama)实现零API密钥、零自建后端的多智能体模拟
  • 模拟规则全部落在纯函数中,模型输出必须经过apply校验,因而可脱离模型做单元测试
  • 三级知识边界(public/insider/hidden)用结构性方式隔离信息,同时避免读取敏感文件
  • 流式解析“半个JSON”支撑逐字打字的UI体验,是流式渲染的实用范式
  • 报告区分模型判断与世界计数,承认模拟的局限而非包装成预测工具
#多智能体模拟器#SwiftUI#Sorcerer#Claude Code CLI#Ollama#开源macOS应用#流式JSON解析
分享:

相关推荐