diff --git a/server-rs/crates/platform-llm/src/lib.rs b/server-rs/crates/platform-llm/src/lib.rs index b3a7429ca..f759cc8c0 100644 --- a/server-rs/crates/platform-llm/src/lib.rs +++ b/server-rs/crates/platform-llm/src/lib.rs @@ -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(