在客户的环境里构建

19 分钟读完

10
第 II 部分 · 一次交付

在客户的环境里构建

在别人的基础设施上,说了算的是他们的约束,不是你的偏好。客户口中“绝不能”发生的规则,不能寄托在一个大多数时候正确的模型上,所以要在模型之外用普通代码来强制执行。然后,构建一个能通过评测、他们的运维人员不靠你也能运行的、最不花哨的系统,并把这两个选择写下来,交给他们的平台团队和模型风险部门签字。

10.1

这个阶段为了什么,又会怎样失败

到这一步,这次交付已经有了签字的阈值、一条数据通路、一个模型风险决定,以及一个正在攀升的评测分数。它缺的是一个在客户运行业务的地方跑起来的系统。这个阶段就是要把它造出来:跑在他们的托管环境上,通过他们的身份提供商认证,调用他们批准的端点,经由他们的变更流程上线,并且在供应商的人回家之后依然能用。

地基不由 FDE 选。产品工程师在自己公司选定的基础设施上构建;FDE 则在别人多年前、出于与 AI 无关的理由选定的基础设施上构建。这些约束不是需要绕过去的障碍,它们就是规格。

这个阶段会以三种方式失败,而三者都源于把客户的环境当成一个部署细节。

搬来自己的技术栈。设计默认用上供应商在自家用惯的服务:托管向量数据库、托管编排框架、对公网端点的出站调用。然后客户的平台团队告诉你,这些在他们的网络里一样都没有,于是设计在第二个月推倒重来。

把规则写进提示词。客户有一条绝不能破的规则,团队把它用大写字母写进系统提示词。演示里它守住了,评测里大多数时候也守住了。问题就出在“大多数”。它在生产环境第一次被打破时,你要面对的是客户的风险部门,而不是评测。

没人能运维的花哨设计。团队觉得有意思,就造了一个带工具、记忆和规划器的智能体循环,而其实一次固定输出的模型调用就能过阈值。FDE 在场读追踪记录时它能用;FDE 离开的那一周,就没人能维护它了。

关于证据,先说一句。这是本书证据最单薄的章节之一:没有任何经过测量的研究告诉我们,哪些架构在客户环境里能成功。现有的只有关于 FDE 团队的二手报道(T3)、一家供应商的工程指南和一位前 FDE 的回忆录(都是 T4),以及招聘启事,而招聘启事只能说明雇主要求什么,不能说明工作实际怎么做。本章的论证建立在这些材料之上,并借助 wiki 中的相关页面讲解技术栈,不在这里重复。

10.2

合成案例:他们的 VPC,他们的规则,一条平淡的流水线

在这家保险公司的第二、第三个月。年度合同已经签了,上线以分派准确率阈值为门槛,而评测分数还在从 80% 出头往上爬。生产系统必须跑在保险公司的私有云里。平台团队在第一周就发来了条件:工作负载跑在他们的容器平台上,位于他们的 VPC 之内,没有出站互联网;模型调用只能发往模型风险部门批准的区域内端点;每个服务都通过保险公司的身份提供商认证;变更要经过每周一次的变更评审会上线。FDE 的第一版草图原本假定了一个托管向量库和供应商自己的编排框架。两者都不在平台团队的清单上,后来也证明两者都不需要。

规则。理赔总监的规则里有两条没有商量余地:涉诉标记的案件绝不能分到快速队列;某些理赔类型无论文档怎么写,都必须进人工队列。FDE 把这两条完全挡在提示词之外,写成几十行普通代码,在模型之前和之后运行。理赔系统里设置的涉诉标记会推翻任何建议;建议的队列如果不在允许清单里,就直接丢弃。每条规则都有自己的测试,每次变更都要跑,而且必须每次都通过,而不是得分高就行。资深理赔员为评测挑了一个棘手案例:一个未被标记的案件,邮件往来里夹着一封律师函。遇到这种情况,模型可以把案件推向人工队列,但它说什么都不能把案件从人工队列移走。

