Ordered raw strings to mine for the structured object, most authoritative first: tool-call arguments, then text content, then the reasoning channel. The reasoning fallback is the crux of the cross-model fix: reasoning models (GLM/zhipu, DeepSeek-R1, kimi…) frequently emit the final object inside `reasoning` with `content` empty and no tool call. Earlier extraction only looked at the tool call / t
(message: &super::Message, mode: StructuredMode)
| 1016 | /// at the tool call / text, so those models yielded an empty string and the whole |
| 1017 | /// generate_object failed even though a perfectly good object was produced. |
| 1018 | fn extract_raw_candidates(message: &super::Message, mode: StructuredMode) -> Vec<String> { |
| 1019 | let mut out: Vec<String> = Vec::new(); |
| 1020 | if mode == StructuredMode::Tool { |
| 1021 | if let Some(call) = message.tool_calls().first() { |
| 1022 | push_candidate( |
| 1023 | &mut out, |
| 1024 | serde_json::to_string(&call.args).unwrap_or_default(), |
| 1025 | ); |
| 1026 | } |
| 1027 | } |
| 1028 | push_candidate(&mut out, message.text()); |
| 1029 | if let Some(reasoning) = message.reasoning_content.as_deref() { |
| 1030 | push_candidate(&mut out, reasoning.to_string()); |
| 1031 | } |
| 1032 | out |
| 1033 | } |
| 1034 | |
| 1035 | /// Every JSON object/array value mineable from possibly-dirty text, in document order |
| 1036 | /// (direct parse, code fences, then all balanced `{...}` / `[...]`). Deduped. |