微信扫一扫,关注公众号

  • 科技行者

  • 算力行者

见证连接与计算的「力量」

首页 EPFL团队让AI像人类程序员一样"边写代码边检查",这项突破将彻底改变AI助手的安全边界

EPFL团队让AI像人类程序员一样"边写代码边检查",这项突破将彻底改变AI助手的安全边界

2026-06-03 10:17
分享至:
----..---.-...-/--...-.-......./-...-....-..--../-............-.- ----..---.-...-/--...-.-......./-...-....-..--../-............-.- ----..---.-...-/--...-.-......./-...-....-..--../-............-.- ----..---.-...-/--...-.-......./-...-....-..--../-............-.-
2026-06-03 10:17 科技行者

这项由瑞士联邦理工学院洛桑分校(EPFL)研究团队开发的技术,以预印本论文形式发布于2026年5月,编号为arXiv:2605.28617,有兴趣深入了解的读者可以通过该编号查询完整论文。

**一、问题的起点:AI助手写代码,但谁来把关?**

假设你雇了一位助手帮你处理日常事务。你给他一把钥匙,让他帮你取文件、发邮件、查账单。绝大多数时候他都做得很好,但有时候他会犯错——把文件发错地方,或者在没搞清楚情况时就动了你的账户余额。更麻烦的是,如果有人伪装成你给他留了一张字条,让他"顺便"把你的银行密码发出去,他可能就真的照做了。

这就是当今AI智能体(AI agent,也就是能自主执行任务的AI程序)面临的核心困境。现在越来越多的AI系统已经不满足于简单地回答问题,而是真的去"做事"——搜索网页、读写文件、调用各种外部服务。为了更灵活地完成任务,这些AI会直接写代码来组织自己的行动步骤。

但问题来了:AI写的代码,谁来检查?

现有的AI智能体系统里,有一道清晰的隔离线:运行AI的"框架"(类似于那位助手的工作规程手册)和AI自己写出来的代码之间,存在严格的界限。框架控制着整个工作流程,AI只能在框架允许的范围内行动,没有太多自主性。这样固然安全,但也很死板——AI没办法灵活地决定"我要多查几个数据源"、"这个任务可以分成三个子任务并行处理",诸如此类的决策。

EPFL的研究团队想做的,正是打破这道隔离线,同时又不放弃安全保障。他们开发的系统叫做**LACUNA**(Lazy Agent Code Under Nested Assertions,也可以理解为"递归程序中的洞"),核心思路可以用一个简单的比喻来理解:在一份正式合同里预留空白处,让AI来填写,但填好之后必须经过律师审核,确认填写的内容符合合同条款,才能生效。

**二、"洞"的概念:在代码里留下AI专属的填空题**

要理解LACUNA的工作方式,先要理解什么是"类型化的洞"(typed hole)。

在编程世界里,代码必须遵守严格的规则。一个函数如果要求输入数字,你就不能给它传一个名字;一个操作如果应该返回列表,它就不能返回一个单独的数字。这些规则由编程语言的"类型系统"来强制执行,就像语法规则检查你的英文作文一样。

LACUNA的核心发明,是一个叫做`agent[T](task)`的操作。这里的`T`表示"期望的结果类型",`task`是一段自然语言描述,告诉AI它需要完成什么任务。当程序运行到这个位置时,AI就会收到请求,用Scala语言(一种严格的静态类型编程语言)写一段代码来填充这个"洞"。写完之后,编译器立刻对这段代码进行检查:它的结果类型对不对?它用到的变量都存在吗?各个数据的类型匹配吗?

只有通过检查,这段代码才会被执行。一旦检查失败,这段代码会被完整地丢弃,一个字节都不会运行——编译器会把错误信息反馈给AI,让它重新写一遍,直到写对为止,或者达到最大重试次数上限。

这个设计的精妙之处在于,AI生成的代码不仅仅是一行单独的工具调用,而是可以包含完整的程序逻辑:判断分支、循环、嵌套调用其他工具,甚至在里面再次调用`agent`来请求AI处理子任务。整个过程就像是一位AI程序员在填写一道大题,而不是做一道选择题。

举一个具体的例子。假设程序里有一个整数列表`xs = [0, 1, 2, 4, 7, 9, 10]`,然后有一行代码`agent[List[Int]]("filter the prime numbers from xs")`,意思是"请AI从xs中筛选出质数,结果应该是整数列表"。AI收到请求后,会生成这样的代码:先定义一个判断质数的辅助函数`isPrime`,然后调用`xs.filter(isPrime)`。编译器检查通过后,程序运行,得到结果`[2, 7]`。这里有几个关键点:AI生成的代码直接用到了外部定义的变量`xs`,而且结果类型必须是整数列表,不能是字符串,不能是单个数字,必须是整数列表。

