模型接口正常不代表功能可靠,授权范围、业务事实、数据时效和状态变更仍须由应用代码保证。文章建议把架构依赖转化为可验证的行为契约,例如目录版本、价格来源、库存时效和账户隔离规则。
模型端点可以处于健康状态,而围绕它构建的功能却可能已经失效。
模型返回有效响应,并不意味着应用程序使用了经过授权的数据、核验了最新的业务事实,或完成了用户请求的状态变更。
模型并不负责功能契约中的大部分内容。应用程序代码必须强制执行账户作用域、选择正确的记录系统、验证硬性约束,并报告状态变更是否成功。
依赖项登记表为架构图中展示的交互补充了行为契约。
“从我们当前的商品目录中推荐一把合适的吉他”是一项有用的产品承诺,但还不够精确,无法作为操作契约。
应将其转化为应用程序可以强制执行的属性:
推荐的每个产品 ID,都能在该请求所采用的目录版本中解析到对应产品。
展示的每个价格都来自指定的定价来源,并且在生成响应时,其数据时间不超过允许的最大时效。
只有当库存观测数据足够新、足以支持该声明时,功能才会显示“有库存”。
应用程序使用任何客户记录之前,该记录都必须与经过身份验证的账户作用域匹配。
只有在确认精选列表写入成功后,响应才会报告保存成功。
并非产品承诺的每一部分都会成为运行时不变量。产品是否存在、账户作用域、数据新鲜度和保存结果都可以通过确定性方式强制执行。推荐是否真正有用或主观上是否合适,则仍然是一个评估问题。它需要质量标准、具有代表性的测试用例和反馈;应用程序通常无法针对每个请求证明推荐在主观上是合适的。
新鲜度限制和时间预算属于产品及其服务目标。明确命名它们、指定负责人,并强制执行。
对于吉他助手,请求路径可能如下所示:
authenticated shopper
-> resolve account scope
-> authorize profile access
-> read preferences and optional purchase history when enabled
-> resolve the accepted catalog version
-> verify that the version is still acceptable
-> read product records and specifications
-> read current price observations
-> read inventory observations when availability matters
-> resolve prompt, schema, route, deployment, and model versions
-> call the model
-> validate schema and selected product references
-> resolve authoritative prices and availability when relevant
-> revalidate budget and applicable availability constraints
-> compose authoritative facts into the response
-> write a shortlist when requested
-> return recommendation and save outcome
observability: signals across the complete path
目录版本选择和请求时的新鲜度验证是两个独立环节,因为它们的失败方式不同。
对于精确的业务事实,可以让模型对产品 ID 进行排序,再由应用程序代码从相应的记录系统中解析价格和相关库存状态。如果最终价格超出预算,或必要的库存状态已不再满足请求,就拒绝该候选项。可以改用另一个经过验证的候选项;在契约允许时进行一次有界的重新生成;或者明确说明无法完成这项依赖产品数据的请求。
依赖项登记表将成熟的可靠性思维应用于任何分布式功能。它对 AI 尤其有用,因为即使模型调用成功,结果仍可能无效。当团队主要依赖服务架构图时,生成的引用、概率性的质量、token 和配额限制、内容过滤器以及回退方案的兼容性尤其容易被遗漏。
并非每个依赖项都会参与即时响应。在判断某项交互的重要程度之前,我会先区分三种角色。
某项运维依赖可能不会影响单次响应,但对于审计、计费、安全或恢复仍然是必需的。分析数据可以采用尽力而为的方式;必须投递的安全事件则不能如此。
一项能力很少只与请求存在一种关系:目录发布发生在请求之前,版本验证以内联方式进行,恢复则发生在之后。应当为每个可识别的交互单独建立一行,标明其角色、条件、关键程度与作用时段、时机和契约引用。如果交互跨越了另一个由不同团队负责的边界或运维生命周期,就应再次拆分;同一次调用的不同失败类别则应保留在该调用的失败契约中。
时机并不代表重要程度。关键程度有三个实用取值:
必需:缺少该交互,就无法履行声明的能力、承诺或运维义务。
可降级:系统可以在明确定义的降级模式下省略或削减受影响的能力或运维行为。任何用户可见的限制都必须明确说明。
尽力而为:失败既不会使用户可见的结果失效,也不会违反任何必要的运维义务,但可能降低分析或运维价值。
关键程度描述的是:在指定条件下,是否可以省略受影响的能力或运维义务。只要尝试了某项交互,关键程度就绝不能放宽安全、完整性、授权或正确性约束。“实时库存是必需的”过于宽泛。“当响应声称某件商品有库存时,此请求必须获得当前的库存观测数据”才具有实际的可操作性。
对于吉他推荐,初始分类可能如下所示:
在实际使用的登记表中,最后一个单元格会链接到共享契约和任何功能细节。如果明确承诺使用已存储的购买历史,那么除非请求提供了相同的信息,否则读取购买历史就是必需的。
当目录、定价和库存读取属于不同调用或不同责任方管理的契约时,不要将它们合并。反过来,DNS 故障、限流、内容过滤和输出格式错误可能只是同一次模型调用的不同失败类别,而不是四条登记项。
应在具备可观测性、拥有明确负责人和支持契约的边界处停止。除非应用程序会单独与内部组件交互,或者它们具有独立的运维生命周期,否则不要将这些内部组件纳入登记表。
分类表就是功能的依赖项登记表。对于处理受保护数据、变更状态、返回不明确结果、允许降级或需要独立运维响应的行,应展开详细说明。其他行可以直接引用共享契约。
共享事实只存储一次;功能条目仅包含特定于操作的行为和覆盖项。
仅在风险值得详细说明时使用以下扩展模板:
### <Interaction name>
- Feature register row: <link>
- Capability contract: <link and version>
- Data classification: <organization's classification>
- Freshness or completion requirement: <maximum age or completion target>
- Per-attempt and total time budget: <attempt and end-to-end allocation>
- Failure behavior: <response by material failure class>
- Retry override: <feature-specific difference>
- Degraded mode: <valid reduced behavior, or none>
- Consistency and idempotency: <read or write guarantees>
- Concurrency control: <lease claims and conditional transitions>
- Diagnostic signals: <outcome, duration, attempt, controlled identifiers>
- Escalation path: <owner or runbook>
- Feature register row: Execution, inline, and degradable when optional purchase-history personalization is enabled; authorization remains required
- Capability contract: Versioned profile-read contract with enforced authenticated account scope
- Data classification: Organization's protected-customer-data class
- Freshness or completion requirement: No feature-specific freshness override
- Per-attempt and total time budget: All attempts fit inside the request's profile-read allocation
- Failure behavior:
- Unavailable or timed out: continue without purchase-history personalization
- Authorization denied: do not read; continue only if an unpersonalized result is valid
- Scope or cache-partition mismatch: suppress the result and start the security incident path
- Malformed response: reject the data and record a contract failure
- Retry override: Do not retry authorization denials, scope mismatches, malformed data, or after caller cancellation
- Degraded mode: Omit personalization without claiming otherwise. Suspected cross-account exposure has no degraded mode
- Consistency and idempotency: Use account-partitioned cache keys and preserve account scope through every call
- Diagnostic signals: Outcome, duration, attempt, authorization reference, and controlled scope identifier; exclude purchase-history contents
- Escalation path: Profile runbook for availability; security incident runbook for suspected cross-account access
这个区别很容易被忽视:购买历史个性化是可降级的,但正确的授权不是。数据缺失可能产生有效的降级结果,而来自错误账户的数据会使整个操作失效,并且可能意味着受保护的数据已被泄露。
尝试保存候选清单
- 功能登记表条目:执行阶段、内联;当购物者要求保存时,这是满足“保存成功”声明的必要交互
- 能力契约:带版本控制的幂等写入状态机,支持按操作 ID 查询状态
- 命令持久性:`PendingDispatch` 存储不可变的保存命令,或足以重建并验证该命令的持久引用,以及它的指纹、操作 ID、幂等键、尝试租约和 `nextAttemptAt`
- 命令身份:如果使用不同的命令指纹复用操作 ID 或幂等键,则拒绝请求
- 数据分类:组织适用的客户数据类别
- 新鲜度或完成要求:只有确认写入后才能报告成功
- 单次尝试和总时间预算:写入与内联查询共享请求的保存时间配额
- 失败行为:
- 已确认成功:报告候选清单已保存
- 已确认拒绝:返回推荐结果,并单独报告保存失败
- 超时或响应丢失:标记为 `OutcomeUnknown`,并在仍有内联时间时查询;如果结果仍然未知,则返回操作 ID,并将保存状态标记为未确认
- 重试覆盖规则:出现未知结果后,仅当能够保证安全去重时,才使用相同的幂等键重试
- 降级模式:推荐仍然可以成功,但保存结果保持独立
- 一致性和幂等性:使用同一个操作 ID、幂等键和命令指纹标识逻辑保存操作
- 并发控制:分发需要通过原子条件更新获取租约,并使用预期版本将符合条件的状态转换为 `Submitting`。终态更新需要预期版本和尝试身份,以防较早的尝试覆盖较新的结果
- 诊断信号:操作 ID、结果、耗时和尝试次数;排除候选清单内容
- 升级处理路径:持久化错误使用写入运行手册;恢复调度器发现符合条件的未解决操作
已确认拒绝与结果未知的超时是不同的状态。内联交互会随响应结束;未解决操作的恢复则不会。
恢复或协调非终态的候选清单操作
- 功能登记表条目:执行阶段、延迟执行;当持久化契约承诺恢复时,在操作达到终态之前,该交互始终是必需的
- 能力契约:针对原始操作 ID 的状态机、权威状态查询和幂等重试契约
- 数据分类:与原始保存操作相同的类别
- 新鲜度或完成要求:在恢复目标规定的时间内达到终态结果
- 单次尝试和总时间预算:计划任务的尝试使用恢复预算,而非请求预算
- 恢复资格:内联尝试的租约已过期,且 `nextAttemptAt` 已到期
- 并发控制:工作进程在执行操作前,以原子方式获取租约,并转换预期的状态和版本。终态更新需要当前有效的尝试身份和预期版本
- 恢复行为:
- `PendingDispatch`:使用已有的操作 ID 和幂等键分发已存储的命令
- `Submitting` 或 `OutcomeUnknown`:执行权威查询;或者在共享契约允许时,对已存储的命令进行安全的幂等重试
- 已确认保存:记录终态成功
- 已确认拒绝,或者在契约规定的可见性窗口结束后由权威来源确认不存在:记录终态失败
- 非权威来源显示不存在、结果未知或服务不可用:根据恢复策略重新调度;恢复期限耗尽时升级处理
- 重试覆盖规则:复用原始操作 ID、幂等键和命令指纹;绝不创建新的逻辑保存操作
- 降级模式:保存操作不可降级;已返回的推荐结果仍然有效
- 一致性和幂等性:查询、重试和恢复均指向同一个逻辑操作
- 结果发布:持久化终态状态,使购物者和支持工具能够根据原始操作 ID 确定结果
- 诊断信号:操作 ID、结果类别、尝试次数、操作时长和终态结果;排除候选清单内容
- 升级处理路径:恢复尝试耗尽后,使用持久化协调运行手册
恢复会将请求的操作解析为终态结果,但并不总是完成状态变更。它属于延迟执行工作。发现调度器属于运维机制:它扫描符合条件的未解决操作,并暴露恢复延迟。功能的运行策略或准入策略决定:恢复目标被突破后,是停止新的保存操作、改变保存行为、限制流量,还是触发运维人员介入。
回退路由以及跨区域或跨提供商的尝试也会消耗同一份剩余预算,除非功能契约明确规定了另一种执行模型。
另一个模型并不会自动成为安全的回退方案。它在结构化输出、上下文限制、工具、延迟或安全控制方面可能表现不同。使用旧版本目录或许能够恢复访问,但可能违反新鲜度限制。跳过失败的验证步骤并不属于优雅降级。
对于一般性的比较,有效的降级方式可以是省略可选的购买历史个性化,或者省略未经验证的可用性声明。当请求明确要求推荐当前有货的产品时,应返回明确标识的部分结果,或者说明无法完成基于可用性信息的请求。即使保存失败,也仍然可以返回推荐结果,但必须单独报告保存失败。
功能可以返回更少的内容,但绝不能悄然改变成功的含义。
登记表综合了已有决策。图表用于识别交互和信任边界;故障分析提供故障类别;SLO 提供预算;威胁模型和运行手册提供控制措施与升级处理路径。
登记表条目还可以为追踪设计提供信息。使用稳定的交互名称,并记录耗时、结果、重试次数和受控的关联标识符。不要不加区分地记录提示词、检索到的文档、购买历史或工具参数。可观测性本身也有访问和保留边界。
从一个用户可见的操作开始:
将可强制执行的属性与质量标准分开。
映射交互并对其分类;拆分不同的边界和生命周期。
关联共享契约,仅展开高风险条目。
测试缓慢、陈旧、格式错误、未授权、重复、不可用和结果未知的路径。
将其用于受保护数据、检索、工具、状态变更、权威声明或运维义务。一次性的本地原型可能只需要基本的错误处理。
一份有用的依赖项登记表会在事故发生前确定运行时行为,而不只是事后解释事故。模型成功响应并不能推翻遭到违反的账户边界、过期价格、未经验证的可用性声明或未经确认的写入。
调用模型很容易,运行 AI 系统则不然
使用 OpenTelemetry、Aspire 和 Application Insights 实现 AI 智能体可观测性
本文中的依赖项登记表结构是一种综合方法。以下参考资料提供了其背后更广泛的故障分析、AI 应用设计、测试、重试、异步处理和遥测指导:
Microsoft Learn:执行故障模式分析的架构策略
Microsoft Learn:Azure 上 AI 工作负载的应用程序设计
Microsoft Learn:测试和评估 Azure 上的 AI 工作负载
Microsoft Learn:重试模式
AWS Builders' Library:使用幂等 API 确保重试安全
Microsoft Learn:异步请求-响应模式
OpenTelemetry:生成式 AI 语义约定
如需采取进一步措施,你可以考虑屏蔽此人和/或举报滥用行为