利用 JSON Schema 实现参数自动修复

利用 JSON Schema 实现参数自动修复 利用 JSON Schema 实现参数自动修复在构建轻量 Agent 时Tool Calling工具调用是最容易发生运行时异常的环节。大模型在输出结构化 JSON 参数时往往不是完全崩坏而是出现一些“轻微残缺”声明了age: number模型却返回了28字符串类型数字声明了tags: string[]模型却返回了tag1,tag2或直接返回了一个单字符串react声明了可选对象pagination: { page: number, pageSize: number }模型只返回了{ page: 1 }漏掉了非必填但下游逻辑依赖默认值的字段或者仅仅是在 JSON 结尾多了一个非法逗号导致标准JSON.parse抛错。如果一遇到参数类型不匹配就盲目发起二次 LLM 重试Re-ask / Self-Correction不仅会导致单次工具执行延迟增加 1~3 秒还会白白消耗数十倍的 Token 成本。对于轻量 Agent 而言利用 JSON Schema 在本地构建一套具有容错与自愈能力的“参数自动修复器”是极高 ROI 的工程选择。容错解析与 Schema 驱动的类型自愈参数自愈分为两步第一步是非严苛文本提取与清洗处理 Markdown 代码块包围、未闭合括号、末尾逗号第二步是基于 JSON Schema 定义进行运行时属性对齐与默认值回填。我们使用轻量级的递归校验函数结合标准 JSON Schema 定义在本地完成 90% 以上的类型纠偏。export interface SchemaProperty { type: string | number | boolean | array | object; default?: unknown; items?: SchemaProperty; properties?: Recordstring, SchemaProperty; required?: string[]; } export interface ToolJSONSchema { type: object; properties: Recordstring, SchemaProperty; required?: string[]; } // 第一步文本层面的容错提取与解析 export function robustJSONParse(rawText: string): Recordstring, unknown { let cleaned rawText.trim(); // 剥离 json ... 标记 if (cleaned.startsWith()) { cleaned cleaned.replace(/^(?:json)?\s*/i, ).replace(/\s*$/, ).trim(); } // 清除常见的行尾多余逗号 (Trailing comma) cleaned cleaned.replace(/,\s*([}\]])/g, $1); try { return JSON.parse(cleaned); } catch (err) { // 针对截断情况尝试补全未闭合的花括号 const openBraces (cleaned.match(/{/g) || []).length; const closeBraces (cleaned.match(/}/g) || []).length; if (openBraces closeBraces) { cleaned }.repeat(openBraces - closeBraces); } return JSON.parse(cleaned); } } // 第二步基于 Schema 的数据修复与补全 export function repairBySchema( data: unknown, schema: SchemaProperty, fieldPath root ): unknown { // 1. 处理 null 或 undefined if (data undefined || data null) { if (schema.default ! undefined) { return schema.default; } if (schema.type array) return []; if (schema.type object) return {}; return data; } // 2. 处理 number 类型纠偏 if (schema.type number) { if (typeof data number) return data; if (typeof data string) { const parsed Number(data.trim()); if (!Number.isNaN(parsed)) return parsed; } return schema.default ?? 0; } // 3. 处理 boolean 类型纠偏 if (schema.type boolean) { if (typeof data boolean) return data; if (typeof data string) { const lower data.toLowerCase().trim(); if (lower true || lower 1 || lower yes) return true; if (lower false || lower 0 || lower no) return false; } return schema.default ?? false; } // 4. 处理 string 类型纠偏 if (schema.type string) { if (typeof data string) return data; if (typeof data number || typeof data boolean) { return String(data); } return schema.default ?? ; } // 5. 处理 array 类型纠偏 if (schema.type array) { let arr: unknown[] []; if (Array.isArray(data)) { arr data; } else if (typeof data string) { // 支持逗号分隔的字符串转数组 arr data.split(,).map((item) item.trim()).filter(Boolean); } else { arr [data]; } if (schema.items) { return arr.map((item, idx) repairBySchema(item, schema.items!, ${fieldPath}[${idx}])); } return arr; } // 6. 处理 object 类型纠偏 if (schema.type object) { if (typeof data ! object || data null || Array.isArray(data)) { data {}; } const record data as Recordstring, unknown; const result: Recordstring, unknown {}; if (schema.properties) { for (const [key, propSchema] of Object.entries(schema.properties)) { result[key] repairBySchema(record[key], propSchema, ${fieldPath}.${key}); } } // 复制 schema 未明确定义但模型额外输出的其他字段 for (const [key, val] of Object.entries(record)) { if (!(key in result)) { result[key] val; } } return result; } return data; }接入 Agent 工具执行流在 Agent 的调度主循环中我们将修复器插在“模型返回”与“工具执行”之间。当参数经过修复后如果仍然缺少必填的required字段才走快速失败或精准重试绝不把脏数据漏给底层业务逻辑。export interface ToolDefinition { name: string; description: string; parameters: ToolJSONSchema; execute: (args: Recordstring, unknown) Promiseunknown; } export async function executeAgentTool( tool: ToolDefinition, rawArgsString: string ): Promise{ success: boolean; result?: unknown; error?: string } { let parsedRaw: Recordstring, unknown; try { parsedRaw robustJSONParse(rawArgsString); } catch (parseError) { return { success: false, error: JSON 格式损毁无法解析: ${(parseError as Error).message}, }; } // 执行基于 Schema 的自动清洗与类型对齐 const repairedArgs repairBySchema(parsedRaw, { type: object, properties: tool.parameters.properties, required: tool.parameters.required, }) as Recordstring, unknown; // 校验必须存在的关键必填项 const missingRequired (tool.parameters.required || []).filter( (field) repairedArgs[field] undefined || repairedArgs[field] ); if (missingRequired.length 0) { return { success: false, error: 参数缺失必填项: ${missingRequired.join(, )}。请重新提供该参数。, }; } try { const res await tool.execute(repairedArgs); return { success: true, result: res }; } catch (execErr) { return { success: false, error: 工具执行异常: ${(execErr as Error).message}, }; } }实践效果与避坑总结在我们的文件检索与代码重构 Agent 中引入该修复机制前后获得了显著的收益工具调用成功率提升由于弱类型语言转换如字符串数字传参、单字符串变数组引起的 400 参数校验失败率从原先的 8.4% 直接压降到 0.3% 以下。端到端延迟降低免去了大量的 Invalid parameter type - Re-Prompt 的往返网络调用单个复杂任务的处理时间平均减少了 2.4 秒。保持边界清醒自动修复只适用于确定性的类型降级与默认值回填例如 String 到 Number 的合理转换绝不要在修复逻辑里猜测业务语义。如果模型把targetFilePath字段漏掉了代码不能凭空瞎猜一个文件路径填进去该报错立刻报错。保持本地校验逻辑的纯粹与轻量既不引入庞大的重量级框架又能在第一时间收敛大模型的不确定性。