Skip to content

fix(agent): resolve refs under schema-keyword property names - #45093

Merged
BeautyyuYanli merged 3 commits into
langgenius:mainfrom
BlueX888:fix/output-schema-keyword-property
Nov 13, 2026
Merged

BeautyyuYanli merged 3 commits into
langgenius:mainfrom
BlueX888:fix/output-schema-keyword-property

Conversation

@BlueX888

Copy link
Copy Markdown
Contributor

Fixes #45092

Summary

dify-agent's structured-output layer inlines local #/$defs/... refs before handing the schema to pydantic. The traversal skipped values whose key matched a literal-value keyword (const, default, enum, example, examples) — including in name→schema maps, where the key is user data. A property literally named default that held a local $ref was therefore treated as literal data: the ref was never inlined, the root $defs was dropped, and pydantic's JSON-schema generation raised KeyError: '#/$defs/Widget' during create_agent. The same misclassification let remote $refs under such properties bypass the non-local-ref rejection.

  • Make both traversals position-aware: keyword-name skipping applies only to schema objects, while every entry value of a name→schema map (properties, patternProperties, $defs, definitions, dependentSchemas, draft-07 dependencies) is traversed as a schema regardless of the entry name.
  • The ref-rejection walk (_iter_schema_refs) uses the same rule, so remote refs under keyword-named entries are rejected consistently; array-form dependencies values are preserved untouched.

Screenshots

Not a UI change.

Checklist

  • This change requires a documentation update, included: Dify Document
  • I understand that this PR may be closed in case there was no previous discussion or issues.
  • I've verified the change and added or updated tests where meaningful regression risk justifies coverage.
    • New tests cover all five literal keywords as property names (local ref inlined, remote ref rejected), agent construction, a keyword-named dependencies entry (local and remote), literal default/examples values staying untouched, and nested/$ref-sibling/boolean/recursion cases via probes.
    • On e5acabc3, 13 of the new tests fail (dangling $ref / non-local refs accepted); with this change the output-layer module is 33/33.
    • The end-to-end reproductions now inline the default property and the agent run returns {'default': {'id': 'x'}}; ruff check/format clean.
  • I've updated the documentation accordingly.

Known pre-existing limitation (not caused by, and not fixable in this change): a literal "$ref" string inside a default/const/enum value still confuses pydantic's own JSON-schema walker (identical on e5acabc3; examples is unaffected). This fix correctly leaves that literal data untouched.

From Claude Code

Local #/$defs refs under name-to-schema maps (properties, patternProperties,
$defs, definitions, dependentSchemas, draft-07 dependencies) were skipped
whenever the map entry name matched a literal-value keyword ("const",
"default", "enum", "example", "examples"). A property named "default"
holding a local ref was therefore treated as literal data: the ref was never
inlined and pydantic's JSON schema generation raised KeyError: '#/$defs/..'
during create_agent. The same misclassification let remote refs under such
properties bypass the non-local ref rejection.

Make both traversals position-aware: keyword-name skipping only applies to
schema objects, while every entry value of a name-to-schema map is traversed
as a schema regardless of the entry name.

Fixes langgenius#45092

Co-Authored-By: Claude Code <[email protected]>
@github-actions github-actions Bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Oct 30, 2026
@BeautyyuYanli

Copy link
Copy Markdown
Contributor

Thanks for the fix! I merged origin/main into this branch and pushed a small follow-up commit (6b37062823f3) with minor review refinements:

  • Simplified _inline_local_defs_refs and _iter_schema_refs in dify-agent/src/dify_agent/layers/output/output_layer.py by removing redundant wrapper/type-guard and deepcopy calls while preserving container reconstruction and literal keyword deep-copy isolation.
  • Expanded dify-agent/tests/local/dify_agent/layers/output/test_configs.py to cover $defs, definitions, dependentSchemas, dependencies (with Draft-07 $schema and triggered validation), and patternProperties, exercise the $defs inlining path alongside literal {"$ref": ...} values, and execute an end-to-end synchronous create_agent run with final_output.

(This message is automated while I'm responsible for it.)

@BeautyyuYanli
BeautyyuYanli added this pull request to the merge queue Nov 13, 2026
Merged via the queue into langgenius:main with commit f91cb45 Nov 13, 2026
38 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Structured output crashes when a property name collides with a schema keyword and holds a local $ref

2 participants