Semantic Kernel 破坏性变更管理规范(ADR 0045)深度解析:从钻石依赖到平滑迁移的工程实践
【免费下载链接】semantic-kernelIntegrate cutting-edge LLM technology quickly and easily into your apps项目地址: https://gitcode.com/GitHub_Trending/se/semantic-kernel
本文基于 Semantic Kernel 仓库中的架构决策记录 docs/decisions/0045-breaking-changes-guidance.md(ADR 0045),系统解读该 .NET 开源项目如何管理与约束破坏性变更(Breaking Changes)。文中结合仓库内的版本化策略、实验性 API 标记机制、废弃(Obsolete)实践与真实迁移指南,帮助读者理解:为什么 Semantic Kernel 将"避免破坏性变更"视为硬性约束、在何种例外场景下允许变更、以及当变更不可避免时项目如何通过迁移指南与新旧 API 并存期保护下游用户。读完本文,你将掌握一套可直接借鉴的开源 SDK 兼容性治理方法论,并能据此评估、规划你自己的库或应用中 API 演进策略。
一、为什么必须避免破坏性变更:钻石依赖问题的本质
ADR 0045 开篇即点明决策的核心动因——钻石依赖问题(Diamond Dependency Issue)。这是 .NET 生态中一个经典且棘手的依赖冲突场景:
假设你的应用同时引用了包 A 和包 B,而 A 与 B 又各自依赖同一包 C 的不同版本(如 C v1.0 与 C v2.0)。依赖图呈现"钻石"形状,此时 .NET 运行时只能加载其中一个版本的 C。如果 C v1.0 与 v2.0 之间存在破坏性 API 变更,那么依赖旧版本的一方就会在运行时抛出MethodNotFound、TypeLoadException等异常,且这类问题往往在编译期无法发现,只有运行到具体调用路径时才爆发。
对于 Semantic Kernel 这类被大量应用作为"中间层"基础设施引用的 SDK 而言,破坏性变更的风险被进一步放大:同一进程中,不同业务模块可能经由不同版本的 Semantic Kernel 间接交互。因此 ADR 0045 的结论非常明确:
Chosen option: We must avoid breaking changes in .Net because of the well known diamond dependency issue.
这条决策不仅约束 .NET 实现,其精神也辐射到仓库中 Python、Java 等其他语言实现的发布节奏(见 docs/decisions/0036-semantic-kernel-release-versioning.md)。
二、决策驱动:破坏性变更仅允许在两种情形下发生
ADR 0045 明确列出允许破坏性变更的全部例外情形,除此之外一律禁止:
- 实验性功能的更新(Updates to an experimental feature):当项目从实验特性中学习到新认知、需要修改其设计时,允许破坏性变更。这与 Semantic Kernel 的"实验性 API 可随时变化"的定位一致。
- 依赖项引入无法避免的破坏性变更(When one of our dependencies introduces an unavoidable breaking change):当下游依赖(如 OpenAI SDK)升级导致必须跟进时,允许变更。
同时,文档也列举了"为了适应新需求而必须移动"(must move to accommodate)的典型场景,这些场景本身不是豁免许可,而是需要通过废弃(Obsolete)+ 迁移路径来处理的特殊情况:
- 发现安全漏洞或严重缺陷(如数据丢失);
- 依赖项引入重大破坏性变更(如全新的 OpenAI SDK);
- 当前实现存在严重局限(如 AI 服务引入新能力,旧 API 无法承载)。
针对上述场景,决策规定了标准处理流程:计划废弃相关 API(obsolete the API(s)),并提供文档化的迁移路径(documented migration path)指向新的推荐模式。ADR 0045 特别以"切换到新的 OpenAI .NET SDK"为例——在过渡期内,新旧 API 将并存支持(a period where the new and old API's will be supported),让客户有充足时间完成迁移。
三、强制要求:破坏性变更必须被清晰记录与公示
即便在允许破坏性变更的例外情形下,ADR 0045 也提出了两项不可妥协的记录义务:
- 在 PR 描述中详细描述破坏性变更:以便该描述被自动纳入发布说明(release notes)。这是变更信息的源头,也是下游用户最先接触到的变更入口。
- 更新 Learn Site 迁移指南文档:并确保迁移指南的发布时间与包含该破坏性变更的版本发布时间同步(coincide),避免"版本已发布、迁移文档缺席"的空窗期。
这两条要求构成了 Semantic Kernel 变更治理的完整闭环:PR 描述 → 发布说明 → 迁移指南,任何一环缺失都视为不合规。
四、源码落地:实验性特性与废弃 API 的工程机制
ADR 0045 描述的是决策层原则,而仓库源码则展示了这些原则如何在代码层面强制执行。理解这些机制,能帮助贡献者判断"我的改动是否构成破坏性变更、应走哪条流程"。
4.1 实验性 API:ExperimentalAttribute 与 SKEXP 诊断码
仓库在 dotnet/src/InternalUtilities/src/Diagnostics/ExperimentalAttribute.cs 中内联了一份ExperimentalAttribute的实现(该实现源自 .NET Runtime,Semantic Kernel 以 internal 形式复制以便在 .NET 8 之前的目标框架上使用)。该特性接受一个diagnosticId参数,编译器会据此对调用实验性 API 的调用方产生诊断警告或错误。其 XML 注释明确说明:"Indicates that an API is experimental and it may change in the future"——这正是 ADR 0045 中"实验性功能允许破坏性变更"条款的代码级前置声明。
配套的 dotnet/docs/EXPERIMENTS.md 维护着一张实验性功能诊断码对照表,例如:
| SKEXP 代码 | 实验性功能类别 |
|---|---|
| SKEXP0001 | Semantic Kernel 核心功能(Embedding、Image、Memory 连接器、Kernel filters、Audio 服务) |
| SKEXP0010 | OpenAI 与 Azure OpenAI 服务 |
| SKEXP0020 | Memory 连接器 |
| SKEXP0040 | 函数类型(GRPC / Markdown / OpenAPI / Prompty) |
| SKEXP0050 | 开箱即用的插件 |
| SKEXP0060 | Planners |
| SKEXP0080 | Process Framework |
| SKEXP0110 | Agent Framework |
对于使用方,可以在项目文件中通过NoWarn抑制特定实验性 API 的警告。例如 dotnet/docs/EXPERIMENTS.md 给出的配置:
<PropertyGroup> <NoWarn>$(NoWarn);SKEXP0001,SKEXP0010</NoWarn> </PropertyGroup>仓库示例代码中也有大量#pragma warning disable SKEXP0001的用法(如 dotnet/samples/Demos/AIModelRouter/CustomRouter.cs),说明这是社区普遍采用的按需抑制方式。
4.2 正式 API 的废弃:Obsolete 特性与替代指引
对于已稳定但需要被替代的 API,仓库使用 .NET 标准的[Obsolete]特性,并且在废弃消息中明确指出替代方案。最典型的例子是 dotnet/src/SemanticKernel.Abstractions/Kernel.cs 中Kernel的四个事件——FunctionInvoking、FunctionInvoked、PromptRendering、PromptRendered:
[EditorBrowsable(EditorBrowsableState.Never)] [Obsolete("Events are deprecated in favor of filters. Example in dotnet/samples/GettingStarted/Step7_Observability.cs of Semantic Kernel repository.")] public event EventHandler<FunctionInvokingEventArgs>? FunctionInvoking;这里体现了三个细节:
- 指明替代方向:消息明确"Events 已废弃,改用 Filters",并给出仓库内示例文件 dotnet/samples/GettingStarted/Step7_Observability.cs 作为迁移指引;
- 隐藏过时 API:
[EditorBrowsable(EditorBrowsableState.Never)]让过时成员在 IDE 智能提示中不再出现,引导开发者主动迁移,但并不删除,保证既有代码仍可编译运行; - 源码与决策的呼应:这套"废弃 + 引导迁移 + 保留兼容"的流程,正是 ADR 0045 中"obsolete the API(s) and provide a documented migration path"的落地形态。
五、配套的版本化策略:ADR 0036 如何配合变更治理
破坏性变更治理与版本号策略密不可分。仓库中的 docs/decisions/0036-semantic-kernel-release-versioning.md 补充了 ADR 0045 的发布侧约束,两者共同构成完整的兼容性契约:
- 不严格遵循语义化版本(semver):因为 NuGet 生态并不严格遵循 semver,Semantic Kernel 选择务实策略;
- 低影响的不兼容 API 变更不提升 MAJOR 版本:这类变更"通常只影响 Semantic Kernel 内部实现或单元测试",且项目预期 API 表面不会有重大调整;
- 实验性功能或 alpha 包的 API 变更不提升 MAJOR 版本:与 ADR 0045"实验性功能允许破坏性变更"条款严格对应;
- MINOR 版本在向后兼容地新增功能时递增;PATCH 版本在仅含向后兼容的缺陷修复时递增;
- 版本后缀约定:
preview(.NET)/beta(Python)表示接近正式发布、接口已基本冻结;alpha表示功能未完成、公共接口仍在开发中且预期会变化。
对下游用户而言,这套策略的含义是:使用alpha后缀的包时,必须预期 API 随时变化;使用正式版本时,项目承诺尽最大努力避免破坏性变更;一旦发生破坏性变更,必有发布说明与迁移指南。
六、实战范例:OpenAI Connector 迁移指南的完整剖析
ADR 0045 中"切换到新 OpenAI .NET SDK"的示例,在仓库中已有完整的实践产物——dotnet/docs/OPENAI-CONNECTOR-MIGRATION.md。这份文档是理解"项目如何处理一次大规模破坏性变更"的最佳教材,它展示了 ADR 0045 要求的迁移路径文档应该包含哪些内容:
1. 包与命名空间迁移(兼容性过渡)
- // Before - using Microsoft.SemanticKernel.Connectors.OpenAI; + After + using Microsoft.SemanticKernel.Connectors.AzureOpenAI;其中特别说明:Microsoft.SemanticKernel.Connectors.AzureOpenAI包依赖Microsoft.SemanticKernel.Connectors.OpenAI包,因此使用 OpenAI 相关类型时无需同时引用两个包。这正是 ADR 0045 所述"新旧 API 并存期"的包级实现。
2. 被移除的能力与替代方案
OpenAITextGenerationService/AzureOpenAITextGenerationService被移除(新版 OpenAI SDK 不支持 text generation modality),需改用OpenAIChatCompletionService/AzureOpenAIChatCompletionService;同时注明 ChatCompletion 服务仍实现ITextGenerationService接口,面向接口的代码可能无需改动;ResultsPerPrompt(多候选结果)从OpenAIPromptExecutionSettings中移除;OpenAIFileService被废弃,推荐改用OpenAIClient.GetFileClient()。
3. 行为变化清单(Breaking glass scenarios)
文档第 9 节列出了一系列"你可能需要更新代码"的行为级变化,例如:
- 元数据键名变化:
Created→CreatedAt、tool_calls→ToolCalls; - FinishReason 字符串值从
stop变为Stop; - Token 命名约定从
Completion/Prompt改为Output/Input,类型从CompletionsUsage改为ChatTokenUsage:
- var usage = FunctionResult.Metadata?["Usage"] as CompletionsUsage; - var completionTokesn = usage?.CompletionTokens ?? 0; - var promptTokens = usage?.PromptTokens ?? 0; + var usage = FunctionResult.Metadata?["Usage"] as ChatTokenUsage; + var promptTokens = usage?.InputTokens ?? 0; + var completionTokens = usage?.OutputTokens ?? 0;- 传输管道配置从
Azure.Core.Pipeline的HttpClientTransport改为新 OpenAI SDK(基于System.ClientModel)的HttpClientPipelineTransport:
var clientOptions = new OpenAIClientOptions { - // Before: From Azure.Core.Pipeline - Transport = new HttpClientTransport(httpClient), + // After: From OpenAI SDK -> System.ClientModel + Transport = new HttpClientPipelineTransport(httpClient), };这份迁移指南完美印证了 ADR 0045 的治理闭环:依赖(OpenAI SDK)引入不可避免的破坏性变更 → 项目跟进 → 用迁移指南完整记录每一项变更及其替代方案 → 与新版发布同步公示。
七、给贡献者与下游开发者的实践清单
综合 ADR 0045 与仓库源码,可以提炼出对两类角色的可操作建议:
如果你是 Semantic Kernel 贡献者:
- 改动前先判断是否触及公共 API;若涉及,确认目标 API 是否为实验性(是否带有
SKEXPxxxx诊断码); - 非实验性 API 的破坏性变更默认不被接受;若确属依赖强制升级等例外,必须在 PR 描述中详尽列出变更点,使其进入发布说明;
- 采用"先废弃、后移除"的节奏:用
[Obsolete]标注并提供替代方案指引(参考 Kernel.cs 的写法),保留新旧并存过渡期; - 同步编写迁移指南文档,并保证其与发布同步上线。
如果你使用 Semantic Kernel(下游消费者):
- 优先使用
preview/beta之前的正式版本,或固定版本号并关注发布说明; - 升级前先检索对应版本的迁移指南(如 dotnet/docs/OPENAI-CONNECTOR-MIGRATION.md),按"代码替换清单"逐项核对;
- 当引入实验性功能时,记录其
SKEXP诊断码并在项目文件中显式NoWarn或集中管理,便于日后追踪实验性 API 的变更。
八、总结
ADR 0045 表面上看是一条简短的决策记录,但它在 Semantic Kernel 仓库中串联起了一整套完整的兼容性工程体系:以钻石依赖问题为约束原点,以"实验性功能 + 依赖强制升级"为唯二例外,以 PR 描述、发布说明、迁移指南三级文档为公示机制,以 ExperimentalAttribute、Obsolete 特性、SKEXP 诊断码为代码落地手段,以版本化策略(ADR 0036)与真实迁移指南为配套支撑。这种"原则上零破坏、例外必记录、迁移必有文档"的治理模式,正是大型开源 SDK 在快速迭代与下游稳定性之间取得平衡的关键,值得任何面向开发者的库或框架团队借鉴。
延伸阅读(仓库内):
- 决策记录原文:docs/decisions/0045-breaking-changes-guidance.md
- 版本化策略:docs/decisions/0036-semantic-kernel-release-versioning.md
- 实验性功能清单与 SKEXP 代码表:dotnet/docs/EXPERIMENTS.md
- 真实迁移指南范例:dotnet/docs/OPENAI-CONNECTOR-MIGRATION.md
- ExperimentalAttribute 实现:dotnet/src/InternalUtilities/src/Diagnostics/ExperimentalAttribute.cs
- Obsolete API 落地示例:dotnet/src/SemanticKernel.Abstractions/Kernel.cs
【免费下载链接】semantic-kernelIntegrate cutting-edge LLM technology quickly and easily into your apps项目地址: https://gitcode.com/GitHub_Trending/se/semantic-kernel
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考