**三、"先检查,再执行"为什么如此重要**

传统的AI系统出错时,是"边做边发现问题"。以Python的`exec`函数为例,你可以把一串代码文字丢给它运行,程序会一行一行地执行,直到某一行出错才停下来。这意味着出错之前的所有步骤已经完成了,产生了真实的效果。

LACUNA的"全有或全无"特性彻底改变了这一点。论文里给出了一个非常直观的演示:假设AI生成了这样的代码,第一行是"把账户余额减去50",第二行是返回一段说明文字(但这段说明文字是字符串,而不是期望的整数类型)。在Python式的运行方式下,账户余额会先被扣掉,然后才发现类型错误——损失已经造成了。在LACUNA里,编译器在任何代码运行之前就发现了类型不匹配,整段代码被整体丢弃,账户余额纹丝未动,依然是100。

这个原子性(要么全做,要么全不做)对AI安全来说意义重大,因为AI经常会犯的错误正是这种"前面做对了,后面写错了"的情况,而在现有系统里,"前面做的"已经无法撤回。

静态检查还能拦截另外几类常见错误。比如,如果AI代码里用到了一个根本不存在于当前作用域的变量名,编译器会立刻报错,告知"找不到该名称",代码不会运行。如果AI试图把一个字符串类型的值传给需要整数的参数,编译器同样会在执行前拒绝。对于使用了"密封数据类型"(就是那种枚举型的数据,比如颜色只有红、绿、蓝三种)的代码,如果AI写的判断分支没有覆盖所有情况,编译器也会警告"模式匹配可能不完整"。甚至在使用了明确空值检查的模式下,如果AI试图用`null`来填充一个不允许为空的字段,也会被直接拦截。

**四、能力边界:AI只能用它"被授权"使用的工具**

在LACUNA里,"工具"的概念被彻底简化了。不需要什么特殊的工具注册表,不需要JSON格式的接口描述文件,不需要维护复杂的协议层。一个工具就是一个函数,定义好函数签名放到作用域里,AI就能用,编译器负责检查调用方式是否正确。

但LACUNA更进一步,借助Scala 3语言的"捕获检查"(capture checking)机制,实现了更精细的权限控制。这里的核心概念叫做"能力"(capability),在LACUNA里它是一种特殊的值,代表对某类资源或操作的授权——比如文件访问权限、网络连接权限、数据库写入权限等等。

这和我们日常生活中的工作证很像。你的工作证上可能有读取A区档案室的权限,但没有B区的权限。在LACUNA里,如果某个能力(比如网络访问权限`networkIO`)没有出现在当前代码的作用域里,那么AI生成的任何代码都无法使用网络,编译器会直接拒绝任何试图发起网络请求的代码,不管AI怎么写都没用。

更厉害的是对信息泄露的防范。即便AI在某个临时作用域内拿到了一个能力,它也不能把这个能力"带出去"——也就是说,它不能把持有能力的函数或值返回给外部作用域,让外部程序之后继续使用这个能力。编译器会检测这种"能力逃逸"行为,并在发现时拒绝执行。

论文里给出的例子非常清晰:在一个文件访问块内,AI可以用`io`能力直接读取文件内容并返回纯字符串,这没问题,因为返回的字符串里不包含任何能力;但如果AI试图返回一个"文件读取函数"(本质上是把`io`能力包装在一个函数里带出去),编译器会报告"能力`io`的生命周期超出了它的作用域",并拒绝这段代码。

这套机制对于防御"提示注入"攻击(prompt injection attack,即恶意内容伪装成任务指令欺骗AI)格外有价值。攻击者可能在AI读取的网页内容里藏入恶意指令,试图让AI做不该做的事情。LACUNA无法阻止AI被这些指令影响,但它能限制被影响后的AI所能做的事情——它只能动用当前作用域里已有的权限,不能凭空获得新的权限。

**五、敏感数据的保护:即使AI来处理,内容也不会泄露**

LACUNA还解决了一个非常现实的问题:当用户有一份敏感文件需要AI处理,但又不想把文件内容暴露给云端的AI模型时,怎么办?

论文里描述了一种叫做`Classified[T]`的容器类型。这个容器里装着敏感内容,但对外只暴露一个`map`方法,接受一个"纯函数"(pure function,即不能访问任何外部资源的函数)来处理内部数据。由于纯函数无法发起网络请求、无法写入文件、无法调用外部API,所以即便AI在编写这个处理函数时被恶意指令污染,它也没有任何渠道把数据发送出去。处理完成后,结果仍然包装在`Classified`容器里,防泄漏的特性得以保持。