流水线。生产环境里跑的东西刻意做得很平淡。理赔材料进来,提取文本。一次模型调用以固定格式返回一个队列和一句简短理由,再由规则检查结果。建议落到保险公司团队拥有的那张旁路表里,输入、输出和每一个版本都记入日志。如果端点宕了,或者输出无法解析,案件就进默认的人工队列,也就是交付之前每个案件的去处。没有智能体循环,也没有检索索引。接下来几周的评测工作改的是文本提取、提示词、示例和规则,从不改流水线的形态。在评测缩小差距期间,生产栈以影子模式运行,所以达到阈值之后的上线只是一次配置变更。这一段背后的模式,是 OpenAI 一次与亚太地区汽车制造商的 FDE 交付中报道的确定性硬约束,以及 Anthropic 关于寻找“尽可能简单的方案”的建议。

这一段靠三个选择撑起来。平台团队的条件成了设计说明书。规则离开模型,进入可以测到 100% 的代码,模型只保留“更谨慎”的权力。架构在评测说“够了”的地方停下。

10.3

先盘点他们的约束,再动手设计

FDE 构建工作最根本的特征,是代码放在哪里。Gergely Orosz 报道,OpenAI 的 FDE“直接在客户的基础设施上写代码,并为此使用客户的工具”。相比之下,解决方案架构师“很少在客户的基础设施上写代码”(Pragmatic Engineer,2025 年 8 月;T3,基于访谈的二手报道)。在 Palantir 做了七年 FDE 的 Anjor Kanekar“曾驻在航空航天制造商空客的总装线上”,文章还提到,“他的许多 FDE 同事也在类似非常规的环境中工作,包括物理隔离的环境”(T3)。

雇主把同样的期望写进了招聘启事。Sierra 的部署基础设施工程师需要有“在客户自有云环境中管理部署的经历,包括应对安全审查、合规要求和变更管理流程”(招聘启事,2026 年 4 月;T4,证明雇主要求什么,而不是工作实际怎么做)。同一份启事希望找一个“从一开始就考虑升级、回滚、监控和故障模式,而不是事后才想到”的人。这个光谱的最远端,是需要安全许可、驻场且物理隔离的工作,那里的约束是绝对的(见下框)。

公共部门的 FDE 招聘启事要求什么。OpenAI 的前线部署软件工程师(政府方向,2025 年 9 月发布)要求“持有有效的 TS/SCI 安全许可或同等许可”,并熟悉“云部署模式(Azure、AWS)、Kubernetes、Terraform”(招聘启事)。Anthropic 的应用 AI 工程师(公共部门)将“主要在安全的政府设施内,与客户的工程和任务团队一起驻场工作”,要求“持有有效的 TS/SCI 安全许可并通过测谎”,并优先考虑“在物理隔离或涉密环境中部署和运维软件的经验”(招聘启事)。Cohere 的前线部署工程师(基础设施专家,公共部门)是一个常驻渥太华、2026 年 9 月发布的岗位,将“主导 North 在私有云和本地环境中的端到端部署”,要求生产级 Kubernetes 和 Helm 经验,并写明“强烈优先考虑持有有效的绝密级安全许可者”(招聘启事)。就工作实际怎么做而言,三者都是 T4;招聘启事会变更或关闭,引用前请先核对。

大多数交付并不是物理隔离的,但这条经验可以迁移:在画出第一个方框之前,先弄清环境禁止什么。在构建的第一周,请平台团队书面回答七个问题:

  1. 托管。工作负载能跑在哪里:他们的云账号、他们的容器平台、他们的数据中心,还是供应商托管的租户?由谁开通,要多久?
  2. 模型端点。模型风险部门批准的是哪个端点,在哪个区域,哪个版本?它变化时会通知谁?如果批准的选项是他们自己托管的模型,wiki 的自托管推理页面讲了这会给构建增加什么。
  3. 身份。服务和人员通过哪个身份提供商认证,服务身份由谁签发?一把共用的供应商密钥是错误答案。
  4. 网络。有没有出站互联网访问?有没有代理、白名单、软件包镜像?你的构建到底能不能拉取依赖?
  5. 数据存储。有哪些现成的存储和队列允许你使用?一个新数据库,就是一样他们必须备份、打补丁和审计的新东西。
  6. 变更流程。变更怎样进入生产:一条流水线、一个变更评审会,还是一个发布窗口?从合并到运行要多久?
  7. 运维。出故障时会呼叫谁,他们平时已经在盯着哪些工具?

