Skip to content

fix(workflow): validate structured agent output - #1324

Open
ai-yang wants to merge 1 commit into
claude-code-best:mainfrom
ai-yang:agent/workflow-schema-validation
Open

fix(workflow): validate structured agent output#1324
ai-yang wants to merge 1 commit into
claude-code-best:mainfrom
ai-yang:agent/workflow-schema-validation

Conversation

@ai-yang

@ai-yang ai-yang commented Aug 1, 2026

Copy link
Copy Markdown

Fixes #1323

根因

structured output 的 schema 校验依赖具体 adapter;workflow engine 的统一结果边界没有二次校验,因此第三方 adapter、fallback runner 或旧 journal 可以把不符合 schema 的 ok 结果继续向下游传播。

修复

  • 在 adapter/fallback 的统一结果边界调用现有 Ajv 校验。
  • 新增 invalid-structured-output dead reason,并接入严格的一次重试。
  • 连续两次无效时返回 null、不计 output token,并持久化 dead journal entry。
  • 重新校验旧 journal 的成功结果;无效缓存会 truncate 并现场重跑。
  • 预编译 schema;非法或异步 schema 作为配置错误直接抛出,不重试。
  • 修复原有 dead -> retry throw 可能触发第三次 backend 调用的问题。

兼容性

合法 structured output、普通文本成功结果及新格式 dead journal 的行为保持不变。无 schema 的正常成功和一次重试语义保持不变;dead -> retry throw 现在严格止于两次 backend 调用,修复了此前意外触发第三次调用的问题。旧 journal 类型仍向后兼容。

测试

  • Bun 1.3.13 定向测试连续两次:每次 55 pass / 0 fail
  • 相关生产与测试文件严格 TypeScript 5.9.3 检查
  • Ajv 非法/异步 schema 行为检查
  • git diff --check
  • 上游 main@987e550 基线完整 CI
  • 修复提交完整 CI 连续两次(包含 Biome、typecheck、coverage、Vite build)

CI 证据:

Summary by CodeRabbit

  • New Features

    • Added validation for structured outputs against caller-provided JSON Schemas.
    • Added clear failure reporting when structured output is invalid.
    • Improved retry handling for failed or invalid agent results.
    • Added validation and recovery for cached and replayed results.
  • Bug Fixes

    • Prevented invalid cached results from being reused.
    • Preserved abort behavior while handling retries and failures.
    • Improved journaling of final failed runs.

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The workflow engine now validates configured JSON Schemas and structured agent outputs. Invalid cached results are rerun. Invalid live results retry once, become dead results after repeated failure, and do not receive output-token credit.

Changes

Structured output validation

Layer / File(s) Summary
Schema validation and result contracts
packages/workflow-engine/src/engine/structuredOutput.ts, packages/workflow-engine/src/types.ts, packages/workflow-engine/src/index.ts, packages/workflow-engine/src/__tests__/structuredOutput.test.ts, packages/workflow-engine/src/__tests__/types.test.ts
Adds cached synchronous schema validation, the invalid-structured-output dead reason, explicit exports, and related tests.
Schema preflight and journal handling
packages/workflow-engine/src/engine/hooks.ts, packages/workflow-engine/src/__tests__/hooks.test.ts
Validates schemas before execution. Revalidates journal hits, invalidates legacy invalid entries, and replays journaled invalid results.
Result validation and retry flow
packages/workflow-engine/src/engine/hooks.ts, packages/workflow-engine/src/__tests__/hooks.test.ts
Validates adapter and runner results, retries dead results and exceptions, preserves abort behavior, journals final failures, and verifies token accounting and completion events.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

