mcp_langbase_reasoning/modes/
mod.rs1mod auto;
18mod backtracking;
19mod core;
20mod decision;
21mod detection;
22mod divergent;
23mod evidence;
24mod got;
25mod linear;
26mod reflection;
27mod tree;
28
29pub use auto::*;
30pub use backtracking::*;
31pub use core::*;
32pub use decision::*;
33pub use detection::*;
34pub use divergent::*;
35pub use evidence::*;
36pub use got::*;
37pub use linear::*;
38pub use reflection::*;
39pub use tree::*;
40
41use serde::{Deserialize, Serialize};
42use tracing::warn;
43
44pub(crate) fn serialize_for_log<T: serde::Serialize>(
54 value: &T,
55 context: &str,
56) -> serde_json::Value {
57 serde_json::to_value(value).unwrap_or_else(|e| {
58 warn!(
59 error = %e,
60 context = %context,
61 "Failed to serialize value for invocation log"
62 );
63 serde_json::json!({
64 "serialization_error": e.to_string(),
65 "context": context
66 })
67 })
68}
69
70pub(crate) fn extract_json_from_completion(completion: &str) -> Result<&str, String> {
80 let trimmed = completion.trim();
82 if trimmed.starts_with('{') || trimmed.starts_with('[') {
83 return Ok(trimmed);
84 }
85
86 if completion.contains("```json") {
88 return completion
89 .split("```json")
90 .nth(1)
91 .and_then(|s| s.split("```").next())
92 .map(|s| s.trim())
93 .filter(|s| !s.is_empty())
94 .ok_or_else(|| "Found ```json block but content was empty or malformed".to_string());
95 }
96
97 if completion.contains("```") {
99 return completion
100 .split("```")
101 .nth(1)
102 .map(|s| s.trim())
103 .filter(|s| !s.is_empty())
104 .ok_or_else(|| "Found ``` block but content was empty or malformed".to_string());
105 }
106
107 Err(format!(
108 "No JSON found in response. First 100 chars: '{}'",
109 completion.chars().take(100).collect::<String>()
110 ))
111}
112
113#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
115#[serde(rename_all = "snake_case")]
116pub enum ReasoningMode {
117 Linear,
119 Tree,
121 Divergent,
123 Reflection,
125 Backtracking,
127 Auto,
129 Got,
131 Decision,
133 Evidence,
135}
136
137impl ReasoningMode {
138 pub fn as_str(&self) -> &'static str {
140 match self {
141 ReasoningMode::Linear => "linear",
142 ReasoningMode::Tree => "tree",
143 ReasoningMode::Divergent => "divergent",
144 ReasoningMode::Reflection => "reflection",
145 ReasoningMode::Backtracking => "backtracking",
146 ReasoningMode::Auto => "auto",
147 ReasoningMode::Got => "got",
148 ReasoningMode::Decision => "decision",
149 ReasoningMode::Evidence => "evidence",
150 }
151 }
152}
153
154impl std::fmt::Display for ReasoningMode {
155 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
156 write!(f, "{}", self.as_str())
157 }
158}
159
160impl std::str::FromStr for ReasoningMode {
161 type Err = String;
162
163 fn from_str(s: &str) -> Result<Self, Self::Err> {
164 match s.to_lowercase().as_str() {
165 "linear" => Ok(ReasoningMode::Linear),
166 "tree" => Ok(ReasoningMode::Tree),
167 "divergent" => Ok(ReasoningMode::Divergent),
168 "reflection" => Ok(ReasoningMode::Reflection),
169 "backtracking" => Ok(ReasoningMode::Backtracking),
170 "auto" => Ok(ReasoningMode::Auto),
171 "got" => Ok(ReasoningMode::Got),
172 "decision" => Ok(ReasoningMode::Decision),
173 "evidence" => Ok(ReasoningMode::Evidence),
174 _ => Err(format!("Unknown reasoning mode: {}", s)),
175 }
176 }
177}
178
179#[cfg(test)]
180mod tests {
181 use super::*;
182
183 #[test]
184 fn test_reasoning_mode_as_str() {
185 assert_eq!(ReasoningMode::Linear.as_str(), "linear");
186 assert_eq!(ReasoningMode::Tree.as_str(), "tree");
187 assert_eq!(ReasoningMode::Divergent.as_str(), "divergent");
188 assert_eq!(ReasoningMode::Reflection.as_str(), "reflection");
189 assert_eq!(ReasoningMode::Backtracking.as_str(), "backtracking");
190 assert_eq!(ReasoningMode::Auto.as_str(), "auto");
191 assert_eq!(ReasoningMode::Got.as_str(), "got");
192 assert_eq!(ReasoningMode::Decision.as_str(), "decision");
193 assert_eq!(ReasoningMode::Evidence.as_str(), "evidence");
194 }
195
196 #[test]
197 fn test_reasoning_mode_display() {
198 assert_eq!(format!("{}", ReasoningMode::Linear), "linear");
199 assert_eq!(format!("{}", ReasoningMode::Tree), "tree");
200 assert_eq!(format!("{}", ReasoningMode::Divergent), "divergent");
201 assert_eq!(format!("{}", ReasoningMode::Reflection), "reflection");
202 assert_eq!(format!("{}", ReasoningMode::Backtracking), "backtracking");
203 assert_eq!(format!("{}", ReasoningMode::Auto), "auto");
204 assert_eq!(format!("{}", ReasoningMode::Got), "got");
205 assert_eq!(format!("{}", ReasoningMode::Decision), "decision");
206 assert_eq!(format!("{}", ReasoningMode::Evidence), "evidence");
207 }
208
209 #[test]
210 fn test_reasoning_mode_from_str_valid() {
211 assert_eq!(
212 "linear".parse::<ReasoningMode>().unwrap(),
213 ReasoningMode::Linear
214 );
215 assert_eq!(
216 "tree".parse::<ReasoningMode>().unwrap(),
217 ReasoningMode::Tree
218 );
219 assert_eq!(
220 "divergent".parse::<ReasoningMode>().unwrap(),
221 ReasoningMode::Divergent
222 );
223 assert_eq!(
224 "reflection".parse::<ReasoningMode>().unwrap(),
225 ReasoningMode::Reflection
226 );
227 assert_eq!(
228 "backtracking".parse::<ReasoningMode>().unwrap(),
229 ReasoningMode::Backtracking
230 );
231 assert_eq!(
232 "auto".parse::<ReasoningMode>().unwrap(),
233 ReasoningMode::Auto
234 );
235 assert_eq!("got".parse::<ReasoningMode>().unwrap(), ReasoningMode::Got);
236 assert_eq!(
237 "decision".parse::<ReasoningMode>().unwrap(),
238 ReasoningMode::Decision
239 );
240 assert_eq!(
241 "evidence".parse::<ReasoningMode>().unwrap(),
242 ReasoningMode::Evidence
243 );
244 }
245
246 #[test]
247 fn test_reasoning_mode_from_str_case_insensitive() {
248 assert_eq!(
249 "LINEAR".parse::<ReasoningMode>().unwrap(),
250 ReasoningMode::Linear
251 );
252 assert_eq!(
253 "Tree".parse::<ReasoningMode>().unwrap(),
254 ReasoningMode::Tree
255 );
256 assert_eq!(
257 "DIVERGENT".parse::<ReasoningMode>().unwrap(),
258 ReasoningMode::Divergent
259 );
260 }
261
262 #[test]
263 fn test_reasoning_mode_from_str_invalid() {
264 let result = "invalid".parse::<ReasoningMode>();
265 assert!(result.is_err());
266 assert_eq!(result.unwrap_err(), "Unknown reasoning mode: invalid");
267 }
268
269 #[test]
270 fn test_reasoning_mode_equality() {
271 assert_eq!(ReasoningMode::Linear, ReasoningMode::Linear);
272 assert_ne!(ReasoningMode::Linear, ReasoningMode::Tree);
273 }
274
275 #[test]
276 fn test_reasoning_mode_is_copy() {
277 let mode = ReasoningMode::Divergent;
278 let copied = mode; assert_eq!(mode, copied);
280 }
281
282 #[test]
283 fn test_reasoning_mode_copy() {
284 let mode = ReasoningMode::Auto;
285 let copied = mode; assert_eq!(mode, copied);
287 }
288
289 #[test]
294 fn test_extract_json_raw_object() {
295 let result = extract_json_from_completion(r#"{"key": "value"}"#);
296 assert_eq!(result.unwrap(), r#"{"key": "value"}"#);
297 }
298
299 #[test]
300 fn test_extract_json_raw_array() {
301 let result = extract_json_from_completion(r#"[1, 2, 3]"#);
302 assert_eq!(result.unwrap(), r#"[1, 2, 3]"#);
303 }
304
305 #[test]
306 fn test_extract_json_with_whitespace() {
307 let result = extract_json_from_completion(" \n {\"key\": \"value\"} \n ");
308 assert_eq!(result.unwrap(), r#"{"key": "value"}"#);
309 }
310
311 #[test]
312 fn test_extract_json_from_json_code_block() {
313 let input = "Here is the response:\n```json\n{\"result\": true}\n```\nDone.";
314 let result = extract_json_from_completion(input);
315 assert_eq!(result.unwrap(), r#"{"result": true}"#);
316 }
317
318 #[test]
319 fn test_extract_json_from_plain_code_block() {
320 let input = "Response:\n```\n{\"data\": 123}\n```";
321 let result = extract_json_from_completion(input);
322 assert_eq!(result.unwrap(), r#"{"data": 123}"#);
323 }
324
325 #[test]
326 fn test_extract_json_empty_json_block() {
327 let input = "```json\n\n```";
328 let result = extract_json_from_completion(input);
329 assert!(result.is_err());
330 assert!(result.unwrap_err().contains("empty or malformed"));
331 }
332
333 #[test]
334 fn test_extract_json_empty_plain_block() {
335 let input = "```\n \n```";
336 let result = extract_json_from_completion(input);
337 assert!(result.is_err());
338 assert!(result.unwrap_err().contains("empty or malformed"));
339 }
340
341 #[test]
342 fn test_extract_json_no_json_found() {
343 let input = "This is just plain text without any JSON.";
344 let result = extract_json_from_completion(input);
345 assert!(result.is_err());
346 assert!(result.unwrap_err().contains("No JSON found"));
347 }
348
349 #[test]
350 fn test_extract_json_truncates_long_error_message() {
351 let input = "a".repeat(200);
352 let result = extract_json_from_completion(&input);
353 assert!(result.is_err());
354 let err = result.unwrap_err();
355 assert!(err.contains("First 100 chars"));
356 assert!(err.len() < 200);
358 }
359
360 #[test]
365 fn test_serialize_for_log_success() {
366 let value = serde_json::json!({"test": "value", "number": 42});
367 let result = serialize_for_log(&value, "test_context");
368 assert_eq!(result["test"], "value");
369 assert_eq!(result["number"], 42);
370 }
371
372 #[test]
373 fn test_serialize_for_log_simple_types() {
374 assert_eq!(serialize_for_log(&"hello", "string"), "hello");
375 assert_eq!(serialize_for_log(&42i32, "int"), 42);
376 assert_eq!(serialize_for_log(&true, "bool"), true);
377 }
378
379 #[test]
380 fn test_serialize_for_log_vec() {
381 let vec = vec![1, 2, 3];
382 let result = serialize_for_log(&vec, "vec");
383 assert_eq!(result, serde_json::json!([1, 2, 3]));
384 }
385}