文章 Lilian Huang · 1 小时 前 7m read

如何教导 AI Coding Agent 安全地对接遗留的 InterSystems Caché 应用程序

我们一开始并没有大的人工智能战略。

我们有一个传统的 InterSystems Caché 2018 应用程序、大量老旧的业务逻辑和一个实际需求:构建一个新的用户界面并改进已运行多年的代码。起初,我认为人工智能编码代理只能帮助完成一小部分工作。也许是一些模板、系统周围的一些 REST 工作,以及帮助阅读旧的 ObjectScript。

但实际上,它的作用远不止这些。

当我们开始认真使用它时,我们意识到它可以跨越庞大的代码库,理解模式,提出重构建议,并帮助我们以比我预期快得多的速度实现 Caché 的现代化。但这只是在开始时经历了一段令人沮丧的时期之后才发生的。

真正的挑战不是获得代码建议。而是教会代理我们的 Caché 环境如何实际运行。

我们为什么能使用它

在进行任何技术工作之前,我们必须回答一个安全问题。

我们不能将企业资源规划代码和内部业务逻辑直接发送给公共人工智能服务,并寄希望于最好的结果。这样做永远无法通过严格的安全审查。

亚马逊 Bedrock 改变了我们的想法。我们的服务器、数据和开发环境已经在 AWS 中,因此 Bedrock 可以很自然地融入我们已经信任和管理的云环境。我们可以使用该模型,同时将流量、访问控制和周边安全控制保持在我们已经在使用的 AWS 框架内。

该模型本身很有趣,但它之所以能在实际企业环境中使用,是因为它符合我们已有的安全模型,而不是为敏感的开发工作创建一个单独的、控制较少的路径。一旦明确了这一点,与安全和管理团队的讨论就变得容易多了。

一开始不成功的地方

一开始,这很令人沮丧。

该模型对 ObjectScript 语法的了解超出了我的预期,但它并不了解我们的版本、我们的惯例,也不了解 Caché 系统多年发展的老角落。它也不知道危险的部分在哪里。老实说,我们也不知道如何与它合作。

我们必须学会什么时候使用更强的模型才有意义,什么时候使用更快的模型就足够了。我们还必须学会如何保持会话的集中性,以免令牌的使用失控。在大型遗留代码库中,这一点很快就变得非常重要。在实践中,降低成本的同样纪律也提高了工作质量--更小、更紧凑的会话通常能带来更好的结果。

我们还花了一些时间来理解为什么我们感觉自己在重复教授同样的东西。起初,这是整个经历中最令人讨厌的部分之一。直到后来,我们才明白,如果我们想要获得一致的行为,就不能依靠聊天记录,也不能希望模型能以某种方式吸收项目内容。我们必须开始通过文档、CLAUDE.md 和钩子为其提供结构化的上下文。

一旦我们做到了这一点,工作就开始变得更加有效。

像对待新开发人员一样对待代理

当我们不再像对待聊天机器人那样对待模型,而是像对待新加入团队的开发人员那样对待它时,我们取得了突破性进展。

这就像入职培训一样。

我们给它提供文档。我们解释了项目中的模式。我们记录了已经发生的错误。我们写下了哪些是 ObjectScript 的标准行为,哪些是我们的环境所特有的。

CLAUDE.md 文件成了这个过程的中心。它的存在不是为了让文字更漂亮。而是为了防止同样的技术错误重复发生。随着时间的推移,它变成了系统的知识库:哪些是错误的,哪些是安全的,哪些是误导的,哪些是绝对不能做的。

有一个细节比我预想的更重要:措辞。"优先选择 X "与 "永远做 X,永远不做 Y "的行为方式不同。一旦指令变得直接,模型就不太可能回到通用默认值。

Caché 终端是挑战的一部分

代理必须学会的最大事情之一是,Caché 2018 中的部署与现代 JavaScript 或 Python 协议栈中的部署感觉不同。

没有 git push 到生产。有的只是 Caché 终端。

在我们的环境中,代码通常通过交互式终端会话到达服务器。命令一次发送一条。由于终端宽度的原因,长命令可能会缠绕在一起,变成断断续续的输入。交互式循环不像普通脚本那样可以依赖。一些理论上看起来很好的操作,一旦需要逐个提示通过终端,就会变得一团糟。

