diff --git a/crates/agentic-server-core/src/executor/accumulator.rs b/crates/agentic-server-core/src/executor/accumulator.rs index b0e0869b..fca3f9cc 100644 --- a/crates/agentic-server-core/src/executor/accumulator.rs +++ b/crates/agentic-server-core/src/executor/accumulator.rs @@ -17,11 +17,11 @@ use futures::{Stream, StreamExt}; use crate::events::{EventFrame, EventPayload, SSEEventType, SSEItemType, normalize_sse_line}; use crate::executor::error::{ExecutorError, ExecutorResult}; use crate::types::event::{MessageStatus, ResponseStatus}; -use crate::types::io::McpCall; use crate::types::io::{ ApplyDone, CustomToolCall, FunctionToolCall, OutputItem, OutputMessage, OutputTextContent, ReasoningOutput, ReasoningTextContent, ResponseUsage, }; +use crate::types::io::{McpCall, WebSearchCall}; use crate::types::request_response::{IncompleteDetails, ResponsePayload}; use crate::utils::common::{deserialize_from_str, deserialize_from_value_opt}; use crate::utils::uuid7_str; @@ -33,6 +33,7 @@ enum InFlight { Reasoning { item: ReasoningOutput, text: String }, FunctionCall { item: FunctionToolCall, arguments: String }, CustomToolCall { item: CustomToolCall, input: String }, + WebSearchCall { item: Option }, McpCall { item: McpCall }, } @@ -43,42 +44,44 @@ impl std::fmt::Debug for InFlight { Self::Reasoning { .. } => write!(f, "InFlight::Reasoning {{ .. }}"), Self::FunctionCall { .. } => write!(f, "InFlight::FunctionCall {{ .. }}"), Self::CustomToolCall { .. } => write!(f, "InFlight::CustomToolCall {{ .. }}"), + Self::WebSearchCall { .. } => write!(f, "InFlight::WebSearchCall {{ .. }}"), Self::McpCall { .. } => write!(f, "InFlight::McpCall {{ .. }}"), } } } impl InFlight { - fn finalize(self) -> OutputItem { + fn finalize(self) -> Option { match self { Self::Reasoning { mut item, text } => { if !text.is_empty() { item.content.push(ReasoningTextContent::new(text)); } - OutputItem::Reasoning(item) + Some(OutputItem::Reasoning(item)) } Self::FunctionCall { mut item, arguments } => { if !arguments.is_empty() && item.arguments.is_empty() { item.arguments = arguments; } item.status = MessageStatus::Completed; - OutputItem::FunctionCall(item) + Some(OutputItem::FunctionCall(item)) } Self::Message { mut item, text } => { if !text.is_empty() { item.content.push(OutputTextContent::new(text)); } item.status = MessageStatus::Completed; - OutputItem::Message(item) + Some(OutputItem::Message(item)) } Self::CustomToolCall { mut item, input } => { if item.input.is_empty() { item.input = input; } item.status = Some(MessageStatus::Completed); - OutputItem::CustomToolCall(item) + Some(OutputItem::CustomToolCall(item)) } - Self::McpCall { item } => OutputItem::McpCall(item), + Self::WebSearchCall { item } => item.map(OutputItem::WebSearchCall), + Self::McpCall { item } => Some(OutputItem::McpCall(item)), } } } @@ -234,7 +237,7 @@ impl ResponseAccumulator { self.completed.extend( self.in_flight .drain(..) - .map(|(_, entry)| (entry.output_index, entry.item.finalize())), + .filter_map(|(_, entry)| entry.item.finalize().map(|item| (entry.output_index, item))), ); self.completed.sort_by_key(|(output_index, _)| *output_index); self.output @@ -397,6 +400,7 @@ impl ResponseAccumulator { item, text: String::with_capacity(256), }), + SSEItemType::WebSearchCall if !item_id.is_empty() => Some(InFlight::WebSearchCall { item: None }), SSEItemType::WebSearchCall => None, SSEItemType::McpCall => McpCall::try_from(payload).ok().map(|item| InFlight::McpCall { item }), }; @@ -453,15 +457,46 @@ impl ResponseAccumulator { else { return; }; - if *item_type == SSEItemType::McpCall { - if let Some(InFlight::McpCall { item }) = self.in_flight.get_mut(item_id).map(|entry| &mut entry.item) { - item.apply_done(payload, &mut String::new()); + if *item_type == SSEItemType::WebSearchCall { + let Some(OutputItem::WebSearchCall(mut call)) = deserialize_from_value_opt::(raw_item.clone()) + else { return; + }; + let in_flight_key = self + .in_flight + .get(item_id) + .filter(|entry| matches!(entry.item, InFlight::WebSearchCall { .. })) + .map(|_| item_id.to_owned()) + .or_else(|| { + self.in_flight.iter().find_map(|(key, entry)| { + (entry.output_index == *output_index && matches!(entry.item, InFlight::WebSearchCall { .. })) + .then(|| key.clone()) + }) + }); + if call.id.is_empty() { + call.id = in_flight_key + .as_deref() + .filter(|id| !id.is_empty()) + .map_or_else(|| uuid7_str("ws_"), str::to_owned); } + if let Some(InFlight::WebSearchCall { item }) = in_flight_key + .as_deref() + .and_then(|key| self.in_flight.get_mut(key)) + .map(|entry| &mut entry.item) + { + *item = Some(call); + } else { + self.completed.push((*output_index, OutputItem::WebSearchCall(call))); + } + return; } - if let Some(output_item @ (OutputItem::WebSearchCall(_) | OutputItem::McpCall(_))) = - deserialize_from_value_opt::(raw_item.clone()) + if let (SSEItemType::McpCall, Some(InFlight::McpCall { item })) = + (item_type, self.in_flight.get_mut(item_id).map(|entry| &mut entry.item)) { + item.apply_done(payload, &mut String::new()); + return; + } + if let Some(output_item @ OutputItem::McpCall(_)) = deserialize_from_value_opt::(raw_item.clone()) { self.completed.push((*output_index, output_item)); } } @@ -506,7 +541,7 @@ impl ResponseAccumulator { mod tests { use super::*; use crate::events::WireEvent; - use crate::types::io::{McpCallError, McpCallStatus}; + use crate::types::io::{McpCallError, McpCallStatus, WebSearchCallStatus}; #[test] fn test_accumulator_new() { @@ -732,6 +767,108 @@ mod tests { assert!(matches!(acc.output[1], OutputItem::McpCall(_))); } + #[test] + fn test_accumulator_reasoning_before_web_search_call_preserves_order() { + let lines = vec![ + r#"data: {"type":"response.created","response":{"id":"resp_abc"}}"#.to_string(), + r#"data: {"type":"response.output_item.added","output_index":0,"item":{"id":"rs_1","type":"reasoning","summary":[]}}"#.to_string(), + r#"data: {"type":"response.reasoning_text.done","text":"thinking...","item_id":"rs_1"}"#.to_string(), + r#"data: {"type":"response.output_item.added","output_index":1,"item":{"type":"web_search_call","id":"ws_1","status":"in_progress","action":{"type":"search","query":"","sources":[]}}}"#.to_string(), + r#"data: {"type":"response.web_search_call.in_progress","item_id":"ws_1","output_index":1}"#.to_string(), + r#"data: {"type":"response.output_item.done","output_index":1,"item":{"type":"web_search_call","id":"ws_1","status":"completed","action":{"type":"search","query":"rust","sources":[]}}}"#.to_string(), + r#"data: {"type":"response.done","response":{"id":"resp_abc","status":"completed","usage":{"input_tokens":5,"output_tokens":2,"total_tokens":7}}}"#.to_string(), + ]; + + let acc = ResponseAccumulator::from_sse_lines(lines, None); + assert_eq!(acc.output.len(), 2); + assert!(matches!(acc.output[0], OutputItem::Reasoning(_))); + let OutputItem::WebSearchCall(call) = &acc.output[1] else { + panic!("expected web_search_call"); + }; + assert_eq!(call.status, WebSearchCallStatus::Completed); + assert_eq!(call.action.as_search().unwrap().query, "rust"); + } + + #[test] + fn test_accumulator_preserves_open_page_web_search_action() { + let lines = vec![ + r#"data: {"type":"response.output_item.added","output_index":0,"item":{"type":"web_search_call","id":"ws_1","status":"in_progress"}}"#.to_string(), + r#"data: {"type":"response.output_item.done","output_index":0,"item":{"type":"web_search_call","id":"ws_1","status":"completed","action":{"type":"open_page","url":"https://example.com"}}}"#.to_string(), + r#"data: {"type":"response.done","response":{"id":"resp_1","status":"completed"}}"#.to_string(), + ]; + + let acc = ResponseAccumulator::from_sse_lines(lines, None); + assert_eq!(acc.output.len(), 1); + let action = match &acc.output[0] { + OutputItem::WebSearchCall(call) => serde_json::to_value(&call.action).unwrap(), + _ => panic!("expected web_search_call"), + }; + assert_eq!(action["type"], "open_page"); + assert_eq!(action["url"], "https://example.com"); + } + + #[test] + fn test_accumulator_preserves_find_in_page_web_search_action() { + let lines = vec![ + r#"data: {"type":"response.output_item.added","output_index":0,"item":{"type":"web_search_call","id":"ws_1","status":"in_progress"}}"#.to_string(), + r#"data: {"type":"response.output_item.done","output_index":0,"item":{"type":"web_search_call","id":"ws_1","status":"completed","action":{"type":"find_in_page","url":"https://example.com","pattern":"needle"}}}"#.to_string(), + r#"data: {"type":"response.done","response":{"id":"resp_1","status":"completed"}}"#.to_string(), + ]; + + let acc = ResponseAccumulator::from_sse_lines(lines, None); + assert_eq!(acc.output.len(), 1); + let action = match &acc.output[0] { + OutputItem::WebSearchCall(call) => serde_json::to_value(&call.action).unwrap(), + _ => panic!("expected web_search_call"), + }; + assert_eq!(action["type"], "find_in_page"); + assert_eq!(action["url"], "https://example.com"); + assert_eq!(action["pattern"], "needle"); + } + + #[test] + fn test_accumulator_drops_unfinished_web_search_placeholder() { + let lines = vec![ + r#"data: {"type":"response.output_item.added","output_index":0,"item":{"type":"web_search_call","id":"ws_1","status":"in_progress"}}"#.to_string(), + r#"data: {"type":"response.done","response":{"id":"resp_1","status":"completed"}}"#.to_string(), + ]; + + let acc = ResponseAccumulator::from_sse_lines(lines, None); + assert!(acc.output.is_empty()); + } + + #[test] + fn test_accumulator_empty_added_id_then_stable_done_does_not_duplicate() { + let lines = vec![ + r#"data: {"type":"response.output_item.added","output_index":0,"item":{"type":"web_search_call","id":"","status":"in_progress"}}"#.to_string(), + r#"data: {"type":"response.output_item.done","output_index":0,"item":{"type":"web_search_call","id":"ws_1","status":"completed","action":{"type":"search","query":"rust","sources":[]}}}"#.to_string(), + r#"data: {"type":"response.done","response":{"id":"resp_1","status":"completed"}}"#.to_string(), + ]; + + let acc = ResponseAccumulator::from_sse_lines(lines, None); + assert_eq!(acc.output.len(), 1); + let OutputItem::WebSearchCall(call) = &acc.output[0] else { + panic!("expected web_search_call"); + }; + assert_eq!(call.id, "ws_1"); + } + + #[test] + fn test_accumulator_stable_added_id_survives_empty_done_id() { + let lines = vec![ + r#"data: {"type":"response.output_item.added","output_index":0,"item":{"type":"web_search_call","id":"ws_added","status":"in_progress"}}"#.to_string(), + r#"data: {"type":"response.output_item.done","output_index":0,"item":{"type":"web_search_call","id":"","status":"completed","action":{"type":"search","query":"rust","sources":[]}}}"#.to_string(), + r#"data: {"type":"response.done","response":{"id":"resp_1","status":"completed"}}"#.to_string(), + ]; + + let acc = ResponseAccumulator::from_sse_lines(lines, None); + assert_eq!(acc.output.len(), 1); + let OutputItem::WebSearchCall(call) = &acc.output[0] else { + panic!("expected web_search_call"); + }; + assert_eq!(call.id, "ws_added"); + } + #[test] fn test_unknown_mcp_call_error_shape_is_not_dropped() { let lines = vec![ diff --git a/crates/agentic-server-core/src/lib.rs b/crates/agentic-server-core/src/lib.rs index 8124ed7b..be8f9126 100644 --- a/crates/agentic-server-core/src/lib.rs +++ b/crates/agentic-server-core/src/lib.rs @@ -26,8 +26,8 @@ pub use types::{ InputImageContent, InputItem, InputMessage, InputMessageContent, InputTextContent, InputTokenDetails, McpCall, McpCallStatus, McpToolParam, NonEmptyToolName, OutputItem, OutputMessage, OutputTextContent, OutputTokenDetails, ReasoningOutput, ReasoningTextContent, RequestPayload, ResponsePayload, ResponseUsage, ResponsesInput, - ResponsesTool, ToolChoice, UpstreamRequest, UpstreamTool, WebSearchActionSearch, WebSearchCall, - WebSearchCallStatus, WebSearchContextSize, WebSearchFilters, WebSearchSource, WebSearchToolParam, - WebSearchUserLocation, + ResponsesTool, ToolChoice, UpstreamRequest, UpstreamTool, WebSearchAction, WebSearchActionFindInPage, + WebSearchActionOpenPage, WebSearchActionSearch, WebSearchCall, WebSearchCallStatus, WebSearchContextSize, + WebSearchFilters, WebSearchSource, WebSearchToolParam, WebSearchUserLocation, }; pub use utils::{utcnow_str, uuid7_str}; diff --git a/crates/agentic-server-core/src/types/io/mod.rs b/crates/agentic-server-core/src/types/io/mod.rs index 0c10b484..fdc923bb 100644 --- a/crates/agentic-server-core/src/types/io/mod.rs +++ b/crates/agentic-server-core/src/types/io/mod.rs @@ -10,7 +10,8 @@ pub use input::{ pub use output::{ ApplyDone, CustomToolCall, FunctionToolCall, GatewayCallStatus, McpCall, McpCallError, McpCallStatus, McpToolExecutionError, McpToolExecutionErrorContent, OutputItem, OutputMessage, OutputTextContent, ReasoningOutput, - ReasoningTextContent, WebSearchActionSearch, WebSearchCall, WebSearchCallStatus, WebSearchSource, + ReasoningTextContent, WebSearchAction, WebSearchActionFindInPage, WebSearchActionOpenPage, WebSearchActionSearch, + WebSearchCall, WebSearchCallStatus, WebSearchSource, }; pub use tools::{FunctionTool, ToolChoice}; pub(crate) use tools::{resolve_tool_choice, resolve_tools}; diff --git a/crates/agentic-server-core/src/types/io/output.rs b/crates/agentic-server-core/src/types/io/output.rs index c184870d..e25aedbe 100644 --- a/crates/agentic-server-core/src/types/io/output.rs +++ b/crates/agentic-server-core/src/types/io/output.rs @@ -249,29 +249,76 @@ pub struct WebSearchSource { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct WebSearchActionSearch { - #[serde(rename = "type")] + #[serde(skip, default = "default_web_search_action_search_type")] pub type_: String, pub query: String, #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub queries: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub sources: Vec, } +fn default_web_search_action_search_type() -> String { + "search".to_owned() +} + impl WebSearchActionSearch { #[must_use] pub fn new(query: impl Into, sources: Vec) -> Self { Self { - type_: "search".to_owned(), + type_: default_web_search_action_search_type(), query: query.into(), + queries: Vec::new(), sources, } } } +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct WebSearchActionOpenPage { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub url: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct WebSearchActionFindInPage { + pub pattern: String, + pub url: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(tag = "type", rename_all = "snake_case")] +#[non_exhaustive] +pub enum WebSearchAction { + Search(WebSearchActionSearch), + OpenPage(WebSearchActionOpenPage), + FindInPage(WebSearchActionFindInPage), +} + +impl WebSearchAction { + #[must_use] + pub const fn type_str(&self) -> &'static str { + match self { + Self::Search(_) => "search", + Self::OpenPage(_) => "open_page", + Self::FindInPage(_) => "find_in_page", + } + } + + #[must_use] + pub const fn as_search(&self) -> Option<&WebSearchActionSearch> { + match self { + Self::Search(action) => Some(action), + Self::OpenPage(_) | Self::FindInPage(_) => None, + } + } +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct WebSearchCall { pub id: String, pub status: WebSearchCallStatus, - pub action: WebSearchActionSearch, + pub action: WebSearchAction, } impl WebSearchCall { @@ -285,7 +332,7 @@ impl WebSearchCall { Self { id: id.into(), status, - action: WebSearchActionSearch::new(query, sources), + action: WebSearchAction::Search(WebSearchActionSearch::new(query, sources)), } } } diff --git a/crates/agentic-server-core/src/types/mod.rs b/crates/agentic-server-core/src/types/mod.rs index 18ceee2b..892f52cf 100644 --- a/crates/agentic-server-core/src/types/mod.rs +++ b/crates/agentic-server-core/src/types/mod.rs @@ -10,7 +10,8 @@ pub use io::{ InputMessage, InputMessageContent, InputTextContent, InputTokenDetails, McpCall, McpCallError, McpCallStatus, McpToolExecutionError, McpToolExecutionErrorContent, OutputItem, OutputMessage, OutputTextContent, OutputTokenDetails, ReasoningOutput, ReasoningTextContent, ResponseUsage, ResponsesInput, ToolChoice, - WebSearchActionSearch, WebSearchCall, WebSearchCallStatus, WebSearchSource, + WebSearchAction, WebSearchActionFindInPage, WebSearchActionOpenPage, WebSearchActionSearch, WebSearchCall, + WebSearchCallStatus, WebSearchSource, }; pub use request_response::{ CompactRequest, CompactedResponse, ContextManagement, IncompleteDetails, RequestPayload, ResponsePayload, diff --git a/crates/agentic-server-core/tests/accumulator_cassette_test.rs b/crates/agentic-server-core/tests/accumulator_cassette_test.rs index a1e17b30..54f21e5f 100644 --- a/crates/agentic-server-core/tests/accumulator_cassette_test.rs +++ b/crates/agentic-server-core/tests/accumulator_cassette_test.rs @@ -9,13 +9,17 @@ use serde::Deserialize; use agentic_core::executor::accumulator::ResponseAccumulator; use agentic_core::types::event::MessageStatus; -use agentic_core::types::io::{CustomToolCall, FunctionToolCall, OutputItem}; +use agentic_core::types::io::{CustomToolCall, FunctionToolCall, OutputItem, WebSearchCall}; const CASSETTE_DIR: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/cassettes/events"); const TOOL_CALLS_DIR: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/cassettes/tool_calls"); const REASONING_DIR: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/cassettes/reasoning/responses"); const CODEX_DIR: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/cassettes/codex"); const WEB_SEARCH_DIR: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/cassettes/web_search"); +const WEB_SEARCH_GATEWAY_MODEL: &str = "Qwen/Qwen3.5-35B-A3B-FP8"; +const WEB_SEARCH_GATEWAY_MODEL_SLUG: &str = "Qwen-Qwen3.5-35B-A3B-FP8"; +const WEB_SEARCH_OPENAI_MODEL: &str = "gpt-5.6"; +const WEB_SEARCH_OPENAI_MODEL_SLUG: &str = "gpt-5.6"; // --- Legacy event cassette format --- @@ -88,6 +92,17 @@ fn load_web_search_cassette(filename: &str) -> TurnCassette { load_turn_cassette_from(WEB_SEARCH_DIR, filename) } +fn load_web_search_cassette_pair(streaming: bool) -> (TurnCassette, TurnCassette) { + let mode = if streaming { "streaming" } else { "nonstreaming" }; + let openai = load_web_search_cassette(&format!( + "web-search-openai-reference-{WEB_SEARCH_OPENAI_MODEL_SLUG}-{mode}.yaml" + )); + let gateway = load_web_search_cassette(&format!( + "web-search-gateway-{WEB_SEARCH_GATEWAY_MODEL_SLUG}-{mode}.yaml" + )); + (openai, gateway) +} + /// Extracts `data: ...` lines from raw SSE entries (which may include /// `event:` lines and blank separators). fn extract_data_lines(sse_entries: &[String]) -> Vec { @@ -868,7 +883,7 @@ fn has_reasoning(output: &[OutputItem]) -> bool { output.iter().any(|item| matches!(item, OutputItem::Reasoning(_))) } -fn assert_completed_potato_web_search(output: &[OutputItem]) { +fn assert_completed_potato_web_search(output: &[OutputItem]) -> &WebSearchCall { assert_eq!( count_function_calls(output), 0, @@ -882,18 +897,49 @@ fn assert_completed_potato_web_search(output: &[OutputItem]) { }) .expect("cassette output should include a web_search_call item"); assert_eq!(web_search.status.as_str(), "completed"); - assert_eq!(web_search.action.query, "potato"); + assert_eq!(web_search.action.as_search().unwrap().query, "potato"); + assert_eq!(web_search.action.type_str(), "search"); + assert!(web_search.id.starts_with("ws_")); assert!( - web_search - .action + output.iter().any(|item| matches!(item, OutputItem::Message(_))), + "cassette output should include a final assistant message" + ); + web_search +} + +fn assert_matching_web_search_output(openai: &[OutputItem], gateway: &[OutputItem]) { + for (provider, output) in [("OpenAI", openai), ("gateway", gateway)] { + let public_types: Vec<&str> = output + .iter() + .filter_map(|item| match item { + OutputItem::Reasoning(_) => None, + OutputItem::WebSearchCall(_) => Some("web_search_call"), + OutputItem::Message(_) => Some("message"), + _ => Some("unexpected"), + }) + .collect(); + assert_eq!( + public_types, + ["web_search_call", "message"], + "{provider} output should preserve web_search_call before message" + ); + } + + let openai_call = assert_completed_potato_web_search(openai); + let gateway_call = assert_completed_potato_web_search(gateway); + assert_eq!(gateway_call.status, openai_call.status); + assert_eq!(gateway_call.action.type_str(), openai_call.action.type_str()); + + let openai_action = openai_call.action.as_search().unwrap(); + let gateway_action = gateway_call.action.as_search().unwrap(); + assert_eq!(gateway_action.query, openai_action.query); + assert_eq!(openai_action.queries, ["potato"]); + assert!( + gateway_action .sources .iter() .any(|source| source.url == "https://en.wikipedia.org/wiki/Potato"), - "recorded web_search_call should include the Wikipedia potato source" - ); - assert!( - output.iter().any(|item| matches!(item, OutputItem::Message(_))), - "cassette output should include a final assistant message" + "gateway web_search_call should retain its recorded sources" ); } @@ -912,52 +958,33 @@ fn get_first_fc_arguments(output: &[OutputItem]) -> String { } #[test] -fn test_web_search_gateway_cassette_nonstreaming() { - let cassette = load_web_search_cassette("gpt_oss_web_search_nonstreaming.yaml"); - assert_eq!(cassette.turns.len(), 1); - let body = cassette.turns[0].request.as_mapping().unwrap(); - assert_eq!(body["body"]["tools"][0]["type"].as_str().unwrap(), "web_search_preview"); - assert_eq!(body["body"]["max_output_tokens"].as_i64().unwrap(), 1024); - - let output = process_nonstreaming_turn(&cassette, 0, "openai/gpt-oss-20b"); +fn test_web_search_accumulator_nonstreaming_matches_openai() { + let (openai, gateway) = load_web_search_cassette_pair(false); + let openai_output = process_nonstreaming_turn(&openai, 0, WEB_SEARCH_OPENAI_MODEL); + let gateway_output = process_nonstreaming_turn(&gateway, 0, WEB_SEARCH_GATEWAY_MODEL); + assert!( - has_reasoning(&output), - "gpt-oss web_search cassette should include reasoning" + has_reasoning(&openai_output), + "reasoning-capable OpenAI reference should include reasoning" ); - assert_completed_potato_web_search(&output); + assert!( + has_reasoning(&gateway_output), + "gateway output should include reasoning" + ); + assert_matching_web_search_output(&openai_output, &gateway_output); } #[test] -fn test_web_search_gateway_cassette_streaming() { - let cassette = load_web_search_cassette("gpt_oss_web_search_streaming.yaml"); - assert_eq!(cassette.turns.len(), 1); - let body = cassette.turns[0].request.as_mapping().unwrap(); - assert_eq!(body["body"]["tools"][0]["type"].as_str().unwrap(), "web_search_preview"); - assert_eq!(body["body"]["max_output_tokens"].as_i64().unwrap(), 1024); - - let data_lines = extract_data_lines(&cassette.turns[0].response.sse); - let events: Vec = data_lines - .iter() - .filter_map(|line| line.strip_prefix("data: ")) - .filter(|data| *data != "[DONE]") - .filter_map(|data| serde_json::from_str(data).ok()) - .collect(); - let event_types: Vec<&str> = events.iter().filter_map(|event| event["type"].as_str()).collect(); - assert!(event_types.contains(&"response.web_search_call.in_progress")); - assert!(event_types.contains(&"response.web_search_call.searching")); - assert!(event_types.contains(&"response.web_search_call.completed")); +fn test_web_search_accumulator_streaming_matches_openai() { + let (openai, gateway) = load_web_search_cassette_pair(true); + let openai_output = process_streaming_turn(&openai, 0, WEB_SEARCH_OPENAI_MODEL); + let gateway_output = process_streaming_turn(&gateway, 0, WEB_SEARCH_GATEWAY_MODEL); - let final_payload = events - .iter() - .find(|event| event["object"] == "response") - .expect("streaming web_search cassette should include final response payload"); - let output: Vec = serde_json::from_value(final_payload["output"].clone()) - .expect("final response payload output should deserialize"); assert!( - has_reasoning(&output), - "gpt-oss web_search cassette should include reasoning" + has_reasoning(&gateway_output), + "gateway output should preserve reasoning around web_search_call" ); - assert_completed_potato_web_search(&output); + assert_matching_web_search_output(&openai_output, &gateway_output); } // ═══════════════════════════════════════════════════════════════════ diff --git a/crates/agentic-server-core/tests/cassettes/README.md b/crates/agentic-server-core/tests/cassettes/README.md index 59b864c8..b5d31756 100644 --- a/crates/agentic-server-core/tests/cassettes/README.md +++ b/crates/agentic-server-core/tests/cassettes/README.md @@ -167,6 +167,7 @@ turns: | `record_tool_call_cassettes.sh` | 8 tool-call cassettes (4 tool_choice modes x streaming + non-streaming) | vLLM | | `record_codex_cli_tool_call_cassettes.sh` | Codex function/namespace/custom-tool matrix | gateway, vLLM, and OpenAI | | `record_mcp_cassettes.sh` | Native MCP counter tool discovery and calls (streaming + non-streaming) | gateway and OpenAI reference | +| `record_web_search_cassettes.sh` | Matching web-search calls (streaming + non-streaming) | gateway and OpenAI reference | ### Text-only (OpenAI) @@ -191,6 +192,16 @@ vllm serve Qwen/Qwen3-30B-A3B-FP8 --tool-call-parser hermes --enable-auto-tool-c VLLM_URL=http://0.0.0.0:5050 MODEL=Qwen/Qwen3-30B-A3B-FP8 bash tests/cassettes/record_tool_call_cassettes.sh ``` +### Web search (gateway and OpenAI) + +The default records both providers. Use `WEB_SEARCH_RECORD_SET=gateway` or +`WEB_SEARCH_RECORD_SET=openai` to record only one side. + +```bash +OPENAI_API_KEY=sk-... \ +bash crates/agentic-server-core/tests/cassettes/record_web_search_cassettes.sh +``` + ### Codex custom tools (gateway, vLLM, and OpenAI) The custom fixture uses a Lark grammar and records two turns: the model returns raw `custom_tool_call.input`, then the diff --git a/crates/agentic-server-core/tests/cassettes/record_web_search_cassettes.sh b/crates/agentic-server-core/tests/cassettes/record_web_search_cassettes.sh new file mode 100755 index 00000000..f9c742ac --- /dev/null +++ b/crates/agentic-server-core/tests/cassettes/record_web_search_cassettes.sh @@ -0,0 +1,126 @@ +#!/usr/bin/env bash +# Records the same web-search scenario against the gateway and OpenAI. +# +# Each provider records: +# 1. one streaming response +# 2. one non-streaming response +# +# The default records both providers so the gateway behavior can be compared +# with the OpenAI Responses API ground truth. OPENAI_API_KEY must be set for +# the default run. +# +# Usage from the repository root: +# bash crates/agentic-server-core/tests/cassettes/record_web_search_cassettes.sh +# WEB_SEARCH_RECORD_SET=gateway \ +# bash crates/agentic-server-core/tests/cassettes/record_web_search_cassettes.sh +# WEB_SEARCH_RECORD_SET=openai OPENAI_API_KEY=sk-... \ +# bash crates/agentic-server-core/tests/cassettes/record_web_search_cassettes.sh + +set -euo pipefail + +SCRIPTS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +BASE_DIR="$SCRIPTS_DIR/web_search" +TOOLS_FILE="$BASE_DIR/tools.json" +GATEWAY_URL="${GATEWAY_URL:-http://localhost:9000}" +MODEL="${MODEL:-Qwen/Qwen3.5-35B-A3B-FP8}" +MODEL_SLUG="$(echo "$MODEL" | tr '/: ' '---')" +OPENAI_MODEL="${OPENAI_MODEL:-gpt-5.6}" +OPENAI_MODEL_SLUG="$(echo "$OPENAI_MODEL" | tr '/: ' '---')" +WEB_SEARCH_RECORD_SET="${WEB_SEARCH_RECORD_SET:-all}" +PROMPT='Use web search to search for the exact query "potato", then summarize the result in one sentence.' + +green() { printf '\033[32m%s\033[0m\n' "$*"; } +bold() { printf '\033[1m%s\033[0m\n' "$*"; } + +record_single_turn() { + local endpoint_flag="$1" + local endpoint="$2" + local model="$3" + local output="$4" + local stream_flag="$5" + local temporary_output + + temporary_output="$(mktemp "$BASE_DIR/.web-search-cassette.XXXXXX")" + + if ! printf '%s\n' "$PROMPT" \ + | python "$SCRIPTS_DIR/record_cassette.py" \ + --mode responses \ + --turns 1 \ + "$stream_flag" \ + --model "$model" \ + "$endpoint_flag" "$endpoint" \ + --tools "$TOOLS_FILE" \ + --tool-choice auto \ + --max-output-tokens 1024 \ + --output "$temporary_output" + then + rm -f -- "$temporary_output" + return 1 + fi + + mv -- "$temporary_output" "$output" + green "✓ web-search cassette recorded -> $output" +} + +record_provider_suite() { + local provider="$1" + local endpoint_flag="$2" + local endpoint="$3" + local model="$4" + local streaming_output="$5" + local nonstreaming_output="$6" + + bold "$provider web-search cassettes" + bold "Endpoint: $endpoint" + bold "Model: $model" + + bold "$provider streaming web-search response" + record_single_turn \ + "$endpoint_flag" "$endpoint" "$model" "$streaming_output" --stream + + bold "$provider non-streaming web-search response" + record_single_turn \ + "$endpoint_flag" "$endpoint" "$model" "$nonstreaming_output" --no-stream +} + +case "$WEB_SEARCH_RECORD_SET" in + gateway|openai|all) ;; + *) + echo "ERROR: WEB_SEARCH_RECORD_SET must be gateway, openai, or all" >&2 + exit 1 + ;; +esac + +if [[ ! -f "$TOOLS_FILE" ]]; then + echo "ERROR: web-search tools file does not exist: $TOOLS_FILE" >&2 + exit 1 +fi + +# Validate OpenAI requirements before recording the gateway suite so a default +# `all` run cannot leave only half of the comparison fixtures. +if [[ "$WEB_SEARCH_RECORD_SET" == "openai" || "$WEB_SEARCH_RECORD_SET" == "all" ]]; then + if [[ -z "${OPENAI_API_KEY:-}" ]]; then + echo "ERROR: OPENAI_API_KEY must be set for WEB_SEARCH_RECORD_SET=$WEB_SEARCH_RECORD_SET" >&2 + exit 1 + fi +fi + +mkdir -p "$BASE_DIR" + +if [[ "$WEB_SEARCH_RECORD_SET" == "openai" || "$WEB_SEARCH_RECORD_SET" == "all" ]]; then + record_provider_suite \ + OpenAI \ + --openai https://api.openai.com \ + "$OPENAI_MODEL" \ + "$BASE_DIR/web-search-openai-reference-${OPENAI_MODEL_SLUG}-streaming.yaml" \ + "$BASE_DIR/web-search-openai-reference-${OPENAI_MODEL_SLUG}-nonstreaming.yaml" +fi + +if [[ "$WEB_SEARCH_RECORD_SET" == "gateway" || "$WEB_SEARCH_RECORD_SET" == "all" ]]; then + record_provider_suite \ + Gateway \ + --gateway "$GATEWAY_URL" \ + "$MODEL" \ + "$BASE_DIR/web-search-gateway-${MODEL_SLUG}-streaming.yaml" \ + "$BASE_DIR/web-search-gateway-${MODEL_SLUG}-nonstreaming.yaml" +fi diff --git a/crates/agentic-server-core/tests/cassettes/web_search/web-search-gateway-Qwen-Qwen3.5-35B-A3B-FP8-nonstreaming.yaml b/crates/agentic-server-core/tests/cassettes/web_search/web-search-gateway-Qwen-Qwen3.5-35B-A3B-FP8-nonstreaming.yaml new file mode 100644 index 00000000..22061448 --- /dev/null +++ b/crates/agentic-server-core/tests/cassettes/web_search/web-search-gateway-Qwen-Qwen3.5-35B-A3B-FP8-nonstreaming.yaml @@ -0,0 +1,125 @@ +turns: +- filename: t1 + request: + body: + input: Use web search to search for the exact query "potato", then summarize + the result in one sentence. + max_output_tokens: 1024 + model: Qwen/Qwen3.5-35B-A3B-FP8 + store: true + stream: false + tool_choice: auto + tools: + - type: web_search_preview + headers: + accept: '*/*' + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + conversation_id: null + created_at: 1785502364 + error: null + id: resp_019fb83b-e9f3-71a0-a1c9-17c377ce823e + incomplete_details: null + instructions: null + model: Qwen/Qwen3.5-35B-A3B-FP8 + object: response + output: + - content: + - text: 'The user wants me to search for the exact query "potato" using web + search and then summarize the results in one sentence. Let me perform + this search. + + ' + type: reasoning_text + encrypted_content: null + id: rs_982cee05b6d2bfe9 + status: null + summary: [] + type: reasoning + - action: + query: potato + sources: + - title: Potato - Wikipedia + url: https://en.wikipedia.org/wiki/Potato + - title: Potatoes | Potato Nutrition | Types of Potatoes | Recipes + url: https://potatogoodness.com/ + - title: Potato Facts + url: https://www.idahopotatomuseum.com/potato-facts/ + - title: 16 Types of Potatoes (And How to Use Them in Your Cooking!) + url: https://www.thekitchn.com/potato-varieties-64061 + - title: Potato Facts and Figures - International Potato Center + url: https://cipotato.org/potato/potato-facts-and-figures/ + - title: Understanding Potatoes + url: https://www.thechoppingblock.com/blog/understanding-potatoes + - title: Home of U.S. Potato Farmers | Potatoes USA + url: https://potatoesusa.com/ + - title: Growing potatoes in home gardens | UMN Extension + url: https://extension.umn.edu/vegetables/growing-potatoes + - title: 'Potatoes 101: Nutrition Facts, Health Benefits, and Types' + url: https://www.healthline.com/nutrition/foods/potatoes + - title: Potato | Definition, Plant, Origin, & Facts | Britannica + url: https://www.britannica.com/plant/potato + type: search + id: ws_9f91b89f8e3e1b7d + status: completed + type: web_search_call + - content: + - text: 'Now I have the search results for "potato". Let me summarize them + in one sentence. The key information includes: + + - Potatoes are starchy tuberous vegetables native to the Americas (Peru/Bolivia + area) + + - They are a staple food worldwide with over 5,000 varieties + + - They belong to the Solanum tuberosum plant in the nightshade family + + - They are grown in many countries and are the third most important food + crop after rice and wheat + + - They are high in nutrients like potassium and vitamin C + + + Let me create a concise one-sentence summary. + + ' + type: reasoning_text + encrypted_content: null + id: rs_803ae55ccd27866f + status: null + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + Based on the search results, **potato** is a starchy tuberous vegetable + native to the Peruvian-Bolivian Andes, cultivated for thousands of years, + now grown in over 125 countries worldwide with more than 5,000 varieties, + making it the third most important food crop after rice and wheat, rich + in nutrients like vitamin C and potassium, and commonly consumed boiled, + baked, or fried.' + type: output_text + id: msg_8c98283e5c66ff1e + role: assistant + status: completed + type: message + previous_response_id: null + status: completed + usage: + input_tokens: 4674 + input_tokens_details: + cached_tokens: 0 + output_tokens: 277 + output_tokens_details: + reasoning_tokens: 0 + total_tokens: 4951 + headers: + content-type: application/json + status_code: 200 diff --git a/crates/agentic-server-core/tests/cassettes/web_search/web-search-gateway-Qwen-Qwen3.5-35B-A3B-FP8-streaming.yaml b/crates/agentic-server-core/tests/cassettes/web_search/web-search-gateway-Qwen-Qwen3.5-35B-A3B-FP8-streaming.yaml new file mode 100644 index 00000000..f7963dd3 --- /dev/null +++ b/crates/agentic-server-core/tests/cassettes/web_search/web-search-gateway-Qwen-Qwen3.5-35B-A3B-FP8-streaming.yaml @@ -0,0 +1,2120 @@ +turns: +- filename: t1 + request: + body: + input: Use web search to search for the exact query "potato", then summarize + the result in one sentence. + max_output_tokens: 1024 + model: Qwen/Qwen3.5-35B-A3B-FP8 + store: true + stream: true + tool_choice: auto + tools: + - type: web_search_preview + headers: + accept: '*/*' + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + headers: + content-type: text/event-stream; charset=utf-8 + sse: + - 'data: {"type":"response.created","sequence_number":0,"response":{"background":false,"created_at":1785502357,"ec_transfer_params":null,"frequency_penalty":0.0,"id":"resp_019fb83b-d721-7061-a039-96fb0507c3f7","incomplete_details":null,"input_messages":null,"instructions":null,"kv_transfer_params":null,"max_output_tokens":1024,"max_tool_calls":null,"metadata":null,"model":"Qwen/Qwen3.5-35B-A3B-FP8","object":"response","output":[],"output_messages":null,"parallel_tool_calls":false,"presence_penalty":0.0,"previous_response_id":null,"prompt":null,"reasoning":null,"service_tier":"auto","status":"in_progress","temperature":1.0,"text":null,"tool_choice":"auto","tools":[{"allowed_callers":null,"defer_loading":null,"description":"Search + the public web for current information and return structured web and news results.","name":"web_search","output_schema":null,"parameters":{"properties":{"count":{"description":"Maximum + results per section, from 1 to 100.","type":"integer"},"country":{"description":"Optional + ISO 3166-1 alpha-2 country code.","type":"string"},"exclude_domains":{"description":"Optional + domain blocklist.","items":{"type":"string"},"type":"array"},"freshness":{"description":"Optional + recency filter: day, week, month, year, or YYYY-MM-DDtoYYYY-MM-DD.","type":"string"},"include_domains":{"description":"Optional + strict allowlist of domains.","items":{"type":"string"},"type":"array"},"language":{"description":"Optional + BCP 47 language code.","type":"string"},"query":{"description":"The natural + language web search query.","type":"string"}},"required":["query"],"type":"object"},"strict":false,"type":"function"}],"top_logprobs":null,"top_p":0.95,"truncation":"disabled","usage":null,"user":null}} + + ' + - ' + + ' + - 'data: {"type":"response.in_progress","sequence_number":1,"response":{"background":false,"created_at":1785502357,"ec_transfer_params":null,"frequency_penalty":0.0,"id":"resp_019fb83b-d721-7061-a039-96fb0507c3f7","incomplete_details":null,"input_messages":null,"instructions":null,"kv_transfer_params":null,"max_output_tokens":1024,"max_tool_calls":null,"metadata":null,"model":"Qwen/Qwen3.5-35B-A3B-FP8","object":"response","output":[],"output_messages":null,"parallel_tool_calls":false,"presence_penalty":0.0,"previous_response_id":null,"prompt":null,"reasoning":null,"service_tier":"auto","status":"in_progress","temperature":1.0,"text":null,"tool_choice":"auto","tools":[{"allowed_callers":null,"defer_loading":null,"description":"Search + the public web for current information and return structured web and news results.","name":"web_search","output_schema":null,"parameters":{"properties":{"count":{"description":"Maximum + results per section, from 1 to 100.","type":"integer"},"country":{"description":"Optional + ISO 3166-1 alpha-2 country code.","type":"string"},"exclude_domains":{"description":"Optional + domain blocklist.","items":{"type":"string"},"type":"array"},"freshness":{"description":"Optional + recency filter: day, week, month, year, or YYYY-MM-DDtoYYYY-MM-DD.","type":"string"},"include_domains":{"description":"Optional + strict allowlist of domains.","items":{"type":"string"},"type":"array"},"language":{"description":"Optional + BCP 47 language code.","type":"string"},"query":{"description":"The natural + language web search query.","type":"string"}},"required":["query"],"type":"object"},"strict":false,"type":"function"}],"top_logprobs":null,"top_p":0.95,"truncation":"disabled","usage":null,"user":null}} + + ' + - ' + + ' + - 'data: {"type":"response.output_item.added","sequence_number":2,"output_index":0,"item":{"content":null,"encrypted_content":null,"id":"a056d039d6f61314","status":"in_progress","summary":[],"type":"reasoning"}} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_part.added","sequence_number":3,"output_index":0,"content_index":0,"item_id":"a056d039d6f61314","part":{"text":"","type":"reasoning_text"}} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":4,"output_index":0,"content_index":0,"delta":"The","item_id":"a056d039d6f61314"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":5,"output_index":0,"content_index":0,"delta":" + user","item_id":"a056d039d6f61314"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":6,"output_index":0,"content_index":0,"delta":" + wants","item_id":"a056d039d6f61314"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":7,"output_index":0,"content_index":0,"delta":" + me","item_id":"a056d039d6f61314"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":8,"output_index":0,"content_index":0,"delta":" + to","item_id":"a056d039d6f61314"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":9,"output_index":0,"content_index":0,"delta":" + search","item_id":"a056d039d6f61314"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":10,"output_index":0,"content_index":0,"delta":" + for","item_id":"a056d039d6f61314"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":11,"output_index":0,"content_index":0,"delta":" + the","item_id":"a056d039d6f61314"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":12,"output_index":0,"content_index":0,"delta":" + exact","item_id":"a056d039d6f61314"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":13,"output_index":0,"content_index":0,"delta":" + query","item_id":"a056d039d6f61314"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":14,"output_index":0,"content_index":0,"delta":" + \"","item_id":"a056d039d6f61314"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":15,"output_index":0,"content_index":0,"delta":"pot","item_id":"a056d039d6f61314"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":16,"output_index":0,"content_index":0,"delta":"ato","item_id":"a056d039d6f61314"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":17,"output_index":0,"content_index":0,"delta":"\"","item_id":"a056d039d6f61314"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":18,"output_index":0,"content_index":0,"delta":" + on","item_id":"a056d039d6f61314"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":19,"output_index":0,"content_index":0,"delta":" + the","item_id":"a056d039d6f61314"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":20,"output_index":0,"content_index":0,"delta":" + web","item_id":"a056d039d6f61314"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":21,"output_index":0,"content_index":0,"delta":" + and","item_id":"a056d039d6f61314"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":22,"output_index":0,"content_index":0,"delta":" + then","item_id":"a056d039d6f61314"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":23,"output_index":0,"content_index":0,"delta":" + summarize","item_id":"a056d039d6f61314"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":24,"output_index":0,"content_index":0,"delta":" + the","item_id":"a056d039d6f61314"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":25,"output_index":0,"content_index":0,"delta":" + results","item_id":"a056d039d6f61314"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":26,"output_index":0,"content_index":0,"delta":" + in","item_id":"a056d039d6f61314"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":27,"output_index":0,"content_index":0,"delta":" + one","item_id":"a056d039d6f61314"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":28,"output_index":0,"content_index":0,"delta":" + sentence","item_id":"a056d039d6f61314"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":29,"output_index":0,"content_index":0,"delta":".","item_id":"a056d039d6f61314"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":30,"output_index":0,"content_index":0,"delta":" + I","item_id":"a056d039d6f61314"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":31,"output_index":0,"content_index":0,"delta":"''ll","item_id":"a056d039d6f61314"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":32,"output_index":0,"content_index":0,"delta":" + use","item_id":"a056d039d6f61314"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":33,"output_index":0,"content_index":0,"delta":" + the","item_id":"a056d039d6f61314"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":34,"output_index":0,"content_index":0,"delta":" + web","item_id":"a056d039d6f61314"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":35,"output_index":0,"content_index":0,"delta":"_search","item_id":"a056d039d6f61314"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":36,"output_index":0,"content_index":0,"delta":" + function","item_id":"a056d039d6f61314"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":37,"output_index":0,"content_index":0,"delta":" + with","item_id":"a056d039d6f61314"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":38,"output_index":0,"content_index":0,"delta":" + the","item_id":"a056d039d6f61314"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":39,"output_index":0,"content_index":0,"delta":" + exact","item_id":"a056d039d6f61314"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":40,"output_index":0,"content_index":0,"delta":" + query","item_id":"a056d039d6f61314"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":41,"output_index":0,"content_index":0,"delta":" + they","item_id":"a056d039d6f61314"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":42,"output_index":0,"content_index":0,"delta":" + specified","item_id":"a056d039d6f61314"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":43,"output_index":0,"content_index":0,"delta":".","item_id":"a056d039d6f61314"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":44,"output_index":0,"content_index":0,"delta":"\n","item_id":"a056d039d6f61314"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.done","sequence_number":45,"output_index":0,"content_index":0,"item_id":"a056d039d6f61314","text":"The + user wants me to search for the exact query \"potato\" on the web and then summarize + the results in one sentence. I''ll use the web_search function with the exact + query they specified.\n"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_part.done","sequence_number":46,"output_index":0,"content_index":0,"item_id":"a056d039d6f61314","part":{"text":"The + user wants me to search for the exact query \"potato\" on the web and then summarize + the results in one sentence. I''ll use the web_search function with the exact + query they specified.\n","type":"reasoning_text"}} + + ' + - ' + + ' + - 'data: {"type":"response.output_item.done","sequence_number":47,"output_index":0,"item":{"content":[{"text":"The + user wants me to search for the exact query \"potato\" on the web and then summarize + the results in one sentence. I''ll use the web_search function with the exact + query they specified.\n","type":"reasoning_text"}],"encrypted_content":null,"id":"a056d039d6f61314","status":"completed","summary":[],"type":"reasoning"}} + + ' + - ' + + ' + - 'data: {"type":"response.output_item.added","sequence_number":48,"item":{"action":{"query":"potato","type":"search"},"id":"ws_9bc2e80d191aba9e","status":"in_progress","type":"web_search_call"},"output_index":1} + + ' + - ' + + ' + - 'data: {"type":"response.web_search_call.in_progress","sequence_number":49,"item_id":"ws_9bc2e80d191aba9e","output_index":1} + + ' + - ' + + ' + - 'data: {"type":"response.web_search_call.searching","sequence_number":50,"item_id":"ws_9bc2e80d191aba9e","output_index":1} + + ' + - ' + + ' + - 'data: {"type":"response.web_search_call.completed","sequence_number":51,"item":{"action":{"query":"potato","sources":[{"title":"Potato + - Wikipedia","url":"https://en.wikipedia.org/wiki/Potato"},{"title":"Potatoes + | Potato Nutrition | Types of Potatoes | Recipes","url":"https://potatogoodness.com/"},{"title":"Potato + Facts","url":"https://www.idahopotatomuseum.com/potato-facts/"},{"title":"16 + Types of Potatoes (And How to Use Them in Your Cooking!)","url":"https://www.thekitchn.com/potato-varieties-64061"},{"title":"Potato + Facts and Figures - International Potato Center","url":"https://cipotato.org/potato/potato-facts-and-figures/"},{"title":"Understanding + Potatoes","url":"https://www.thechoppingblock.com/blog/understanding-potatoes"},{"title":"Home + of U.S. Potato Farmers | Potatoes USA","url":"https://potatoesusa.com/"},{"title":"Growing + potatoes in home gardens | UMN Extension","url":"https://extension.umn.edu/vegetables/growing-potatoes"},{"title":"Potatoes + 101: Nutrition Facts, Health Benefits, and Types","url":"https://www.healthline.com/nutrition/foods/potatoes"},{"title":"Potato + | Definition, Plant, Origin, & Facts | Britannica","url":"https://www.britannica.com/plant/potato"}],"type":"search"},"id":"ws_9bc2e80d191aba9e","status":"completed","type":"web_search_call"},"item_id":"ws_9bc2e80d191aba9e","output_index":1} + + ' + - ' + + ' + - 'data: {"type":"response.output_item.done","sequence_number":52,"item":{"action":{"query":"potato","sources":[{"title":"Potato + - Wikipedia","url":"https://en.wikipedia.org/wiki/Potato"},{"title":"Potatoes + | Potato Nutrition | Types of Potatoes | Recipes","url":"https://potatogoodness.com/"},{"title":"Potato + Facts","url":"https://www.idahopotatomuseum.com/potato-facts/"},{"title":"16 + Types of Potatoes (And How to Use Them in Your Cooking!)","url":"https://www.thekitchn.com/potato-varieties-64061"},{"title":"Potato + Facts and Figures - International Potato Center","url":"https://cipotato.org/potato/potato-facts-and-figures/"},{"title":"Understanding + Potatoes","url":"https://www.thechoppingblock.com/blog/understanding-potatoes"},{"title":"Home + of U.S. Potato Farmers | Potatoes USA","url":"https://potatoesusa.com/"},{"title":"Growing + potatoes in home gardens | UMN Extension","url":"https://extension.umn.edu/vegetables/growing-potatoes"},{"title":"Potatoes + 101: Nutrition Facts, Health Benefits, and Types","url":"https://www.healthline.com/nutrition/foods/potatoes"},{"title":"Potato + | Definition, Plant, Origin, & Facts | Britannica","url":"https://www.britannica.com/plant/potato"}],"type":"search"},"id":"ws_9bc2e80d191aba9e","status":"completed","type":"web_search_call"},"output_index":1} + + ' + - ' + + ' + - 'data: {"type":"response.output_item.added","sequence_number":53,"output_index":2,"item":{"content":null,"encrypted_content":null,"id":"97f01571873d3200","status":"in_progress","summary":[],"type":"reasoning"}} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_part.added","sequence_number":54,"output_index":2,"content_index":0,"item_id":"97f01571873d3200","part":{"text":"","type":"reasoning_text"}} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":55,"output_index":2,"content_index":0,"delta":"The","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":56,"output_index":2,"content_index":0,"delta":" + user","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":57,"output_index":2,"content_index":0,"delta":" + asked","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":58,"output_index":2,"content_index":0,"delta":" + me","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":59,"output_index":2,"content_index":0,"delta":" + to","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":60,"output_index":2,"content_index":0,"delta":" + search","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":61,"output_index":2,"content_index":0,"delta":" + for","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":62,"output_index":2,"content_index":0,"delta":" + the","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":63,"output_index":2,"content_index":0,"delta":" + exact","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":64,"output_index":2,"content_index":0,"delta":" + query","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":65,"output_index":2,"content_index":0,"delta":" + \"","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":66,"output_index":2,"content_index":0,"delta":"pot","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":67,"output_index":2,"content_index":0,"delta":"ato","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":68,"output_index":2,"content_index":0,"delta":"\"","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":69,"output_index":2,"content_index":0,"delta":" + and","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":70,"output_index":2,"content_index":0,"delta":" + summarize","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":71,"output_index":2,"content_index":0,"delta":" + the","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":72,"output_index":2,"content_index":0,"delta":" + results","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":73,"output_index":2,"content_index":0,"delta":" + in","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":74,"output_index":2,"content_index":0,"delta":" + one","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":75,"output_index":2,"content_index":0,"delta":" + sentence","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":76,"output_index":2,"content_index":0,"delta":".","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":77,"output_index":2,"content_index":0,"delta":" + Based","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":78,"output_index":2,"content_index":0,"delta":" + on","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":79,"output_index":2,"content_index":0,"delta":" + the","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":80,"output_index":2,"content_index":0,"delta":" + web","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":81,"output_index":2,"content_index":0,"delta":" + search","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":82,"output_index":2,"content_index":0,"delta":" + results","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":83,"output_index":2,"content_index":0,"delta":",","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":84,"output_index":2,"content_index":0,"delta":" + I","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":85,"output_index":2,"content_index":0,"delta":" + have","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":86,"output_index":2,"content_index":0,"delta":" + comprehensive","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":87,"output_index":2,"content_index":0,"delta":" + information","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":88,"output_index":2,"content_index":0,"delta":" + about","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":89,"output_index":2,"content_index":0,"delta":" + potatoes","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":90,"output_index":2,"content_index":0,"delta":" + from","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":91,"output_index":2,"content_index":0,"delta":" + multiple","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":92,"output_index":2,"content_index":0,"delta":" + sources","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":93,"output_index":2,"content_index":0,"delta":" + including","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":94,"output_index":2,"content_index":0,"delta":" + Wikipedia","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":95,"output_index":2,"content_index":0,"delta":",","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":96,"output_index":2,"content_index":0,"delta":" + Britann","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":97,"output_index":2,"content_index":0,"delta":"ica","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":98,"output_index":2,"content_index":0,"delta":",","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":99,"output_index":2,"content_index":0,"delta":" + Health","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":100,"output_index":2,"content_index":0,"delta":"line","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":101,"output_index":2,"content_index":0,"delta":",","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":102,"output_index":2,"content_index":0,"delta":" + and","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":103,"output_index":2,"content_index":0,"delta":" + other","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":104,"output_index":2,"content_index":0,"delta":" + reputable","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":105,"output_index":2,"content_index":0,"delta":" + sites","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":106,"output_index":2,"content_index":0,"delta":".","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":107,"output_index":2,"content_index":0,"delta":"\n\n","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":108,"output_index":2,"content_index":0,"delta":"Let","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":109,"output_index":2,"content_index":0,"delta":" + me","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":110,"output_index":2,"content_index":0,"delta":" + summarize","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":111,"output_index":2,"content_index":0,"delta":" + the","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":112,"output_index":2,"content_index":0,"delta":" + key","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":113,"output_index":2,"content_index":0,"delta":" + findings","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":114,"output_index":2,"content_index":0,"delta":" + in","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":115,"output_index":2,"content_index":0,"delta":" + one","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":116,"output_index":2,"content_index":0,"delta":" + sentence","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":117,"output_index":2,"content_index":0,"delta":":","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":118,"output_index":2,"content_index":0,"delta":"\n\n","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":119,"output_index":2,"content_index":0,"delta":"-","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":120,"output_index":2,"content_index":0,"delta":" + Pot","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":121,"output_index":2,"content_index":0,"delta":"atoes","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":122,"output_index":2,"content_index":0,"delta":" + are","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":123,"output_index":2,"content_index":0,"delta":" + st","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":124,"output_index":2,"content_index":0,"delta":"archy","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":125,"output_index":2,"content_index":0,"delta":" + tub","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":126,"output_index":2,"content_index":0,"delta":"ers","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":127,"output_index":2,"content_index":0,"delta":" + native","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":128,"output_index":2,"content_index":0,"delta":" + to","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":129,"output_index":2,"content_index":0,"delta":" + the","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":130,"output_index":2,"content_index":0,"delta":" + Americas","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":131,"output_index":2,"content_index":0,"delta":" + (","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":132,"output_index":2,"content_index":0,"delta":"Per","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":133,"output_index":2,"content_index":0,"delta":"u","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":134,"output_index":2,"content_index":0,"delta":"/B","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":135,"output_index":2,"content_index":0,"delta":"ol","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":136,"output_index":2,"content_index":0,"delta":"ivia","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":137,"output_index":2,"content_index":0,"delta":")","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":138,"output_index":2,"content_index":0,"delta":"\n","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":139,"output_index":2,"content_index":0,"delta":"-","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":140,"output_index":2,"content_index":0,"delta":" + They","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":141,"output_index":2,"content_index":0,"delta":"''re","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":142,"output_index":2,"content_index":0,"delta":" + a","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":143,"output_index":2,"content_index":0,"delta":" + staple","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":144,"output_index":2,"content_index":0,"delta":" + food","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":145,"output_index":2,"content_index":0,"delta":" + worldwide","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":146,"output_index":2,"content_index":0,"delta":"\n","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":147,"output_index":2,"content_index":0,"delta":"-","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":148,"output_index":2,"content_index":0,"delta":" + They","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":149,"output_index":2,"content_index":0,"delta":" + belong","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":150,"output_index":2,"content_index":0,"delta":" + to","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":151,"output_index":2,"content_index":0,"delta":" + the","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":152,"output_index":2,"content_index":0,"delta":" + night","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":153,"output_index":2,"content_index":0,"delta":"shade","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":154,"output_index":2,"content_index":0,"delta":" + family","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":155,"output_index":2,"content_index":0,"delta":" + (","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":156,"output_index":2,"content_index":0,"delta":"S","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":157,"output_index":2,"content_index":0,"delta":"olan","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":158,"output_index":2,"content_index":0,"delta":"um","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":159,"output_index":2,"content_index":0,"delta":" + tub","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":160,"output_index":2,"content_index":0,"delta":"eros","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":161,"output_index":2,"content_index":0,"delta":"um","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":162,"output_index":2,"content_index":0,"delta":")","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":163,"output_index":2,"content_index":0,"delta":"\n","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":164,"output_index":2,"content_index":0,"delta":"-","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":165,"output_index":2,"content_index":0,"delta":" + Over","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":166,"output_index":2,"content_index":0,"delta":" + ","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":167,"output_index":2,"content_index":0,"delta":"5","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":168,"output_index":2,"content_index":0,"delta":",","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":169,"output_index":2,"content_index":0,"delta":"0","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":170,"output_index":2,"content_index":0,"delta":"0","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":171,"output_index":2,"content_index":0,"delta":"0","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":172,"output_index":2,"content_index":0,"delta":" + varieties","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":173,"output_index":2,"content_index":0,"delta":" + exist","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":174,"output_index":2,"content_index":0,"delta":"\n","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":175,"output_index":2,"content_index":0,"delta":"-","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":176,"output_index":2,"content_index":0,"delta":" + Third","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":177,"output_index":2,"content_index":0,"delta":" + most","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":178,"output_index":2,"content_index":0,"delta":" + important","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":179,"output_index":2,"content_index":0,"delta":" + food","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":180,"output_index":2,"content_index":0,"delta":" + crop","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":181,"output_index":2,"content_index":0,"delta":" + globally","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":182,"output_index":2,"content_index":0,"delta":" + after","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":183,"output_index":2,"content_index":0,"delta":" + rice","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":184,"output_index":2,"content_index":0,"delta":" + and","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":185,"output_index":2,"content_index":0,"delta":" + wheat","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":186,"output_index":2,"content_index":0,"delta":"\n","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":187,"output_index":2,"content_index":0,"delta":"-","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":188,"output_index":2,"content_index":0,"delta":" + High","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":189,"output_index":2,"content_index":0,"delta":" + in","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":190,"output_index":2,"content_index":0,"delta":" + nutrients","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":191,"output_index":2,"content_index":0,"delta":" + like","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":192,"output_index":2,"content_index":0,"delta":" + potassium","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":193,"output_index":2,"content_index":0,"delta":" + and","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":194,"output_index":2,"content_index":0,"delta":" + vitamin","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":195,"output_index":2,"content_index":0,"delta":" + C","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":196,"output_index":2,"content_index":0,"delta":"\n\n","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":197,"output_index":2,"content_index":0,"delta":"I","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":198,"output_index":2,"content_index":0,"delta":"''ll","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":199,"output_index":2,"content_index":0,"delta":" + craft","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":200,"output_index":2,"content_index":0,"delta":" + a","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":201,"output_index":2,"content_index":0,"delta":" + concise","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":202,"output_index":2,"content_index":0,"delta":" + one","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":203,"output_index":2,"content_index":0,"delta":"-s","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":204,"output_index":2,"content_index":0,"delta":"entence","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":205,"output_index":2,"content_index":0,"delta":" + summary","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":206,"output_index":2,"content_index":0,"delta":".","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":207,"output_index":2,"content_index":0,"delta":"\n","item_id":"97f01571873d3200"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_text.done","sequence_number":208,"output_index":2,"content_index":0,"item_id":"97f01571873d3200","text":"The + user asked me to search for the exact query \"potato\" and summarize the results + in one sentence. Based on the web search results, I have comprehensive information + about potatoes from multiple sources including Wikipedia, Britannica, Healthline, + and other reputable sites.\n\nLet me summarize the key findings in one sentence:\n\n- + Potatoes are starchy tubers native to the Americas (Peru/Bolivia)\n- They''re + a staple food worldwide\n- They belong to the nightshade family (Solanum tuberosum)\n- + Over 5,000 varieties exist\n- Third most important food crop globally after + rice and wheat\n- High in nutrients like potassium and vitamin C\n\nI''ll craft + a concise one-sentence summary.\n"} + + ' + - ' + + ' + - 'data: {"type":"response.reasoning_part.done","sequence_number":209,"output_index":2,"content_index":0,"item_id":"97f01571873d3200","part":{"text":"The + user asked me to search for the exact query \"potato\" and summarize the results + in one sentence. Based on the web search results, I have comprehensive information + about potatoes from multiple sources including Wikipedia, Britannica, Healthline, + and other reputable sites.\n\nLet me summarize the key findings in one sentence:\n\n- + Potatoes are starchy tubers native to the Americas (Peru/Bolivia)\n- They''re + a staple food worldwide\n- They belong to the nightshade family (Solanum tuberosum)\n- + Over 5,000 varieties exist\n- Third most important food crop globally after + rice and wheat\n- High in nutrients like potassium and vitamin C\n\nI''ll craft + a concise one-sentence summary.\n","type":"reasoning_text"}} + + ' + - ' + + ' + - 'data: {"type":"response.output_item.done","sequence_number":210,"output_index":2,"item":{"content":[{"text":"The + user asked me to search for the exact query \"potato\" and summarize the results + in one sentence. Based on the web search results, I have comprehensive information + about potatoes from multiple sources including Wikipedia, Britannica, Healthline, + and other reputable sites.\n\nLet me summarize the key findings in one sentence:\n\n- + Potatoes are starchy tubers native to the Americas (Peru/Bolivia)\n- They''re + a staple food worldwide\n- They belong to the nightshade family (Solanum tuberosum)\n- + Over 5,000 varieties exist\n- Third most important food crop globally after + rice and wheat\n- High in nutrients like potassium and vitamin C\n\nI''ll craft + a concise one-sentence summary.\n","type":"reasoning_text"}],"encrypted_content":null,"id":"97f01571873d3200","status":"completed","summary":[],"type":"reasoning"}} + + ' + - ' + + ' + - 'data: {"type":"response.output_item.added","sequence_number":211,"output_index":3,"item":{"content":[],"id":"b4f76217829dc525","phase":null,"role":"assistant","status":"in_progress","type":"message"}} + + ' + - ' + + ' + - 'data: {"type":"response.content_part.added","sequence_number":212,"output_index":3,"content_index":0,"item_id":"b4f76217829dc525","part":{"annotations":[],"logprobs":[],"text":"","type":"output_text"}} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":213,"output_index":3,"content_index":0,"delta":"\n\nBased","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":214,"output_index":3,"content_index":0,"delta":" + on","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":215,"output_index":3,"content_index":0,"delta":" + the","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":216,"output_index":3,"content_index":0,"delta":" + web","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":217,"output_index":3,"content_index":0,"delta":" + search","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":218,"output_index":3,"content_index":0,"delta":" + results","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":219,"output_index":3,"content_index":0,"delta":",","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":220,"output_index":3,"content_index":0,"delta":" + a","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":221,"output_index":3,"content_index":0,"delta":" + potato","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":222,"output_index":3,"content_index":0,"delta":" + is","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":223,"output_index":3,"content_index":0,"delta":" + a","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":224,"output_index":3,"content_index":0,"delta":" + st","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":225,"output_index":3,"content_index":0,"delta":"archy","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":226,"output_index":3,"content_index":0,"delta":" + tuber","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":227,"output_index":3,"content_index":0,"delta":"ous","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":228,"output_index":3,"content_index":0,"delta":" + vegetable","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":229,"output_index":3,"content_index":0,"delta":" + native","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":230,"output_index":3,"content_index":0,"delta":" + to","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":231,"output_index":3,"content_index":0,"delta":" + the","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":232,"output_index":3,"content_index":0,"delta":" + Per","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":233,"output_index":3,"content_index":0,"delta":"uvian","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":234,"output_index":3,"content_index":0,"delta":"-B","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":235,"output_index":3,"content_index":0,"delta":"oliv","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":236,"output_index":3,"content_index":0,"delta":"ian","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":237,"output_index":3,"content_index":0,"delta":" + And","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":238,"output_index":3,"content_index":0,"delta":"es","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":239,"output_index":3,"content_index":0,"delta":" + in","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":240,"output_index":3,"content_index":0,"delta":" + the","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":241,"output_index":3,"content_index":0,"delta":" + night","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":242,"output_index":3,"content_index":0,"delta":"shade","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":243,"output_index":3,"content_index":0,"delta":" + family","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":244,"output_index":3,"content_index":0,"delta":" + (","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":245,"output_index":3,"content_index":0,"delta":"S","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":246,"output_index":3,"content_index":0,"delta":"olan","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":247,"output_index":3,"content_index":0,"delta":"um","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":248,"output_index":3,"content_index":0,"delta":" + tub","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":249,"output_index":3,"content_index":0,"delta":"eros","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":250,"output_index":3,"content_index":0,"delta":"um","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":251,"output_index":3,"content_index":0,"delta":")","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":252,"output_index":3,"content_index":0,"delta":" + that","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":253,"output_index":3,"content_index":0,"delta":" + has","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":254,"output_index":3,"content_index":0,"delta":" + been","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":255,"output_index":3,"content_index":0,"delta":" + cultivated","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":256,"output_index":3,"content_index":0,"delta":" + for","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":257,"output_index":3,"content_index":0,"delta":" + thousands","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":258,"output_index":3,"content_index":0,"delta":" + of","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":259,"output_index":3,"content_index":0,"delta":" + years","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":260,"output_index":3,"content_index":0,"delta":" + and","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":261,"output_index":3,"content_index":0,"delta":" + is","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":262,"output_index":3,"content_index":0,"delta":" + now","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":263,"output_index":3,"content_index":0,"delta":" + the","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":264,"output_index":3,"content_index":0,"delta":" + third","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":265,"output_index":3,"content_index":0,"delta":" + most","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":266,"output_index":3,"content_index":0,"delta":" + important","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":267,"output_index":3,"content_index":0,"delta":" + food","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":268,"output_index":3,"content_index":0,"delta":" + crop","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":269,"output_index":3,"content_index":0,"delta":" + globally","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":270,"output_index":3,"content_index":0,"delta":" + after","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":271,"output_index":3,"content_index":0,"delta":" + rice","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":272,"output_index":3,"content_index":0,"delta":" + and","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":273,"output_index":3,"content_index":0,"delta":" + wheat","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":274,"output_index":3,"content_index":0,"delta":",","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":275,"output_index":3,"content_index":0,"delta":" + with","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":276,"output_index":3,"content_index":0,"delta":" + over","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":277,"output_index":3,"content_index":0,"delta":" + ","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":278,"output_index":3,"content_index":0,"delta":"5","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":279,"output_index":3,"content_index":0,"delta":",","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":280,"output_index":3,"content_index":0,"delta":"0","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":281,"output_index":3,"content_index":0,"delta":"0","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":282,"output_index":3,"content_index":0,"delta":"0","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":283,"output_index":3,"content_index":0,"delta":" + varieties","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":284,"output_index":3,"content_index":0,"delta":" + used","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":285,"output_index":3,"content_index":0,"delta":" + as","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":286,"output_index":3,"content_index":0,"delta":" + a","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":287,"output_index":3,"content_index":0,"delta":" + staple","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":288,"output_index":3,"content_index":0,"delta":" + food","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":289,"output_index":3,"content_index":0,"delta":" + in","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":290,"output_index":3,"content_index":0,"delta":" + countless","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":291,"output_index":3,"content_index":0,"delta":" + dishes","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":292,"output_index":3,"content_index":0,"delta":" + worldwide","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":293,"output_index":3,"content_index":0,"delta":".","item_id":"b4f76217829dc525","logprobs":[]} + + ' + - ' + + ' + - 'data: {"type":"response.output_text.done","sequence_number":294,"output_index":3,"content_index":0,"item_id":"b4f76217829dc525","logprobs":[],"text":"\n\nBased + on the web search results, a potato is a starchy tuberous vegetable native to + the Peruvian-Bolivian Andes in the nightshade family (Solanum tuberosum) that + has been cultivated for thousands of years and is now the third most important + food crop globally after rice and wheat, with over 5,000 varieties used as a + staple food in countless dishes worldwide."} + + ' + - ' + + ' + - 'data: {"type":"response.content_part.done","sequence_number":295,"output_index":3,"content_index":0,"item_id":"b4f76217829dc525","part":{"annotations":[],"logprobs":null,"text":"\n\nBased + on the web search results, a potato is a starchy tuberous vegetable native to + the Peruvian-Bolivian Andes in the nightshade family (Solanum tuberosum) that + has been cultivated for thousands of years and is now the third most important + food crop globally after rice and wheat, with over 5,000 varieties used as a + staple food in countless dishes worldwide.","type":"output_text"}} + + ' + - ' + + ' + - 'data: {"type":"response.output_item.done","sequence_number":296,"output_index":3,"item":{"content":[{"annotations":[],"logprobs":null,"text":"\n\nBased + on the web search results, a potato is a starchy tuberous vegetable native to + the Peruvian-Bolivian Andes in the nightshade family (Solanum tuberosum) that + has been cultivated for thousands of years and is now the third most important + food crop globally after rice and wheat, with over 5,000 varieties used as a + staple food in countless dishes worldwide.","type":"output_text"}],"id":"b4f76217829dc525","phase":null,"role":"assistant","status":"completed","summary":[],"type":"message"}} + + ' + - ' + + ' + - 'data: {"type":"response.completed","sequence_number":297,"response":{"conversation_id":null,"created_at":1785502360,"error":null,"id":"resp_019fb83b-d721-7061-a039-96fb0507c3f7","incomplete_details":null,"instructions":null,"model":"Qwen/Qwen3.5-35B-A3B-FP8","object":"response","output":[{"content":[{"text":"The + user wants me to search for the exact query \"potato\" on the web and then summarize + the results in one sentence. I''ll use the web_search function with the exact + query they specified.\n","type":"reasoning_text"}],"encrypted_content":null,"id":"a056d039d6f61314","status":null,"summary":[],"type":"reasoning"},{"action":{"query":"potato","sources":[{"title":"Potato + - Wikipedia","url":"https://en.wikipedia.org/wiki/Potato"},{"title":"Potatoes + | Potato Nutrition | Types of Potatoes | Recipes","url":"https://potatogoodness.com/"},{"title":"Potato + Facts","url":"https://www.idahopotatomuseum.com/potato-facts/"},{"title":"16 + Types of Potatoes (And How to Use Them in Your Cooking!)","url":"https://www.thekitchn.com/potato-varieties-64061"},{"title":"Potato + Facts and Figures - International Potato Center","url":"https://cipotato.org/potato/potato-facts-and-figures/"},{"title":"Understanding + Potatoes","url":"https://www.thechoppingblock.com/blog/understanding-potatoes"},{"title":"Home + of U.S. Potato Farmers | Potatoes USA","url":"https://potatoesusa.com/"},{"title":"Growing + potatoes in home gardens | UMN Extension","url":"https://extension.umn.edu/vegetables/growing-potatoes"},{"title":"Potatoes + 101: Nutrition Facts, Health Benefits, and Types","url":"https://www.healthline.com/nutrition/foods/potatoes"},{"title":"Potato + | Definition, Plant, Origin, & Facts | Britannica","url":"https://www.britannica.com/plant/potato"}],"type":"search"},"id":"ws_9bc2e80d191aba9e","status":"completed","type":"web_search_call"},{"content":[{"text":"The + user asked me to search for the exact query \"potato\" and summarize the results + in one sentence. Based on the web search results, I have comprehensive information + about potatoes from multiple sources including Wikipedia, Britannica, Healthline, + and other reputable sites.\n\nLet me summarize the key findings in one sentence:\n\n- + Potatoes are starchy tubers native to the Americas (Peru/Bolivia)\n- They''re + a staple food worldwide\n- They belong to the nightshade family (Solanum tuberosum)\n- + Over 5,000 varieties exist\n- Third most important food crop globally after + rice and wheat\n- High in nutrients like potassium and vitamin C\n\nI''ll craft + a concise one-sentence summary.\n","type":"reasoning_text"}],"encrypted_content":null,"id":"97f01571873d3200","status":null,"summary":[],"type":"reasoning"},{"content":[{"annotations":[],"text":"\n\nBased + on the web search results, a potato is a starchy tuberous vegetable native to + the Peruvian-Bolivian Andes in the nightshade family (Solanum tuberosum) that + has been cultivated for thousands of years and is now the third most important + food crop globally after rice and wheat, with over 5,000 varieties used as a + staple food in countless dishes worldwide.","type":"output_text"}],"id":"b4f76217829dc525","role":"assistant","status":"completed","type":"message"}],"previous_response_id":null,"status":"completed","usage":{"input_tokens":4686,"input_tokens_details":{"cached_tokens":0},"output_tokens":307,"output_tokens_details":{"reasoning_tokens":0},"total_tokens":4993}}} + + ' + - ' + + ' + - 'data: [DONE] + + ' + - ' + + ' + status_code: 200 diff --git a/crates/agentic-server-core/tests/cassettes/web_search/web-search-openai-reference-gpt-5.6-nonstreaming.yaml b/crates/agentic-server-core/tests/cassettes/web_search/web-search-openai-reference-gpt-5.6-nonstreaming.yaml new file mode 100644 index 00000000..889f7a33 --- /dev/null +++ b/crates/agentic-server-core/tests/cassettes/web_search/web-search-openai-reference-gpt-5.6-nonstreaming.yaml @@ -0,0 +1,130 @@ +turns: +- filename: t1 + request: + body: + input: Use web search to search for the exact query "potato", then summarize + the result in one sentence. + max_output_tokens: 1024 + model: gpt-5.6 + store: true + stream: false + tool_choice: auto + tools: + - type: web_search_preview + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + background: false + billing: + payer: developer + completed_at: 1785502355 + created_at: 1785502347 + error: null + frequency_penalty: 0.0 + id: resp_05b26e957aa7a7d7006a6c9a8bdc648199ba757441be02e7a6 + incomplete_details: null + instructions: null + max_output_tokens: 1024 + max_tool_calls: null + metadata: {} + model: gpt-5.6-sol + moderation: null + object: response + output: + - content: [] + encrypted_content: gAAAAABqbJqT3jzQZaHOg1Wsz1ch-PsZdMKOBETTJuBoEWxvKF4Y1V6NzmWHE7zovHk_Xdevg5u1JDQszrvMKmvJMnlTREBqxR4bt9roowxwJj9M3TM1SJ__-ZzpzHtbk-d6_io_xscOxBtrzx8tvVxyCMBU-VvdBJC5CG4IWoEPdIUDAoyczGfRTSlZKDpVZLKJRxI_aw0G1UxBMl_W8UvGtCjfzs9XxiNp2CWA65YPZX2rGTmRN9_8AIJyEXMkm3d1Pvp45gYGZLYC-tRSrAiM9rD5F2Xej6LG_2rkcLRFLgWZBz5typRRWKAw9Yi0D3iuAn-Z4jx3EkK-gJWKKWy6_EWJw1pJbOsSLTu2wgItX5RGASQOpfgOtTpOcfoC1ci6yzM0DQ6ltEcoO8Uy66J3YXT83xTOMPxsvV7fmhGItVyr8SLqn9j8WzjQ7V1XHDNU3tOVMkqrEuyhiDbVIHri9bk--aaGE2E7evhMHfmZilx3GQ-2vX1zlkGCL4ooT7Udefqx8Ot7BmEC8uInPGo0AbmwXdKoucX_5sqEH6foLPSmr1yj6dDxUDzxhlsPkp_EohI1EBWv44YNUxv0Tz0NFgZp9BDQvLg6RBPOl1cjJNR7tw8VyBoHCBQ7bRbn8DPhgjnxWzsUmx0cdDkXmtI8umiaH7CcE688ns9MJBnRzEMeWSsoYszLGb1BTdT5m24eDD5wmNf27NFE_srdQUtQWQk7ZTrLUQAG9WnsNBTf29hGv478sT8fcW5DM9n1WQuf84quOc8HqOe6s2Mux3QVfJwHKFP6WEzqOFPKwTsX-5j_M-G4x0RWB4U_jjh2Smd-A6JoQf7pp1cTF-2I0pVSNl_WginPo1iSNE8NVQ2PAkl4rbiO-ee-jCW0BL46WNmZyNRDd9GxnVitwH2I1eQ3BldI5adaNHTIIxS5fH_KjRSp80T1wzJNSDARtrJ7wcAe9OpRwXbb0FFBYVeUJN0UeivYX5L6pcHE7nj0OPTGkRrWYxqiccSMETmiiLT0RSfPf0lkNZGg26cn724_W4ZtRBCEl8EpDg== + id: rs_05b26e957aa7a7d7006a6c9a8d1ad4819995cfa8e832d7f52b + summary: [] + type: reasoning + - action: + queries: + - potato + query: potato + type: search + id: ws_05b26e957aa7a7d7006a6c9a8d934c8199b6e86ca1372c4621 + status: completed + type: web_search_call + - content: + - annotations: + - end_index: 265 + start_index: 173 + title: POTATO Definition & Meaning - Merriam-Webster + type: url_citation + url: https://www.merriam-webster.com/dictionary/potato?utm_source=openai + logprobs: [] + text: Search results for **“potato”** primarily define it as an edible, + starchy underground tuber, alongside cooking guides, recipes, agricultural + resources, and related content. ([merriam-webster.com](https://www.merriam-webster.com/dictionary/potato?utm_source=openai)) + type: output_text + id: msg_05b26e957aa7a7d7006a6c9a92bcc081998faad7bf98c9b051 + phase: final_answer + role: assistant + status: completed + type: message + parallel_tool_calls: true + presence_penalty: 0.0 + previous_response_id: null + prompt_cache_key: null + prompt_cache_retention: 24h + reasoning: + context: all_turns + effort: medium + mode: standard + summary: null + safety_identifier: null + service_tier: default + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + verbosity: medium + tool_choice: auto + tool_usage: + image_gen: + input_tokens: 0 + input_tokens_details: + image_tokens: 0 + text_tokens: 0 + output_tokens: 0 + output_tokens_details: + image_tokens: 0 + text_tokens: 0 + total_tokens: 0 + web_search: + num_requests: 1 + tools: + - search_content_types: + - text + search_context_size: medium + type: web_search_preview + user_location: + city: null + country: US + region: null + timezone: null + type: approximate + top_logprobs: 0 + top_p: 0.98 + truncation: disabled + usage: + input_tokens: 8775 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 4420 + output_tokens: 114 + output_tokens_details: + reasoning_tokens: 47 + total_tokens: 8889 + user: null + headers: + content-type: application/json + status_code: 200 diff --git a/crates/agentic-server-core/tests/cassettes/web_search/web-search-openai-reference-gpt-5.6-streaming.yaml b/crates/agentic-server-core/tests/cassettes/web_search/web-search-openai-reference-gpt-5.6-streaming.yaml new file mode 100644 index 00000000..cdbe3a53 --- /dev/null +++ b/crates/agentic-server-core/tests/cassettes/web_search/web-search-openai-reference-gpt-5.6-streaming.yaml @@ -0,0 +1,204 @@ +turns: +- filename: t1 + request: + body: + input: Use web search to search for the exact query "potato", then summarize + the result in one sentence. + max_output_tokens: 1024 + model: gpt-5.6 + store: true + stream: true + tool_choice: auto + tools: + - type: web_search_preview + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + headers: + content-type: text/event-stream; charset=utf-8 + sse: + - 'event: response.created + + ' + - 'data: {"type":"response.created","response":{"id":"resp_0ba19d44d2c0ff32006a6c9a817264819899cdb62d72aec42c","object":"response","created_at":1785502337,"status":"in_progress","background":false,"completed_at":null,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":1024,"max_tool_calls":null,"model":"gpt-5.6-sol","moderation":null,"output":[],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":"24h","reasoning":{"context":"all_turns","effort":"medium","mode":"standard","summary":null},"safety_identifier":null,"service_tier":"auto","store":true,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[{"type":"web_search_preview","search_content_types":["text"],"search_context_size":"medium","user_location":{"type":"approximate","city":null,"country":"US","region":null,"timezone":null}}],"top_logprobs":0,"top_p":0.98,"truncation":"disabled","usage":null,"user":null,"metadata":{}},"sequence_number":0} + + ' + - ' + + ' + - 'event: response.in_progress + + ' + - 'data: {"type":"response.in_progress","response":{"id":"resp_0ba19d44d2c0ff32006a6c9a817264819899cdb62d72aec42c","object":"response","created_at":1785502337,"status":"in_progress","background":false,"completed_at":null,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":1024,"max_tool_calls":null,"model":"gpt-5.6-sol","moderation":null,"output":[],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":"24h","reasoning":{"context":"all_turns","effort":"medium","mode":"standard","summary":null},"safety_identifier":null,"service_tier":"auto","store":true,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[{"type":"web_search_preview","search_content_types":["text"],"search_context_size":"medium","user_location":{"type":"approximate","city":null,"country":"US","region":null,"timezone":null}}],"top_logprobs":0,"top_p":0.98,"truncation":"disabled","usage":null,"user":null,"metadata":{}},"sequence_number":1} + + ' + - ' + + ' + - 'event: response.output_item.added + + ' + - 'data: {"type":"response.output_item.added","item":{"id":"ws_0ba19d44d2c0ff32006a6c9a825fbc8198a4b7261bbd0bddc4","type":"web_search_call","status":"in_progress"},"output_index":0,"sequence_number":2} + + ' + - ' + + ' + - 'event: response.web_search_call.in_progress + + ' + - 'data: {"type":"response.web_search_call.in_progress","item_id":"ws_0ba19d44d2c0ff32006a6c9a825fbc8198a4b7261bbd0bddc4","output_index":0,"sequence_number":3} + + ' + - ' + + ' + - 'event: response.web_search_call.searching + + ' + - 'data: {"type":"response.web_search_call.searching","item_id":"ws_0ba19d44d2c0ff32006a6c9a825fbc8198a4b7261bbd0bddc4","output_index":0,"sequence_number":4} + + ' + - ' + + ' + - 'event: response.web_search_call.completed + + ' + - 'data: {"type":"response.web_search_call.completed","item_id":"ws_0ba19d44d2c0ff32006a6c9a825fbc8198a4b7261bbd0bddc4","output_index":0,"sequence_number":5} + + ' + - ' + + ' + - 'event: response.output_item.done + + ' + - 'data: {"type":"response.output_item.done","item":{"id":"ws_0ba19d44d2c0ff32006a6c9a825fbc8198a4b7261bbd0bddc4","type":"web_search_call","status":"completed","action":{"type":"search","queries":["potato"],"query":"potato"}},"output_index":0,"sequence_number":6} + + ' + - ' + + ' + - 'event: response.output_item.added + + ' + - 'data: {"type":"response.output_item.added","item":{"id":"msg_0ba19d44d2c0ff32006a6c9a88f4c081988a8bd964c0afd564","type":"message","status":"in_progress","content":[],"phase":"final_answer","role":"assistant"},"output_index":1,"sequence_number":7} + + ' + - ' + + ' + - 'event: response.content_part.added + + ' + - 'data: {"type":"response.content_part.added","content_index":0,"item_id":"msg_0ba19d44d2c0ff32006a6c9a88f4c081988a8bd964c0afd564","output_index":1,"part":{"type":"output_text","annotations":[],"logprobs":[],"text":""},"sequence_number":8} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","content_index":0,"delta":"Search + results primarily define the potato as an","item_id":"msg_0ba19d44d2c0ff32006a6c9a88f4c081988a8bd964c0afd564","logprobs":[],"obfuscation":"","output_index":1,"sequence_number":9} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","content_index":0,"delta":" edible + starchy tuber,","item_id":"msg_0ba19d44d2c0ff32006a6c9a88f4c081988a8bd964c0afd564","logprobs":[],"obfuscation":"2M76Hrxf9m","output_index":1,"sequence_number":10} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","content_index":0,"delta":" with + links to dictionaries, agricultural resources, recipes, and related topics. + ","item_id":"msg_0ba19d44d2c0ff32006a6c9a88f4c081988a8bd964c0afd564","logprobs":[],"obfuscation":"prDJxkznWI1djo","output_index":1,"sequence_number":11} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","content_index":0,"delta":"([merriam-webster.com](https://www.merriam-webster.com/dictionary/potato?utm_source=openai))","item_id":"msg_0ba19d44d2c0ff32006a6c9a88f4c081988a8bd964c0afd564","logprobs":[],"obfuscation":"BRpy","output_index":1,"sequence_number":12} + + ' + - ' + + ' + - 'event: response.output_text.annotation.added + + ' + - 'data: {"type":"response.output_text.annotation.added","annotation":{"type":"url_citation","end_index":244,"start_index":152,"title":"POTATO + Definition & Meaning - Merriam-Webster","url":"https://www.merriam-webster.com/dictionary/potato?utm_source=openai"},"annotation_index":0,"content_index":0,"item_id":"msg_0ba19d44d2c0ff32006a6c9a88f4c081988a8bd964c0afd564","output_index":1,"sequence_number":13} + + ' + - ' + + ' + - 'event: response.output_text.done + + ' + - 'data: {"type":"response.output_text.done","content_index":0,"item_id":"msg_0ba19d44d2c0ff32006a6c9a88f4c081988a8bd964c0afd564","logprobs":[],"output_index":1,"sequence_number":14,"text":"Search + results primarily define the potato as an edible starchy tuber, with links to + dictionaries, agricultural resources, recipes, and related topics. ([merriam-webster.com](https://www.merriam-webster.com/dictionary/potato?utm_source=openai))"} + + ' + - ' + + ' + - 'event: response.content_part.done + + ' + - 'data: {"type":"response.content_part.done","content_index":0,"item_id":"msg_0ba19d44d2c0ff32006a6c9a88f4c081988a8bd964c0afd564","output_index":1,"part":{"type":"output_text","annotations":[{"type":"url_citation","end_index":244,"start_index":152,"title":"POTATO + Definition & Meaning - Merriam-Webster","url":"https://www.merriam-webster.com/dictionary/potato?utm_source=openai"}],"logprobs":[],"text":"Search + results primarily define the potato as an edible starchy tuber, with links to + dictionaries, agricultural resources, recipes, and related topics. ([merriam-webster.com](https://www.merriam-webster.com/dictionary/potato?utm_source=openai))"},"sequence_number":15} + + ' + - ' + + ' + - 'event: response.output_item.done + + ' + - 'data: {"type":"response.output_item.done","item":{"id":"msg_0ba19d44d2c0ff32006a6c9a88f4c081988a8bd964c0afd564","type":"message","status":"completed","content":[{"type":"output_text","annotations":[{"type":"url_citation","end_index":244,"start_index":152,"title":"POTATO + Definition & Meaning - Merriam-Webster","url":"https://www.merriam-webster.com/dictionary/potato?utm_source=openai"}],"logprobs":[],"text":"Search + results primarily define the potato as an edible starchy tuber, with links to + dictionaries, agricultural resources, recipes, and related topics. ([merriam-webster.com](https://www.merriam-webster.com/dictionary/potato?utm_source=openai))"}],"phase":"final_answer","role":"assistant"},"output_index":1,"sequence_number":16} + + ' + - ' + + ' + - 'event: response.completed + + ' + - 'data: {"type":"response.completed","response":{"id":"resp_0ba19d44d2c0ff32006a6c9a817264819899cdb62d72aec42c","object":"response","created_at":1785502337,"status":"completed","background":false,"completed_at":1785502345,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":1024,"max_tool_calls":null,"model":"gpt-5.6-sol","moderation":null,"output":[{"id":"ws_0ba19d44d2c0ff32006a6c9a825fbc8198a4b7261bbd0bddc4","type":"web_search_call","status":"completed","action":{"type":"search","queries":["potato"],"query":"potato"}},{"id":"msg_0ba19d44d2c0ff32006a6c9a88f4c081988a8bd964c0afd564","type":"message","status":"completed","content":[{"type":"output_text","annotations":[{"type":"url_citation","end_index":244,"start_index":152,"title":"POTATO + Definition & Meaning - Merriam-Webster","url":"https://www.merriam-webster.com/dictionary/potato?utm_source=openai"}],"logprobs":[],"text":"Search + results primarily define the potato as an edible starchy tuber, with links to + dictionaries, agricultural resources, recipes, and related topics. ([merriam-webster.com](https://www.merriam-webster.com/dictionary/potato?utm_source=openai))"}],"phase":"final_answer","role":"assistant"}],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":"24h","reasoning":{"context":"all_turns","effort":"medium","mode":"standard","summary":null},"safety_identifier":null,"service_tier":"default","store":true,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":1}},"tools":[{"type":"web_search_preview","search_content_types":["text"],"search_context_size":"medium","user_location":{"type":"approximate","city":null,"country":"US","region":null,"timezone":null}}],"top_logprobs":0,"top_p":0.98,"truncation":"disabled","usage":{"input_tokens":8749,"input_tokens_details":{"cache_write_tokens":4420,"cached_tokens":0},"output_tokens":85,"output_tokens_details":{"reasoning_tokens":25},"total_tokens":8834},"user":null,"metadata":{}},"sequence_number":17} + + ' + - ' + + ' + status_code: 200 diff --git a/crates/agentic-server-core/tests/support/mod.rs b/crates/agentic-server-core/tests/support/mod.rs index 0c0a99bb..3a2754e1 100644 --- a/crates/agentic-server-core/tests/support/mod.rs +++ b/crates/agentic-server-core/tests/support/mod.rs @@ -44,6 +44,8 @@ pub struct TurnRequest { #[derive(Debug, Deserialize, Default)] pub struct TurnBody { + #[serde(default)] + pub model: Option, #[serde(default)] pub input: Value, #[serde(default = "default_true")] @@ -54,6 +56,8 @@ pub struct TurnBody { pub tools: Vec, #[serde(default)] pub tool_choice: Option, + #[serde(default)] + pub max_output_tokens: Option, } fn default_true() -> bool { diff --git a/crates/agentic-server-core/tests/web_search_tool_test.rs b/crates/agentic-server-core/tests/web_search_tool_test.rs index 5ba3565c..bdb84735 100644 --- a/crates/agentic-server-core/tests/web_search_tool_test.rs +++ b/crates/agentic-server-core/tests/web_search_tool_test.rs @@ -21,6 +21,243 @@ use tokio::sync::{Notify, mpsc}; mod support; +const WEB_SEARCH_CASSETTE_DIR: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/cassettes/web_search"); +const WEB_SEARCH_GATEWAY_MODEL: &str = "Qwen/Qwen3.5-35B-A3B-FP8"; +const WEB_SEARCH_GATEWAY_MODEL_SLUG: &str = "Qwen-Qwen3.5-35B-A3B-FP8"; +const WEB_SEARCH_OPENAI_MODEL: &str = "gpt-5.6"; +const WEB_SEARCH_OPENAI_MODEL_SLUG: &str = "gpt-5.6"; + +fn load_recorded_web_search_pair(streaming: bool) -> (support::Cassette, support::Cassette) { + let mode = if streaming { "streaming" } else { "nonstreaming" }; + let openai = support::load_cassette(&format!( + "{WEB_SEARCH_CASSETTE_DIR}/web-search-openai-reference-{WEB_SEARCH_OPENAI_MODEL_SLUG}-{mode}.yaml" + )); + let gateway = support::load_cassette(&format!( + "{WEB_SEARCH_CASSETTE_DIR}/web-search-gateway-{WEB_SEARCH_GATEWAY_MODEL_SLUG}-{mode}.yaml" + )); + (openai, gateway) +} + +fn recorded_sse_events(turn: &support::Turn) -> Vec { + turn.response + .sse + .as_ref() + .expect("streaming cassette should contain SSE") + .iter() + .flat_map(|entry| entry.lines()) + .filter_map(|line| line.strip_prefix("data: ")) + .filter(|data| *data != "[DONE]") + .map(|data| serde_json::from_str(data).expect("cassette SSE data should be valid JSON")) + .collect() +} + +fn recorded_terminal_output(turn: &support::Turn) -> Vec { + if let Some(body) = &turn.response.body { + assert_eq!(body["status"], "completed"); + return body["output"] + .as_array() + .expect("blocking response should contain output") + .clone(); + } + + let events = recorded_sse_events(turn); + let response = events + .iter() + .find(|event| event["type"] == "response.completed") + .and_then(|event| event.get("response")) + .expect("streaming response should contain response.completed"); + assert_eq!(response["status"], "completed"); + response["output"] + .as_array() + .expect("completed response should contain output") + .clone() +} + +fn assert_recorded_web_search_requests(openai: &support::Cassette, gateway: &support::Cassette, streaming: bool) { + assert_eq!(openai.turns.len(), 1); + assert_eq!(gateway.turns.len(), 1); + let openai = &openai.turns[0].request; + let gateway = &gateway.turns[0].request; + + assert_eq!(openai.path, "/v1/responses"); + assert_eq!(gateway.path, openai.path); + assert_eq!(openai.body.model.as_deref(), Some(WEB_SEARCH_OPENAI_MODEL)); + assert_eq!(gateway.body.model.as_deref(), Some(WEB_SEARCH_GATEWAY_MODEL)); + assert_eq!(gateway.body.input, openai.body.input); + assert_eq!(gateway.body.store, openai.body.store); + assert_eq!(gateway.body.stream, streaming); + assert_eq!(gateway.body.stream, openai.body.stream); + assert_eq!(gateway.body.tools, openai.body.tools); + assert_eq!( + gateway.body.tools, + vec![serde_json::json!({"type": "web_search_preview"})] + ); + assert_eq!(gateway.body.tool_choice, openai.body.tool_choice); + assert_eq!(gateway.body.tool_choice, Some(serde_json::json!("auto"))); + assert_eq!(gateway.body.max_output_tokens, openai.body.max_output_tokens); + assert_eq!(gateway.body.max_output_tokens, Some(1024)); +} + +fn assert_recorded_web_search_output(output: &[serde_json::Value]) -> &serde_json::Value { + assert!( + !output + .iter() + .any(|item| item["type"] == "function_call" && item["name"] == "web_search"), + "internal web_search function calls must not appear in public output" + ); + let public_types: Vec<&str> = output + .iter() + .filter_map(|item| item["type"].as_str()) + .filter(|item_type| *item_type != "reasoning") + .collect(); + assert_eq!(public_types, ["web_search_call", "message"]); + + let call = output + .iter() + .find(|item| item["type"] == "web_search_call") + .expect("output should contain web_search_call"); + assert!(call["id"].as_str().is_some_and(|id| id.starts_with("ws_"))); + assert_eq!(call["status"], "completed"); + assert_eq!(call["action"]["type"], "search"); + assert_eq!(call["action"]["query"], "potato"); + assert!( + output + .iter() + .any(|item| item["type"] == "message" && item["status"] == "completed"), + "output should contain a completed assistant message" + ); + call +} + +fn assert_matching_recorded_web_search_action(openai_call: &serde_json::Value, gateway_call: &serde_json::Value) { + let openai_action = &openai_call["action"]; + let gateway_action = &gateway_call["action"]; + + assert_eq!(gateway_action["type"], openai_action["type"]); + assert_eq!(gateway_action["query"], openai_action["query"]); + assert_eq!(openai_action["queries"], serde_json::json!(["potato"])); + + let gateway_sources = gateway_action["sources"] + .as_array() + .expect("gateway search action should contain resolved sources"); + assert!(!gateway_sources.is_empty()); + assert!( + gateway_sources + .iter() + .all(|source| { source["url"].as_str().is_some_and(|url| url.starts_with("https://")) }) + ); +} + +fn assert_recorded_web_search_lifecycle(turn: &support::Turn) -> String { + let events = recorded_sse_events(turn); + let expected_types = [ + "response.output_item.added", + "response.web_search_call.in_progress", + "response.web_search_call.searching", + "response.web_search_call.completed", + "response.output_item.done", + ]; + let lifecycle: Vec<&serde_json::Value> = events + .iter() + .filter(|event| { + event["type"] + .as_str() + .is_some_and(|event_type| event_type.starts_with("response.web_search_call.")) + || event["item"]["type"] == "web_search_call" + }) + .collect(); + assert_eq!( + lifecycle + .iter() + .filter_map(|event| event["type"].as_str()) + .collect::>(), + expected_types + ); + + let item_ids: Vec<&str> = lifecycle + .iter() + .map(|event| { + event["item_id"] + .as_str() + .or_else(|| event["item"]["id"].as_str()) + .expect("web-search lifecycle event should contain an item id") + }) + .collect(); + assert!(item_ids[0].starts_with("ws_")); + assert!(item_ids.iter().all(|item_id| *item_id == item_ids[0])); + + let output_indexes: Vec = lifecycle + .iter() + .map(|event| { + event["output_index"] + .as_u64() + .expect("web-search lifecycle event should contain output_index") + }) + .collect(); + assert!( + output_indexes + .iter() + .all(|output_index| *output_index == output_indexes[0]) + ); + + let sequence_numbers: Vec = events + .iter() + .map(|event| { + event["sequence_number"] + .as_u64() + .expect("recorded event should contain sequence_number") + }) + .collect(); + assert_eq!( + sequence_numbers, + (0..u64::try_from(events.len()).unwrap()).collect::>() + ); + + item_ids[0].to_owned() +} + +#[test] +fn recorded_web_search_nonstreaming_matches_openai_contract() { + let (openai, gateway) = load_recorded_web_search_pair(false); + assert_recorded_web_search_requests(&openai, &gateway, false); + + let openai_output = recorded_terminal_output(&openai.turns[0]); + let gateway_output = recorded_terminal_output(&gateway.turns[0]); + assert!( + openai_output.iter().any(|item| item["type"] == "reasoning"), + "reasoning-capable OpenAI reference should contain a reasoning item" + ); + assert!( + gateway_output.iter().any(|item| item["type"] == "reasoning"), + "gateway recording should contain a reasoning item" + ); + let openai_call = assert_recorded_web_search_output(&openai_output); + let gateway_call = assert_recorded_web_search_output(&gateway_output); + assert_eq!(gateway_call["status"], openai_call["status"]); + assert_matching_recorded_web_search_action(openai_call, gateway_call); +} + +#[test] +fn recorded_web_search_streaming_matches_openai_contract() { + let (openai, gateway) = load_recorded_web_search_pair(true); + assert_recorded_web_search_requests(&openai, &gateway, true); + + let openai_id = assert_recorded_web_search_lifecycle(&openai.turns[0]); + let gateway_id = assert_recorded_web_search_lifecycle(&gateway.turns[0]); + let openai_output = recorded_terminal_output(&openai.turns[0]); + let gateway_output = recorded_terminal_output(&gateway.turns[0]); + assert!( + gateway_output.iter().any(|item| item["type"] == "reasoning"), + "gateway stream should preserve its reasoning items" + ); + let openai_call = assert_recorded_web_search_output(&openai_output); + let gateway_call = assert_recorded_web_search_output(&gateway_output); + assert_eq!(openai_call["id"], openai_id); + assert_eq!(gateway_call["id"], gateway_id); + assert_eq!(gateway_call["status"], openai_call["status"]); + assert_matching_recorded_web_search_action(openai_call, gateway_call); +} + #[derive(Debug)] struct CapturedSearchRequest { api_key: String,