在写决策记录(10.7 节)的“决定”部分之前,先把这些答案写进它的背景部分。每个答案都会排除一些选项,而这正是目的:一个尊重全部七条约束的设计,通常比你独自画出的那个更小,而且只有它能过变更评审会。如果系统必须运行不可信的输入或代码,wiki 的沙箱与隔离模式讲了可选方案;至于运行其中哪一种,由客户的平台团队决定。

10.4

把必须守住的规则放到模型之外

每次交付都会冒出几条绝不能破的规则。涉诉标记的案件绝不进快速队列。超过某个额度的付款总要第二个人审批。关键零部件总要至少有两家供应商。客户会在需求挖掘时把这些规则说出来,划定范围时也常常把它们写成护栏。构建之前,用一个问题从阈值负责人那里拿到完整清单:“哪一种错误答案,是你必须向监管机构、法庭或董事会解释的?”把每个答案写成一句话,包含触发条件、动作和负责人。然后由构建来决定每条规则放在哪里。

记录最充分的现场案例,是 OpenAI 与一家亚太地区汽车制造商的 FDE 交付。ZenML 的 LLMOps 数据库根据对团队负责人 Colin Jarvis 的访谈做了总结(ZenML,2025;T3,对供应商自述的二手总结,没有报告任何结果指标)。团队按一条原则构建了一个供应链智能体:“尽可能使用确定性方法,只在 LLM 的概率性能带来价值的地方使用 LLM。”像“关键零部件(如轮胎)始终保持至少两家供应商”这样的硬约束,“通过确定性代码 100% 地检查,而不是信任 LLM”。模型也不直接优化计划;它“被赋予一个模拟器的访问权限”去探索选项,由代码强制执行限制。这是一个二手的单一案例,没有附带结果:把它当作值得照搬的模式,而不是它有效的证据。

不过,这个道理并不依赖这个案例。模型的输出是一个分布,最好的评测结果也只是一个百分比。客户口中的“绝不能”,是百分比满足不了的,所以它必须放在每次请求都会运行、每次都做同样事情的代码里。实践中,这些代码放在三个位置。

  • 模型之前。系统已知的事实直接决定去向,不必问模型。如果理赔系统已经设置了涉诉标记,案件就进涉诉队列,模型的意见要么不请求,要么被忽略。
  • 模型之后。输出按固定的 schema 解析,只有允许清单里的值能留下来。规则禁止该理赔类型使用的队列名,会被替换成人工队列。格式不对的答案视同没有答案。
  • 动作周围。模型的权限在规则开始的地方结束。在合成案例中,智能体只提建议,由理赔员执行。wiki 的人在回路页面讲了审批放在哪里;当规则多到几十行装不下时,面向智能体的策略即代码讲了规则引擎。

难办的是那种只有模型才能看到触发条件的规则。涉诉标记是一个字段;而未被标记的案件邮件往来里的一封律师函不是。代码读不了这封信,但它可以限制模型读了之后能做什么。只给模型一个方向上的权力:它可以把案件推向谨慎,比如因为某份文档提到了律师,就把理赔送去给人看;但绝不能把案件推离谨慎,既不能清除标记,也不能把被拦下的理赔送进快速通道。一次误报只花理赔员一分钟;一次漏报,正是这条规则要防止的事。在决策记录里,把允许的方向写在规则旁边。

规则要和评测分开测试。评测以百分比报告分派准确率,允许低于 100%。规则测试则是普通的单元测试:每条规则一个用例,加上客户专家点名的边缘情况,任何一个失败,构建就失败。把它们算进评测百分比,会让那一个要紧的失败淹没在几百个无关紧要的失败里。

这也是让安全审查的决定站得住的原因(见安全一章)。写在代码里的规则,审查者可以读、可以测、可以签字。写在提示词里的规则,是要审查者去信任一个分布,而一个谨慎的审查者不会这么做。

10.5

构建能通过评测的最不花哨的东西

规则拿出去之后,留给模型的工作通常比预想的小:读一份杂乱的文档,从一个短清单里选一项,给出理由。这种工作很少需要智能体。

