Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
45557a2
feat(harness): add tool-call parsing as harness::tool_calling
senamakel Aug 13, 2026
64242dd
fix(tool_calling): handle empty tool call list in harness
senamakel Aug 13, 2026
ba916de
fix(harness): handle tool call with no arguments
senamakel Aug 13, 2026
674f657
fix(parse): handle empty tool call blocks without crashing
senamakel Aug 13, 2026
da7ad20
chore: files changed src/harness/tool_calling/parse_test.rs
senamakel Aug 13, 2026
7ebfe28
fix(parse): handle missing tool call arguments gracefully
senamakel Aug 13, 2026
64bb8b4
fix(parse_test): correct test for tool call with no arguments
senamakel Aug 13, 2026
b282504
chore: files changed src/harness/tool_calling/parse.rs
senamakel Aug 13, 2026
cdbd350
fix(parse_test): correct test for tool call with no arguments
senamakel Aug 13, 2026
5d6f549
fix(parse): handle empty tool call arguments
senamakel Aug 13, 2026
3bcc858
fix(parse_test): handle empty tool call block in parsing
senamakel Aug 13, 2026
cde87a7
fix(parse): handle empty tool call blocks without crashing
senamakel Aug 13, 2026
49f2c2a
fix(parse_test): correct test assertion for tool call parsing
senamakel Aug 13, 2026
b7087fd
fix(parse): handle missing tool call block in parse_tool_call
senamakel Aug 13, 2026
1ae73ef
fix(parse): handle empty tool call blocks without crashing
senamakel Aug 13, 2026
d701c30
fix(parse_test): correct test assertion for tool call parsing
senamakel Aug 13, 2026
eadc9fd
Merge remote-tracking branch 'origin/main' into harness-tool-calling
senamakel Aug 13, 2026
6962ba9
Merge branch 'main' into harness-tool-calling
senamakel Aug 13, 2026
d7e9ace
test(parse): add regression tests for mixed p-format and non-JSON sib…
senamakel Aug 13, 2026
ffbbc0c
test(parse): add missing imports for regression probe test
senamakel Aug 13, 2026
628977a
fix(parse): align fallback logic with canonical JSON parser
senamakel Aug 13, 2026
8679dde
fix(parse): rework tag-walk to preserve multi-call JSON bodies
senamakel Aug 13, 2026
b66142a
Merge remote-tracking branch 'origin/harness-tool-calling' into harne…
senamakel Aug 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@ futures = "0.3"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
sha2 = "0.11"
# `regex` strips markup / data-URIs / whitespace from oversized tool payloads
# in `harness::handoff`. Already resolved in OpenHuman's kernel profile, so
# this adds no package there.
# `regex` has two consumers: the prompt-guided tool-call parsers in
# `harness::tool_calling`, and the markup / data-URI / whitespace stripping
# applied to oversized payloads in `harness::handoff`. Already resolved in
# OpenHuman's kernel profile, so it adds no package there.
regex = "1"
thiserror = "2"
tracing = "0.1"
Expand Down
1 change: 1 addition & 0 deletions src/harness/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ pub mod subagent;
pub mod summarization;
pub mod testkit;
pub mod tool;
pub mod tool_calling;
#[cfg(feature = "tools")]
pub mod tools;
pub mod usage;
Expand Down
48 changes: 48 additions & 0 deletions src/harness/tool_calling/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//! Recovering tool calls from model output.
//!
//! A model that supports native tool use hands back structured calls and none
//! of this is needed. Everything else — prompt-guided models, local models,
//! providers whose native mode is unavailable or disabled — emits tool calls as
//! *text*, in whatever shape the model was trained to produce. This module
//! turns that text back into calls.
//!
//! ## Why it is this forgiving
//!
//! Each accommodation here exists because a model actually produced it and the
//! alternative was dropping a well-formed call and burning an agent iteration.
//! Concretely, the parsers accept `<tool_call>` tags in several spellings,
//! fenced `tool_call` blocks, bare JSON objects, Anthropic-style
//! `<invoke name="…"><parameter name="…">` XML, and the compact positional
//! [`pformat`] syntax.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
//!
//! The permissiveness is bounded on purpose, and the boundary is worth knowing
//! before widening anything:
//!
//! * **Argument keys are aliased; tool names are not.** A model drifting from
//! `arguments` to `args`/`parameters`/`params`/`input` still yields a usable
//! call. The *name* stays strict, because loosening it risks reading a plain
//! JSON answer as a tool call in the whole-response path — turning an ordinary
//! reply into a phantom invocation.
//! * **The generic `input` alias is only honoured behind an explicit marker**
//! (a `tool_calls` array, a `<tool_call>` tag, a fenced block). Untagged text
//! does not get it.
//! * **[`pformat`] refuses to invent argument names for an unknown tool**, so a
//! model cannot tunnel arbitrary JSON through by guessing a tool name that
//! does not exist.
//!
//! ## What the host still owns
//!
//! This module takes **schemas**, never a tool trait object. A host's tool type
//! is its own vocabulary, and depending on it here would defeat the point — so
//! [`pformat::build_registry`] takes `(name, schema)` pairs and the host keeps a
//! one-line adapter over its own tool slice. Dispatch and execution stay host-side
//! too: this module answers "what did the model ask for", never "what happens next".

pub(crate) mod parse;
pub(crate) mod pformat;

pub use parse::{ParsedToolCall, parse_tool_calls, parse_tool_calls_with_pformat};
pub use pformat::{
PFormatParamType, PFormatRegistry, PFormatToolParams, build_registry, parse_call,
render_signature, render_signature_from_schema,
};
Loading