Repository navigation
fix: accept cross-model arg aliases in str_replace_editor (Qwen etc.) - #4
Conversation
Weaker/local models such as Qwen3 systematically call str_replace_editor with argument names from the OpenAI `str_replace_based_edit_tool` schema (old_string/new_string) or `content` for the created body, and often omit working_dir. pydantic-ai rejects these as unknown/missing arguments, and since the model emits the same shape every attempt, no number of tool retries can recover — the whole module agent aborts with "exceeded max retries". Make the tool tolerant instead of relying on retries: - Accept old_string/new_string/content as aliases, mapped onto the canonical old_str/new_str/file_text (canonical wins if both are supplied). - Default working_dir to "docs" (the write target) so an omitted value is no longer a hard validation failure; command stays required and is moved first so the defaulted parameter is legal. Extends the existing path/file alias + JSON-string coercion compatibility shims. pydantic-ai invokes tools by keyword, so the parameter reorder is safe. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3930f73323
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if new_str is None and new_string is not None: | ||
| new_str = new_string |
There was a problem hiding this comment.
Preserve an explicitly null canonical replacement
When a caller supplies both spellings with new_str: null and a non-null new_string, this condition replaces the canonical value with the alias despite the stated canonical-wins behavior. An explicit null is valid and meaningful for str_replace because EditTool.str_replace converts it to an empty string to delete the matched text; the current mapping instead inserts the alias text and can silently produce the wrong document.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Acknowledged, but I'm leaving the mapping as-is — the failure mode is effectively unreachable and the strict fix has real downsides.
- It requires a single call carrying both spellings with contradictory intent:
new_str: null(delete the matched text) and a non-nullnew_string(replace with that text). A model uses one spelling or the other; it doesn't emit a self-contradicting call, so there's no real (Qwen or otherwise) behavior that reaches this branch. - For
str_replace,nulland""are already equivalent —EditTool.str_replacecoercesNone→""— so the "wrong document" outcome only exists inside that contradictory call. - Honoring an explicit null over the alias isn't cleanly expressible here: at the pydantic-ai tool signature the default is
None, sois Nonecan't distinguish "explicitly null" from "omitted". Doing it properly needs a sentinel default (_UNSET) on anOptional[str]param, which muddies the auto-generated JSON schema and validation for a case that can't occur.
Net: the alias only kicks in when the canonical value is absent, which is the intended behavior for every real call. Happy to revisit if a concrete tool call is observed that actually sends both keys.
Problem
Running against Qwen3 (35B),
str_replace_editorfails every retry — even at--max-retries 5— and the module agent aborts withexceeded max retries. This is a systematic schema mismatch, not a transient miss: the model emits the same argument shape on every attempt, so retries can't recover.Weaker/local models trained on the OpenAI
str_replace_based_edit_toolschema call the tool with:old_string/new_stringinstead of this tool'sold_str/new_strcontentinstead offile_text(forcreate)working_dir, which was a requiredLiteralwith no default → hard validation failurepydantic-ai rejects unknown/missing arguments, and the retry prompt doesn't change the model's behavior.
Fix
Make the tool tolerant of what these models actually send (extending the existing
path/filealias and the JSON-string coercion already in this file):old_string/new_string/contentas aliases, mapped onto the canonicalold_str/new_str/file_text(canonical value wins if both are present).working_dirto"docs"(the write target) so an omitted value is no longer fatal.commandstays required and is moved to the first parameter so the defaultedworking_diris legal Python.Retries remain useful for genuine transient misses; this removes the systematic failures they can't fix.
Notes / safety
commandbeforeworking_dirdoes not affect any call site (verified: no positional callers of the function).old_str/new_str/file_textcalls are unchanged.py_compilepasses.Testing
codewiki generate --verbose—str_replace_editorcalls that previously failed validation now apply, and module agents complete instead of aborting after retries.🤖 Generated with Claude Code