{"record":{"id":"d900c2d4d43e757c","repo":"dotnet/machinelearning","slug":"failed-to-deserialize-tool-calls","errorCode":null,"errorMessage":"Failed to deserialize tool calls.","messagePattern":"Failed to deserialize tool calls\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/Microsoft.ML.GenAI.Mistral/MistralCausalLMAgent.cs","lineNumber":149,"sourceCode":"        }\n    }\n\n    private class MistralToolCall\n    {\n        [JsonPropertyName(\"name\")]\n        public string? Name { get; set; }\n\n        [JsonPropertyName(\"arguments\")]\n        public JsonObject? Arguments { get; set; }\n    }\n\n    private ToolCallMessage ParseAsToolCallMessage(string content)\n    {\n        var json = content.Substring(\"[TOOL_CALLS]\".Length).Trim();\n\n        // the json string should be a list of tool call messages\n        // e.g. [{\"name\": \"get_current_weather\", \"parameters\": {\"location\": \"Seattle\"}}]\n        var mistralToolCalls = JsonSerializer.Deserialize<List<MistralToolCall>>(json) ?? throw new InvalidOperationException(\"Failed to deserialize tool calls.\");\n        var toolCalls = mistralToolCalls\n            .Select(tc => new ToolCall(tc.Name!, JsonSerializer.Serialize(tc.Arguments)) { ToolCallId = this.GenerateToolCallId() });\n\n        return new ToolCallMessage(toolCalls, from: this.Name);\n    }\n\n    /// <summary>\n    /// 9 random alphanumeric characters\n    /// </summary>\n    private string GenerateToolCallId(int length = 9)\n    {\n        const string chars = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\";\n        var random = new Random();\n        return new string(Enumerable.Repeat(chars, length)\n          .Select(s => s[random.Next(s.Length)]).ToArray());\n    }\n}\n","sourceCodeStart":131,"sourceCodeEnd":167,"githubUrl":"https://github.com/dotnet/machinelearning/blob/7b76e69cf964daeca3f1377af6bc5543284d56c6/src/Microsoft.ML.GenAI.Mistral/MistralCausalLMAgent.cs#L131-L167","documentation":"ParseAsToolCallMessage strips the \"[TOOL_CALLS]\" prefix and JSON-deserializes the remainder into List<MistralToolCall>; JsonSerializer.Deserialize can return null (or the JSON shape mismatches) and the code throws InvalidOperationException. This indicates the model's tool-call output was not in the expected Mistral format.","triggerScenarios":"Model output starting with \"[TOOL_CALLS]\" but whose remainder is not a JSON array of {name, arguments} objects — e.g. malformed/truncated JSON, a JSON object instead of a list, or different field names.","commonSituations":"Model hallucinating a tool-call format from its chat template; output truncated by maxLen or stop sequences mid-JSON; using a non-Mistral finetune with a different tool-call syntax.","solutions":["Increase maxLen / adjust stop sequences so the JSON tool call is not truncated.","Validate/log the raw output before parsing; if the shape differs (object not array, wrong keys), adapt parsing or update the chat template/prompt to enforce Mistral's tool-call format.","Wrap the parse in try-catch on JsonException and InvalidOperation to fall back to a plain assistant message."],"exampleFix":"// before\nvar msg = await agent.GenerateReplyAsync(history, tools: tools); // throws on malformed JSON\n// after\ntry { var msg = await agent.GenerateReplyAsync(history, tools: tools); }\ncatch (InvalidOperationException) { /* fall back: re-prompt model to emit valid [TOOL_CALLS] JSON */ }","handlingStrategy":"try-catch","validationCode":"var json = content.StartsWith(\"[TOOL_CALLS]\") ? content.Substring(12).Trim() : null;\nbool IsParseableToolCalls(string? json) => json is not null && json.StartsWith(\"[\") && json.EndsWith(\"]\");","typeGuard":null,"tryCatchPattern":"try { return agent.GenerateReplyAsync(history, options: opts); } catch (InvalidOperationException ex) when (ex.Message.Contains(\"Failed to deserialize tool calls\")) { /* treat output as plain text or re-prompt */ }","preventionTips":["Keep maxLen high enough that tool-call JSON is never truncated","Log the raw model output when tool-calling fails","Keep the chat template strict about the [TOOL_CALLS] JSON array format","Guard against finetuned models that use different tool-call syntax"],"tags":["json","tool-calls","mistral"],"backgroundTag":"json-unmarshal-failed","analyzedSha":"7b76e69cf964daeca3f1377af6bc5543284d56c6","analyzedAt":"2026-09-11T12:35:38.930Z","contentChangedAt":"2026-09-11T12:35:38.930Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}