{"record":{"id":"22decde6af9dd7dd","repo":"spring-projects/spring-ai","slug":"cannot-deserialize-thinkoption-from-token","errorCode":null,"errorMessage":"Cannot deserialize ThinkOption from token: ","messagePattern":"Cannot deserialize ThinkOption from token: ","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/api/ThinkOption.java","lineNumber":92,"sourceCode":"\t */\n\tclass ThinkOptionDeserializer extends ValueDeserializer<ThinkOption> {\n\n\t\t@Override\n\t\tpublic @Nullable ThinkOption deserialize(JsonParser p, DeserializationContext ctxt) {\n\t\t\tJsonToken token = p.currentToken();\n\t\t\tif (token == JsonToken.VALUE_TRUE) {\n\t\t\t\treturn ThinkBoolean.ENABLED;\n\t\t\t}\n\t\t\telse if (token == JsonToken.VALUE_FALSE) {\n\t\t\t\treturn ThinkBoolean.DISABLED;\n\t\t\t}\n\t\t\telse if (token == JsonToken.VALUE_STRING) {\n\t\t\t\treturn new ThinkLevel(p.getValueAsString());\n\t\t\t}\n\t\t\telse if (token == JsonToken.VALUE_NULL) {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\tthrow new IllegalStateException(\"Cannot deserialize ThinkOption from token: \" + token);\n\t\t}\n\n\t}\n\n\t/**\n\t * Boolean-style think option for models that support simple enable/disable. Supported\n\t * by Qwen 3, DeepSeek-v3.1, and DeepSeek R1 models.\n\t *\n\t * @param enabled whether thinking is enabled\n\t */\n\trecord ThinkBoolean(boolean enabled) implements ThinkOption {\n\n\t\t/**\n\t\t * Constant for enabled thinking.\n\t\t */\n\t\tpublic static final ThinkBoolean ENABLED = new ThinkBoolean(true);\n\n\t\t/**","sourceCodeStart":74,"sourceCodeEnd":110,"githubUrl":"https://github.com/spring-projects/spring-ai/blob/98a7beda4f29d80a71c5837eb4053b03a93a46f7/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/api/ThinkOption.java#L74-L110","documentation":"The ThinkOption custom Jackson deserializer only accepts JSON boolean tokens (true/false -> ThinkBoolean.ENABLED/DISABLED), string tokens (-> ThinkLevel), and null. Any other JSON token — a number, array, or object — reaches the final throw, so the library raises an IllegalStateException because the 'think' field in an Ollama request/response has an unsupported JSON shape.","triggerScenarios":"Deserializing a payload where the 'think' field is a JSON number (e.g. think: 1), array, or object instead of a boolean or a string like \"low\"/\"medium\"/\"high\"; typically happens when a hand-crafted request body, a config file, or a proxied Ollama response supplies a numeric or structured think value.","commonSituations":"Hand-editing request JSON and writing think: 1 instead of true; sending an Ollama API response captured from a different Ollama version with a differently shaped think field; YAML/JSON config where an unquoted low is parsed as something other than a string; wrapping/serializing ThinkOption through a middleware that converts booleans to 0/1.","solutions":["Change the think value to a JSON boolean (true/false) for Qwen 3, DeepSeek-v3.1, or DeepSeek R1 models.","For GPT-OSS, change the value to a quoted string \"low\", \"medium\", or \"high\" (or use ThinkLevel.LOW/MEDIUM/HIGH constants).","If the value comes from config, quote it or normalize it before building the request.","If the payload comes from the server/proxy, inspect the raw JSON to see the actual token type and fix the producer."],"exampleFix":"// before\n{\"model\":\"gpt-oss\",\"think\":1}\n// after\n{\"model\":\"gpt-oss\",\"think\":\"medium\"}","handlingStrategy":"validation","validationCode":"// Normalize a think config value before it reaches ThinkOption deserialization\nObject raw = config.get(\"think\");\nif (!(raw instanceof Boolean) && !(raw instanceof String s && List.of(\"low\",\"medium\",\"high\").contains(s.toLowerCase())) && raw != null) {\n    throw new IllegalArgumentException(\"think must be boolean, one of low/medium/high, or null; got: \" + raw);\n}","typeGuard":"boolean isValidThinkValue(Object v) {\n    return v == null || v instanceof Boolean\n        || (v instanceof String s && List.of(\"low\", \"medium\", \"high\").contains(s.toLowerCase(Locale.ROOT)));\n}","tryCatchPattern":"try {\n    ThinkOption think = mapper.readValue(json, RequestPayload.class).think();\n} catch (IllegalStateException e) {\n    if (e.getMessage() != null && e.getMessage().startsWith(\"Cannot deserialize ThinkOption\")) {\n        logger.warn(\"Unsupported 'think' JSON token, defaulting to null\", e);\n        think = null;\n    } else throw e;\n}","preventionTips":["Always send think as a JSON boolean (Qwen3/DeepSeek) or quoted string low/medium/high (GPT-OSS).","Quote string values in YAML/JSON config so parsers don't coerce them.","Never pass numeric 0/1 for think; convert numbers to booleans before building requests.","Add a startup config validation step for think-related settings."],"tags":["jackson","deserialization","ollama","json"],"backgroundTag":"json-unmarshal-failed","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"}