在过去的几十年里,“工程”一词的含义几乎等同于“消除歧义”。它意味着定义严格的接口,强制执行类型安全,并确保 这种确定性的结果。
传统软件工程是“确定性的”。 此时我们扮演的是交通管理员角色:我们拥有道路、信号灯和规则的制定权。我们精确地决定数据流向何处、何时流动。
智能体工程(Agent Engineering)则是“概率性的”(Probabilistic)。 我们是调度员。我们给司机(即大型语言模型,LLM)下达指令,而这位司机可能会选择抄近道、迷路,或者认为“这样更快”就决定在人行道上行驶。
这是一个耐人寻味的悖论:初级工程师往往能比资深工程师更快地交付出可用的智能体。 为什么?因为工程师的资历越深,他们越倾向于不信任智能体的推理和指令遵循能力。他们试图与模型“对抗”,通过“写代码”的方式来消除其概率性。
以下是 5 个传统工程习惯与智能体工程新现实发生冲突的例子:
1. 文本是新的状态(Text is the New State)
在传统工程中,我们使用数据结构来建模世界。我们定义模式(schemas)、接口和严格的类型。这种方式让人感到安全,因为它具有可预测性。我们本能地试图将智能体强行塞进这个“盒子”里。
陷阱所在: 真实的意图、偏好或配置很少是二元/结构化的。用户的输入是连续的(自然语言),而不是离散的(结构化字段)。
文本即是新的状态。 我们必须放弃对布尔值(boolean)的依赖,转而拥抱语义意义。
想象一个深度研究计划审批的用例,用户说:“这个计划看起来不错,但请把重点放在美国市场。”一个确定性系统会强行将其简化为 is_approved: true/false,结果就是阉割了(lobotomize)所有的上下文。
|
|
|
|
|---|---|---|
| 传统软件工程 | {"plan_id": "123", "status": "APPROVED"} |
细微差别丢失 |
| 智能体工程 | {"plan_id": "123", "text": "这个计划看起来不错,但请把重点放在美国市场。"} |
保留完整反馈 |
通过保留文本,下游的智能体可以阅读完整的反馈(“已批准,但侧重美国市场”)并动态调整其行为。
另一个例子是用户偏好。确定性系统可能会存储 is_celsius: true。而智能体系统则会存储 “我查天气时喜欢用摄氏度,但烹饪时要用华氏度”。智能体可以根据当前任务动态切换上下文。
2. 放弃控制权(Hand over Control)
在微服务中,用户的意图对应一个路由(例如:POST /subscription/cancel)。而在智能体中,我们只有一个自然语言的单一入口,由一个大脑(LLM)根据可用的工具、输入和指令来决定控制流。
陷阱所在: 我们试图将流程硬编码到智能体中,但用户交互并不会沿着直线前进。它们会循环、回溯,还会突然转向。用户可能原本想取消订阅,最后却同意续订。
-
用户: “我想取消我的订阅。”(意图:流失 Churn) -
智能体: “我可以为您提供五折优惠留下来。” -
用户: “好吧,这个可以接受。”(意图:挽留 Retention)
相信智能体能驾驭流程。 如果我们试图硬编码每一个边缘案例,那我们根本就不是在构建 AI 智能体。我们必须相信智能体能够基于完整的上下文来理解当前的意图。
3. 错误也是一种输入(Errors are just inputs)
在传统软件中,如果一个 API 调用失败或缺少一个变量,我们会抛出异常。我们希望程序立即崩溃,以便我们修复错误。
陷阱所在: 一个智能体可能需要运行 5 分钟,并花费 0.50 美元。如果 5 个步骤中的第 4 步因为输入缺失或错误而失败,让整个执行过程崩溃是不可接受的。
错误也仅仅是另一种输入。 我们不应该崩溃,而是应该捕获错误,将其反馈给智能体,并尝试恢复。
4. 从单元测试到评估(From Unit Tests to Evals)
测试驱动开发(TDD)帮助我们编写更健壮的代码,但我们无法对智能体进行单元测试。工程师可能会浪费数周时间,试图在一个概率性系统中寻找二元正确性。我们必须评估行为。
陷阱所在: 我们无法为创造性或推理任务编写二元断言。“总结这封电子邮件”可以有无数个有效的输出。如果我们试图模拟(Mock)LLM,我们测试的就不是智能体,而是我们自己的字符串拼接逻辑。
评估(Evals)优于测试(Tests)。 我们无法对推理进行单元测试。我们必须验证可靠性和质量,并追踪中间检查步骤:
-
可靠性(Pass^k): 我们不问“成功了吗?” 而是问“它成功的频率是多少?” -
质量(LLM 作为裁判):“答案是否有帮助?语气是否正确?总结是否准确?” -
追踪: 不要只检查最终答案。检查中间步骤。智能体在回答之前是否搜索了知识库?
如果我们的智能体在 50 次中成功了 45 次,且质量评分为 4.5/5,它就可以投入生产。我们管理的是风险,而不是消除差异性。
5. 智能体演进,API 不变(Agents Evolve, APIs Don't)
过去,我们为人类开发者设计 API,依赖隐性上下文和“简洁”的接口。人类可以推断上下文,但智能体不会。智能体是字面主义者(Literalists)。如果 ID 格式不明确,智能体就会臆造(hallucinate)一个出来。
陷阱所在: 我们经常构建“人类级”API——这些端点依赖于隐性上下文。例如,一个名为 id 的变量,对我们来说显然是用户唯一标识符(UUID),可以用于 get_user(id)。但智能体可能没有这个上下文,并可能试图在 get_user(id) 中使用电子邮件或姓名。
智能体需要冗长、“傻瓜式”的语义类型(例如,使用 "user_email_address" 而不是 "email"),以及高度描述性的文档字符串(docstrings)来充当“上下文”。
|
|
|
|
|---|---|---|
糟糕的:delete_item(id) |
|
好的:delete_item_by_uuid(uuid: str)
|
此外,智能体允许即时适应(Just-in-Time adaptation)。普通 API 是对开发者的承诺;我们提交依赖这些 API 的代码后就走开了。如果我们将 API 从 get_user_by_id(id) 更改为 get_user_by_email(email),我们就打破了这一承诺,所有东西都会立即崩溃。然而,智能体会读取新的工具定义,并能够自行调整适应。
从确定性系统向概率性智能体的过渡是令人不适的。它要求我们用确定性换取语义的灵活性。我们不再知道并拥有精确的执行路径。我们实质上是把控制流交给了一个非确定性模型,并将我们的应用程序状态存储在自然语言中。
对于一个习惯了严格接口的思维来说,这感觉是错误的。但试图将智能体强行塞入确定性框架中,就违背了使用它的初衷。你无法通过编写代码来消除概率性。 你必须通过评估和自我修正来管理它。
然而,“信任”智能体并不意味着任由它胡来。我们必须找到一个中间地带。智能体会以许多意想不到的方式失败,但发展趋势是明确的。我们必须停止试图通过编写代码来消除歧义,而是开始设计出足够有韧性来处理这种歧义的系统。

