Skip to content

Widen legacy tool parameter root coverage - #91

Merged
maiphucgiang merged 2 commits into
mainfrom
fix/tool-parameter-roots-coverage
Oct 22, 2026
Merged

maiphucgiang merged 2 commits into
mainfrom
fix/tool-parameter-roots-coverage

Conversation

@maiphucgiang

@maiphucgiang maiphucgiang commented Oct 22, 2026 •

Copy link
Copy Markdown
Owner

Follow-up to #90.

Summary

  • Treat JSON Schema keywords such as pattern, description or title as parameter names when their value is a legacy field, so common tool parameters no longer hide a legacy map from normalization.
  • Recognize a legacy map from one boolean required flag plus all-object values; fields with array types, enum-only fields or no type no longer abort the conversion.
  • Normalize tools entries that carry a function object without an explicit "type": "function", matching _declared_tool_names and dsml.tool_name_allowlist.
  • Merge app/tool_schema.py into app/upstream_compat.py: parameter roots and RE2 pattern escapes now share one copy-on-write pass (normalize_tool_schemas) instead of two traversals.
  • Standard schemas, $/x- extension roots and nested schemas are still passed through unchanged. README (en/zh-CN) updated accordingly.

Verification

Summary by Sourcery

Widen tool schema normalization to cover more legacy parameter roots and consistently prepare function declarations for upstream processing.

New Features:

  • Broaden legacy tool-parameter root normalization to recognize keyword-named fields and mixed field definitions while preserving standard and nested schemas.
  • Normalize function tool entries without an explicit function type so they are recognized consistently across tool handling and selection.

Bug Fixes:

  • Prevent valid legacy parameter maps from being skipped when fields use schema keywords, array types, enum-only definitions, or omitted types.

Enhancements:

  • Consolidate parameter-root and pattern normalization into a single copy-on-write schema normalization pass in upstream compatibility handling.

Documentation:

  • Update English and Chinese README guidance for keyword-named legacy fields and preserved standard schemas.

Tests:

  • Expand normalization coverage for keyword collisions, mixed field types, untyped function entries, and combined root and pattern normalization.

Chores:

  • Remove the standalone tool schema module after merging its functionality into upstream compatibility handling.

Treat schema keywords as field names when they hold legacy fields, accept
legacy maps whose other fields lack a single string type, and normalize
function entries without an explicit type. Merge tool_schema into
upstream_compat so parameter roots and pattern escapes share one
copy-on-write pass over tools.
@sourcery-ai

sourcery-ai Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Reviewer's Guide

This PR widens legacy tool-parameter detection without misclassifying standard schemas, then consolidates root wrapping and RE2 pattern cleanup into a copy-on-write normalize_tool_schemas traversal. It also supports untyped function entries, updates converter integration and documentation, and adds focused regression tests for the new edge cases.

Sequence diagram for legacy tool parameter normalization

sequenceDiagram
    participant Converter
    participant Normalizer
    participant Tool
    participant Upstream
    Converter->>Normalizer: normalize_tool_schemas(body)
    Normalizer->>Tool: _normalize_tool(tool, key)
    Tool->>Normalizer: _parameter_root(parameters)
    alt legacy field map
        Normalizer-->>Tool: object schema with properties and required
    else standard or extension schema
        Normalizer-->>Tool: unchanged parameters
    end
    Tool->>Normalizer: _normalize_schema_patterns(parameters)
    Normalizer-->>Converter: copy-on-write body
    Converter->>Upstream: send normalized request
Loading

Flow diagram for unified tool schema normalization

flowchart LR
    A["Chat request"] --> B["normalize_tool_schemas"]
    B --> C["Normalize tools/functions"]
    C --> D["Detect legacy parameter root"]
    D --> E["Wrap as object properties"]
    C --> F["Normalize RE2 pattern escapes"]
    B --> G["Preserve standard, nested, and extension schemas"]
    E --> H["Copy-on-write normalized request"]
    F --> H
Loading

File-Level Changes

Change Details Files
Broaden legacy parameter-root detection while preserving standard and extension schemas.
  • Treat schema-keyword names as legacy fields when their values look like field definitions.
  • Require only one boolean required flag and allow mixed, array-typed, enum-only, or untyped field values.
  • Continue bypassing normalization for standard schemas, $/x- roots, and ambiguous keyword values.
  • Keep normalization copy-on-write and preserve required-field ordering and metadata.
