Parse `MistralAI` response to `GraphBit` response
(&self, response: MistralAiResponse)
| 122 | |
| 123 | /// Parse `MistralAI` response to `GraphBit` response |
| 124 | fn parse_response(&self, response: MistralAiResponse) -> GraphBitResult<LlmResponse> { |
| 125 | let choice = response.choices.into_iter().next().ok_or_else(|| { |
| 126 | GraphBitError::llm_provider("mistralai", "No choices in response".to_string()) |
| 127 | })?; |
| 128 | |
| 129 | let tool_calls: Vec<LlmToolCall> = choice |
| 130 | .message |
| 131 | .tool_calls |
| 132 | .unwrap_or_default() |
| 133 | .into_iter() |
| 134 | .map(|tc| { |
| 135 | // Production-grade argument parsing with error handling |
| 136 | let parameters = if tc.function.arguments.trim().is_empty() { |
| 137 | serde_json::Value::Object(serde_json::Map::new()) |
| 138 | } else { |
| 139 | match serde_json::from_str(&tc.function.arguments) { |
| 140 | Ok(params) => params, |
| 141 | Err(e) => { |
| 142 | tracing::warn!( |
| 143 | "Failed to parse tool call arguments for {}: {e}. Arguments: '{}'", |
| 144 | tc.function.name, |
| 145 | tc.function.arguments |
| 146 | ); |
| 147 | // Try to create a simple object with the raw arguments |
| 148 | serde_json::json!({ "raw_arguments": tc.function.arguments }) |
| 149 | } |
| 150 | } |
| 151 | }; |
| 152 | |
| 153 | LlmToolCall { |
| 154 | id: tc.id, |
| 155 | name: tc.function.name, |
| 156 | parameters, |
| 157 | } |
| 158 | }) |
| 159 | .collect(); |
| 160 | |
| 161 | // Handle content - provide default message for tool calls |
| 162 | let mut content = choice.message.content.unwrap_or_default(); |
| 163 | if content.trim().is_empty() && !tool_calls.is_empty() { |
| 164 | content = "I'll help you with that using the available tools.".to_string(); |
| 165 | } |
| 166 | |
| 167 | let finish_reason = match choice.finish_reason.as_deref() { |
| 168 | Some("stop") => FinishReason::Stop, |
| 169 | Some("length") => FinishReason::Length, |
| 170 | Some("tool_calls") => FinishReason::ToolCalls, |
| 171 | Some("content_filter") => FinishReason::ContentFilter, |
| 172 | Some(other) => FinishReason::Other(other.to_string()), |
| 173 | None => FinishReason::Stop, |
| 174 | }; |
| 175 | |
| 176 | let usage = LlmUsage::new( |
| 177 | response.usage.prompt_tokens, |
| 178 | response.usage.completion_tokens, |
| 179 | ); |
| 180 | |
| 181 | Ok(LlmResponse::new(content, &self.model) |
no test coverage detected