但这里有个局限:用于处理数据的函数必须提前写死,无法根据数据内容动态调整处理逻辑。LACUNA的嵌套`agent`调用解决了这个问题。在处理函数内部,可以调用一个"本地可信模型"(比如部署在用户自己设备上的小型AI模型)来生成处理代码,而这个嵌套调用依然在纯函数的约束下运行,所有的能力检查照样有效。结果就是:云端AI负责规划整体流程,本地AI在完全隔离的环境里处理敏感内容,云端AI始终看不到实际数据,安全边界得以维持,同时处理逻辑又足够灵活。

**六、用一个原语就能搭出各种AI工作模式**

LACUNA最令人印象深刻的特性之一,是它仅凭`agent`这一个原语,就能自然地表达出AI领域里各种复杂的工作模式,而不需要为每种模式专门设计框架支持。

以"技能"(skill)为例。在AI系统里,技能指的是可复用的专业知识模块。现有方案要么把技能写成纯文本指南(AI理解了但执行时可能随意偏离),要么把技能固化成代码(执行可靠但无法适应新情况)。LACUNA里的技能是一个有固定类型签名的函数,函数体由AI在每次调用时生成,可以纯粹委托给AI,也可以由人类程序员写一个代码骨架、只把其中的关键步骤留给AI填充,还可以完全写死成固定代码。这三种形态可以根据需要自由选择,共用同一个接口。

技能的自我改进也因此变得自然而然。在一个持续运行的编程环境里,AI可以生成一个新版本的函数,用同样的名字和签名覆盖旧版本,之后的所有调用都会自动使用新版本,相当于AI在实时修改自己的"技能库"。

"ReAct循环"是目前最主流的AI推理模式,它让AI在一轮轮的"思考-行动-观察"中逐步解决问题。在LACUNA里,这就是一个尾递归调用:AI每一轮生成的代码末尾再次调用`agent`,积累更多上下文,直到掌握足够信息直接给出答案为止。整个循环没有任何额外的框架支持,就是普通的递归代码。

"子智能体"(sub-agent)的隔离也很简单:只要把`agent`调用包裹在一个独立的函数里,这个函数内部的作用域就成了子智能体的权限边界,调用方的局部变量和能力不会自动流入子智能体的视野。

并行处理同样如此:Scala的并行集合操作`par.map`加上`agent`调用,就能实现对一批文档的并行独立处理,不需要任何特殊的"并行智能体"框架。

多模型协作规划,即用一个强大的大模型做整体规划,用多个轻量模型并行执行子任务,然后再由大模型汇总结果,也只是把不同的`agent`实例分配给不同的模型配置,代码上和普通函数调用没有区别。

**七、技术实现:如何在程序运行中调用编译器**

LACUNA的底层实现面临的挑战不小。通常来说,编程语言的编译器只在程序运行之前工作——它把代码检查一遍,生成可执行文件,然后退场。等程序跑起来的时候,编译器早就不在了。

LACUNA需要的恰恰相反:在程序运行过程中,动态接收AI生成的代码字符串,把它编译检查一遍,然后立刻执行。这种能力在Python、JavaScript等动态语言里是标配,但在Scala这样的静态类型语言里,实现起来需要克服几个障碍。

首先,编译器必须能在程序运行时被再次调用。EPFL团队在Scala 3编译器里添加了一个新的内置操作`eval[T](source)`,让正在运行中的程序能够启动一次新的编译过程。其次,AI生成的代码必须在正确的上下文里被编译——也就是说,编译器需要知道当前程序里有哪些变量、它们的类型是什么、哪些权限是可用的。研究团队通过一个编译器扩展阶段来实现这一点:在原始程序的编译阶段,编译器会把每个`eval`调用扩展成更详细的形式,自动填入三个参数:当前作用域里所有变量和它们的运行时值的绑定关系、期望的结果类型文本、以及包含调用位置占位符的外层代码文本。

实际执行时分四步进行。第一步是"拼接",把AI生成的代码字符串嵌入到外层代码的占位符位置,形成一段完整的、看起来像是程序员手写的代码。第二步是"重新编译",把这段拼接好的代码送给同一个Scala编译器的全新实例,使用完全相同的编译选项(包括所有的类型检查和权限检查规则)进行处理。第三步是"提取",如果编译成功,编译器生成对应的字节码,加载进正在运行的程序里。第四步是"执行",在程序原本的执行环境里(同一个线程、同一个类加载器)运行这段新代码,得到类型为`T`的返回值。

