给你的 code agent 搓一个错误检测机制

0 阅读7分钟

前言

众所周知,code agent 是无法判断出生成的代码是否正确的。也就是只有生成环节,没有验证环节,所以需要我们一点点做 debug,这也是很多人最开始不看好 ai 的原因。

但反过来想,是不是解决了验证环节,ai 的体验就会获得大幅提升呢。

锁定问题

要想解决验证环节,可以先思考我们是如何做 debug 的。

对于前端,一般而言都是先安装依赖,看看代码有没有爆红。然后运行,看项目是否能正常启动,最后打开页面和控制台,点一点看哪里有问题。

拆解来看,其实分为了几个问题:

  • 是否能正常安装依赖
  • 代码是否直接在编辑器就出现了错误(静态检测/开发时错误)
  • 项目是否能正常启动(构建时错误)
  • 控制台是否报错(运行时错误)

所以,我们的目标是依次解决这些问题。

解决方案

能否正常安装依赖

这一步其实相对简单,运行 npm i 就行了。需要注意的点有:

  1. npm 安装依赖存在不少问题,它使用扁平化所有依赖,并逐个进行下载的做法,这可能会导致幽灵依赖(能引用到没安装 package.json 的部分包),安装缓慢(内存存储双爆炸),依赖冲突(子依赖使用了不同版本)等问题。为了解决这些问题,pnpm 出现了,目前大部分前端项目都会优先考虑使用 pnpm
  2. 关于锁文件的问题

真正决定安装版本的是锁文件,其生成依赖于 package.json。但不要手动修改锁文件。

安装会有很多种情况

情况1:当有锁文件,且与 package.json 中包一致时,执行安装,会完全按照锁文件进行安装,这是最常见且最安全的情况。

情况2:当有锁文件,且与 package.json 中包不一致(比如手动在 package.json 中更新了包),执行安装,锁文件会更新有变化的部分。锁文件更新后,后续再进行安装就会进入到情况1

情况3:当没有锁文件,执行安装依赖时,会按 package.json 里的依赖

  • 精确版本号时,会选择精确版本("axios": "1.5.0" 会安装 1.5.0)
  • 有 ^ 时,允许小版本自动更新("axios": "^1.5.0" 会选择最新且小于 2.0.0 的版本)
  • 有 ~ 时,允许小特性自动更新("axios": "~1.5.0" 会安装最新且小于 1.6.0 的版本)

根据这些规则,会选择出所有适合的包,生成锁文件,接着进入情况1,使后续该项目使用的包稳定,不会出现突然升级的问题。

暂时无法在昆仑万维文档外展示此内容

最佳实践:安装/更新新依赖,最好还是走命令行

npm i xxx // 安装
npm i xxx@latest // 更新最新版,也可以指定版本如 xxx@1.0.0

这样能自动同时更新 package.json 和锁文件,且不影响其他包。

退而求其次,是手动修改 package.json,随后再进行 npm i 重新安装,这样也能让锁文件进行更新,

如果单独修改 package.json 文件,可能是无效的,比如:package.json 中 "axios": "^1.5.0",lock 中 1.5.2,此时你将 package.json 的 ^1.5.0 改为 ^1.5.1,由于 lock 中的 1.5.2 仍符合^1.5.1,所以是无事发生,不会产生任何更新。

是否直接在编辑器就出现了错误

ai 的很多错误,其实在前端通常都能很直接的看到,如下:

这里的报错其实有两个原因:eslint 报错与 ts 报错

我们可以通过这两个命令扫描出对应的错误:

npx eslint ./
npx tsc --noEmit -p tsconfig.app.json --strict

我们可以在对应的文件中,配置对应的规则集,eslint 的配置位于 eslint.config.js 中;ts 的配置位于 tsconfig.app.json。

严格的规则,有助于 ai 生成产物的稳定,更不容易出现白屏报错等问题,但相对的时间会更长,因为要解决更多 error。宽松的规则,则对应 token 的减少,时间更快。