Suggested reviewers: claude-code-best

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly states the main change: validating structured agent output in the workflow engine.
Linked Issues check ✅ Passed The changes implement boundary validation, one retry, dead results, journal invalidation, and invalid-schema errors required by [#1323].
Out of Scope Changes check ✅ Passed All implementation and test changes directly support the structured-output validation requirements in [#1323].
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@ai-yang
ai-yang force-pushed the agent/workflow-schema-validation branch from 788da3c to a4e9648 Compare August 1, 2026 02:29
@ai-yang
ai-yang marked this pull request as ready for review August 1, 2026 04:10

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
packages/workflow-engine/src/__tests__/hooks.test.ts (1)

500-543: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert the revalidation warning so the test pins the intended branch.

truncated and journalInvalidated are set by both journal-invalidation branches in hooks.ts: the key-divergence branch at Line 111 and the schema-revalidation branch at Line 94. The current assertions therefore do not distinguish the two. If cached-result revalidation regressed and the key diverged for any reason, this test would still pass.

Capture the warn message and assert it. The message text is owned by hooks.ts, so it is a stable assertion target.

💚 Proposed test change
 test('invalid legacy structured output journal hit is invalidated and rerun live', async () => {
   let calls = 0
   const truncated: string[] = []
+  const warnings: string[] = []
   const params: AgentRunParams = {
     prompt: 'p',
     schema: STRUCTURED_SCHEMA,
   }
@@
     truncated,
-    loggerWarn: () => {},
+    loggerWarn: msg => {
+      warnings.push(msg)
+    },
   })
@@
   expect(truncated).toEqual(['r1'])
   expect(ctx.journalInvalidated).toBe(true)
+  expect(
+    warnings.some(msg =>
+      msg.includes('does not match its structured output schema'),
+    ),
+  ).toBe(true)
   expect(ctx.journal).toHaveLength(1)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/workflow-engine/src/__tests__/hooks.test.ts` around lines 500 - 543,
Update the test’s loggerWarn setup in the invalid legacy structured output case
to capture the warning message, then assert the captured message matches the
schema-revalidation warning emitted by hooks.ts. Keep the existing truncated,
journalInvalidated, and journal-result assertions so the test specifically
distinguishes schema revalidation from key divergence.
packages/workflow-engine/src/engine/hooks.ts (1)

358-361: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Include the failing property path in detail.

validateAgainstSchema maps each Ajv error to e.message only. A message such as must be number does not identify the failing property. The joined detail is persisted in the journal and is the primary signal for diagnosing an invalid-structured-output death.

Add e.instancePath in packages/workflow-engine/src/engine/structuredOutput.ts so each entry reads, for example, /count must be number.

♻️ Proposed change in structuredOutput.ts
     errors: valid
       ? []
-      : (validate.errors ?? []).map(e => e.message ?? 'validation error'),
+      : (validate.errors ?? []).map(e =>
+          `${e.instancePath || '(root)'} ${e.message ?? 'validation error'}`.trim(),
+        ),

Note that packages/workflow-engine/src/__tests__/types.test.ts line 40 uses the bare Ajv message form as illustrative detail text; that test does not assert against real Ajv output, so it stays valid.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/workflow-engine/src/engine/hooks.ts` around lines 358 - 361, Update
validateAgainstSchema in structuredOutput.ts to map each Ajv error to its
instancePath followed by its message, so the joined detail entries identify the
failing property (for example, “/count must be number”). Preserve the existing
fallback when no errors are present and leave the types test unchanged.
packages/workflow-engine/src/__tests__/structuredOutput.test.ts (1)

45-59: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Group the new tests in describe() blocks across the three test files. The coding guidelines require the describe() + test() pattern, and all three files add top-level test() calls instead. The shared root cause is that the new tests follow each file's pre-existing flat layout.

  • packages/workflow-engine/src/__tests__/structuredOutput.test.ts#L45-L59: wrap the three assertValidJsonSchema tests in describe('assertValidJsonSchema', ...).
  • packages/workflow-engine/src/__tests__/hooks.test.ts#L259-L346: wrap the new structured-output tests in describe('structured output validation', ...), and group the journal-replay tests at Lines 466-575 under a sibling describe.
  • packages/workflow-engine/src/__tests__/types.test.ts#L36-L45: wrap the new round-trip test with the existing AgentRunResult round-trip tests in describe('AgentRunResult', ...).

Grouping the adjacent pre-existing tests in the same pass keeps each file consistent.

As per coding guidelines: "use describe() + test() pattern with English descriptions".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/workflow-engine/src/__tests__/structuredOutput.test.ts` around lines
45 - 59, Group the tests according to the describe/test convention: in
packages/workflow-engine/src/__tests__/structuredOutput.test.ts lines 45-59,
wrap the three assertValidJsonSchema tests in describe('assertValidJsonSchema',
...); in packages/workflow-engine/src/__tests__/hooks.test.ts lines 259-346,
wrap the structured-output tests in describe('structured output validation',
...) and the journal-replay tests at lines 466-575 in a sibling describe; in
packages/workflow-engine/src/__tests__/types.test.ts lines 36-45, place the new
round-trip test alongside the existing AgentRunResult round-trip tests within
describe('AgentRunResult', ...).

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/workflow-engine/src/__tests__/hooks.test.ts`:
- Around line 500-543: Update the test’s loggerWarn setup in the invalid legacy
structured output case to capture the warning message, then assert the captured
message matches the schema-revalidation warning emitted by hooks.ts. Keep the
existing truncated, journalInvalidated, and journal-result assertions so the
test specifically distinguishes schema revalidation from key divergence.

In `@packages/workflow-engine/src/__tests__/structuredOutput.test.ts`:
- Around line 45-59: Group the tests according to the describe/test convention:
in packages/workflow-engine/src/__tests__/structuredOutput.test.ts lines 45-59,
wrap the three assertValidJsonSchema tests in describe('assertValidJsonSchema',
...); in packages/workflow-engine/src/__tests__/hooks.test.ts lines 259-346,
wrap the structured-output tests in describe('structured output validation',
...) and the journal-replay tests at lines 466-575 in a sibling describe; in
packages/workflow-engine/src/__tests__/types.test.ts lines 36-45, place the new
round-trip test alongside the existing AgentRunResult round-trip tests within
describe('AgentRunResult', ...).

In `@packages/workflow-engine/src/engine/hooks.ts`:
- Around line 358-361: Update validateAgainstSchema in structuredOutput.ts to
map each Ajv error to its instancePath followed by its message, so the joined
detail entries identify the failing property (for example, “/count must be
number”). Preserve the existing fallback when no errors are present and leave
the types test unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9fe83868-fe2b-47f2-8475-a9b8e47bda3f

📥 Commits

Reviewing files that changed from the base of the PR and between 987e550 and a4e9648.

📒 Files selected for processing (7)
  • packages/workflow-engine/src/__tests__/hooks.test.ts
  • packages/workflow-engine/src/__tests__/structuredOutput.test.ts
  • packages/workflow-engine/src/__tests__/types.test.ts
  • packages/workflow-engine/src/engine/hooks.ts
  • packages/workflow-engine/src/engine/structuredOutput.ts
  • packages/workflow-engine/src/index.ts
  • packages/workflow-engine/src/types.ts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

workflow: structured agent output 未在 engine 边界按 schema 校验

1 participant