兼容缺少槽位的非工具 Responses 事件
Project CI / Repository checks (pull_request) Successful in 2m41s
Project CI / Frontend tests (pull_request) Successful in 3m13s
Project CI / Backend tests (pull_request) Successful in 6m10s
Project CI / Native shell tests (pull_request) Successful in 19m54s

非 function_call output item 缺少 output_index 时忽略

保留 function_call 槽位缺失的 fail-closed 契约并补回归测试
This commit is contained in:
2026-09-11 12:33:37 +00:00
parent 8012008d83
commit d9530047db
+35 -2
View File
@@ -3774,8 +3774,18 @@ fn parse_responses_output_item_event(
let Some(item) = parsed.get("item").filter(|item| item.is_object()).cloned() else {
return Ok(None);
};
let slot = responses_output_slot(parsed)
.ok_or_else(|| missing_tool_slot_error("Responses", event_type, "output_index"))?;
let is_function_call =
item.get("type").and_then(serde_json::Value::as_str) == Some("function_call");
let Some(slot) = responses_output_slot(parsed) else {
if !is_function_call {
return Ok(None);
}
return Err(missing_tool_slot_error(
"Responses",
event_type,
"output_index",
));
};
let mut event = ParsedStreamEvent {
output_items: vec![(slot, item.clone())],
..Default::default()
@@ -7432,6 +7442,29 @@ mod tests {
.await;
}
#[tokio::test]
async fn stream_run_ignores_non_tool_output_item_without_output_index() {
let server_url = spawn_mock_server(vec![MockResponse {
status_line: "200 OK",
content_type: "text/event-stream; charset=utf-8",
body: concat!(
r#"data: {"type":"response.output_item.added","item":{"type":"reasoning","id":"rs_1"}}"#, "\n\n",
r#"data: {"type":"response.output_text.delta","delta":"杭州今天多云。"}"#, "\n\n",
r#"data: {"type":"response.completed"}"#, "\n\n"
)
.to_string(),
extra_headers: Vec::new(),
}]);
let response = build_test_client(server_url, 0)
.stream_run(weather_tool_request(LlmApiKind::OpenAiResponses), |_| {})
.await
.expect("非工具 output item 缺少 output_index 不应中断流");
assert_eq!(response.text, "杭州今天多云。");
assert!(response.tool_calls.is_empty());
}
#[tokio::test]
async fn stream_run_rejects_anthropic_tool_use_without_block_index() {
expect_stream_missing_slot_error(