Skip to content

fix(tool-args): coerce JSON-encoded strings on str_replace_editor numeric args#54

Open
sp2935 wants to merge 1 commit intoFSoft-AI4Code:mainfrom
sp2935:fix/openai-compatible-tool-args
Open

fix(tool-args): coerce JSON-encoded strings on str_replace_editor numeric args#54
sp2935 wants to merge 1 commit intoFSoft-AI4Code:mainfrom
sp2935:fix/openai-compatible-tool-args

Conversation

@sp2935
Copy link
Copy Markdown

@sp2935 sp2935 commented Apr 28, 2026

Summary

Fix str_replace_editor tool argument validation when CodeWiki runs against an OpenAI compatible backend (LiteLLM, vLLM, Ollama, etc.) instead of native Anthropic.

Problem

Some local models emit list-typed and int-typed tool arguments as JSON-encoded strings rather than proper structured values:

{
  "command": "view",
  "path": "/repo/foo.py",
  "view_range": "[1, 50]"   ← string, not array
}

Pydantic strict validation in pydantic-ai then rejects the call with:

ValidationError: 1 validation error for str_replace_editor
view_range
  Input should be a valid array
  [type=list_type, input_value='[1, 50]', input_type=str]

After the configured max_retries, the run aborts with UnexpectedModelBehavior: Tool 'str_replace_editor' exceeded max retries. The same model under reasoning enabled mode hits this consistently.

This makes CodeWiki effectively unusable on local LLM stacks, even though --provider openai-compatible is documented as a supported configuration.

Fix

Add a Pydantic BeforeValidator that parses a JSON-encoded string back into its structured form before strict validation runs. Applied to the two args observed to fail in practice: view_range (list) and insert_line (int).

def _coerce_json_string(value):
    if isinstance(value, str):
        try:
            return json.loads(value)
        except (json.JSONDecodeError, ValueError):
            pass
    return value


ViewRange = Annotated[Optional[List[int]], BeforeValidator(_coerce_json_string)]
InsertLine = Annotated[Optional[int], BeforeValidator(_coerce_json_string)]

The validator is a no-op when the value is already typed correctly. Anthropic native API users (whose tool calls already arrive as structured JSON) see no behavior change.

Empirical validation

Same model, same prompt, same target repo. The only change between runs is this patch:

  • Before patch: Tool 'str_replace_editor' exceeded max retries count of 1. Generation aborted at the documentation phase. Reproduced on two different OpenAI compatible local models.
  • After patch: Documentation generation completed in 3 minutes 21 seconds. All expected pages produced. Banner intact, no Table of Contents duplicates.

The runtime check inside EditTool.view (len(view_range) != 2 or not all(isinstance(i, int) for i in view_range)) still rejects malformed values that happen to slip through the JSON parse, so the failure surface is unchanged for genuinely bad input.

Test plan

  • python -c "import ast; ast.parse(open('codewiki/src/be/agent_tools/str_replace_editor.py').read())" parses clean.
  • End to end documentation generation against an OpenAI compatible local model produces the expected output tree.
  • No regression for Anthropic native runs (validator is a no-op on typed input).

Related

This addresses a class of issue similar to #50 (fallback-model persistence). Both surface as "configuration says X is supported but X breaks at runtime."

…eric args

Some local models routed through OpenAI compatible endpoints (LiteLLM,
vLLM, Ollama, etc.) emit list-typed and int-typed tool arguments as
JSON-encoded strings instead of proper structured values, e.g.
`"view_range": "[1, 50]"` rather than `"view_range": [1, 50]`. Pydantic
strict validation in pydantic-ai then rejects the call with:

    ValidationError: 1 validation error for str_replace_editor
    view_range
      Input should be a valid array
      [type=list_type, input_value='[1, 50]', input_type=str]

After the configured `max_retries` the run aborts with
`UnexpectedModelBehavior: Tool 'str_replace_editor' exceeded max retries`.

This patch adds a Pydantic `BeforeValidator` that parses a JSON-encoded
string back into its structured form before strict validation runs, on
the two args observed to fail in practice (`view_range` and
`insert_line`). The validator is a no-op when the value is already typed
correctly, so Anthropic native API users (who emit structured JSON tool
args) are unaffected.

Empirically validated: same model, same prompt, same repo, the only
change is this validator. Before patch: tool exceeds max retries on
every run. After patch: documentation generation completes successfully.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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