这就产生了一类与 ObjectScript 语法本身无关的故障。生成的代码可能在技术上是正确的,但仍然会失败,因为在服务器上执行代码的方式对于环境来说是错误的。

我们必须明确地教授这一点。

为什么 Atelier 仍然重要

我们还将 Atelier 用作工作流程的一部分。

尽管故事中有趣的部分是人工智能代理,但现实情况是,在旧的 Caché 系统上,你仍然需要传统工具来了解实际发生的情况。Atelier 对于浏览类定义、检查编译行为、查看代码结构以及在开发过程中运行临时查询非常有用。在实践中,我们的工作是混合进行的:代理帮助我们加快了工作进度,但 Atelier 仍是我们验证事物、探索类、立足于真实系统的一部分。

代理并不是在真空中工作。它是在非常具体的 InterSystems 开发现实中工作。

最重要的 InterSystems 经验教训

有趣的是,我们并不需要从零开始教代理 ObjectScript。它已经知道了很多。我们需要教它的是我们的 Caché 环境与安全默认设置的不同之处。

ProcedureBlock 就是一个例子。在旧代码库中,这样的细节并不是学术性的。它们直接影响到方法的行为方式,以及什么样的 QUIT 模式在实践中是安全的。在我们的项目中,直到我们明确地记录下来,这才导致了真正的错误。如果假设是标准情况,该模式生成的代码看起来是正确的,但在我们的实际环境中却是错误的。

教训很简单:模型可能知道 ObjectScript,但它不知道你的类配置,除非你告诉它。

另一个例子是表达式评估。ObjectScript 的思维方式与 C、JavaScript 或 Python 不同。如果您来自这些语言,您会很自然地假定运算符优先。在 ObjectScript 中,这种假设可能会成为一个 bug。

WRITE 1+2*3   ; outputs 9, not 7

主要基于现代语言训练的模型通常会让复合表达式不进行括弧化,除非您将规则明确化。一旦我们为复合表达式添加了一个简单的括号规则,这类错误就会立即减少。

条件式也是如此。如果你来自其他语言,很容易就会忽略总是两边都求值的运算符和短路运算符之间的区别。在传统系统中,这种区别会很快变成真正的生产问题。一旦我们将模式清晰地记录下来,模型就会变得更加可靠。

我们还教给它一些在 Caché 中非常自然的模式,只要你对它们非常熟悉。进程私有全局就是一个很好的例子。一旦我们向模型展示了使用它们的位置和原因,它就会开始自然而然地将这种模式应用于较长流程中的临时工作数据。

然后是 JSON 细节。如果需要真正的 JSON 布尔值,%DynamicObject.%Set() 需要一个显式类型。这听起来并不重要,直到 API 消费者期望 true 却得到 1

// Returns {"active":1}
DO tObj.%Set("active", isActive)

// Returns {"active":true}
DO tObj.%Set("active", isActive, "boolean")

在一条规则和一个示例之后,模型开始正确运行。

还有一些知识在任何官方文档中都没有出现过。

在我们的系统中,一些希伯来文本的处理不仅取决于值本身,还取决于产生该值的代码路径。正确的转换有时取决于方法链中前面发生的事情。这种知识并不存在于产品手册中。它存在于应用程序的历史中。模型无法推断。我们必须把它写下来。老实说,写下来也有助于我们更好地理解它。

我们这样做之后的变化

在我们建立了正确的上下文后,价值就变得真实了。

代理在阅读旧的业务逻辑、建议更安全的初稿、帮助重构以及加快系统周围的用户界面和应用程序接口工作方面变得非常有用。它并没有取代我们对 Caché 的理解,相反,它使我们对 Caché 的理解变得更加重要,因为每一个重复出现的错误都必须转化为一条规则,而编写规则又迫使我们准确理解其行为。

不要问模型是否了解 ObjectScript。要问的是,你是否已经对自己的系统进行了足够的记录,以便模型能在其中安全地工作。

结论

如果您正在开发一个长期的 InterSystems 应用程序,并考虑使用人工智能编码代理,那么不要从提示开始。从入门开始。

人工智能模型已经对代码了如指掌。它不知道的是你的 Caché 版本、配置、基于终端的部署习惯、模式和遗留行为。一旦我们在文档、CLAUDE.md 和钩子中捕获了这些知识,结果就完全改变了。在此之前,这只是一个有趣的实验。之后,它成为我们工作方式的真正组成部分。