app/upstream_compat.py
tests/test_tool_schema.py
README.md
README.zh-CN.md
Consolidate tool-root and pattern normalization into one upstream compatibility pass.
  • Replace the deleted parameter-root and pattern helpers with normalize_tool_schemas.
  • Normalize nested function declarations and bare function entries consistently, including entries without an explicit function type.
  • Apply RE2 underscore-escape cleanup in the same traversal and retain response-format schema normalization.
  • Update converter integration and all affected tests to use the unified API.
app/upstream_compat.py
app/tool_schema.py
converter.py
tests/test_tool_schema.py
tests/test_upstream_compat.py
Add regression coverage and document the expanded compatibility behavior.
  • Cover keyword-named fields, mixed field types, keyword collisions with real schema values, untyped function entries, and combined root/pattern conversion.
  • Verify unchanged inputs and identity-preserving no-op behavior.
  • Update English and Chinese documentation to describe keyword-field handling and untouched schema cases.
tests/test_tool_schema.py
tests/test_upstream_compat.py
README.md
README.zh-CN.md

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey - I've found 3 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="app/upstream_compat.py" line_range="90" />
<code_context>
-    """Normalize tool declarations and structured-output schemas, copying only what changes."""
+def _looks_like_field(value) -> bool:
+    """A legacy field carries a boolean required flag or a field type."""
+    return type(value.get("required")) is bool or isinstance(value.get("type"), (str, list))
+
+
</code_context>
<issue_to_address>
**Constant schema is rewritten as fields**

When `parameters` is a schema root such as `{"const":{"type":"object","required":true}}`, `_looks_like_field` treats the object value of `const` as a legacy field because its data contains a string `type` and boolean `required`; `_parameter_root` then removes that data and emits a different schema, so the caller’s valid constant constraint is lost.

Preserve arbitrary-data keywords such as `const` and `default` when identifying legacy field maps, unless the root can be unambiguously distinguished from a standard schema.
</issue_to_address>

### Comment 2
<location path="app/upstream_compat.py" line_range="90" />
<code_context>
-    """Normalize tool declarations and structured-output schemas, copying only what changes."""
+def _looks_like_field(value) -> bool:
+    """A legacy field carries a boolean required flag or a field type."""
+    return type(value.get("required")) is bool or isinstance(value.get("type"), (str, list))
+
+
</code_context>
<issue_to_address>
**Legacy parameters reach upstream unwrapped**

When a legacy parameter map has a schema-keyword-named field containing only `enum` and another field with a boolean `required` flag, `_looks_like_field` returns false for the enum-only field, so `_parameter_root` leaves the legacy map unwrapped and upstream receives the wrong parameter schema.

Recognize enum-only values as field schemas in `_looks_like_field`.

Also at `app/upstream_compat.py:108-109`.
</issue_to_address>

### Comment 3
<location path="app/upstream_compat.py" line_range="90" />
<code_context>
-    """Normalize tool declarations and structured-output schemas, copying only what changes."""
+def _looks_like_field(value) -> bool:
+    """A legacy field carries a boolean required flag or a field type."""
+    return type(value.get("required")) is bool or isinstance(value.get("type"), (str, list))
+
+
</code_context>
<issue_to_address>
**Named tool choice is rejected**

When a tool has a `function` object but omits `type`, and the request selects it by name, `_normalize_tool_choice` matches only entries with an explicit `type: "function"`, finds no declaration, and returns HTTP 400 even though tool normalization treats the omitted type as a function.

Make `_normalize_tool_choice` match omitted types the same way tool normalization does.

Also at `app/upstream_compat.py:140-142`.
</issue_to_address>

Sourcery assessment

Approval pending. 3 findings to address first.

Blocking findings: app/upstream_compat.py:90, app/upstream_compat.py:90, app/upstream_compat.py:90


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread app/upstream_compat.py Outdated
Comment thread app/upstream_compat.py Outdated
Comment thread app/upstream_compat.py Outdated
Keep const/default roots untouched since their values are instance data,
treat enum-only objects as fields, and decide keyword-named keys by the
keyword's value kind instead of a full keyword list. Add an explicit
"type": "function" to untyped function entries so named tool_choice
matches them.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Sourcery assessment

Approved.

@maiphucgiang
maiphucgiang merged commit b8a577f into main Oct 22, 2026
6 checks passed
@maiphucgiang
maiphucgiang deleted the fix/tool-parameter-roots-coverage branch October 22, 2026 16:22
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.

1 participant