Repository navigation
feat(agent): add AgentResult delivery contract with opt-in structured output finalizer (T39) - #173
Conversation
… output finalizer (T39)
|
hi @konlue the ci is failed, please check it, thanks |
Thanks for pinging! Will check this tomorrow. 🌹 |
…ts without an output schema do not load the schema validator (T39)
|
Found the real root cause — it was on my side, and the earlier transient-download theory was wrong. The PR1 eagerly instantiated the Pushed a fix: the finalizer is now created lazily on first use, so agents without an output schema never load the validator — consistent with how the planner codec already coexists with
|
Describe what this PR does / why we need it
First part of T39 (#142), following the claim agreement: the framework already turns
outputType/outputSchemainto prompt constraints, but prompt constraints are not validation —ReactAgent.callreturns the raw assistant message with no enforced delivery boundary. This PR adds an opt-in structured delivery contract:ReactAgent.callResult(...), mirroring the existingcall(...)entries, returns anAgentResultthat parses and schema-validates the configured output schema. The existingcall/invoke/streamsignatures and behavior are untouched.Does this pull request fix one issue?
NONE
Describe how you did it
AgentResultrecord (message / status / usage / validated data / issues) with a four-stateStatusenum (COMPLETED / VALIDATION_FAILED / INTERRUPTED / ERROR) and anOutputValidationIssuerecord (errorCode / fieldPath / message) in the style of the T31 argument-validation protocol. The status vocabulary is deliberately separate from T38's tool-levelToolOutcome: one classifies a tool call, the other the whole run's delivery.StructuredOutputFinalizercarries the terminal message through unchanged and never regenerates it: parsing uses a strict Jackson 3 mapper (duplicate-key and trailing-token detection), validation usesSchemaRegistrywith the same DRAFT_2020_12 dialect the planner codec andToolSchema.forTypealready produce — zero new dependencies. The component never throws; every failure mode becomesVALIDATION_FAILEDwith issues that do not echo the full output content.JumpTo.endall surface as the last assistant message and are finalized identically; a run that ends without a terminal message (e.g. interrupted) is reported as INTERRUPTED and never as completed; execution failures and thethrowOnModelError=false"Exception: ..." pseudo message are reported as ERROR. Token usage is read from the runner'sNodeOutput.tokenUsage()(the runner deliberately keeps the reserved usage key out of the state).Describe how to verify it
StructuredOutputFinalizerTest(6 cases): schema-valid output completes with parsed data; non-JSON / trailing-token output → INVALID_JSON; schema violations carry document-pointer instance paths (/status style); missing schema completes without validation; a malformed schema → SCHEMA_MALFORMED.ReactAgentCallResultTest(7 cases, stub ChatClient end to end): COMPLETED with usage carried; INVALID_JSON and SCHEMA_VIOLATION through the full graph; failing client → ERROR (MODEL_FAILED); a failing run never marks completed; and a guard test asserting the legacycall()still returns the raw message untouched.Run:
Full regression: graph-core 829 + agent-framework 1210 tests; only the documented pre-existing Windows-environment failures remain (FileSystem* symlink/virtual-root family and the skills/prompt-template cases, identical on clean main).
Special notes for reviews
outputSchemasignals validation intent and thecallResultentry signals delivery intent — a double opt-in, so a permanently-false knob is avoided and legacycallcannot change behavior.Exception:pseudo-message detection is prefix-based, matching the exact string produced byAgentLlmNodewhenthrowOnModelError=false; a user message that legitimately starts with that prefix would be misclassified as ERROR (the stricter alternative — wiring the failure through the graph — belongs to the streaming follow-up).