执行摘要
本 PR 在工具解析器 parse_buffered_event 失败时,将错误消息从简单的 e.to_string() 扩充为包含输入缓冲区片段(最多 80 字符)的 near "...": ... 格式。这一改动直接响应了 utils.rs:324 的 TODO,并在 11 个基于 winnow 的工具解析器中统一更新了测试期望。合并后,调试工具解析错误将更加直观。
功能与动机
PR 描述明确说明 "Adds 80-char buffer snippet to parse_buffered_event to enrich error messages on failed parsing",并指出这是为了完成 utils.rs 中留下的 TODO "enrich context for error reporting"。当模型输出不符合工具调用格式时,之前只输出解析器内部错误(如 invalid Granite4 arguments),完全没有输入本身的信息,导致难以定位问题。
实现拆解
-
核心逻辑修改:在 rust/src/parser/src/utils.rs 的 parse_buffered_event 函数中,将错误分支从直接输出 e.to_string() 改为构造 format!("near {snippet:?}: {e}"),其中 snippet 通过 buffer.char_indices().nth(80).map_or(buffer, |(i, _)| &buffer[..i]) 获取最多前 80 字符。
-
测试添加:在 utils.rs 的测试模块中新增两个测试函数 parse_buffered_event_error_includes_input_snippet 和 parse_buffered_event_error_truncates_long_input,验证错误消息包含片段且长输入被截断。
-
更新各工具解析器的测试期望:修改了 11 个工具解析器源文件中的测试断言,将原有的精确错误字符串更新为包含 near "..." 前缀的新格式,涉及 granite4.rs、llama.rs、deepseek_v3.rs 等所有基于 parse_buffered_event 的解析器。
rust/src/parser/src/utils.rs
核心变更文件:修改 parse_buffered_event 函数以包含输入片段,并新增两个测试用例
/// Parse one event from a buffered streaming input.
///
/// Returns:
/// - `Ok(Some((event, consumed_len)))` if an event was successfully parsed, along with the number
/// of bytes consumed from the buffer.
/// - `Ok(None)` if the buffer does not contain a full event yet, and more data is needed.
/// - `Err` if a parsing error occurred.
pub fn parse_buffered_event<E>(
buffer: &str,
parse: impl FnOnce(&mut Partial<&str>) -> ModalResult<E>,
) -> Result<Option<(E, usize)>> {
let mut input = Partial::new(buffer);
let checkpoint = input.checkpoint();
let event = match parse(&mut input) {
Ok(event) => event,
Err(ErrMode::Incomplete(_)) => return Ok(None),
Err(ErrMode::Backtrack(e) | ErrMode::Cut(e)) => {
// 截取输入缓冲区前 80 字符作为上下文片段
// 使用 char_indices().nth(80) 安全处理 Unicode 边界
// 若输入不足 80 字符则使用整个 buffer
let snippet = buffer.char_indices().nth(80).map_or(buffer, |(i, _)| &buffer[..i]);
return Err(ToolParserError::ParsingFailed {
// 格式:near " 输入片段 ": 原始错误描述
message: format!("near {snippet:?}: {e}"),
});
}
};
let consumed_len = input.offset_from(&checkpoint);
if consumed_len == 0 {
return Ok(None);
}
Ok(Some((event, consumed_len)))
}
评论区精华
Codex 机器人 (P1):指出新增的 near 前缀会使所有现有测试期望失效,要求更新。作者回复已更新了受影响解析器的测试。
depthfirst-app (安全低风险):指出错误消息中包含的输入片段(最多 80 字符)可能泄漏敏感用户内容到服务器日志。作者回应片段仅出现在结构异常时,并愿意进一步限制长度或改用 tracing::debug! 避免记录。
风险与影响
- 安全风险:最多 80 字符的输入片段可能包含用户敏感信息(如 API 密钥),若记录到服务器日志可能导致泄漏。风险较低,因为解析失败通常发生在结构异常输入,但仍需留意。
- 兼容性风险:错误消息字符串格式变更,依赖精确错误消息的监控或告警系统可能需要适配。
- 性能风险:
char_indices().nth(80) 的复杂度为 O(1)(char_indices 是惰性的),几乎无性能影响。
- 用户影响:开发者在调试工具解析错误时能直接获取失败输入片段,大大降低排查成本。
关联脉络
本 PR 是 #44624(Python 桥接)的延续,后者将 ToolParserError 转换为 PyValueError。本 PR 在此基础之上为错误消息添加了输入片段上下文,使错误更易于诊断。此外,随着更多统一工具解析器基于 winnow 构建,本 PR 的片段机制将惠及更多解析器。
参与讨论