fix(tool_calling): stop dropping a GLM tag beside a p-format tag - #104
Merged
Conversation
`parse_tool_calls_with_pformat` walks `<tool_call>`-family tags, taking a
p-format body where one parses and re-parsing the rest as JSON. Once ANY
tag yields a p-format call the walk never falls back to the canonical
parse, so each remaining tag is on its own - and the JSON path is all it
has. A GLM body (`shell/command>ls -la`) is not JSON, so the call was
silently dropped: the agent lost a tool invocation it had asked for, and
nothing reported it.
Adds a GLM fallback for a tag body the JSON path could not read.
Ordering is load-bearing: the fallback runs ONLY when the JSON path found
nothing. GLM's `name/key>value` shape can occur inside a JSON string value
(`{"command": "cat a/b>c"}`), so an unconditional fallback would count
that body once as JSON and again as GLM, and the agent would execute the
same tool twice. Pinned by
`a_json_body_is_not_double_counted_by_the_glm_fallback`.
Routing the branch through `parse_tool_calls` instead was the obvious
alternative and is wrong. That function forbids the `args`/`parameters`/
`input` argument-key aliases for a bare top-level object, deliberately, so
a plain JSON answer cannot be misread as a tool call. But a `<tool_call>`
tag IS an explicit marker, where the aliases DO apply - so routing through
it would have traded this silent drop for a different one, on aliased
tagged calls. Pinned by `a_tagged_body_still_honours_argument_key_aliases`.
The bug is inherited, not introduced by the relocation: the probe fails
identically against the pre-port OpenHuman code. It was landed as an
`#[ignore]`d test in #102 precisely so it stayed visible; this un-ignores
it.
1823 lib tests pass, no ignores. Clippy and fmt clean.
Co-authored-by: Medulla <medulla@tinyhumans.ai>
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
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. Comment |
senamakel
added a commit
that referenced
this pull request
Aug 13, 2026
… usable by a host Two halves, both discovered by actually wiring OpenHuman onto this module. 1. The GLM sibling drop (re-lands #104 against main) #104 merged into #102's branch, but #102 had already merged to main - so the fix never reached main. `main` today carries `tool_calling` with the `#[ignore]`d test and no fix. `parse_tool_calls_with_pformat` walks `<tool_call>`-family tags. Once ANY tag yields a p-format call the walk never falls back to the canonical parse, so every remaining tag has only the JSON path. A GLM body (`shell/command>ls -la`) is not JSON, so the call was silently dropped. The fallback runs ONLY when the JSON path found nothing: GLM's `name/key>value` shape can occur inside a JSON string value (`{"command": "cat a/b>c"}`), and an unconditional fallback would count that body twice, making the agent run the same tool twice. Routing through `parse_tool_calls` instead is also wrong - it forbids the argument-key aliases for a bare top-level object, but a `<tool_call>` tag IS an explicit marker where they apply, so that would trade one silent drop for another. Both pinned by tests. 2. Host-consumability fixes The first real consumer could not compile against this module: - `extract_json_values` was unreachable, and it is not a test helper: pulling the first JSON object out of model prose is how a host checks a required-output contract, which has nothing to do with tool calls. Now exported, along with `parse_arguments_value`, `parse_glm_style_tool_calls`, `parse_tool_call_value` and `parse_tool_calls_from_json_value`, all of which host tests exercise directly. - `parse_tool_call_value` was `#[cfg(test)]`, inherited from a host where it was test-only. It is a reasonable primitive - parse one JSON value as a tool call - so it is un-gated and documented rather than duplicated host-side. - `build_registry` took `&Value`. A host tool trait that RETURNS a schema by value - the common shape - then has to collect into a temporary just to hand out references. It takes `Borrow<Value>` now, so both forms work. Without these a host either shadows the code it just stopped owning, or loses the tests that covered it. OpenHuman keeps all 62 of its parser tests working against the crate. 1823 lib tests pass, no ignores. Clippy and fmt clean. Co-authored-by: Medulla <medulla@tinyhumans.ai>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #102 — based on
harness-tool-callingso the diff is just the fix (2 files). GitHub retargets this tomainautomatically when #102 merges.Fixes the silent tool-call loss documented as an
#[ignore]d test in #102, and un-ignores it.The bug
parse_tool_calls_with_pformatwalks<tool_call>-family tags, taking a p-format body where one parses and re-parsing the rest as JSON. Once any tag yields a p-format call, the walk never falls back to the canonical parse — so every remaining tag is on its own, and the JSON path is all it has.A GLM body is not JSON:
The
shellcall was dropped. Silently: the agent asked for a tool, didn't get it, and nothing reported it.Inherited, not introduced by the relocation. The probe fails identically against the pre-port OpenHuman code and against
45557a2. #102 landed it#[ignore]d specifically so it stayed visible rather than being quietly carried forward.The fix, and the two ways it could have gone wrong
A GLM fallback for a tag body the JSON path could not read. Both risks it introduces are pinned by tests, because both fail silently:
1. Ordering is load-bearing — the fallback runs only when the JSON path found nothing. GLM's
name/key>valueshape can appear inside a JSON string value:{"name": "shell", "arguments": {"command": "cat a/b>c"}}An unconditional fallback counts that body once as JSON and again as GLM, and the agent executes the same tool twice. →
a_json_body_is_not_double_counted_by_the_glm_fallback2. Routing through
parse_tool_callswas the obvious alternative and is wrong. That function deliberately forbids theargs/parameters/inputargument-key aliases for a bare top-level object, so a plain JSON answer cannot be misread as a tool call. But a<tool_call>tag is an explicit marker, where the aliases do apply. Routing through it would have traded this silent drop for a different one, on aliased tagged calls. →a_tagged_body_still_honours_argument_key_aliasesSo the permissive JSON path stays exactly as it was, and GLM is tried only after it comes back empty.
Scope note
Only the GLM grammar is added to that branch. A markdown-fenced JSON body already works —
extract_json_valuesfinds the JSON inside the fence — and that is pinned bya_pformat_tag_does_not_suppress_a_sibling_fenced_json_tag, which passed before this change and still does.Verification
cargo test --all-features --lib— 1823 passed, 0 failed, 0 ignored (the ignore from feat(harness): add tool-call parsing as harness::tool_calling #102 is gone)cargo clippy --all-features --all-targets— cleancargo fmt --check— clean🤖 Generated with Claude Code