整个流程中最关键的洞见是:复用了未经修改的Scala编译器本身,对AI代码进行检查。所有的安全保证都来自编译器自身的能力,不需要额外的安全层或单独的检查逻辑。

当然,这套机制有一些局限。反射机制和原始进程执行是两个"后门"——任何代码都能在没有能力授权的情况下使用它们,因此如果启用了这两个功能,AI代码的安全边界就会被突破。LACUNA的推荐做法是开启Scala的"安全模式"(safe mode)编译选项,这个选项会直接禁止在源代码里使用这两类不安全构造。此外,资源耗尽、无限递归、运行时间过长等问题不属于类型系统的能力范围,需要由运行时的深度限制和超时配置来控制。

**八、实验结果:在真实任务上的表现**

研究团队在三类不同的场景下评估了LACUNA的效果。

第一类是类型系统的正确性验证。团队构建了约400个测试用例,每个用例包含一段主程序代码和一段AI生成的代码片段,有些是格式正确的(应该编译通过并返回期望结果),有些是格式错误的(应该被拒绝)。所有测试用例全部通过,确认了编译器对主程序代码和AI生成代码使用了完全一致的检查标准。

第二类是复杂信息检索任务。研究团队在BrowseComp-Plus这个数据集上进行了测试,这是一个包含830个困难信息检索问题的基准,每个问题需要AI在一个固定的文档库里搜索多次、阅读文档、综合信息才能回答。AI能使用的工具只有两个:搜索工具和文档获取工具。

使用DeepSeek V4 Flash模型的LACUNA智能体答对了27.1%的问题,平均每个问题进行了5.9轮思考、执行了15.5次搜索,展示出了真实的多步骤研究行为。更引人关注的是安全指标:8.6%的AI生成代码在执行前被编译器拒绝,平均每个问题只需要0.7次重新生成,三分之二的问题一次就写对了,整体编译成功率达到91.4%。这说明类型检查几乎不影响整体效率,但保证了所有执行过的代码都是"结构正确"的。使用Gemini 3.1 Flash Lite模型的LACUNA得到了相近的26.2%正确率,而使用GPT-5.4 mini这个较弱模型的版本只有9.2%,主要是因为这个模型搜索次数少得多,上下文探索深度不够。

第三类是多轮对话任务。在τ?-bench这个基准上,LACUNA被用来模拟客服场景:AI需要和模拟用户进行多轮对话,调用各种工具处理零售、航空、电信等行业的客服任务,共392个任务。

使用DeepSeek V4 Flash的LACUNA总体解决了76.0%的任务,在航空领域达到88.6%,在零售领域相对较低(58.8%),总体和参考的工具调用智能体相当。这些对话是真实且复杂的,零售领域平均每个任务有5.7轮用户对话和26.7次工具调用。

不过,多轮对话任务里的代码生成失败率明显高于单次信息检索——零售领域有22.4%的AI生成代码被拒绝,远高于BrowseComp-Plus的8.6%。这是因为多轮对话需要AI的代码同时处理工具返回的数据格式、历史对话状态和业务规则,出错的机会更多。对于能力较弱的Gemini 3.1 Flash Lite模型,在难度较高的电信领域,高达89%的代码会被编译器拒绝,模型几乎无法取得进展——但LACUNA本身的安全保证依然完全成立,只是这个模型的代码生成能力不足以支撑它完成任务。

针对提示注入攻击的专项测试被集成到了AgentDojo环境中进行,覆盖银行、工作区、通讯和旅行四个领域。实验对比了LACUNA与CaMeL和TACIT两个专门针对安全性设计的系统。在大多数场景下,LACUNA成功阻止了注入攻击,极少数情况下有攻击成功——这与论文在局限性中坦承的情况一致:当攻击者能够操控AI去误用它本就被授权使用的工具时,纯粹的类型和能力检查无法拦截,这是系统设计上固有的边界,需要开发者在划定权限范围时保持谨慎。

**九、研究的局限与未来方向**

LACUNA的研究团队对系统的局限性保持了相当清醒的认识,并在论文中坦诚地列出了主要问题。

类型检查只能保证代码的"形状"正确,不能保证代码做的事情是对的。一段通过编译检查的代码完全可能在逻辑上是错误的——调用了错误的工具、做了错误的计算、返回了语法上合法但语义上不正确的值。真正的语义正确性验证需要另外的机制,比如测试用例、人工审查,或者未来可能实现的精化类型(refinement types,即在类型里直接指定值的取值范围和约束条件)。

