[Rust Frontend] Add error context in tool parser failures (#46512)
Signed-off-by: Maria Guevara <kawaiiplush14@gmail.com>
Maria Guevara committed
Jul 1, 2026 at 06:48 UTC
aeb35b90f0b66e5c5f12a3c86864ea92dccbd180
12 files changed
+57
-19
rust/src/parser/src/tool/deepseek_json/deepseek_v3.rs
+4
-1
@@ -238,6 +238,9 @@ mod tests {
238
239
let error = parser.parse_chunk(&input).unwrap_err();
240
241
- expect!["tool parser parsing failed: "].assert_eq(&error.to_report_string());
241
+ expect![[
242
+ r#"tool parser parsing failed: near "tool<|tool▁sep|>get_weather\n```json\n{}": "#
243
+ ]]
244
+ .assert_eq(&error.to_report_string());
245
}
246
}
rust/src/parser/src/tool/deepseek_json/deepseek_v31.rs
+2
-1
@@ -242,6 +242,7 @@ mod tests {
242
243
let error = parser.parse_chunk(&input).unwrap_err();
244
245
- expect!["tool parser parsing failed: "].assert_eq(&error.to_report_string());
245
+ expect![[r#"tool parser parsing failed: near "<|tool▁sep|>{}": "#]]
246
+ .assert_eq(&error.to_report_string());
247
}
248
}
rust/src/parser/src/tool/json/granite4.rs
+4
-2
@@ -540,8 +540,10 @@ mod tests {
540
.parse_chunk(r#"<tool_call>{"name":"f","arguments":42}</tool_call>"#)
541
.unwrap_err();
542
543
- expect!["tool parser parsing failed: invalid Granite4 arguments"]
544
- .assert_eq(&error.to_report_string());
543
+ expect![[
544
+ r#"tool parser parsing failed: near "42}</tool_call>": invalid Granite4 arguments"#
545
+ ]]
546
+ .assert_eq(&error.to_report_string());
547
}
548
549
#[test]
rust/src/parser/src/tool/json/internlm2.rs
+1
-1
@@ -335,7 +335,7 @@ mod tests {
335
let error = parser.parse_chunk(&input).unwrap_err();
336
337
expect![[r#"
338
- tool parser parsing failed: invalid InternLM2
338
+ tool parser parsing failed: near "{\"name\":\"get_weather\",\"params\":{\"location\":\"Tokyo\"}}<|action_end|>": invalid InternLM2
339
expected `parameters`, `arguments`"#]]
340
.assert_eq(&error.to_report_string());
341
}
rust/src/parser/src/tool/json/llama.rs
+3
-3
@@ -336,7 +336,7 @@ mod tests {
336
.unwrap_err();
337
338
expect![[r#"
339
- tool parser parsing failed: invalid Llama JSON
339
+ tool parser parsing failed: near "{\"name\":\"get_weather\",\"arguments\":{\"location\":\"Tokyo\"}}": invalid Llama JSON
340
expected `parameters`"#]]
341
.assert_eq(&error.to_report_string());
342
}
@@ -474,7 +474,7 @@ mod tests {
474
let error = parser.parse_chunk(r#"{"parameters":{},"name":"get_weather"}"#).unwrap_err();
475
476
expect![[r#"
477
- tool parser parsing failed: invalid Llama JSON
477
+ tool parser parsing failed: near "{\"parameters\":{},\"name\":\"get_weather\"}": invalid Llama JSON
478
expected `name`"#]]
479
.assert_eq(&error.to_report_string());
480
}
@@ -489,7 +489,7 @@ mod tests {
489
))
490
.unwrap_err();
491
492
- expect!["tool parser parsing failed: invalid Llama JSON"]
492
+ expect![[r#"tool parser parsing failed: near " trailing": invalid Llama JSON"#]]
493
.assert_eq(&error.to_report_string());
494
}
495
}
rust/src/parser/src/tool/json/mistral.rs
+1
-1
@@ -240,7 +240,7 @@ mod tests {
240
.unwrap_err();
241
242
expect![[r#"
243
- tool parser parsing failed: invalid Mistral
243
+ tool parser parsing failed: near "{\"arguments\":{},\"name\":\"get_weather\"}]": invalid Mistral
244
expected `name`"#]]
245
.assert_eq(&error.to_report_string());
246
}
rust/src/parser/src/tool/json/qwen.rs
+1
-1
@@ -280,7 +280,7 @@ mod tests {
280
.unwrap_err();
281
282
expect![[r#"
283
- tool parser parsing failed: invalid Qwen XML
283
+ tool parser parsing failed: near "{\"arguments\":{},\"name\":\"get_weather\"}\n</tool_call>": invalid Qwen XML
284
expected `name`"#]]
285
.assert_eq(&error.to_report_string());
286
}
rust/src/parser/src/tool/kimi_k2.rs
+4
-1
@@ -594,6 +594,9 @@ mod tests {
594
595
let error = parser.parse_chunk(&input).unwrap_err();
596
597
- expect!["tool parser parsing failed: "].assert_eq(&error.to_report_string());
597
+ expect![[
598
+ r#"tool parser parsing failed: near "get_weather<|tool_call_argument_begin|>{}": "#
599
+ ]]
600
+ .assert_eq(&error.to_report_string());
601
}
602
}
rust/src/parser/src/tool/minimax_m2.rs
+2
-1
@@ -593,6 +593,7 @@ mod tests {
593
let mut parser = MinimaxM2ToolParser::new(&test_tools());
594
let error = parser.parse_chunk("<minimax:tool_call><bad></minimax:tool_call>").unwrap_err();
595
596
- expect!["tool parser parsing failed: "].assert_eq(&error.to_report_string());
596
+ expect![[r#"tool parser parsing failed: near "<bad></minimax:tool_call>": "#]]
597
+ .assert_eq(&error.to_report_string());
598
}
599
}
rust/src/parser/src/tool/minimax_m3.rs
+4
-1
@@ -878,7 +878,10 @@ mod tests {
878
))
879
.unwrap_err();
880
881
- expect!["tool parser parsing failed: "].assert_eq(&error.to_report_string());
881
+ expect![[
882
+ r#"tool parser parsing failed: near "]<]minimax[>[<bad>]<]minimax[>[</tool_call>": "#
883
+ ]]
884
+ .assert_eq(&error.to_report_string());
885
}
886
887
#[test]
rust/src/parser/src/tool/qwen_coder.rs
+3
-2
@@ -685,7 +685,8 @@ mod tests {
685
let mut parser = Qwen3CoderToolParser::new(&test_tools());
686
let error = parser.parse_chunk("<tool_call>\n<bad>\n</tool_call>").unwrap_err();
687
688
- expect!["tool parser parsing failed: "].assert_eq(&error.to_report_string());
688
+ expect![[r#"tool parser parsing failed: near "\n<bad>\n</tool_call>": "#]]
689
+ .assert_eq(&error.to_report_string());
690
}
691
692
#[test]
@@ -697,7 +698,7 @@ mod tests {
698
)
699
.unwrap_err();
700
700
- expect!["tool parser parsing failed: "].assert_eq(&error.to_report_string());
701
+ expect![[r#"tool parser parsing failed: near "\n<function=get_weather>\n<parameter=location>SF</function>\n</tool_call>": "#]].assert_eq(&error.to_report_string());
702
}
703
704
#[test]
rust/src/parser/src/utils.rs
+28
-4
@@ -395,9 +395,9 @@ pub fn parse_buffered_event<E>(
395
Ok(event) => event,
396
Err(ErrMode::Incomplete(_)) => return Ok(None),
397
Err(ErrMode::Backtrack(e) | ErrMode::Cut(e)) => {
398
- // TODO: enrich context for error reporting
398
+ let snippet = buffer.char_indices().nth(80).map_or(buffer, |(i, _)| &buffer[..i]);
399
return Err(ToolParserError::ParsingFailed {
400
- message: e.to_string(),
400
+ message: format!("near {snippet:?}: {e}"),
401
});
402
}
403
};
@@ -423,8 +423,9 @@ mod tests {
423
use winnow::stream::{Offset, Partial, Stream};
424
425
use super::{
426
- JsonObjectScanState, JsonStringScanState, MarkerScanState, json_str, partial_prefix_len,
427
- safe_text_len, safe_text_len_mul, take_json_object, take_json_string, take_until_marker,
426
+ JsonObjectScanState, JsonStringScanState, MarkerScanState, json_str, parse_buffered_event,
427
+ partial_prefix_len, safe_text_len, safe_text_len_mul, take_json_object, take_json_string,
428
+ take_until_marker,
429
};
430
431
#[test]
@@ -832,4 +833,27 @@ mod tests {
833
834
assert!(matches!(error, ErrMode::Incomplete(_)));
835
}
836
+
837
+ #[test]
838
+ fn parse_buffered_event_error_includes_input_snippet() {
839
+ let result = parse_buffered_event(" {\"x\":1}", |input| {
840
+ take_json_object(input, &mut JsonObjectScanState::default())
841
+ });
842
+ let err = result.unwrap_err().to_string();
843
+ assert!(err.contains("near \""), "error must include snippet");
844
+ }
845
+
846
+ #[test]
847
+ fn parse_buffered_event_error_truncates_long_input() {
848
+ let long_input = format!(" {}", "x".repeat(100));
849
+ let result = parse_buffered_event(&long_input, |input| {
850
+ take_json_object(input, &mut JsonObjectScanState::default())
851
+ });
852
+ let err = result.unwrap_err().to_string();
853
+ assert!(err.contains("near \""), "error must include snippet");
854
+ assert!(
855
+ !err.contains(&long_input),
856
+ "snippet must be truncated for long input"
857
+ );
858
+ }
859
}