From f6774ae60d52e3525d8603575c49be82d7dd9364 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Wed, 23 Apr 2025 14:08:26 -0400 Subject: [PATCH] More graceful invalid JSON handling (#29295) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Now we're more tolerant of invalid JSON coming back from the model (possibly because it was incomplete and we're streaming), plus if we do end up with invalid JSON once it has all streamed back, we report what the malformed JSON actually was: Screenshot 2025-04-23 at 1 49 14 PM Release Notes: - N/A --- .../language_models/src/provider/anthropic.rs | 45 ++++++++----------- 1 file changed, 19 insertions(+), 26 deletions(-) diff --git a/crates/language_models/src/provider/anthropic.rs b/crates/language_models/src/provider/anthropic.rs index 6c8c664a26..f255740c03 100644 --- a/crates/language_models/src/provider/anthropic.rs +++ b/crates/language_models/src/provider/anthropic.rs @@ -714,39 +714,32 @@ pub fn map_to_language_model_completion_events( if let Some(tool_use) = state.tool_uses_by_index.get_mut(&index) { tool_use.input_json.push_str(&partial_json); - return Some(( - vec![maybe!({ - Ok(LanguageModelCompletionEvent::ToolUse( + // Try to convert invalid (incomplete) JSON into + // valid JSON that serde can accept, e.g. by closing + // unclosed delimiters. This way, we can update the + // UI with whatever has been streamed back so far. + if let Ok(input) = serde_json::Value::from_str( + &partial_json_fixer::fix_json(&tool_use.input_json), + ) { + return Some(( + vec![Ok(LanguageModelCompletionEvent::ToolUse( LanguageModelToolUse { id: tool_use.id.clone().into(), name: tool_use.name.clone().into(), is_input_complete: false, - input: if tool_use.input_json.is_empty() { - serde_json::Value::Object( - serde_json::Map::default(), - ) - } else { - serde_json::Value::from_str( - // Convert invalid (incomplete) JSON into - // JSON that serde will accept, e.g. by closing - // unclosed delimiters. This way, we can update - // the UI with whatever has been streamed back so far. - &partial_json_fixer::fix_json( - &tool_use.input_json, - ), - ) - .map_err(|err| anyhow!(err))? - }, + input, }, - )) - })], - state, - )); + ))], + state, + )); + } } } }, Event::ContentBlockStop { index } => { if let Some(tool_use) = state.tool_uses_by_index.remove(&index) { + let input_json = tool_use.input_json.trim(); + return Some(( vec![maybe!({ Ok(LanguageModelCompletionEvent::ToolUse( @@ -754,15 +747,15 @@ pub fn map_to_language_model_completion_events( id: tool_use.id.into(), name: tool_use.name.into(), is_input_complete: true, - input: if tool_use.input_json.is_empty() { + input: if input_json.is_empty() { serde_json::Value::Object( serde_json::Map::default(), ) } else { serde_json::Value::from_str( - &tool_use.input_json, + input_json ) - .map_err(|err| anyhow!(err))? + .map_err(|err| anyhow!("Error parsing tool call input JSON: {err:?} - JSON string was: {input_json:?}"))? }, }, ))