权限控制的精细程度完全取决于开发者如何划定能力边界。如果开发者为一个只需要读取文件的任务授予了文件写入权限,那么AI在这个任务里写错了东西,LACUNA并不会拦截,因为这个权限已经被授予了。系统强制执行开发者设定的边界,但无法替开发者做出"应该授予哪些权限"的决定。

对Scala语言能力的依赖也是一个现实制约。目前绝大多数AI框架都基于Python,而Python是动态类型语言,无法提供LACUNA所依赖的静态类型安全保证。LACUNA的设计在原理上可以迁移到任何有静态类型系统和运行时编译机制的语言,但实际的迁移需要相当的工程工作。

AI模型的代码生成能力对系统性能有直接影响。目前Scala在AI模型的训练数据里远不如Python常见,导致模型写Scala代码的能力偏弱,出错率较高。这个问题不是LACUNA系统本身的局限,而是当前AI模型的局限,可以通过针对性的微调或训练数据扩充来改善。

研究团队指出的一个有趣的未来方向是"精化类型化的洞":让期望的结果类型`T`不仅描述数据形状,还携带约束条件,比如"一个在1到100之间的整数"、"一个长度不超过10的列表"、"一个输出一定大于输入的函数"。如果能把这类约束直接加入类型系统,那么编译器就能在代码运行前验证AI生成的代码是否满足这些性质,而不仅仅是验证数据类型是否匹配。结合Lean或Stainless这样的形式化验证工具,甚至有可能实现对AI生成代码的数学级别的正确性验证。

说到底,LACUNA解决的问题可以用一句话来概括:它让AI智能体写的代码必须"经过正规批准才能生效",而这个批准的标准是数学意义上确定的,不是靠人的判断,也不是依赖AI的自觉。这并不能保证AI做的事情永远正确,但至少能保证它永远在它被允许的范围内行事。

在AI系统越来越多地介入现实操作的今天,这个区别其实非常重要。一个被允许范围内的错误,和一个越权的错误,后果完全不同。前者可以通过改进任务描述或模型能力来修复,后者可能在被发现之前已经造成了无法挽回的影响。LACUNA提供的,是对后一类风险的系统性防护,而不是什么魔法——只是严格执行了"先检查,再执行"这个简单但有力的原则。有兴趣深入了解技术细节的读者,可以通过arXiv编号2605.28617查阅原论文。

---

Q&A

Q1:LACUNA系统的"类型检查"具体能防止哪些AI错误?

A:LACUNA的编译器检查会在代码执行前拦截以下几类问题:使用了程序里根本不存在的变量名、把错误类型的数据传给函数(比如把字符串传给需要数字的参数)、返回了不符合期望格式的结果、判断分支没有覆盖所有情况、以及向不允许为空的字段传入空值。关键在于这些检查是"全有或全无"的——哪怕只有最后一行出错,整段代码一个字节都不会执行,之前的操作不会留下任何副作用。

Q2:LACUNA的安全机制能完全防止提示注入攻击吗?

A:不能完全防止,但能有效限制攻击的危害范围。LACUNA无法阻止AI被恶意内容"说服"去做坏事,但它能保证被说服后的AI只能动用它本就被授权的权限,无法凭空获得新的能力。比如,一个只有文件读取权限的AI即使被攻击者操控,也无法把数据发送到外部服务器,因为网络访问能力根本不在它的作用域里。少数攻击成功的情况,是攻击者把AI引导去误用它本来就有权限使用的工具,这类情况只能通过更严格地划定权限边界来缓解。

Q3:LACUNA要求用Scala语言,是不是意味着现有的Python AI框架都用不了这套方案?

A:目前确实是这样。LACUNA的安全保证依赖Scala的静态类型系统和运行时编译机制,Python作为动态语言无法提供同等的预执行类型检查。不过研究团队指出,LACUNA的设计原理可以迁移到任何有静态类型系统和运行时代码编译能力的语言,并不是Scala独有的。实际迁移需要工程投入。另一个现实问题是AI模型目前写Scala代码的能力不如写Python,会导致更高的编译失败率和重试次数,这个问题可以通过模型的针对性训练来改善。

分享至
0赞

好文章,需要你的鼓励

推荐文章
----..---.-...-/--...-.-......./-...-....-..--../-............-.- ----..---.-...-/--...-.-......./-...-....-..--../-............-.- ----..---.-...-/--...-.-......./-...-....-..--../-............-.- ----..---.-...-/--...-.-......./-...-....-..--../-............-.-