针对轻量模型幻觉问题设计的脚本运行时沙箱,通过限定开发者能力接口约束 AI 生成代码的可靠性。
我有一些项目,但没有太多钱,所以我经常用 Gemini Flash 来处理 UI 任务和一些功能。
Gemini Flash 很快而且便宜,但有时候出人意料地不可靠。
它会产生幻觉,幻想出并不存在的成员或函数,或者为了看起来一切正常而随意使用 as any。
这让我开始思考一个问题。
轻量级模型正变得极其便宜,我想在我的产品中使用它们。但如果模型要生成代码,也许解决方案并不总是使用更大的模型。
如果运行时本身就是为了克服轻量级模型的弱点而设计的呢?
这就是我开始构建 Autolang 的原因。
项目地址:https://autolang.vercel.app 文档地址:https://autolang.vercel.app/docs
Autolang 是一个脚本运行时库,而不是一种通用编程语言。
我将它构建为一个用于 AI 生成代码的小型运行时沙箱。
Developer-defined capabilities
↓
AI-generated script
↓
Autolang Compiler
↓
VM / Runtime
开发者决定 AI 可以访问什么。
例如,与其暴露:
Database.query(...)
我更倾向于暴露一些更受限的东西:
Products.getProducts()
Users.getUsers()
Orders.getRecentOrders()
AI 可以编写业务逻辑,而开发者控制它能使用的功能。
这也意味着生成的脚本不需要访问整个数据库 API 或操作系统。
为什么要再造一门语言?
主要原因并不是我认为人类需要另一门编程语言。
我对编译器感兴趣,所以 Autolang 也是我学习编译器和运行时设计的项目。
但我也想探索一门围绕 AI 生成代码设计的语言是否可行。
例如,静态类型可以在模型生成错误内容时提供更好的反馈。
something went wrong
运行时可以提供堆栈跟踪和类型错误,给模型足够的信息来修复代码。
这对于更便宜的模型尤其有意义,因为它们可能更频繁地犯错。
Autolang 目前拥有或围绕以下几个特性设计:
语法是静态类型的,灵感来自 Kotlin 和 TypeScript 等语言。
Products.getProducts()
.filter { |product| product.remaining }
.forEach { |product|
...
}
我希望生成的代码相对紧凑,因为 AI 不需要生成包安装代码或大量样板代码。
该语言包含 ?.、??、!.、as、is、fun、闭包和标准库等功能。
开发者定义的绑定
Autolang 最重要的部分之一是绑定系统。
开发者可以定义 Autolang 程序可以使用的库:
compiler.registerBuiltInLibrary(
"company/database",
`
class Product(
remaining: Bool
price: Int
)
@js_object
class Products {
@native("get_products")
fun getProducts(): Array<Product>
}
`,
{ autoImport: true },
{
get_products() {
// Native implementation
}
}
);
Autolang VM 保留类型信息。
所以当原生函数返回产品时,VM 知道结果是:
Array<Product>
这与简单地暴露一个通用 JavaScript 函数并让生成的代码做任何想做的事不同。
如果需要更复杂的对象,它们也可以表示为原生 JavaScript 对象:
@js_object
class Product {
@native("get_price")
fun getPrice(): Int
@native("is_remaining")
fun isRemaining(): Bool
}
开发者决定暴露哪些操作。
TypeScript 应用可以编译并执行 Autolang 脚本:
await compiler.compileAndRun(
"main.atl",
`
var expenseCount = 0
var normalCount = 0
var cheapCount = 0
Products.getProducts()
.filter { |product| product.remaining }
.forEach { |product|
when (product.price) {
>3 -> expenseCount += 1
==3 -> normalCount += 1
else -> cheapCount += 1
}
}
println(...)
`
);
console.log(compiler.getOutput());
重要的一点是,AI 不需要知道数据库是如何工作的。
它只需要知道 Products.getProducts() 存在以及它返回什么类型。
开发者保留对实际原生实现的控制。
为什么不使用工具调用?
这可能是人们问的第一个问题。
我不认为 Autolang 是工具调用的替代品。
工具调用在模型需要与外部系统交互时很有用。
我对模型在获取数据后需要执行大量计算或数据处理的情况感兴趣。
例如,想象一个内部 SME 应用:
Users.getUsers()
而 AI 需要对成百上千的用户进行分类:
VIP
NORMAL
INACTIVE
使用工具调用方法,在模型和应用之间反复发送请求会引入额外的延迟和成本。
相反,应用可以将数据暴露给 Autolang 脚本,让脚本在运行时内部执行处理。
所以模型只需生成一次逻辑,而不是为每个单独操作都要求一次往返。
为什么不使用 JavaScript、Python 或 isolated-vm?
JavaScript 和 Python 是通用编程环境。
isolated-vm 为 JavaScript 提供了隔离。
Autolang 试图解决一个有些不同的问题。
我希望这门语言本身对生成的代码来说是小的、静态类型的、受约束的且可预测的。
运行时知道:
目标不仅仅是执行不受信任的 JavaScript。
目标是创建一个环境,让 AI 可以使用开发者明确提供的功能集生成小型脚本。
它是 Docker 或 KVM 的替代品吗?
我并不打算让 Autolang 替代 Docker、KVM、microVMs 或操作系统级隔离。
Autolang 的关注点更加集中:
AI-generated code
↓
Developer-defined capabilities
↓
Autolang runtime
↓
Controlled execution
它旨在成为一个用于 AI 生成脚本的沙箱,这些脚本通过绑定函数操作。
对于需要强大操作系统级隔离的应用,我仍然期望容器或 VM 等技术是合适的选择。
内存和运行时大小
我的目标之一是保持运行时小巧。
当前启用了完整标准库的 Autolang 实例约占用 0.5 MB。
在 Windows 11 上,一项 1,800 行的测试峰值约为 3.8 MB。
这些是早期测量而非正式基准,但对于我试图构建的轻量级运行时来说,这是令人鼓舞的。
我目前想到的主要用例是内部 SME 软件。
例如,一个应用可能暴露:
Users.getUsers()
Products.getProducts()
Orders.getRecentOrders()
然后 AI 可以生成小程序来回答业务特定问题:
哪些客户可能是 VIP?
哪些产品库存不足?
哪些客户最近没有下单?
哪些订单看起来异常?
开发者不需要将每种可能的分析都实现为单独的功能。
相反,开发者提供安全的功能,让 AI 生成逻辑。
对于需求频繁变化的内部工具,这可能会减少开发时间。
我仍然不确定的地方
Autolang 仍然是一个实验。
我还不知道这是否真的比简单地使用 JavaScript、Python 或现有沙箱解决方案更好。
我也不知道 AI 友好的类型系统和错误报告在实践中实际上会在多大程度上提高轻量级模型的可靠性。
这是我构建它的原因之一。
我想知道围绕 AI 生成代码设计的小型运行时是否能在以下两者之间提供一个有用的中间地带:
"让 AI 运行任意代码"
"将每个操作都作为单独的工具调用"
目前,Autolang 既是我喜欢从事的编译器/运行时项目,也是一个让廉价 AI 生成代码更容易安全、可预测执行的实验。
如果你对实现感兴趣,项目和文档在这里:
Project: https://github.com/hoansdz/Autolang
Documentation: https://autolang.vercel.app/docs