Anthropic 的工程指南总结自几十个构建“各行各业的 LLM 智能体”的团队,它也是这么说的:“我们建议找到尽可能简单的方案,只在需要时才增加复杂度”(Anthropic,2024 年 12 月;T4,供应商基于自身客户工作的指南,而不是经过测量的比较)。它建议开发者“从直接使用 LLM API 开始:许多模式只需几行代码就能实现”。它警告说,框架“常常会增加额外的抽象层,遮蔽底层的提示词和响应,使其更难调试”。它也点明了自主性的代价:“智能体的自主特性意味着更高的成本,以及错误不断累积的可能。”

FDE 还多一个理由听从这条建议。你每加一层,客户团队在你离开后就得多弄懂一层,而且是在一个他们装不了你最爱的调试工具的环境里。Nabeel Qureshi 关于 Palantir 的回忆录从另一面说明了这一点。他第一个真正的客户交付是空客,他“搬到图卢兹住了一年,每周四天在工厂里和制造人员一起工作”。软件把“分散的数据来源,包括工单、缺件、质量问题”汇集到一处。他坦率承认这有多普通:“这些都算是基础的软件东西”,然而“仅仅把这些‘最佳实践’界面部署到真实世界,威力就大得惊人”(Qureshi,2024 年 10 月;T4)。他说 A350 的制造节奏提升了“4 倍”,那是一位前员工没有说明方法的回忆,不要把它当作结果转述。有用的是这份坦白:价值来自贴合真实工作流程的基础软件。

用一架梯子,只在评测告诉你需要时才往上爬一级:

  1. 只用规则。如果代码能根据系统里已有的事实决定一部分案件,就让它决定,只把其余的交给模型。
  2. 一次固定输出的模型调用。一个提示词、几个示例、一个 schema。大多数分类、提取和分派工作到这里就够了。
  3. 一条固定的链。按固定顺序进行的几次调用,每一步都能单独检查:先提取,再分类,再检查。当评测的失败类别显示某一环节在出错时,才加一步。
  4. 一个智能体循环。由模型选择工具和步骤。只有当完成任务的路径确实无法预先知道时才动用它,并且要预料到评测、审计日志和交接都会变得更难。

wiki 的智能体、聊天机器人与工作流页面划出了同样的界线。决定是否往上爬的依据,是评测一章里的失败类别,而不是好奇心。如果分派错误集中在被提取环节弄乱的文档上,就去修提取;规划器帮不上忙。如果客户已经把你需要的系统暴露为工具,MCP 服务器可能是最干净的接口(什么是 MCP),但前提是你所在的那一级里,模型确实在调用工具。

提醒一句。“最不花哨”指的是能通过评测的最不花哨。简单到达不到阈值的设计不叫克制;那是一次代码整洁的失败交付。梯子的存在,是为了让你在数字说该爬时才爬,而不是提前爬。

10.6

按他们能自己运行的方式来构建

Qureshi 对这个阶段必须改掉的习惯很坦白:“FDE 往往写那种能快速把事办成的代码,这通常意味着(说得客气点)技术债和取巧的变通办法”(T4)。在客户的环境里,这笔债在你离开那天就成了他们的。五个习惯能让它保持很小。

从第一周起就走他们的流水线上线。如果变更评审会第一次见到这个系统是在上线时,你会把整个上线窗口耗在他们的队列里。尽早把一个无关紧要的东西推过他们的完整路径。在他们网络内部一个镜像生产环境的预发布环境里测试;wiki 的面向智能体的预发布环境页面讲了这个环境需要什么。

锁定每一个版本,并记入日志。模型端点、提示词、示例、规则和代码各有一个版本,每条建议的审计记录都要记下全部版本。当平台团队升级端点时,在变更进入生产之前重跑评测和规则测试。一个你复现不了的分派结果,在模型风险部门面前是辩护不了的。

出错即安全关闭,退回旧流程。任何环节出错(端点超时、输出不符合 schema、规则拒绝了它),案件都去交付之前它去的地方。客户的运营团队本来就知道怎么处理那条路径。退回旧流程,能把一次故障变成慢一点的一天,而不是一起事故。

