- 编程语言
- AI Agent
- 编译器
- CLI
- 人工智能
【免费下载链接】baml
The programming language for agents
导读
Baml.Bridge.ManagedContractProbe是 BAML C# 桥接层(baml-bridge,.NET 10 运行时)内部的一组"编译期设计证据"探针,用于在产物发布之前,把 gate B5、B6、B10 尚未定案的托管契约证据(optionality 与 nullability 的正交性、语义部分状态、不可变请求/客户端/媒体、SafeHandle 所有权、全部动态值种类与描述符身份、规范泛型绑定、生成代码注册、拥有型集合与字节、精确数值边界、显式不支持的 CLR 形状、环检测、冻结图限制)编译并逐项验证。读完本文,你将掌握该探针的验证矩阵、每个主题域的底层类型设计、关键常量与 ABI 数值,以及如何在仓库中定位与复跑这些证据。
探针的定位:编译期证据,而非最终运行时包
ManagedContractProbe/README.md 开宗明义:这是一个repository-only(仅存在于仓库内)的 .NET 10 可执行程序,它"编译"(compile)的是 gate B5、B6、B10 的未解决(unresolved)托管契约证据。它与 baml-bridge 运行时说明 描述的正式运行时、NuGet 包是两层东西:
- 运行时:
baml-bridge是随 BAML CLI 生成的 C# 客户端共同发布的 .NET 10 托管运行时 + 原生资产,面向osx-arm64、osx-x64、linux-arm64、linux-musl-arm64、linux-x64、linux-musl-x64、win-x64、win-arm64等 RID,支持普通 JIT 与裁剪(trimmed)部署,但不支持 NativeAOT(以BAML0019失败构建)。 - 探针:
ManagedContractProbe及其同类 probe 是仓库内专用工程,不对外发布,职责是在设计阶段把尚存争议的托管契约"钉死"成可编译、可断言、可审计的证据。
因此,README 特别声明:"These definitions are compiled design evidence, not the final runtime package."(这些定义是编译后的设计证据,不是最终运行时包)。相应地,arity 3–32 的联合类型、生成的 V1 接缝(seam)、异常/取消身份、原生媒体还原、流生命周期、程序与原生引导等既有结论仍由仓库中其他探针保持权威,本探针不越俎代庖。
工程配置本身也体现了"证据"的苛刻性——Baml.Bridge.ManagedContractProbe.csproj 设置了net10.0+LangVersion 14.0、Nullable enable、TreatWarningsAsErrors、Deterministic,并且开启EnableTrimAnalyzer与IsTrimmable,让任何警告、非确定性输出或裁剪分析问题都会直接失败编译。
验证入口与检查清单:Main 输出的契约摘要
探针入口 Program.cs 依次执行七组验证,最终输出一组key=value的契约摘要:
| 输出键 | 断言值 | 语义 |
|---|---|---|
optional_nullable | orthogonal_complete | optionality 与 nullability 正交且完整 |
stream_state | pending_incomplete_complete | 流状态三态(Pending/Incomplete/Complete) |
media_values | 4x_url_bytes_base64_file_owned | 四类媒体的四种来源路径全部"拥有"数据 |
http_request | immutable_duplicate_headers_fresh_messages | 请求不可变、保留重复头、每次转换生成新消息 |
client_retry | immutable_structural_checked | 客户端/重试策略不可变且结构相等 |
handle | safe_clone_lease_dispose_identity | SafeHandle 克隆/租约/释放身份正确 |
baml_value | kinds_14_structural_typed | 14 种动态值种类,结构相等且类型化 |
descriptor_kinds | unknown_plus_14_value_shapes | 描述符含 Unknown 加 14 种值形状 |
descriptors | alias_literal_nominal_generic_union | 别名/字面量/名义/泛型/联合描述符 |
dynamic_inspection | enum_class_union_public | 动态值的 enum/class/union 可公开检查 |
dynamic_null | explicit_nullable_only | BAML null 只允许显式可空目标 |
collections | owned_readonly_canonical_maps | 拥有型只读集合与规范 map |
generic_binder | canonical_and_fail_closed | 泛型绑定只接受规范闭包、失败关闭 |
limits | depth_collection_bytes_nodes_bigint_cycle | 深度/集合/字节/节点/bigint/环限制 |
partial_projection | semantic_states | 语义部分投影 |
unsupported_clr | explicit | 不支持的 CLR 形状显式拒绝 |
public_contract | audited | 公共契约经反射审计 |
每组验证都有独立方法(VerifyOptionalNullableAndPartialState、VerifyMediaAsync、VerifyRequestClientAndHandleAsync、VerifyDynamicValues、VerifyGenericBinder、VerifyLimitsAndCycles、VerifyPublicShapeInvariants),并依赖两个自建断言助手Require(condition, message)与Expect<TException>(Action)(Program.cs#L1138-L1160),任何违反都抛出带明确消息的InvalidOperationException。
可选性与可空性:正交的三种状态模型
README 点名的第一个主题是"optionality versus nullability"(可选性与可空性)。探针以 OptionalNullableState.cs 中的两个只读结构体承载这个正交模型:
BamlOptional<T>:表达"调用方是否提供了参数"。核心是IsSet、Unset、FromValue(value)、TryGetValue(out T),并提供从T的隐式转换。未设置时访问Value抛InvalidOperationException。BamlNullable<T>:表达"值本身是否为空"。核心是IsNull、Null、FromValue(value)、TryGetValue(out T)与模式匹配用的Match(onNull, onValue)(两个委托均拒绝 null 参数)。关键语义:BamlNullable<T>.FromValue(null!)会坍缩为 null 态(构造时hasValue = value is not null),这与BamlOptional<T>.FromValue(null)截然不同——后者保持IsSet = true且值为 null。
VerifyOptionalNullableAndPartialState 逐条钉死了不变量:
- 显式默认值不坍缩:
BamlOptional<long>.FromValue(0)必须IsSet == true且Value == 0,绝不因为"等于默认值"而变成 unset; - 显式 null 不坍缩:
BamlOptional<string?>.FromValue(null)必须IsSet == true且值就是 null,且!= default; - 组合保留全部状态:
BamlOptional<BamlNullable<long>>的default(未提供)、BamlNullable.Null<long>()(提供了但为空)、BamlNullable.FromValue(42L)(提供了且有值)三者必须能区分——这正是"正交"的含义; - 流状态三态:
BamlStreamState<T>的Pending(尚未产生部分值)、Incomplete(携带部分值,如"par")、Complete(最终值,可以是 null)必须各自成立,且Complete(null) == Complete(null)。
配套的语义部分投影证据在 Program.cs#L190-L206:ResumePartial类型的RequiredWhenReady、DoneField为可空字段、NonNullPartial为必填子对象、WithState携带BamlStreamState<string?>——验证"部分投影"不会把未就绪字段错误地当成缺失标记。
这一设计与 src/README.md 的运行时约定完全一致:普通可空值位置在无歧义时直接用 C# 可空类型;泛型可空引用绑定因为 CLR 无法区分typeof(string)与typeof(string?),必须使用BamlNullable<T>。
不可变请求、客户端与媒体:快照与脱敏
HTTP 请求
BamlHttpRequest在 VerifyRequestClientAndHandleAsync 中验证:构造时(请求 ID、方法、URL、头列表、content-type、body 字节)即做拥有型快照——调用方随后headers.Clear()、修改body[0]均不影响快照;ToHttpRequestMessage()每次返回全新的HttpRequestMessage与Content(!ReferenceEquals(first, second)),且重复头(X-Trace: one、X-Trace: two)被完整保留;对第一个消息Dispose后再读第二个消息的 body,结果不受影响。ToString()则脱敏 URL 中的secret与 body 内容。
客户端与重试策略
BamlRetryPolicy(maxRetries, initialDelayMilliseconds, maxDelayMilliseconds, multiplier)与BamlClient(name, type, subClients, retry, counter)均不可变:探针把构造时传入的sourceChildren数组在事后篡改为"mutated",client.SubClients[0].Name仍须是"child";结构相等要求client.Equals(equivalent)且GetHashCode()一致。负的重试次数、非法BamlClientType值都会抛ArgumentOutOfRangeException。BamlClient.FromShorthand("name")得到BamlClientType.Primitive。
四类媒体
Media.cs 定义了共享的MediaPayload:FromUrl存 URL、FromBytes/FromBase64/FromFileAsync都复制字节为快照(bytes?.ToArray()),FromFileAsync在删除源文件后仍能取回完整内容("eagerly owned")。BamlImage/BamlAudio/BamlVideo/BamlPdf四类 sealed 值类型均提供FromUrl、FromBytes、FromBase64、FromFileAsync与TryGetUrl/TryGetBytes。
VerifyMediaAsync 验证:
- 字节复制:传入可变数组后篡改首字节,取回的快照首字节仍是原始值;
- URL 脱敏:带
?token=secret#fragment的 URL 在结构化值中保留完整 URL(videoUrl.Contains("token=secret")),但ToString()不得泄露token=secret或fragment; - 文件急切拥有:
BamlPdf.FromFileAsync读入后删除文件,TryGetBytes仍返回完整 PDF 头%PDF; - 结构相等/哈希:bytes 与 base64 同内容相等,URL 与 bytes 形式不相等,
GetHashCode一致。
SafeHandle 所有权:克隆、租约、释放
README 点名 "SafeHandle ownership"。探针在 Program.cs#L386-L432 中通过BamlHandle与原生引用计数表NativeReferenceTable.Releases验证:
Clone()产生独立所有者(!ReferenceEquals(original, clone)),但两个包装器指向同一原生资源(租约返回相同 identity);Dispose()幂等且独立:original.Dispose()两次后original.IsClosed,但clone仍可租约;对已关闭句柄Clone()抛ObjectDisposedException;- 释放恰好一次:两个包装器各自释放后,原生表
Releases精确 +2; - 并发安全:64 个并发租约任务与 1 个
Dispose竞态,最终必须正常关闭且不产生悬挂访问。
这也解释了 VerifyPublicShapeInvariants 的反射断言:媒体类型与BamlHttpRequest不实现IDisposable(无原生句柄),而BamlHandle必须实现;BamlHandle的任何公共属性类型都不得是IntPtr或SafeHandle(原始原生键绝不暴露给应用),BamlValue的任何公共属性也不得是裸object。
动态值:14 种种类与描述符身份
DynamicValues.cs 定义了动态值载体与两种枚举:
| 枚举 | 数值 | 取值 |
|---|---|---|
BamlValueKind : int | 0–13 | Null、Bool、Int、Float、BigInt、String、Bytes、List、Map、Enum、Class、Union、Media、Handle |
BamlTypeDescriptorKind : int | 0–14 | Unknown + 上述 14 种形状(一一对应、偏移 +1) |
BamlTypeDescriptor(公开只读属性恰好为Kind、Fqn、Arguments、IsNullable、Alias、Literal)有严格形状校验(DynamicValues.cs#L151-L182):Enum/Class/Handle必须携带 BAML FQN;List恰好 1 个类型参数、Map恰好 2 个、Union至少 2 个、其余必须为空;构造参数数组被复制为ReadOnlyCollection,任何 null 参数都拒绝。
VerifyDynamicValues 验证 14 种BamlValue工厂(Null、Bool、Int、Float、BigInt、String、Bytes、List、Map、Enum、Class、Union、Media、Handle)与Enum.GetValues<BamlValueKind>()完全一一对应且顺序一致;再验证:
- 描述符身份:
BamlValue.Alias("probe.UserId", ...)的Type.Alias必须保留,普通BamlValue.String("fixed")的Alias/Literal为 null;Literal元数据与载荷矛盾(如Int(1)配"01")抛异常; - 上下文无关解码不猜测:带 alias/literal 的值
TryGet<string>()必须失败(不能脱离出现点猜类型),Union 的激活臂元数据与载荷矛盾必须抛异常; - null 的纪律:
BamlValue.Null只允许解码到BamlValue、long?、BamlNullable<string>;解码到object、string、Person、接口/具体集合一律拒绝;BamlValue.From<object?>(null)、From<string?>(null)、From<List<long>?>(null)一律抛BamlTypeMappingException; - 隐式 null:
long? input = null经BamlValue.From必须得到BamlValue.Null; - 同构容器不猜类型:空列表/异构列表的类型参数为
Unknown,异构列表TryGet<IReadOnlyList<long>>必须失败; - 公共检查方法:
TryGetEnumVariant、TryGetClassFields(返回拥有 wire 顺序的只读快照)、TryGetUnion只能对正确种类返回 true,否则返回 false 且 out 参数为 null。
规范泛型绑定:白名单闭包与显式拒绝
Union2AndBinder.cs 中的BamlClrTypeBinder.Describe(type, path)实现了"canonical generic binding":
支持的闭包:bool、long、double、BigInteger、string、ReadOnlyMemory<byte>、BamlValue(→Unknown)、四类媒体(→Media)、BamlHandle(→Handle)、已注册类型,以及Nullable<T>/BamlNullable<T>(→ 内层描述符 +IsNullable=true)、IReadOnlyList<T>(→List)、IReadOnlyDictionary<K,V>(→Map,键必须是string、生成枚举或字面量,否则拒绝)。
显式拒绝的 CLR 形状(VerifyGenericBinder 中的完整列表):short、int、uint、ulong、float、decimal、List<long>、Dictionary<string,long>、long[]、object、JsonElement、JsonNode、JsonDocument、DateTime、DateTimeOffset、DateOnly、Guid、Uri、ValueTuple<long,string>、BamlOptional<long>、BamlUnion<string,long>、BamlNullable<BamlNullable<string>>(冗余嵌套会坍缩 BAML null 状态)、IReadOnlyDictionary<long,string>(非规范键)。每一个失败都必须是BamlTypeMappingException且Path以$T开头。
非规范数值的诊断替代:int的CanonicalReplacement == "long"、float的CanonicalReplacement == "double"——诊断会明确告诉你"该用什么规范类型",而不是笼统报错。
生成代码注册:类型描述符与动态编解码器
RegisterGeneratedTypes 演示了生成代码侧的注册模式:
BamlClrTypeBinder.Register(typeof(Person), personDescriptor):把 CLR 类型绑定到 BAML 描述符(probe.Person、probe.Color、泛型probe.Box),重复注册抛InvalidOperationException;BamlDynamicRegistry.Register<Person>(encode, decode):注册名义类型的双向编解码器;解码端要求载荷描述符匹配、可通过TryGetClassFields公开检查,再映射回Person { Name, Age };BamlDynamicRegistry.RegisterCanonicalList<long>()与RegisterCanonicalStringMap<long>():注册规范集合编解码,解码产物为ReadOnlyCollection<T>/ReadOnlyDictionary<string,T>(DynamicValues.cs#L840-L978)。
注册之后,BamlValue.From(person)/classValue.As<Person>()、BamlValue.From(listInput)/encodedList.As<IReadOnlyList<long>>()便可在反射之外工作——这正是 src/README.md 强调的"生成代码的字段编解码器与工厂从不通过反射发现模型成员、可裁剪安全"的实现证据。
拥有型集合与精确数值边界
BamlValue.List与BamlValue.Map构造即复制快照:List 用ReadOnlyCollection<BamlValue>,Map 按键StringComparer.Ordinal排序、拒绝重复键与 null 值/键(DynamicValues.cs#L270-L355)。探针验证解码结果也是只读的:对decodedList执行((IList<long>)...)[0] = 0抛NotSupportedException,对decodedMap执行Add同样抛异常(Program.cs#L581-L616)。
数值边界方面:BAMLint映射到long且必须落在 BAML 范围[-2^62, 2^62-1](BamlInteger校验,探针用BamlInteger.Max构造BamlValue.Int);bigint的十六进制编码长度受BamlBigIntCodec.MaxHexLength = (1 << 28) / 4 + 2约束,超限抛BamlTypeMappingException(DynamicValues.cs#L823-L838)。
冻结图限制与环检测
README 点名"all frozen managed graph limits"(全部冻结的托管图限制)。BamlValueLimits 定义了四组硬限制,任何构造或图遍历都会校验:
| 限制 | 值 | 触发路径 |
|---|---|---|
MaxDepth | 64 | 嵌套深度超过即抛 |
MaxCollectionItems | 1_000_000 | 单集合元素数超限即抛 |
MaxBytes | 64 × 1024 × 1024(64 MiB) | 字节/媒体负载超限即抛 |
MaxNodes | 2_000_000 | 图总节点数超限即抛 |
VerifyLimitsAndCycles 用两个特制测试桩验证"限制必须在访问前拒绝":CountOnlyValues(只报告Count、不真正枚举)触发MaxCollectionItems + 1拒绝;OversizeMemory(MemoryManager<byte>子类,GetSpan直接抛)触发MaxBytes + 1拒绝——证明大小校验发生在读取内容之前。环检测由GeneratedCodecTraversal(Union2AndBinder.cs#L356-L387)用引用相等集合完成:Node.Next = node的自环在遍历到$.next时抛出,且异常Path精确为"$.next"、消息包含 "cycle"。
公共表面审计:反射钉死的 ABI
VerifyPublicShapeInvariants(Program.cs#L874-L1118)用反射把公共契约审计到字节级:
BamlTypeDescriptor公开实例属性必须恰好是{Alias, Arguments, Fqn, IsNullable, Kind, Literal},且没有公开构造函数(只能由内部工厂/生成代码构造);BamlValue公开属性恰好{Kind, Null, Type},公开方法恰好{As, BigInt, Bool, Bytes, Equals, Float, From, GetHashCode, Int, List, Map, String, ToString, TryGet, TryGetClassFields, TryGetEnumVariant, TryGetUnion}(Equals重载计一次),同样无公开构造函数;- 桥接层自有枚举的 ABI 数值被冻结:
BamlValueKind底层int且取值为 0–13;BamlTypeDescriptorKind底层int且为 0–14;BamlStreamStateKind底层int且为 0/1/2;BamlClientType底层long且为 1/2/3——任何偏移都会使反射断言失败; - 密封性:
BamlImage、BamlValue、BamlTypeDescriptor、BamlClient、BamlHttpRequest必须 sealed; - 描述符泛型参数快照:构造
Box描述符后篡改源mutableArguments数组,描述符的Arguments[0].Kind仍须是Int。
Union 的二分证据与既有探针的分工
Union2AndBinder.cs#L5-L141 只实现BamlUnion<T0, T1>(arity 2),并验证:
- 显式工厂
FromT0/FromT1,激活分支用内部caseIndex(1/2)区分,default(BamlUnion<T0,T1>)(caseIndex 0)不合法:IsT0/IsT1均为 false,访问AsT0抛"uninitialized"; Match/Switch二分支函数式访问;访问未激活分支抛InvalidOperationException;- 内部编解码视图
ActiveCaseForCodec(1→0、2→1)与ValueForCodec仅限生成代码使用,公开面不暴露; - 公开属性恰好
{AsT0, AsT1, IsT0, IsT1},且没有CaseIndex/IsValid这类会泄露"未初始化"合法性的属性。
这正是 README 分工声明的落点:BamlUnion的 arity 2 形状由本探针钉死,而arity 3–32 的联合、生成的 V1 接缝、异常/取消身份、原生媒体还原、流生命周期、程序/原生引导仍由仓库中既有的其他探针保持权威(参见 bridge_csharp 测试目录 下的EnumDiscriminantProbe、FailureCancellationProbe、GeneratedCodeContractProbe、StreamMediaAbiProbe、ProgramBootstrapProbe、NuGetPackageSmoke等兄弟工程)。
如何查看与复跑证据
探针是仓库内普通 .NET 工程,可以像查看任何源码一样阅读 Program.cs 与同目录的 DynamicValues.cs、Media.cs、OptionalNullableState.cs、Union2AndBinder.cs。若本机具备 .NET 10 SDK 与 C# 14 编译器,也可以在仓库内通过dotnet run(工程已启用Deterministic与TreatWarningsAsErrors,任何警告都会被当作错误)执行该探针;它不依赖任何外部 NuGet 包或原生二进制,纯托管、自包含,全部断言通过后返回退出码 0 并打印上文那张契约摘要表。
需要提醒的是:该探针是设计证据而非公开发行的 SDK 组件,运行时契约的正式落地仍以 baml-bridge 运行时说明 与 BAML CLI 生成的baml_sdk/为准——探针的作用,是让这些契约在进入最终产物之前,先以最严格、最可复现的方式被"编译并钉死"。
- 编程语言
- AI Agent
- 编译器
- CLI
- 人工智能
【免费下载链接】baml
The programming language for agents
相关推荐
BAML C Bridge 泛型与可空性编译证据矩阵:GenericCompileProbe 探针设计与验证实践
BAML C Bridge 泛型与可空性编译证据矩阵:GenericCompileProbe 探针设计与验证实践 导读 本文围绕 BAML(The progra
编程语言AI Agent编译器CLI人工智能BAML C 桥接层程序引导证据探针:从编译器字节恒等到原生初始化失败缓存的完整验证
BAML C 桥接层程序引导证据探针:从编译器字节恒等到原生初始化失败缓存的完整验证 导读 BAML 编译器输出的 .baml 程序字节码最终要进入 C 运行时
编程语言AI Agent编译器CLI人工智能BAML C Bridge 失败与取消契约冻结:深入 FailureCancellationProbe 探针
BAML C Bridge 失败与取消契约冻结:深入 FailureCancellationProbe 探针 本文以 baml_language/sdks/cs
编程语言AI Agent编译器CLI人工智能
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考