Wippy 为什么使用 Lua

每位技术评估者都会问这个问题,所以这里直接给出答案。

运行时要求

Wippy 在隔离进程中运行用户定义的逻辑。每个进程需要自己的内存空间、自己的一组可用能力,并且除非运行时明确允许,否则无法触及其边界之外的任何东西。平台在单个实例上并发运行数千个这样的进程,每一个都可能为不同的租户执行不同的代码。

这意味着嵌入在每个进程内部的语言运行时必须:

  • 极小。 每个进程运行在自己的隔离环境中。在数千个并发进程的规模下,每个进程的内存都很重要。Wippy 的目标是每个进程约 13 KB 的基线开销。
  • 完全可沙箱化。 运行时必须精确控制每个进程可以访问哪些模块、函数和系统调用。没有环境权限。没有全局状态在进程之间泄漏。
  • 可嵌入。 语言运行时必须是一个库,Wippy 的核心(用 Go 编写)可以按进程实例化、配置和销毁它。它不能是一个外部进程或独立的二进制文件。
  • 模块加载具有确定性。 进程启动时,运行时决定它能看到什么代码。没有文件系统访问。没有能够伸入任意路径的 require。依赖来自注册表,按进程限定作用域。
  • 对 LLM 友好的语法。 智能体会生成和修改代码。语言必须足够简单,使得 LLM 能够可靠地读取、编写并推理它,而不会臆造语法。

评估过的语言:Python、JavaScript、Go 和 WASM

Python

AI 工作负载的默认选择。我们排除了它,因为 CPython 每个解释器的内存占用是 10-30 MB,比一个 Lua 进程大好几个数量级。Python 的导入系统赋予代码对文件系统、网络和操作系统的环境访问权。对 Python 做沙箱化要么需要编译为 WASM(这会破坏大多数库),要么需要对解释器做大量打补丁。Python 的并发模型(GIL)也与我们的按进程隔离模型相冲突。生态系统对独立脚本是优势,但对于需要确定性地控制代码可访问范围的沙箱化运行时来说则是负担。

JavaScript(V8/QuickJS)

V8 很快,但体量巨大(每个 isolate 数十 MB)。QuickJS 小到足以嵌入,但 JavaScript 的原型链和动态模块系统使沙箱化比看上去更难。importrequire 都想伸向文件系统。生态系统期待 npm,而 npm 假定存在网络访问和可写文件系统,这两者在 Wippy 进程内都不存在。我们会把更多时间花在与语言的假设作斗争上,而不是构建产品。

Go

Wippy 的核心是用 Go 编写的,所以这个选项很有吸引力。但 Go 无法嵌入。你不能在另一个 Go 程序内部把 Go 运行时实例化为一个库。Go 插件是存在的,但它们很脆弱,与宿主进程共享内存,且无法被沙箱化。Go 适合运行时本身,但不适合用户代码。

WASM

在沙箱化方面确实很强,我们已经把它构建为 Wippy 的第二个运行时(见下文)。但仅靠 WASM 不足以作为智能体开发的主要语言。直接编写和调试 WASM 的开发者体验仍然粗糙,而且 LLM 生成面向 WASM 的代码不如生成 Lua 那样可靠。当你需要在 Wippy 沙箱内运行来自其他语言的已编译代码时,WASM 是正确的选择。而对于主要的开发和智能体编写体验,Lua 是正确的选择。

为什么 Lua 满足全部五项要求

Lua 正是为这种用例而生的。它是生产环境中被嵌入最多的脚本语言,运行在《魔兽世界》、Roblox、Redis、Nginx/OpenResty、Cisco 和 Juniper 网络设备、Adobe Lightroom 以及数百款游戏引擎内部。它已经在充满敌意的环境(用户运行不受信任代码的游戏)中被嵌入超过 25 年。

内存

一个 Wippy Lua 进程的基线开销约为 13 KB。在 10,000 个并发进程时,这大约是 130 MB 的基线进程开销。在 Python 中,同样的数量需要 100-300 GB。这不是理论上的顾虑;它决定了是运行在单台机器上还是需要一个集群。