用他们已经在运行的东西。他们的日志、他们的监控、他们的密钥存储、他们的队列。一个他们还没在运维的组件,就是多一样要交接的东西,也是平台团队多一个说不的理由。

在生产栈上重新测量。试点时的评测数字是在别处测的。请阈值负责人的团队在他们的环境里,把留出集跑过生产部署,确认结果一致之后,才谈上线。文本提取、端点版本或超时上的差异,会在这里暴露,而不是在演示里。

这些对基础设施工程师来说都不新鲜。FDE 的特殊之处在于,要在自己掌控不了的地盘上,为一个将在你之后拥有它的团队做这些事。下一章会把这种所有权变成一项验收测试。

10.7

模板:架构决策记录

每个重要决定一份记录,在构建真正采用它之前写好,并由将来要和它一起过日子的人签字。客户的平台负责人签字确认它适合他们的环境;模型风险部门签字确认规则放在了他们批准的位置;FDE 负责人签字确认它能通过评测。把它放在客户的代码仓库里,而不是供应商的。

ARCHITECTURE DECISION RECORD                ADR-__  date: ________
Status: proposed | accepted | superseded by ADR-__

1. CONTEXT (their constraints, in writing from their platform team)
   Hosting:          _______________________________________________
   Model endpoint:   ____________  region: ________  version: ______
   Identity:         _______________________________________________
   Network/egress:   _______________________________________________
   Allowed stores:   _______________________________________________
   Change process:   _______________________________________________
   On-call owner:    _______________________________________________

2. DECISION (one paragraph)
   ______________________________________________________________

3. RULES THAT MUST HOLD (never decided by the model)
   Rule                       Enforced where      Test
   - ______________________  before | after    ____________
   - ______________________  before | after    ____________
   Rule tests must pass: 100%, on every change
   Model may move a case toward caution only: Y / N  (which rules)

4. WHAT THE MODEL DECIDES
   Input:   ______________   Output schema: ______________________
   Allowed values: _______________________________________________
   Authority: propose only | act with approval | act

5. OPTIONS CONSIDERED
   Option                     Rejected because
   - ______________________  ___________________________________
   - ______________________  ___________________________________

6. FAILURE BEHAVIOUR
   Endpoint down / bad output / rule rejects  ->  ________________

7. VERSIONS PINNED AND LOGGED PER DECISION
   model | prompt | examples | rules | code

8. EVAL GATE
   Held-out set re-run on this stack: ______  matches pilot: Y / N
   Climb to a more complex design only if: _______________________

9. REVERSIBILITY
   How to turn it off: ___________________   Who can: ____________

10. SIGN-OFF
   Customer platform owner (fits our environment): ______  date: __
   Model risk (rules where approved):              ______  date: __
   Vendor FDE lead (can clear the eval):           ______  date: __

下面是按合成案例填好的版本。按合成案例的规则,每个数字都是区间;真实的记录会写上确切的版本和名字。

ARCHITECTURE DECISION RECORD                ADR-03  date: month 2
Status: accepted

1. CONTEXT (their constraints, in writing from their platform team)
   Hosting:          insurer's container platform, inside their VPC
   Model endpoint:   approved by model risk  region: in-region
                     version: pinned; change notice from platform
   Identity:         insurer's identity provider; read-only service
                     identity issued by IT security
   Network/egress:   no outbound internet; internal package mirror
   Allowed stores:   side table owned by claims-platform IT; existing logs
   Change process:   weekly change board, via insurer's pipeline
   On-call owner:    insurer platform ops (runtime, after handoff);
                     claims-platform IT (connector, side table)

2. DECISION (one paragraph)
   Route auto FNOL packets with one model call returning a queue
   and a reason in a fixed schema. Deterministic rules run before
   and after the call. Suggestions go to the side table only; no
   agent loop, no retrieval index, no new datastore.

3. RULES THAT MUST HOLD (never decided by the model)
   Rule                       Enforced where      Test
   - litigation flag set =>   before + after      unit tests,
     never the fast queue                         adjuster edge cases
   - listed claim types =>    before              unit tests
     always a human queue
   - queue not in allowed     after               schema tests
     list => human queue
   - counsel named in packet  after               unit tests,
     (model hint) => human                        attorney-letter case
   Rule tests must pass: 100%, on every change
   Model may move a case toward caution only: Y  (litigation: may
   raise a hint; can never clear a flag or pick the fast queue)

