Runtime arg validation at the model boundary
模型边界处的运行时参数校验
`defineTool` ([the unified schema DSL](2026-07-20-unified-json-value-schema-dsl.md)) gives tool authors a typed `execute(args)` via the `InferArgs<S>` mapping. But that type is a compile-time claim about a value that arrives at runtime as model-generated JSON: nothing forced the model to honor the schema, so a malformed call — missing a required key, a string where a number was declared, or a literal outside the decl
English
Problem
defineTool (the unified schema DSL) gives tool authors a typed execute(args) via the InferArgs<S> mapping. But that type is a compile-time claim about a value that arrives at runtime as model-generated JSON: nothing forced the model to honor the schema, so a malformed call — missing a required key, a string where a number was declared, or a literal outside the declared set — reached execute typed-in-name-only. The tool body then either crashed on the bad shape or silently misbehaved.
Decision
validateArgs(spec, args): string[] compiles a ParameterSchemaSpec and delegates to the shared validateJsonSchemaValue() walker, returning human-readable violations for a well-formed declaration. defineTool snapshots the compiled parameter schema at definition time and runs that validation before the typed body; violations throw ToolArgsError (INVALID_ARGS), which the registry returns as an error result the model can correct.
The validator and compiler therefore share exact semantics: the implicit parameter root is an open object; required keys come only from required: true; defaults remain annotations; explicit nested objects honor their declared openness; arrays recurse through items; scalar literal constraints are type-correct; and oneOf accepts exactly one matching branch. Raw-registered tools own their input validation.
Consequences
- The model gets actionable feedback on its own malformed calls instead of an opaque crash, closing the gap between
InferArgs's promise and runtime reality. - The validator and
InferArgsmust stay in agreement; a property test generates args satisfying a spec and asserts they passvalidateArgs(with targeted corruptions rejected), closing that drift risk mechanically. ToolArgsErrorsubclassesHarnessErrorfrom the structured error taxonomy, keeping itscodefield; callers that read.messageare unaffected by the hierarchy.- Validation cost is negligible next to a model call.
中文
问题
defineTool(统一 schema DSL)为工具作者的 execute(args) 提供了经 InferArgs<S> 映射的类型化参数。但该类型只是对运行时值的编译期声明,而这个值实际上是模型生成的 JSON:没有任何机制强制模型遵守 schema,因此畸形调用(缺少必需键、声明为数字的位置传入字符串,或字面量超出声明的集合)会以「仅在名义上类型化」的状态到达 execute。工具函数体随后要么在处理结构错误的数据时崩溃,要么在不报错的情况下行为异常。
决策
validateArgs(spec, args): string[] 编译 ParameterSchemaSpec,并委托共享的 validateJsonSchemaValue() 遍历器,对格式正确的声明返回可读的违规列表。defineTool 在定义时对编译后的参数 schema 创建快照,并在调用类型化函数体之前执行校验;存在违规时会抛出 ToolArgsError(INVALID_ARGS),注册表将其作为模型可据以修正的错误结果返回。
校验器与编译器因此共享完全一致的语义:隐式参数根是开放对象;必需键仅来自 required: true;默认值仍是注解;显式嵌套对象遵循其声明的开放性;数组通过 items 递归校验;标量字面量约束保证类型正确;oneOf 仅在恰好一个分支匹配时才接受。直接注册的工具自行负责输入校验。