在我们的项目中,使用了默认的规则集,基本上就够用了,不过有几个模型经常报错、但不影响主流程的,我们还是降低为了 warn,从而规避修复所需要花费的成本。

"@typescript-eslint/no-unused-vars": "warn", // 未使用变量降为 warn
"@typescript-eslint/no-empty-object-type": "warn", // 空对象类型降为 warn
"@typescript-eslint/no-explicit-any": "warn", // any 类型降为 warn,这个可能还有待商榷,过多的使用 any 本质上就是忽略报错

项目是否能正常启动

在大部分情况下,如果前两个步骤是正确的,最后一步 build 大概率不会有问题。即使有问题,模型也能在 build 后也能看到构建报错,从而直接进行修复。

如果想要验证也简单,跑一个构建命令就行了

npm run build

控制台是否报错

前面三个环节,都可以用命令行进行验证和 debug,也就是 agent 有办法感知到。但控制台的报错,属于运行时错误,很难报给 agent,所以我们需要设计一套上报机制来解决这个问题。

也就是这个需求 【Websites】增加手动Debug模式,简单来说,就是控制台的所有报错都暴露出来发给 agent

要实现这点,关键在于拦截所有报错,我们来看常见的报错,逐个进行解决。

  1. 普通 js 同步报错,也是最常见的一种,语法问题等引起

解法:可以用 window.onerror 进行回调处理

  1. promise reject 未处理报错,类似 try catch 问题

解法:可以使用 window.addEventListener("unhandledrejection", event => {}) 来做回调处理

  1. 接口报错 / 资源加载失败

虽然严格意义上,不算代码的问题,但是也有必要让 agent 感知到

解法:通信使用 axios 包,并且在其响应拦截器中做处理,如果不是 200,则进行 error 回调

主要的报错就这三种,我们可以基于此封装一套错误抛出的机制。不过 github 实际上有现成的更完备的库,可以考虑直接使用 Sentry 来做这件事。sentry 本身是用于错误上报的,不过我们可以将其拦截掉,在 callback 中只执行我们的逻辑即可。

  Sentry.init({
    dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0', // 使用假 DSN 来启用错误捕获功能
    enabled: true, // 启用 Sentry
    attachStacktrace: true, // 附加堆栈跟踪
    
    // 在发送前处理错误
    beforeSend(event, hint) {
      const error = hint.originalException || hint.syntheticException
      dosomething(error, event)
      // 返回 null 阻止实际上报到 Sentry
      return null
    },
  })

当你的模版中接入了这套流程后,你就可以将错误向 agent 抛出了,比如说:

  1. skywork 中,产物是在 iframe 里,那么我们可以利用 postMessage 将错误反馈到主应用,从而在输入框中引用到错误并发送给 agent

  2. 如果产物完全独立,可以考虑将错误打到接口中,之后的通信让 agent 先去访问这些错误,修复后再做操作

总结

所以最终这个机制有以下几个部分构成:

  1. 安装、更新依赖时走命令行(且最好使用 pnpm 而非 npm)
npm i xxx // 安装
npm i xxx@latest // 更新最新版,也可以指定版本如 xxx@1.0.0

2. build 前先扫描错误并修复(要使用 typescript 而非 js,要接入 eslint)

npx eslint ./
npx tsc --noEmit -p tsconfig.app.json --strict

3. 跑 build 看有没有问题

npm run build

4. 在模版中接入运行时错误监控,并将监控的结果传递给 agent

  Sentry.init({
    dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0', // 使用假 DSN 来启用错误捕获功能
    enabled: true, // 启用 Sentry
    attachStacktrace: true, // 附加堆栈跟踪
    
    // 在发送前处理错误
    beforeSend(event, hint) {
      const error = hint.originalException || hint.syntheticException
      dosomething(error, event) // 自定义实现
      // 返回 null 阻止实际上报到 Sentry
      return null
    },
  })