4. WHAT THE MODEL DECIDES
   Input:   extracted packet text   Output schema: queue + reason
   Allowed values: the auto triage queues the claims director named
   Authority: propose only (adjuster acts)

5. OPTIONS CONSIDERED
   Option                     Rejected because
   - vendor-hosted service    data must stay in insurer VPC
   - agent loop + framework   eval failures were extraction and
                              examples, not planning; harder to run
   - managed vector store     not on platform list; not needed

6. FAILURE BEHAVIOUR
   Endpoint down / bad output / rule rejects  ->  default human
                                                   queue (old path)

7. VERSIONS PINNED AND LOGGED PER DECISION
   model | prompt | examples | rules | code  (in every audit entry)

8. EVAL GATE
   Held-out set re-run on this stack: month 2  matches pilot: Y
   Climb to a more complex design only if: one failure category
   persists that a single call cannot fix

9. REVERSIBILITY
   How to turn it off: config flag hides suggestions  Who can:
   claims ops lead or platform on-call

10. SIGN-OFF
   Customer platform owner (fits our environment): yes   date: m2
   Model risk (rules where approved):              yes   date: m2
   Vendor FDE lead (can clear the eval):           yes   date: m2

填好的版本里,有两处承担了大部分作用。第 3 节把每一条“绝不能”都放进代码,配上各自的测试,这样评测百分比永远不必承担它;它还用一行话说明,对于只有在文档里才能看到的情况,模型最多能把案件推多远。第 5 节记下了更花哨的选项为什么被否决,这样下一位工程师(可能是客户的人)不会在某个清闲的下午又把它们加回来。

10.8

失败信号

这些信号都出现在构建期间,通常在团队之外还没人注意到之前,并且每一个都指向一个解法。

信号
通常意味着什么
怎么做
设计里写着一个他们平台并不运行的服务
它是按供应商的环境画的,而平台团队还没看过。
拿到七个约束问题的书面答案,按答案重画,然后再继续构建。
一条“绝不能”的规则只存在于提示词里
它大多数时候能守住,却会在生产环境里、在要紧的那个案件上失守。
把它移进模型之前或之后的代码,并配一个必须始终通过的单元测试。
某条规则的触发条件只出现在文档里
一条“绝不能”正被交给模型独自决定。
只让模型把案件推向谨慎;任何把案件推离谨慎的输出,都由代码拒绝。
规则结果以百分比报告
规则测试被混进了评测,一个严重失败藏在许多无害的失败中间。
把它们分开:评测可以低于 100%,规则测试不行。
没人说得清某个理赔为什么被分错
设计的活动部件比任务需要的多,或者版本没有记入日志。
在梯子上往下退一级,并把每个版本和每个决定一起记入日志。
只有 FDE 能部署它
系统绕过了他们的流水线上线,而不是经由它上线。
现在就把一个变更推过他们的完整路径,并让他们的工程师来跑下一次。
行为变了,却没人改过代码
模型端点在你脚下被升级了。
锁定版本,要求变更通知,并在每次升级时重跑评测和规则测试。
10.9

进入下一阶段

当每一项都打上勾,就进入上线与交接:

  • 系统运行在客户的环境里,经由他们的流水线部署,使用他们 IT 安全部门签发的身份。
  • 每一条必须守住的规则都在模型之外的代码中强制执行,并有每次变更都通过的测试;对于只有模型能看到触发条件的规则,模型只能把案件推向谨慎,绝不能推离。
  • 架构决策记录已由客户的平台负责人、模型风险部门和 FDE 负责人签字。
  • 每个版本都已锁定,并随每个决定记入日志;退回旧流程的兜底路径已经测试过。
  • 留出集在生产栈上重跑的结果与试点结果一致,并由阈值决定是否上线。

如果打破一条规则的结局是投诉、罚款或法庭,它就不该待在提示词里。把它写成审查者读得懂的代码,每次变更都测试它,只让模型在规则留出的空间里工作。模型可以有时出错;规则不可以。