沙箱化

Lua 的模块系统就是一个由宿主完全控制的函数(require)。把它替换为一个只解析进程被授予内容的自定义加载器,进程就只能看到你允许的东西。这里没有 import os,没有 subprocess,没有环境文件系统访问;这些函数根本不存在于进程的环境中。沙箱是默认状态,而不是在开放系统之上打的补丁。

嵌入

Lua 的接口以小巧著称。标准 C API 大约有 60 个函数,而纯 Go 实现让它嵌入 Wippy 的 Go 核心变得直截了当,无需 cgo。创建和销毁一个进程的 Lua 环境成本很低;Wippy 在每次进程启动时都这样做,没有可测量的开销。

确定性的模块控制

在 Wippy 中,一个进程能加载哪些代码由它的注册表作用域决定。Lua 加载器从注册表而不是文件系统解析模块。如果某个模块没有授予某进程,那么从该进程的视角看这个模块并不存在。这就是多租户隔离在代码层面的工作方式:不同租户可以有不同的可用模块,由运行时强制执行,而不是由应用逻辑执行。

对 LLM 友好

Lua 的语法极简:没有类,没有装饰器,语言中没有内置的类型注解,没有 async/await,没有复杂的模块解析。见过 Lua 的 LLM 一次就生成正确 Lua 的可靠性,远高于生成正确的 Python(带有装饰器模式、上下文管理器和类型系统)或 JavaScript(带有原型链、this 绑定和多种模块风格)。对于一个由智能体编写和修改自身工具的平台来说,这一点很重要。Wippy 用类型注解系统(泛型、联合类型、通道类型)和内置 linter 扩展了 Lua,因此你能获得类型安全而不必承受语法复杂度。

协程

Lua 原生支持协程,这直接映射到 Wippy 的并发进程模型。每个进程运行在一个向调度器让出的协程中。没有线程。没有锁。进程之间没有竞态条件。数千个并发进程可以协作,而无需基于线程的并发所带来的复杂性。

你会失去什么

Lua 的生态系统很小。没有像 pip 或 npm 那样拥有数万个包的等价物。这是有意为之:在 Wippy 中,依赖是带有声明能力和安全策略的注册表条目,而不是从互联网拉取的任意包。但这意味着你无法在 Wippy 进程内 pip install pandas。需要重型库支持的数据处理(ML 模型推理、复杂数值计算)应当作为 Wippy 智能体通过工具调用的外部服务运行,或者作为 WASM 函数在 Wippy 沙箱内运行。

Lua 对大多数开发者来说也比较陌生。学习曲线是真实存在的,但很短;Lua 的整个语言参考大约 30 页。大多数懂任何一门编程语言的开发者都能在一天内写出 Lua。这种陌生感是一项摩擦成本,但对于一个大多数用户代码短小、面向工具且越来越多由 AI 生成的运行时平台来说,架构上的收益(沙箱化、内存、嵌入)超过了它。

Lua + WASM:完整图景

Wippy 不是一个只有 Lua 的平台。它附带两个运行时:

Lua 是用于智能体开发、工具编写和应用逻辑的主要运行时。大多数 Wippy 代码在这里编写,智能体也在这里生成代码。小巧的占用、完全可沙箱化和对 LLM 友好的语法使它成为正确的默认选择。

WASM 是用于已编译工作负载的次要运行时。如果你有 Rust、Go、C 或任何可编译为 WebAssembly 的语言的现有代码,你可以在 Wippy 内运行它,享有与 Lua 相同的进程隔离和注册表集成。WASM 函数和进程通过 WASI 集成时钟、I/O、文件系统(通过挂载的 Wippy 文件系统条目)和环境访问。这意味着你可以把现有业务逻辑带入 Wippy 沙箱,而无需用 Lua 重写。

这两个运行时共享相同的进程模型、相同的注册表和相同的安全策略。Lua 智能体可以调用 WASM 函数。WASM 进程可以通过注册表调用 Lua 函数。它们在同一个系统中是对等的。

另请参阅