Fix refactor recovery and metrics
This commit is contained in:
68
src/dsml.rs
68
src/dsml.rs
@@ -1,5 +1,7 @@
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
const INCOMPLETE_TOOL_CALL: &str = "invalid or incomplete DSML tool call";
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub(crate) struct Syntax {
|
||||
pub(crate) tool_start: &'static str,
|
||||
@@ -64,31 +66,40 @@ pub(crate) fn parse_tool_calls(text: &str) -> Result<(String, Vec<(String, Value
|
||||
if !outer_call_complete && cursor == raw.len() && !calls.is_empty() {
|
||||
break;
|
||||
}
|
||||
if !raw[cursor..].starts_with(syntax.invoke_start) {
|
||||
return Err("invalid or incomplete DSML tool call".into());
|
||||
}
|
||||
let tag_end = raw[cursor..]
|
||||
.find('>')
|
||||
.map(|offset| cursor + offset + 1)
|
||||
.ok_or_else(|| "invalid or incomplete DSML tool call".to_owned())?;
|
||||
let name = attribute(&raw[cursor..tag_end], "name")
|
||||
.ok_or_else(|| "invalid or incomplete DSML tool call".to_owned())?;
|
||||
cursor = tag_end;
|
||||
let mut arguments = Map::new();
|
||||
loop {
|
||||
skip_whitespace(raw, &mut cursor);
|
||||
if raw[cursor..].starts_with(syntax.invoke_end) {
|
||||
cursor += syntax.invoke_end.len();
|
||||
let call = (|| {
|
||||
if !raw[cursor..].starts_with(syntax.invoke_start) {
|
||||
return Err(INCOMPLETE_TOOL_CALL.into());
|
||||
}
|
||||
let tag_end = raw[cursor..]
|
||||
.find('>')
|
||||
.map(|offset| cursor + offset + 1)
|
||||
.ok_or_else(|| INCOMPLETE_TOOL_CALL.to_owned())?;
|
||||
let name = attribute(&raw[cursor..tag_end], "name")
|
||||
.ok_or_else(|| INCOMPLETE_TOOL_CALL.to_owned())?;
|
||||
cursor = tag_end;
|
||||
let mut arguments = Map::new();
|
||||
loop {
|
||||
skip_whitespace(raw, &mut cursor);
|
||||
if raw[cursor..].starts_with(syntax.invoke_end) {
|
||||
cursor += syntax.invoke_end.len();
|
||||
break;
|
||||
}
|
||||
let (name, value) = parse_parameter(raw, &mut cursor, syntax)?
|
||||
.ok_or_else(|| INCOMPLETE_TOOL_CALL.to_owned())?;
|
||||
arguments.insert(name, value);
|
||||
}
|
||||
Ok((name, Value::Object(arguments)))
|
||||
})();
|
||||
match call {
|
||||
Ok(call) => calls.push(call),
|
||||
Err(error) if !calls.is_empty() && error == INCOMPLETE_TOOL_CALL => {
|
||||
break;
|
||||
}
|
||||
let (name, value) = parse_parameter(raw, &mut cursor, syntax)?
|
||||
.ok_or_else(|| "invalid or incomplete DSML tool call".to_owned())?;
|
||||
arguments.insert(name, value);
|
||||
Err(error) => return Err(error),
|
||||
}
|
||||
calls.push((name, Value::Object(arguments)));
|
||||
}
|
||||
if calls.is_empty() {
|
||||
return Err("invalid or incomplete DSML tool call".into());
|
||||
return Err(INCOMPLETE_TOOL_CALL.into());
|
||||
}
|
||||
Ok((content, calls))
|
||||
}
|
||||
@@ -225,4 +236,21 @@ mod tests {
|
||||
assert_eq!(calls[0].0, "read");
|
||||
assert_eq!(calls[0].1["path"], "src/main.rs");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn recovers_complete_invokes_before_an_incomplete_one() {
|
||||
let raw = "<tool_calls><invoke name=\"first\"></invoke><invoke name=\"second\"><parameter name=\"value\">truncated";
|
||||
|
||||
let (_, calls) = parse_tool_calls(raw).unwrap();
|
||||
|
||||
assert_eq!(calls.len(), 1);
|
||||
assert_eq!(calls[0].0, "first");
|
||||
|
||||
let invalid = "<tool_calls><invoke name=\"first\"></invoke><invoke name=\"second\"><parameter name=\"value\" string=\"false\">invalid</parameter></invoke></tool_calls>";
|
||||
assert!(
|
||||
parse_tool_calls(invalid)
|
||||
.unwrap_err()
|
||||
.starts_with("invalid DSML tool arguments")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user