{"record":{"id":"1ea1ce5f50983c64","repo":"spring-projects/spring-ai","slug":"unknown-tool-choice-type","errorCode":null,"errorMessage":"Unknown tool_choice type: ","messagePattern":"Unknown tool_choice type: ","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatModel.java","lineNumber":974,"sourceCode":"\t\tString type = node.get(\"type\").asString();\n\t\tswitch (type) {\n\t\t\tcase \"function\":\n\t\t\t\tString functionName = node.get(\"function\").get(\"name\").asString();\n\t\t\t\tChatCompletionNamedToolChoice.Function func = ChatCompletionNamedToolChoice.Function.builder()\n\t\t\t\t\t.name(functionName)\n\t\t\t\t\t.build();\n\t\t\t\tChatCompletionNamedToolChoice named = ChatCompletionNamedToolChoice.builder().function(func).build();\n\t\t\t\treturn ChatCompletionToolChoiceOption.ofNamedToolChoice(named);\n\t\t\tcase \"auto\":\n\t\t\t\t// There is a built-in “auto” option — but how to get it depends on SDK\n\t\t\t\t// version\n\t\t\t\treturn ChatCompletionToolChoiceOption.ofAuto(ChatCompletionToolChoiceOption.Auto.AUTO);\n\t\t\tcase \"required\":\n\t\t\t\treturn ChatCompletionToolChoiceOption.ofAuto(ChatCompletionToolChoiceOption.Auto.REQUIRED);\n\t\t\tcase \"none\":\n\t\t\t\treturn ChatCompletionToolChoiceOption.ofAuto(ChatCompletionToolChoiceOption.Auto.NONE);\n\t\t\tdefault:\n\t\t\t\tthrow new IllegalArgumentException(\"Unknown tool_choice type: \" + type);\n\t\t}\n\t}\n\n\tprivate String fromAudioData(Object audioData) {\n\t\tif (audioData instanceof byte[] bytes) {\n\t\t\treturn Base64.getEncoder().encodeToString(bytes);\n\t\t}\n\t\tthrow new IllegalArgumentException(\"Unsupported audio data type: \" + audioData.getClass().getSimpleName());\n\t}\n\n\tprivate String fromMediaData(org.springframework.util.MimeType mimeType, Object mediaContentData) {\n\t\tif (mediaContentData instanceof byte[] bytes) {\n\t\t\t// Assume the bytes are an image. So, convert the bytes to a base64 encoded\n\t\t\t// following the prefix pattern.\n\t\t\treturn String.format(\"data:%s;base64,%s\", mimeType.toString(), Base64.getEncoder().encodeToString(bytes));\n\t\t}\n\t\telse if (mediaContentData instanceof String text) {\n\t\t\t// Assume the text is a URLs or a base64 encoded image prefixed by the user.","sourceCodeStart":956,"sourceCodeEnd":992,"githubUrl":"https://github.com/spring-projects/spring-ai/blob/98a7beda4f29d80a71c5837eb4053b03a93a46f7/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatModel.java#L956-L992","documentation":"parseToolChoice maps a parsed toolChoice JSON node to the OpenAI SDK's ChatCompletionToolChoiceOption. It reads the \"type\" field and supports only \"function\", \"auto\", \"required\", and \"none\"; any other value hits the default branch and throws this IllegalArgumentException. It runs when toolChoice was supplied as a JSON string (not a keyword or SDK object) and the JSON parsed successfully but its type discriminator is not one of the four known values.","triggerScenarios":"Setting OpenAiChatOptions.toolChoice to a JSON string whose \"type\" field is something else — e.g. {\"type\":\"named_tool_choice\",...}, {\"type\":\"any\"} (Anthropic-style), {\"type\":\"tool\"} — or omitting structure so node.get(\"type\") yields an unexpected value, then invoking the model so createRequest parses it.","commonSituations":"Copying tool_choice JSON from another provider's SDK (Anthropic uses {\"type\":\"any\"} or {\"type\":\"tool\",\"name\":...}); writing a custom discriminator like \"specific_function\"; typos such as \"autuo\" or \"Auto\" (case-sensitive); embedding the function name at the wrong level instead of inside function.name.","solutions":["Use one of the supported type values in the JSON: \"function\", \"auto\", \"required\", or \"none\" (all lowercase).","For forcing a specific function, use exactly {\"type\":\"function\",\"function\":{\"name\":\"yourFunction\"}} — the function name goes in function.name, not in type.","Translate values from other providers: Anthropic \"any\" → \"required\", \"tool\" → {\"type\":\"function\",\"function\":{\"name\":...}}.","Better: build a ChatCompletionToolChoiceOption with the OpenAI SDK (ofAuto / ofNamedToolChoice) and set that as toolChoice to skip JSON parsing altogether."],"exampleFix":"// before\noptions.setToolChoice(\"{\\\"type\\\":\\\"tool\\\",\\\"name\\\":\\\"get_weather\\\"}\"); // Anthropic-style, unknown type\n\n// after\noptions.setToolChoice(\"{\\\"type\\\":\\\"function\\\",\\\"function\\\":{\\\"name\\\":\\\"get_weather\\\"}}\");","handlingStrategy":"validation","validationCode":"public static void validateToolChoiceJson(String json) throws Exception {\n    com.fasterxml.jackson.databind.JsonNode node =\n        org.springframework.ai.converter.JacksonUtils.getDefaultJsonMapper().readTree(json);\n    String type = node.get(\"type\").asString();\n    if (!java.util.Set.of(\"function\", \"auto\", \"required\", \"none\").contains(type)) {\n        throw new IllegalArgumentException(\"Unsupported tool_choice type: \" + type);\n    }\n}","typeGuard":"boolean hasKnownToolChoiceType(String json) {\n    try {\n        String t = JacksonUtils.getDefaultJsonMapper().readTree(json).get(\"type\").asString();\n        return java.util.Set.of(\"function\", \"auto\", \"required\", \"none\").contains(t);\n    } catch (Exception e) { return false; }\n}","tryCatchPattern":"try {\n    return chatModel.call(prompt);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage() != null && e.getMessage().startsWith(\"Unknown tool_choice type:\")) {\n        logger.warn(\"Unsupported tool_choice type, falling back to auto: {}\", e.getMessage());\n        prompt.getOptions().setToolChoice(ChatCompletionToolChoiceOption\n            .ofAuto(ChatCompletionToolChoiceOption.Auto.AUTO));\n        return chatModel.call(prompt);\n    }\n    throw e;\n}","preventionTips":["Use only type values \"function\", \"auto\", \"required\", \"none\" in toolChoice JSON (lowercase, exact).","Translate provider-specific variants before use: Anthropic \"any\" → \"required\", \"tool\" → OpenAI's function form.","Put the function name in function.name, never as the type discriminator.","Build ChatCompletionToolChoiceOption via the SDK instead of hand-written JSON to eliminate this class of error."],"tags":["openai","spring-ai","tool-choice","invalid-enum","illegal-argument"],"backgroundTag":"invalid-enum-value","analyzedSha":"98a7beda4f29d80a71c5837eb4053b03a93a46f7","analyzedAt":"2026-09-11T14:15:49.441Z","contentChangedAt":"2026-09-11T14:15:49.441Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}