Extraction tool calls: tolerate omitted-empty fields (weak-model trap)
Diagnosis from the real 25-file notes import (user-pasted session logs):
almost every run hit 'Validation failed for tool submit_findings/review:
edges must have required properties edges' — the model (glm flash, low
thinking) omits top-level fields when they're empty, and pi-ai's
structural JSON-Schema validation runs BEFORE the zod layer, so each
omission cost one of the extractor's maxTurns 4 (full payload
regeneration) and occasionally burned out the run entirely (the two
FAILED imports).
The model is technically wrong, but the schema provokes it:
required-but-usually-empty arrays are a known weak-model anti-pattern.
- lhTool's model-facing declaration now converts with io: "input"
(the declaration describes what the MODEL sends): .default() fields
drop out of required. No-op for teacher tools (none use defaults).
- findingsSchema.edges / auditSchema.dropped: .default([]) — omission
means "none"; attribution: .default({quiz:{}}) for quiz-free sessions.
- Prompts hardened in all three agents: include every top-level field,
never omit because empty.
Structural probe: validateToolArguments now passes the exact pasted
failure shape; zod re-parse in execute applies the defaults. 355/355
tests, typecheck, lint, build green. (lh-tool.ts is the shared engine
seam — flagged to master: io:"input" is the correct mode for every
model-facing declaration, teacher path verified unaffected.)
This commit is contained in:
@@ -66,7 +66,10 @@ export function toPiTool(def: LhTool<any, any>): AgentTool<any, any> {
|
||||
name: def.name,
|
||||
label: def.name,
|
||||
description: def.description,
|
||||
parameters: z.toJSONSchema(def.schema as z.ZodType) as never,
|
||||
// io: "input" — the declaration describes what the MODEL must send,
|
||||
// so .default() fields are optional in the schema (omission = the
|
||||
// default); required-empty arrays are a known weak-model trap.
|
||||
parameters: z.toJSONSchema(def.schema as z.ZodType, { io: "input" }) as never,
|
||||
// Question tools opt into SEQUENTIAL execution: pi runs the whole
|
||||
// batch in call order when any called tool opts in, so the
|
||||
// tool-input-available chunks arrive in the model's call order — the
|
||||
@@ -110,6 +113,9 @@ export function piToolDeclarations(registry: Record<string, LhTool<any, any>>) {
|
||||
name,
|
||||
label: name,
|
||||
description: def.description,
|
||||
parameters: z.toJSONSchema(def.schema as z.ZodType) as never,
|
||||
// io: "input" — the declaration describes what the MODEL must send,
|
||||
// so .default() fields are optional in the schema (omission = the
|
||||
// default); required-empty arrays are a known weak-model trap.
|
||||
parameters: z.toJSONSchema(def.schema as z.ZodType, { io: "input" }) as never,
|
||||
}));
|
||||
}
|
||||
Reference in New Issue
Block a user