smolagents_rs/
local_python_interpreter.rs

1use crate::errors::InterpreterError;
2use crate::tools::AnyTool;
3use anyhow::Result;
4use pyo3::prelude::*;
5use pyo3::types::{PyDict, PyModule, PyTuple};
6use rustpython_parser::{
7    ast::{
8        self,
9        bigint::{BigInt, Sign},
10        Constant, Expr, Operator, Stmt, UnaryOp,
11    },
12    Parse,
13};
14use serde_json::{self, json};
15use std::{any::Any, collections::HashMap};
16
17pub fn get_base_python_tools() -> HashMap<&'static str, &'static str> {
18    [
19        ("print", "custom_print"),
20        ("isinstance", "isinstance"),
21        ("range", "range"),
22        ("float", "float"),
23        ("int", "int"),
24        ("bool", "bool"),
25        ("str", "str"),
26        ("set", "set"),
27        ("list", "list"),
28        ("dict", "dict"),
29        ("tuple", "tuple"),
30        ("round", "round"),
31        ("ceil", "math.ceil"),
32        ("floor", "math.floor"),
33        ("log", "math.log"),
34        ("exp", "math.exp"),
35        ("sin", "math.sin"),
36        ("cos", "math.cos"),
37        ("tan", "math.tan"),
38        ("asin", "math.asin"),
39        ("acos", "math.acos"),
40        ("atan", "math.atan"),
41        ("atan2", "math.atan2"),
42        ("degrees", "math.degrees"),
43        ("radians", "math.radians"),
44        ("pow", "math.pow"),
45        ("sqrt", "math.sqrt"),
46        ("len", "len"),
47        ("sum", "sum"),
48        ("max", "max"),
49        ("min", "min"),
50        ("abs", "abs"),
51        ("enumerate", "enumerate"),
52        ("zip", "zip"),
53        ("reversed", "reversed"),
54        ("sorted", "sorted"),
55        ("all", "all"),
56        ("any", "any"),
57        ("map", "map"),
58        ("filter", "filter"),
59        ("ord", "ord"),
60        ("chr", "chr"),
61        ("next", "next"),
62        ("iter", "iter"),
63        ("divmod", "divmod"),
64        ("callable", "callable"),
65        ("getattr", "getattr"),
66        ("hasattr", "hasattr"),
67        ("setattr", "setattr"),
68        ("issubclass", "issubclass"),
69        ("type", "type"),
70        ("complex", "complex"),
71    ]
72    .iter()
73    .cloned()
74    .collect()
75}
76
77
78
79impl From<PyErr> for InterpreterError {
80    fn from(err: PyErr) -> Self {
81        InterpreterError::RuntimeError(err.to_string())
82    }
83}
84
85#[derive(Clone, Debug)]
86pub enum CustomConstant {
87    Int(BigInt),
88    Float(f64),
89    Str(String),
90    Bool(bool),
91    Tuple(Vec<CustomConstant>),
92    PyObj(PyObject),
93    Dict(Vec<String>, Vec<CustomConstant>),
94}
95
96impl CustomConstant {
97    pub fn float(&self) -> Option<f64> {
98        match self {
99            CustomConstant::Float(f) => Some(*f),
100            _ => None,
101        }
102    }
103    pub fn str(&self) -> String {
104        match self {
105            CustomConstant::Str(s) => s.clone(),
106            CustomConstant::Float(f) => f.to_string(),
107            CustomConstant::Int(i) => i.to_string(),
108            CustomConstant::Tuple(t) => {
109                let mut result = String::new();
110                result.push('[');
111                for (i, item) in t.iter().enumerate() {
112                    if i > 0 {
113                        result.push_str(", ");
114                    }
115                    result.push_str(&item.str());
116                }
117                result.push(']');
118                result
119            }
120            CustomConstant::Dict(keys, values) => {
121                let mut result = String::new();
122                result.push('{');
123                for (i, key) in keys.iter().enumerate() {
124                    if i > 0 {
125                        result.push_str(", ");
126                    }
127                    result.push_str(&format!("'{}': {}", key, values[i].str()));
128                }
129                result.push('}');
130
131                for (i, item) in values.iter().enumerate() {
132                    if i > 0 {
133                        result.push_str(", ");
134                    }
135                    result.push_str(&item.str());
136                }
137                result.push('}');
138                result
139            }
140            CustomConstant::PyObj(obj) => obj.to_string(),
141            CustomConstant::Bool(b) => b.to_string(),
142        }
143    }
144    pub fn tuple(&self) -> Option<Vec<CustomConstant>> {
145        match self {
146            CustomConstant::Tuple(t) => Some(t.clone()),
147            _ => None,
148        }
149    }
150}
151
152impl From<CustomConstant> for Constant {
153    fn from(custom: CustomConstant) -> Self {
154        match custom {
155            CustomConstant::Int(i) => Constant::Int(i),
156            CustomConstant::Float(f) => Constant::Float(f),
157            CustomConstant::Str(s) => Constant::Str(s),
158            CustomConstant::Bool(b) => Constant::Bool(b),
159            CustomConstant::PyObj(obj) => Constant::Str(obj.to_string()),
160            CustomConstant::Tuple(t) => {
161                let tuple_items = t
162                    .iter()
163                    .map(|c| Constant::from(c.clone()))
164                    .collect::<Vec<Constant>>();
165                Constant::Tuple(tuple_items)
166            }
167            CustomConstant::Dict(keys, values) => {
168                let tuple_items = keys
169                    .iter()
170                    .zip(values.iter())
171                    .map(|(k, v)| {
172                        Constant::Tuple(vec![Constant::Str(k.clone()), Constant::from(v.clone())])
173                    })
174                    .collect::<Vec<Constant>>();
175                Constant::Tuple(tuple_items)
176            }
177        }
178    }
179}
180
181impl From<Constant> for CustomConstant {
182    fn from(constant: Constant) -> Self {
183        match constant {
184            Constant::Int(i) => CustomConstant::Int(i),
185            Constant::Float(f) => CustomConstant::Float(f),
186            Constant::Str(s) => CustomConstant::Str(s),
187            Constant::Bool(b) => CustomConstant::Bool(b),
188            Constant::None => CustomConstant::Str("None".to_string()),
189            Constant::Tuple(t) => {
190                CustomConstant::Tuple(t.iter().map(|c| c.clone().into()).collect())
191            }
192            _ => panic!("Unsupported constant type"),
193        }
194    }
195}
196
197impl IntoPy<PyObject> for CustomConstant {
198    fn into_py(self, py: Python<'_>) -> PyObject {
199        match self {
200            CustomConstant::Int(i) => convert_bigint_to_i64(&i).into_py(py),
201            CustomConstant::Float(f) => f.into_py(py),
202            CustomConstant::Str(s) => s.into_py(py),
203            CustomConstant::Bool(b) => b.into_py(py),
204            CustomConstant::Tuple(t) => {
205                let py_list = t
206                    .iter()
207                    .map(|x| x.clone().into_py(py))
208                    .collect::<Vec<PyObject>>();
209                py_list.into_py(py)
210            }
211            CustomConstant::PyObj(obj) => obj,
212            CustomConstant::Dict(keys, values) => {
213                let dict = PyDict::new(py);
214                for (key, value) in keys.iter().zip(values.iter()) {
215                    dict.set_item(key, value.clone().into_py(py))
216                        .unwrap_or_default();
217                }
218                dict.into_py(py)
219            }
220        }
221    }
222}
223
224type ToolFunction = Box<dyn Fn(Vec<Constant>) -> Result<CustomConstant, InterpreterError>>;
225type CustomToolFunction =
226    Box<dyn Fn(Vec<Constant>, HashMap<String, String>) -> Result<CustomConstant, InterpreterError>>;
227
228fn setup_custom_tools(tools: Vec<Box<dyn AnyTool>>) -> HashMap<String, CustomToolFunction> {
229    let mut tools_map = HashMap::new();
230    for tool in tools {
231        let tool_info = tool.tool_info();
232        tools_map.insert(
233            tool.name().to_string(),
234            Box::new(
235                move |args: Vec<Constant>, kwargs: HashMap<String, String>| {
236                    //merge args and kwargs
237                    let tool_parameter_names = tool_info.get_parameter_names();
238
239                    let mut new_args = HashMap::new();
240                    for (i, arg) in args.iter().enumerate() {
241                        new_args
242                            .insert(tool_parameter_names[i].clone(), arg.clone().str().unwrap());
243                    }
244                    for (key, value) in kwargs {
245                        new_args.insert(key, value);
246                    }
247                    match tool.forward_json(json!(new_args)) {
248                        Ok(results) => Ok(CustomConstant::Str(results)),
249                        Err(e) => Ok(CustomConstant::Str(format!("Error: {}", e))),
250                    }
251                },
252            ) as CustomToolFunction,
253        );
254    }
255    tools_map
256}
257
258pub fn setup_static_tools(
259    static_tools: HashMap<&'static str, &'static str>,
260) -> HashMap<String, ToolFunction> {
261    let mut tools = HashMap::new();
262    let static_tools_clone = static_tools.clone();
263    let eval_py = move |func: &str, args: Vec<Constant>| {
264        Python::with_gil(|py| {
265            let locals = PyDict::new(py);
266
267            // Import required modules
268            let math = PyModule::import(py, "math")?;
269            locals.set_item("math", math)?;
270
271            for (i, arg) in args.iter().enumerate() {
272                match arg {
273                    Constant::Float(f) => locals.set_item(format!("arg{}", i), f)?,
274                    Constant::Int(int) => {
275                        locals.set_item(format!("arg{}", i), convert_bigint_to_i64(int))?;
276                    }
277                    Constant::Str(s) => locals.set_item(format!("arg{}", i), s)?,
278                    Constant::Tuple(t) => {
279                        let py_list: Vec<f64> = t
280                            .iter()
281                            .map(|x| match x {
282                                Constant::Float(f) => *f,
283                                Constant::Int(i) => convert_bigint_to_f64(i),
284                                _ => 0.0,
285                            })
286                            .collect();
287                        locals.set_item(format!("arg{}", i), py_list)?
288                    }
289                    _ => locals.set_item(format!("arg{}", i), 0.0)?,
290                }
291            }
292
293            let arg_names: Vec<String> = (0..args.len()).map(|i| format!("arg{}", i)).collect();
294            let func_path = static_tools.get(func).unwrap_or(&"builtins.float");
295            let expr = format!("{}({})", func_path, arg_names.join(","));
296
297            let result = py.eval(&expr, None, Some(locals))?;
298            // Handle different return types
299            if let Ok(float_val) = result.extract::<f64>() {
300                Ok(CustomConstant::Float(float_val))
301            } else if let Ok(list_val) = result.extract::<Vec<String>>() {
302                Ok(CustomConstant::Tuple(
303                    list_val.into_iter().map(CustomConstant::Str).collect(),
304                ))
305            } else if let Ok(string_val) = result.extract::<String>() {
306                Ok(CustomConstant::Str(string_val))
307            } else if let Ok(bool_val) = result.extract::<bool>() {
308                Ok(CustomConstant::Bool(bool_val))
309            } else if let Ok(int_val) = result.extract::<i64>() {
310                Ok(CustomConstant::Int(BigInt::from(int_val)))
311            } else {
312                Ok(CustomConstant::PyObj(result.into_py(py)))
313            }
314        })
315    };
316
317    // Register tools after eval_py is defined
318    for func in static_tools_clone.keys() {
319        let func = func.to_string(); // Create owned String
320        let eval_py = eval_py.clone(); // Clone the closure
321        tools.insert(
322            func.clone(),
323            Box::new(move |args| eval_py(&func, args)) as ToolFunction,
324        );
325    }
326
327    tools
328}
329
330fn evaluate_stmt(
331    node: &ast::Stmt,
332    state: &mut HashMap<String, Box<dyn Any>>,
333    static_tools: &HashMap<String, StaticTool>,
334    custom_tools: &HashMap<String, CustomToolFunction>,
335) -> Result<CustomConstant, InterpreterError> {
336    match node {
337        Stmt::FunctionDef(func) => Ok(CustomConstant::Str(format!("Function: {:?}", func.name))),
338        Stmt::Expr(expr) => {
339            let result = evaluate_expr(&expr.value, state, static_tools, custom_tools)?;
340            Ok(result)
341        }
342        Stmt::For(for_stmt) => {
343            let iter = evaluate_expr(&for_stmt.iter.clone(), state, static_tools, custom_tools)?;
344            // Convert PyObj iterator into a vector of values
345            let values = match iter {
346                CustomConstant::PyObj(obj) => {
347                    Python::with_gil(|py| -> Result<Vec<CustomConstant>, InterpreterError> {
348                        let iter = obj.as_ref(py).iter()?;
349                        let mut values = Vec::new();
350
351                        for item in iter {
352                            let item = item?;
353                            if let Ok(num) = item.extract::<i64>() {
354                                values.push(CustomConstant::Int(BigInt::from(num)));
355                            } else if let Ok(float) = item.extract::<f64>() {
356                                values.push(CustomConstant::Float(float));
357                            } else if let Ok(string) = item.extract::<String>() {
358                                values.push(CustomConstant::Str(string));
359                            } else {
360                                return Err(InterpreterError::RuntimeError(
361                                    "Unsupported type in iterator".to_string(),
362                                ));
363                            }
364                        }
365                        Ok(values)
366                    })?
367                }
368                CustomConstant::Tuple(items) => items,
369                _ => {
370                    return Err(InterpreterError::RuntimeError(
371                        "Expected iterable".to_string(),
372                    ))
373                }
374            };
375            // Get the target variable name
376            let target_name = match &*for_stmt.target {
377                ast::Expr::Name(name) => name.id.to_string(),
378                _ => {
379                    return Err(InterpreterError::RuntimeError(
380                        "Expected name as loop target".to_string(),
381                    ))
382                }
383            };
384            let mut for_loop_result = CustomConstant::Str(String::new());
385            // Iterate over the values and execute the body for each iteration
386            for value in values {
387                // Update the loop variable in the state
388                state.insert(target_name.clone(), Box::new(value));
389
390                // Execute each statement in the loop body
391                for stmt in &for_stmt.body {
392                    for_loop_result = evaluate_stmt(stmt, state, static_tools, custom_tools)?;
393                }
394            }
395            Ok(for_loop_result)
396        }
397
398        Stmt::Assign(assign) => {
399            for target in assign.targets.iter() {
400                // let target = evaluate_expr(&Box::new(target.clone()), state, static_tools)?;
401                match target {
402                    ast::Expr::Name(name) => {
403                        let value =
404                            evaluate_expr(&assign.value, state, static_tools, custom_tools)?;
405                        state.insert(name.id.to_string(), Box::new(value));
406                    }
407                    ast::Expr::Tuple(target_names) => {
408                        let value =
409                            evaluate_expr(&assign.value, state, static_tools, custom_tools)?;
410                        let values = value.tuple().ok_or_else(|| {
411                            InterpreterError::RuntimeError(
412                                "Tuple unpacking failed. Expected values of type tuple".to_string(),
413                            )
414                        })?;
415                        if target_names.elts.len() != values.len() {
416                            return Err(InterpreterError::RuntimeError(format!(
417                                "Tuple unpacking failed. Expected {} values, got {}",
418                                target_names.elts.len(),
419                                values.len()
420                            )));
421                        }
422                        for (i, target_name) in target_names.elts.iter().enumerate() {
423                            match target_name {
424                                ast::Expr::Name(name) => {
425                                    state.insert(name.id.to_string(), Box::new(values[i].clone()));
426                                }
427                                _ => panic!("Expected string"),
428                            }
429                        }
430                    }
431                    _ => panic!("Expected string"),
432                }
433            }
434            Ok(CustomConstant::Str(String::new()))
435        }
436
437        _ => Err(InterpreterError::RuntimeError(
438            "Unsupported statement".to_string(),
439        )),
440    }
441}
442
443fn evaluate_ast(
444    ast: &ast::Suite,
445    state: &mut HashMap<String, Box<dyn Any>>,
446    static_tools: &HashMap<String, StaticTool>,
447    custom_tools: &HashMap<String, CustomToolFunction>,
448) -> Result<CustomConstant, InterpreterError> {
449    let mut result = CustomConstant::Str(String::new());
450    for node in ast.iter() {
451        result = evaluate_stmt(node, state, static_tools, custom_tools)?;
452    }
453    Ok(result)
454}
455
456fn convert_bigint_to_f64(i: &BigInt) -> f64 {
457    let i = i.to_u32_digits();
458    let num = i.1.iter().fold(0i64, |acc, &d| acc * (1 << 32) + d as i64);
459    match i.0 {
460        Sign::Minus => -num as f64,
461        Sign::NoSign | Sign::Plus => num as f64,
462    }
463}
464fn convert_bigint_to_i64(i: &BigInt) -> i64 {
465    let i = i.to_u32_digits();
466    let num = i.1.iter().fold(0i64, |acc, &d| acc * (1 << 32) + d as i64);
467    match i.0 {
468        Sign::Minus => -num,
469        Sign::NoSign | Sign::Plus => num,
470    }
471}
472
473type StaticTool = Box<dyn Fn(Vec<Constant>) -> Result<CustomConstant, InterpreterError>>;
474type CustomTool =
475    Box<dyn Fn(Vec<Constant>, HashMap<String, String>) -> Result<CustomConstant, InterpreterError>>;
476
477fn evaluate_expr(
478    expr: &Expr,
479    state: &mut HashMap<String, Box<dyn Any>>,
480    static_tools: &HashMap<String, StaticTool>,
481    custom_tools: &HashMap<String, CustomTool>,
482) -> Result<CustomConstant, InterpreterError> {
483    match &expr {
484        ast::Expr::Dict(dict) => {
485            let keys = dict
486                .keys
487                .iter()
488                .map(|e| {
489                    evaluate_expr(
490                        &Box::new(e.clone().ok_or_else(|| {
491                            InterpreterError::RuntimeError(
492                                "Dictionary key cannot be None".to_string(),
493                            )
494                        })?),
495                        state,
496                        static_tools,
497                        custom_tools,
498                    )
499                    .map(|c| c.str())
500                })
501                .collect::<Result<Vec<String>, _>>()?;
502            let values = dict
503                .values
504                .iter()
505                .map(|e| evaluate_expr(&Box::new(e.clone()), state, static_tools, custom_tools))
506                .collect::<Result<Vec<CustomConstant>, _>>()?;
507            Ok(CustomConstant::Dict(keys, values))
508        }
509        ast::Expr::ListComp(list_comp) => {
510            let iter = evaluate_expr(
511                &list_comp.generators[0].iter,
512                state,
513                static_tools,
514                custom_tools,
515            )?;
516            let result = Python::with_gil(|py| -> Result<Vec<CustomConstant>, InterpreterError> {
517                let iter = iter.into_py(py);
518                let iter = iter.as_ref(py).iter()?;
519                let mut result = Vec::new();
520                for item in iter {
521                    let target = match &list_comp.generators[0].target {
522                        ast::Expr::Name(name) => name.id.to_string(),
523                        _ => panic!("Expected string"),
524                    };
525                    let item = item?;
526                    let item = extract_constant_from_pyobject(item, py)?;
527                    state.insert(target, Box::new(item));
528                    let eval_expr =
529                        evaluate_expr(&list_comp.elt, state, static_tools, custom_tools)?;
530                    result.push(eval_expr);
531                }
532                Ok(result)
533            });
534            let result = result?;
535            Ok(CustomConstant::Tuple(result))
536        }
537        ast::Expr::Call(call) => {
538            let args = call
539                .args
540                .iter()
541                .map(|e| evaluate_expr(&Box::new(e.clone()), state, static_tools, custom_tools))
542                .collect::<Result<Vec<CustomConstant>, InterpreterError>>()?;
543            let func = match &*call.func {
544                ast::Expr::Name(name) => name.id.to_string(),
545                ast::Expr::Attribute(attr) => {
546                    let obj = evaluate_expr(
547                        &Box::new(*attr.value.clone()),
548                        state,
549                        static_tools,
550                        custom_tools,
551                    )?;
552
553                    let func_name = attr.attr.to_string();
554                    let output =
555                        Python::with_gil(|py| -> Result<CustomConstant, InterpreterError> {
556                            let obj = obj.into_py(py);
557                            let func = obj.getattr(py, func_name.as_str())?;
558                            let py_args = args
559                                .iter()
560                                .map(|a| match a {
561                                    // Convert numeric types to strings when calling string methods
562                                    CustomConstant::Float(f) => f.into_py(py),
563                                    CustomConstant::Int(i) => convert_bigint_to_i64(i).into_py(py),
564                                    _ => a.clone().into_py(py),
565                                })
566                                .collect::<Vec<PyObject>>();
567                            let py_tuple = PyTuple::new(py, py_args);
568                            let result = func.call1(py, py_tuple)?;
569
570                            // For methods that modify in place (like append), return the original object
571                            if func_name == "append"
572                                || func_name == "extend"
573                                || func_name == "insert"
574                            {
575                                let target = match &*attr.value {
576                                    ast::Expr::Name(name) => name.id.to_string(),
577                                    _ => panic!("Expected name"),
578                                };
579                                let out = extract_constant_from_pyobject(obj.as_ref(py), py)?;
580                                state.insert(target, Box::new(out.clone()));
581                                return Ok(out);
582                            }
583
584                            extract_constant_from_pyobject(result.as_ref(py), py)
585                        });
586                    return output;
587                }
588                _ => panic!("Expected function name"),
589            };
590
591            let keywords = call
592                .keywords
593                .iter()
594                .map(|k| {
595                    let value = evaluate_expr(
596                        &Box::new(k.value.clone()),
597                        state,
598                        static_tools,
599                        custom_tools,
600                    )?;
601                    Ok((k.arg.as_ref().unwrap().to_string(), value.str()))
602                })
603                .collect::<Result<HashMap<String, String>, InterpreterError>>()?;
604            if func == "final_answer" {
605                if let Some(answer) = keywords.get("answer") {
606                    return Err(InterpreterError::FinalAnswer(answer.to_string()));
607                } else {
608                    return Err(InterpreterError::FinalAnswer(
609                        args.iter()
610                            .map(|c| c.str())
611                            .collect::<Vec<String>>()
612                            .join(" "),
613                    ));
614                }
615            }
616            if func == "print" {
617                match state.get_mut("print_logs") {
618                    Some(logs) => {
619                        if let Some(logs) = logs.downcast_mut::<Vec<String>>() {
620                            logs.push(
621                                args.iter()
622                                    .map(|c| c.str())
623                                    .collect::<Vec<String>>()
624                                    .join(" "),
625                            );
626                        } else {
627                            return Err(InterpreterError::RuntimeError(
628                                "print_logs is not a list".to_string(),
629                            ));
630                        }
631                    }
632                    None => {
633                        state.insert(
634                            "print_logs".to_string(),
635                            Box::new(args.iter().map(|c| c.str()).collect::<Vec<String>>()),
636                        );
637                    }
638                }
639                return Ok(CustomConstant::Str(
640                    args.iter()
641                        .map(|c| c.str())
642                        .collect::<Vec<String>>()
643                        .join(" "),
644                ));
645            }
646            if static_tools.contains_key(&func) {
647                let result =
648                    static_tools[&func](args.iter().map(|c| Constant::from(c.clone())).collect());
649                result
650            } else if custom_tools.contains_key(&func) {
651                let result = custom_tools[&func](
652                    args.iter().map(|c| Constant::from(c.clone())).collect(),
653                    keywords,
654                );
655                result
656            } else {
657                Err(InterpreterError::RuntimeError(format!(
658                    "Function '{}' not found",
659                    func
660                )))
661            }
662        }
663        ast::Expr::BinOp(binop) => {
664            let left_val_exp =
665                evaluate_expr(&binop.left.clone(), state, static_tools, custom_tools)?;
666            let right_val_exp: CustomConstant =
667                evaluate_expr(&binop.right.clone(), state, static_tools, custom_tools)?;
668
669            match binop.op {
670                Operator::Add => match (left_val_exp.clone(), right_val_exp.clone()) {
671                    (CustomConstant::Str(s), CustomConstant::Str(s2)) => {
672                        return Ok(CustomConstant::Str(s + &s2));
673                    }
674                    (CustomConstant::Str(s), CustomConstant::Int(i)) => {
675                        return Ok(CustomConstant::Str(s + &i.to_string()));
676                    }
677                    (CustomConstant::Int(i), CustomConstant::Str(s)) => {
678                        return Ok(CustomConstant::Str(i.to_string() + &s));
679                    }
680                    _ => {}
681                },
682                Operator::Mult => match (left_val_exp.clone(), right_val_exp.clone()) {
683                    (CustomConstant::Str(s), CustomConstant::Int(i)) => {
684                        return Ok(CustomConstant::Str(
685                            s.repeat(convert_bigint_to_i64(&i) as usize),
686                        ));
687                    }
688                    (CustomConstant::Int(i), CustomConstant::Str(s)) => {
689                        return Ok(CustomConstant::Str(
690                            s.repeat(convert_bigint_to_i64(&i) as usize),
691                        ));
692                    }
693                    _ => {}
694                },
695                _ => {}
696            }
697            let left_val = match left_val_exp.clone() {
698                CustomConstant::Float(f) => f,
699                CustomConstant::Int(i) => convert_bigint_to_f64(&i),
700                _ => panic!("Expected float or int"),
701            };
702            let right_val = match right_val_exp.clone() {
703                CustomConstant::Float(f) => f,
704                CustomConstant::Int(i) => convert_bigint_to_f64(&i),
705                _ => panic!("Expected float or int"),
706            };
707
708            match &binop.op {
709                Operator::Add => Ok(CustomConstant::Float(left_val + right_val)),
710                Operator::Sub => Ok(CustomConstant::Float(left_val - right_val)),
711                Operator::Mult => Ok(CustomConstant::Float(left_val * right_val)),
712                Operator::Div => Ok(CustomConstant::Float(left_val / right_val)),
713                Operator::FloorDiv => Ok(CustomConstant::Float(left_val / right_val)),
714                Operator::Mod => Ok(CustomConstant::Float(left_val % right_val)),
715                Operator::Pow => Ok(CustomConstant::Float(left_val.powf(right_val))),
716                Operator::BitOr => Ok(CustomConstant::Int(BigInt::from(
717                    left_val as i64 | right_val as i64,
718                ))),
719                Operator::BitXor => Ok(CustomConstant::Int(BigInt::from(
720                    left_val as i64 ^ right_val as i64,
721                ))),
722                Operator::BitAnd => Ok(CustomConstant::Int(BigInt::from(
723                    left_val as i64 & right_val as i64,
724                ))),
725                Operator::LShift => {
726                    let left_val = left_val as i64;
727                    let right_val = right_val as i64;
728                    Ok(CustomConstant::Int(BigInt::from(left_val << right_val)))
729                }
730                Operator::RShift => {
731                    let left_val = left_val as i64;
732                    let right_val = right_val as i64;
733                    Ok(CustomConstant::Int(BigInt::from(left_val >> right_val)))
734                }
735                Operator::MatMult => Ok(CustomConstant::Float(left_val * right_val)),
736            }
737        }
738        ast::Expr::UnaryOp(unaryop) => {
739            let operand = evaluate_expr(&unaryop.operand, state, static_tools, custom_tools)?;
740            match &unaryop.op {
741                UnaryOp::USub => match operand {
742                    CustomConstant::Float(f) => Ok(CustomConstant::Float(-f)),
743                    CustomConstant::Int(i) => Ok(CustomConstant::Int(-i)),
744                    _ => panic!("Expected float or int"),
745                },
746                UnaryOp::UAdd => Ok(operand),
747                UnaryOp::Not => {
748                    if let CustomConstant::Bool(b) = operand {
749                        Ok(CustomConstant::Bool(!b))
750                    } else {
751                        panic!("Expected boolean")
752                    }
753                }
754                UnaryOp::Invert => {
755                    if let CustomConstant::Float(f) = operand {
756                        Ok(CustomConstant::Float(-(f as i64) as f64))
757                    } else {
758                        panic!("Expected float")
759                    }
760                }
761            }
762        }
763        ast::Expr::Constant(constant) => match &constant.value {
764            Constant::Int(i) => Ok(CustomConstant::Int(i.clone())),
765            _ => Ok(constant.value.clone().into()),
766        },
767        ast::Expr::List(list) => Ok(CustomConstant::Tuple(
768            list.elts
769                .iter()
770                .map(|e| evaluate_expr(&Box::new(e.clone()), state, static_tools, custom_tools))
771                .collect::<Result<Vec<CustomConstant>, _>>()?,
772        )),
773        ast::Expr::Name(name) => {
774            if let Some(value) = state.get(name.id.as_str()) {
775                if let Some(constant) = value.downcast_ref::<CustomConstant>() {
776                    Ok(constant.clone())
777                } else {
778                    Err(InterpreterError::RuntimeError(format!(
779                        "Error in downcasting constant {}",
780                        name.id
781                    )))
782                }
783            } else {
784                Err(InterpreterError::RuntimeError(format!(
785                    "Variable '{}' used before assignment",
786                    name.id
787                )))
788            }
789        }
790        ast::Expr::Tuple(tuple) => Ok(CustomConstant::Tuple(
791            tuple
792                .elts
793                .iter()
794                .map(|e| evaluate_expr(&Box::new(e.clone()), state, static_tools, custom_tools))
795                .collect::<Result<Vec<CustomConstant>, _>>()?,
796        )),
797        ast::Expr::JoinedStr(joinedstr) => Ok(CustomConstant::Str(
798            joinedstr
799                .values
800                .iter()
801                .map(|e| {
802                    evaluate_expr(&Box::new(e.clone()), state, static_tools, custom_tools)
803                        .map(|result| result.str())
804                })
805                .collect::<Result<Vec<String>, _>>()?
806                .join(""),
807        )),
808        ast::Expr::FormattedValue(formattedvalue) => {
809            let result = evaluate_expr(&formattedvalue.value, state, static_tools, custom_tools)?;
810
811            Ok(CustomConstant::Str(result.str()))
812        }
813        ast::Expr::Subscript(subscript) => {
814            let result = Python::with_gil(|py| {
815                // Get the value being subscripted (e.g., the list/string)
816                let value = evaluate_expr(&subscript.value, state, static_tools, custom_tools)?;
817                let value_obj = value.into_py(py);
818
819                let slice = Constant::from(evaluate_expr(
820                    &subscript.slice,
821                    state,
822                    static_tools,
823                    custom_tools,
824                )?);
825
826                // Handle integer indices for lists/sequences
827                if let Constant::Int(i) = slice {
828                    let index = convert_bigint_to_i64(&i);
829                    let result = value_obj.as_ref(py).get_item(index);
830                    match result {
831                        Ok(result) => return extract_constant_from_pyobject(result, py),
832                        Err(e) => return Err(InterpreterError::RuntimeError(e.to_string())),
833                    }
834                }
835
836                // Handle string keys for dictionaries
837                if let Constant::Str(s) = slice {
838                    // Try to extract as dictionary first
839                    if let Ok(dict) = value_obj.as_ref(py).downcast::<PyDict>() {
840                        let result = dict.get_item(s.clone());
841                        match result {
842                            Some(value) => return extract_constant_from_pyobject(value, py),
843                            None => {
844                                return Err(InterpreterError::RuntimeError(format!(
845                                    "KeyError: '{}'",
846                                    s
847                                )))
848                            }
849                        }
850                    }
851                }
852
853                // Handle both simple indexing and slicing
854                let result = match &*subscript.slice {
855                    // For slice operations like num[1:3:2]
856                    ast::Expr::Slice(slice) => {
857                        let start = match &slice.lower {
858                            Some(lower) => {
859                                evaluate_expr(lower, state, static_tools, custom_tools)?.into()
860                            }
861                            None => None,
862                        };
863                        let start = start
864                            .map(|start| {
865                                let constant = Constant::from(start);
866                                constant
867                                    .int()
868                                    .map(|i| convert_bigint_to_i64(&i))
869                                    .ok_or_else(|| {
870                                        InterpreterError::RuntimeError(
871                                            "Invalid start value in slice".to_string(),
872                                        )
873                                    })
874                            })
875                            .transpose()?;
876
877                        let stop = match &slice.upper {
878                            Some(upper) => {
879                                evaluate_expr(upper, state, static_tools, custom_tools)?.into()
880                            }
881                            None => None,
882                        };
883                        let stop = stop
884                            .map(|stop| {
885                                let constant = Constant::from(stop);
886                                constant
887                                    .int()
888                                    .map(|i| convert_bigint_to_i64(&i))
889                                    .ok_or_else(|| {
890                                        InterpreterError::RuntimeError(
891                                            "Invalid stop value in slice".to_string(),
892                                        )
893                                    })
894                            })
895                            .transpose()?;
896
897                        let step = match &slice.step {
898                            Some(step) => {
899                                evaluate_expr(step, state, static_tools, custom_tools)?.into()
900                            }
901                            None => None,
902                        };
903                        let step = step
904                            .map(|step| {
905                                let constant = Constant::from(step);
906                                constant
907                                    .int()
908                                    .map(|i| convert_bigint_to_i64(&i))
909                                    .ok_or_else(|| {
910                                        InterpreterError::RuntimeError(
911                                            "Invalid step value in slice".to_string(),
912                                        )
913                                    })
914                            })
915                            .transpose()?;
916
917                        let slice_obj = py
918                            .eval("slice", None, None)?
919                            .call1((start, stop, step))?
920                            .into_py(py);
921                        value_obj.as_ref(py).get_item(slice_obj)?
922                    }
923                    _ => return Err(InterpreterError::RuntimeError("Invalid slice".to_string())),
924                };
925
926                // Convert the result back to our CustomConstant type
927                extract_constant_from_pyobject(result, py)
928            });
929            result
930        }
931        ast::Expr::Slice(slice) => {
932            let start = match &slice.lower {
933                Some(lower) => evaluate_expr(lower, state, static_tools, custom_tools)?,
934                None => CustomConstant::Int(BigInt::from(0)),
935            };
936            let end = match &slice.upper {
937                Some(upper) => evaluate_expr(upper, state, static_tools, custom_tools)?,
938                None => CustomConstant::Int(BigInt::from(0)),
939            };
940            let step = match &slice.step {
941                Some(step) => evaluate_expr(step, state, static_tools, custom_tools)?,
942                None => CustomConstant::Int(BigInt::from(1)),
943            };
944            Ok(CustomConstant::Tuple(vec![start, end, step]))
945        }
946        _ => {
947            panic!("Unsupported expression: {:?}", expr);
948        }
949    }
950}
951
952fn extract_constant_from_pyobject(
953    obj: &PyAny,
954    py: Python<'_>,
955) -> Result<CustomConstant, InterpreterError> {
956    if let Ok(float_val) = obj.extract::<f64>() {
957        Ok(CustomConstant::Float(float_val))
958    } else if let Ok(string_val) = obj.extract::<String>() {
959        Ok(CustomConstant::Str(string_val))
960    } else if let Ok(bool_val) = obj.extract::<bool>() {
961        Ok(CustomConstant::Bool(bool_val))
962    } else if let Ok(int_val) = obj.extract::<i64>() {
963        Ok(CustomConstant::Int(BigInt::from(int_val)))
964    } else if let Ok(list_val) = obj.extract::<Vec<String>>() {
965        Ok(CustomConstant::Tuple(
966            list_val.into_iter().map(CustomConstant::Str).collect(),
967        ))
968    } else if let Ok(list_val) = obj.extract::<Vec<i64>>() {
969        Ok(CustomConstant::Tuple(
970            list_val
971                .into_iter()
972                .map(|i| CustomConstant::Int(BigInt::from(i)))
973                .collect(),
974        ))
975    } else if let Ok(list_val) = obj.extract::<Vec<f64>>() {
976        Ok(CustomConstant::Tuple(
977            list_val.into_iter().map(CustomConstant::Float).collect(),
978        ))
979    } else if let Ok(dict_value) = obj.extract::<&PyDict>() {
980        let keys = dict_value
981            .keys()
982            .iter()
983            .map(|key| key.extract::<String>())
984            .collect::<Result<Vec<String>, _>>()?;
985        let values = dict_value
986            .values()
987            .iter()
988            .map(|value| extract_constant_from_pyobject(value, py))
989            .collect::<Result<Vec<CustomConstant>, _>>()?;
990        Ok(CustomConstant::Dict(keys, values))
991    } else {
992        Ok(CustomConstant::PyObj(obj.into_py(py)))
993    }
994}
995pub fn evaluate_python_code(
996    code: &str,
997    custom_tools: Vec<Box<dyn AnyTool>>,
998    state: &mut HashMap<String, Box<dyn Any>>,
999) -> Result<String, InterpreterError> {
1000    let base_tools = get_base_python_tools();
1001    let static_tools = setup_static_tools(base_tools);
1002    let custom_tools = setup_custom_tools(custom_tools);
1003    let ast = ast::Suite::parse(code, "<embedded>")
1004        .map_err(|e| InterpreterError::SyntaxError(e.to_string()))?;
1005
1006    let result = evaluate_ast(&ast, state, &static_tools, &custom_tools)?;
1007    Ok(result.str())
1008}
1009
1010pub struct LocalPythonInterpreter {
1011    static_tools: HashMap<String, ToolFunction>,
1012    custom_tools: HashMap<String, CustomToolFunction>,
1013}
1014
1015impl LocalPythonInterpreter {
1016    pub fn new(custom_tools: Vec<Box<dyn AnyTool>>) -> Self {
1017        let custom_tools = setup_custom_tools(custom_tools);
1018        let base_tools = get_base_python_tools();
1019        let static_tools = setup_static_tools(base_tools);
1020        Self {
1021            static_tools,
1022            custom_tools,
1023        }
1024    }
1025    pub fn forward(
1026        &self,
1027        code: &str,
1028        state: &mut Option<HashMap<String, Box<dyn Any>>>,
1029    ) -> Result<(String, String), InterpreterError> {
1030        let mut empty_state = HashMap::new();
1031        let ast = ast::Suite::parse(code, "<embedded>")
1032            .map_err(|e| InterpreterError::SyntaxError(e.to_string()))?;
1033        let state = state.as_mut().unwrap_or(&mut empty_state);
1034        let result = evaluate_ast(&ast, state, &self.static_tools, &self.custom_tools)?;
1035
1036        let mut empty_string = Vec::new();
1037        let execution_logs = state
1038            .get_mut("print_logs")
1039            .and_then(|logs| logs.downcast_mut::<Vec<String>>())
1040            .unwrap_or(&mut empty_string)
1041            .join("\n");
1042        Ok((result.str(), execution_logs))
1043    }
1044}
1045#[cfg(test)]
1046mod tests {
1047    use super::*;
1048    use crate::tools::{DuckDuckGoSearchTool, FinalAnswerTool, VisitWebsiteTool};
1049    use std::collections::HashMap;
1050
1051    #[test]
1052    fn test_evaluate_python_code() {
1053        let code = "print('Hello, world!')";
1054        let mut state = HashMap::new();
1055        let result = evaluate_python_code(code, vec![], &mut state).unwrap();
1056        assert_eq!(result, "Hello, world!");
1057    }
1058
1059    #[test]
1060    fn test_evaluate_python_code_with_joined_str() {
1061        let code = r#"word = 'strawberry'
1062r_count = word.count('r')
1063print(f"The letter 'r' appears {r_count} times in the word '{word}'.")"#;
1064        let mut state = HashMap::new();
1065        let result = evaluate_python_code(code, vec![], &mut state).unwrap();
1066        assert_eq!(
1067            result,
1068            "The letter 'r' appears 3 times in the word 'strawberry'."
1069        );
1070    }
1071
1072    #[test]
1073    fn test_final_answer_execution() {
1074        let tools: Vec<Box<dyn AnyTool>> = vec![Box::new(FinalAnswerTool::new())];
1075        let mut state = HashMap::new();
1076        let result =
1077            evaluate_python_code("final_answer(answer='Hello, world!')", tools, &mut state);
1078        assert_eq!(
1079            result,
1080            Err(InterpreterError::FinalAnswer("Hello, world!".to_string()))
1081        );
1082    }
1083
1084    #[test]
1085    fn test_evaluate_python_code_with_subscript() {
1086        let code = textwrap::dedent(
1087            r#"
1088        word = 'strawberry'
1089        print(word[3])"#,
1090        );
1091        let mut state = HashMap::new();
1092        let result = evaluate_python_code(&code, vec![], &mut state).unwrap();
1093        assert_eq!(result, "a");
1094
1095        let code = textwrap::dedent(
1096            r#"
1097        word = 'strawberry'
1098        print(word[-3])"#,
1099        );
1100        let mut state = HashMap::new();
1101        let result = evaluate_python_code(&code, vec![], &mut state).unwrap();
1102        assert_eq!(result, "r");
1103
1104        let code = textwrap::dedent(
1105            r#"
1106        word = 'strawberry'
1107        print(word[9])"#,
1108        );
1109        let mut state = HashMap::new();
1110        let result = evaluate_python_code(&code, vec![], &mut state).unwrap();
1111        assert_eq!(result, "y");
1112
1113        let code = textwrap::dedent(
1114            r#"
1115        word = 'strawberry'
1116        print(word[10])"#,
1117        );
1118        let mut state = HashMap::new();
1119        let result = evaluate_python_code(&code, vec![], &mut state);
1120        assert_eq!(
1121            result,
1122            Err(InterpreterError::RuntimeError(
1123                "IndexError: string index out of range".to_string()
1124            ))
1125        );
1126
1127        let code = textwrap::dedent(
1128            r#"
1129        numbers = [1, 2, 3, 4, 5]
1130        print(numbers[1])"#,
1131        );
1132        let mut state = HashMap::new();
1133        let result = evaluate_python_code(&code, vec![], &mut state).unwrap();
1134        assert_eq!(result, "2");
1135
1136        let code = textwrap::dedent(
1137            r#"
1138        numbers = [1, 2, 3, 4, 5]
1139        print(numbers[-5])"#,
1140        );
1141        let mut state = HashMap::new();
1142        let result = evaluate_python_code(&code, vec![], &mut state).unwrap();
1143        assert_eq!(result, "1");
1144
1145        let code = textwrap::dedent(
1146            r#"
1147        numbers = [1, 2, 3, 4, 5]
1148        print(numbers[-6])"#,
1149        );
1150        let mut state = HashMap::new();
1151        let result = evaluate_python_code(&code, vec![], &mut state);
1152        assert_eq!(
1153            result,
1154            Err(InterpreterError::RuntimeError(
1155                "IndexError: list index out of range".to_string()
1156            ))
1157        );
1158    }
1159
1160    #[test]
1161    fn test_evaluate_python_code_with_slice() {
1162        let code = textwrap::dedent(
1163            r#"
1164        numbers = [1, 2, 3, 4, 5]
1165        print(numbers[1:3])"#,
1166        );
1167        let mut state = HashMap::new();
1168        let result = evaluate_python_code(&code, vec![], &mut state).unwrap();
1169        assert_eq!(result, "[2, 3]");
1170
1171        let code = textwrap::dedent(
1172            r#"
1173        numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
1174        print(numbers[1:5:2])"#,
1175        );
1176        let mut state = HashMap::new();
1177        let result = evaluate_python_code(&code, vec![], &mut state).unwrap();
1178        assert_eq!(result, "[2, 4]");
1179
1180        let code = textwrap::dedent(
1181            r#"
1182        numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
1183        print(numbers[5:1:-2])"#,
1184        );
1185        let mut state = HashMap::new();
1186        let result = evaluate_python_code(&code, vec![], &mut state).unwrap();
1187        assert_eq!(result, "[6, 4]");
1188
1189        let code = textwrap::dedent(
1190            r#"
1191        word = 'strawberry'
1192        print(word[::-1])"#,
1193        );
1194        let mut state = HashMap::new();
1195        let result = evaluate_python_code(&code, vec![], &mut state).unwrap();
1196        assert_eq!(result, "yrrebwarts");
1197
1198        let code = textwrap::dedent(
1199            r#"
1200        numbers = [1, 2, 3, 4, 5]
1201        print(numbers[::-1])"#,
1202        );
1203        let mut state = HashMap::new();
1204        let result = evaluate_python_code(&code, vec![], &mut state).unwrap();
1205        assert_eq!(result, "[5, 4, 3, 2, 1]");
1206    }
1207
1208    #[test]
1209    fn test_for_loop() {
1210        let code = textwrap::dedent(
1211            r#"
1212        for i in range(5):
1213            print(i)
1214        "#,
1215        );
1216        let mut state = HashMap::new();
1217        let _ = evaluate_python_code(&code, vec![], &mut state).unwrap();
1218        assert_eq!(
1219            state
1220                .get("print_logs")
1221                .unwrap()
1222                .downcast_ref::<Vec<String>>()
1223                .unwrap(),
1224            &vec!["0", "1", "2", "3", "4"]
1225        );
1226    }
1227
1228    #[test]
1229    fn test_for_loop_with_tools() {
1230        let code = textwrap::dedent(
1231            r#"
1232        for i in range(5):
1233            search = duckduckgo_search(query=i)
1234            print(search)
1235        "#,
1236        );
1237        let mut state = HashMap::new();
1238        let tools: Vec<Box<dyn AnyTool>> = vec![Box::new(DuckDuckGoSearchTool::new())];
1239        let _ = evaluate_python_code(&code, tools, &mut state).unwrap();
1240    }
1241
1242    #[test]
1243    fn test_evaluate_python_code_with_dict() {
1244        let code = textwrap::dedent(
1245            r#"
1246        my_dict = {'a': "1", 'b': "2", 'c': "3"}
1247        print(f"my_dict['a'] is {my_dict['a']}")
1248        "#,
1249        );
1250        let mut state = HashMap::new();
1251        let result = evaluate_python_code(&code, vec![], &mut state).unwrap();
1252        assert_eq!(result, "my_dict['a'] is 1");
1253
1254        let code = textwrap::dedent(
1255            r#"
1256dinner_places = [
1257    {
1258        "title": "25 Best Restaurants in Berlin, By Local Foodies",
1259        "url": "https://www.timeout.com/berlin/restaurants/best-restaurants-in-berlin"
1260    },
1261    {
1262        "title": "The 38 Best Berlin Restaurants - Eater",
1263        "url": "https://www.eater.com/maps/best-restaurants-berlin"
1264    },
1265    {
1266        "title": "THE 10 BEST Restaurants in Berlin - Tripadvisor",
1267        "url": "https://www.tripadvisor.com/Restaurants-g187323-Berlin.html"
1268    },
1269    {
1270        "title": "12 Unique Restaurants in Berlin",
1271        "url": "https://www.myglobalviewpoint.com/unique-restaurants-in-berlin/"
1272    },
1273    {
1274        "title": "Berlin's best restaurants: 101 places to eat right now",
1275        "url": "https://www.the-berliner.com/food/best-restaurants-berlin-101-places-to-eat/"
1276    }
1277]
1278
1279for place in dinner_places:
1280    print(f"{place['title']}: {place['url']}")
1281        "#,
1282        );
1283        let state = HashMap::new();
1284        let local_python_interpreter = LocalPythonInterpreter::new(vec![]);
1285        let (_, execution_logs) = local_python_interpreter
1286            .forward(&code, &mut Some(state))
1287            .unwrap();
1288        assert_eq!(execution_logs, "25 Best Restaurants in Berlin, By Local Foodies: https://www.timeout.com/berlin/restaurants/best-restaurants-in-berlin\nThe 38 Best Berlin Restaurants - Eater: https://www.eater.com/maps/best-restaurants-berlin\nTHE 10 BEST Restaurants in Berlin - Tripadvisor: https://www.tripadvisor.com/Restaurants-g187323-Berlin.html\n12 Unique Restaurants in Berlin: https://www.myglobalviewpoint.com/unique-restaurants-in-berlin/\nBerlin's best restaurants: 101 places to eat right now: https://www.the-berliner.com/food/best-restaurants-berlin-101-places-to-eat/");
1289
1290        let code = textwrap::dedent(
1291            r#"
1292movies = [
1293    {"title": "Babygirl", "showtimes": ["12:50 pm", "6:20 pm"]},
1294    {"title": "Better Man", "showtimes": ["9:20 pm"]},
1295    {"title": "La acompañante", "showtimes": ["3:40 pm", "6:30 pm", "9:10 pm"]},
1296    {"title": "Amenaza en el aire", "showtimes": ["9:30 pm"]},
1297    {"title": "Juf Braaksel en de Geniale Ontsnapping", "showtimes": ["12:30 pm"]},
1298    {"title": "Juffrouw Pots", "showtimes": ["10:35 am", "3:50 pm"]},
1299    {"title": "K3 en Het Lied van de Zeemeermin", "showtimes": ["10:00 am"]},
1300    {"title": "Marked Men", "showtimes": ["2:50 pm", "6:50 pm"]},
1301    {"title": "Vaiana 2", "showtimes": ["11:10 am", "12:40 pm"]},
1302    {"title": "Mufasa: El rey león", "showtimes": ["10:20 am", "3:10 pm", "9:00 pm"]},
1303    {"title": "Paddington: Aventura en la selva", "showtimes": ["12:20 pm", "3:30 pm", "6:10 pm"]},
1304    {"title": "Royal Opera House: The Tales of Hoffmann", "showtimes": ["1:30 pm"]},
1305    {"title": "The Growcodile", "showtimes": ["10:10 am"]},
1306    {"title": "Vivir el momento", "showtimes": ["5:20 pm"]},
1307    {"title": "Wicked", "showtimes": ["7:00 pm"]},
1308    {"title": "Woezel & Pip op Avontuur in de Tovertuin", "showtimes": ["10:30 am", "1:50 pm"]}
1309]
1310
1311for movie in movies:
1312    print(f"{movie['title']}: {', '.join(movie['showtimes'])}")
1313
1314        "#,
1315        );
1316        let state = HashMap::new();
1317        let local_python_interpreter = LocalPythonInterpreter::new(vec![]);
1318        let (_, _) = local_python_interpreter
1319            .forward(&code, &mut Some(state))
1320            .unwrap();
1321
1322        let code = textwrap::dedent(
1323            r#"
1324urls = [
1325    "https://www.tripadvisor.com/Restaurants-g187323-Berlin.html",
1326    "https://www.timeout.com/berlin/restaurants/best-restaurants-in-berlin"
1327]
1328
1329for url in urls:
1330    page_content = duckduckgo_search(url)
1331    print(page_content)
1332    print("\n" + "="*80 + "\n")  # Print separator between pages        
1333    "#,
1334        );
1335        let mut state = HashMap::new();
1336        let tools: Vec<Box<dyn AnyTool>> = vec![Box::new(DuckDuckGoSearchTool::new())];
1337        let _ = evaluate_python_code(&code, tools, &mut state).unwrap();
1338    }
1339
1340    #[test]
1341    fn test_evaluate_python_code_with_list_comprehension() {
1342        let code = textwrap::dedent(
1343            r#"
1344        a = [1,2,3]
1345        print([x for x in a])
1346    "#,
1347        );
1348        let mut state = HashMap::new();
1349        let _ = evaluate_python_code(&code, vec![], &mut state).unwrap();
1350        assert_eq!(
1351            state
1352                .get("print_logs")
1353                .unwrap()
1354                .downcast_ref::<Vec<String>>()
1355                .unwrap(),
1356            &vec!["[1, 2, 3]"]
1357        );
1358    }
1359
1360    #[test]
1361    fn test_evaluate_python_code_append_to_list() {
1362        let code = textwrap::dedent(
1363            r#"
1364        a = [1,2,3]
1365        a.append(4)
1366        print(a)
1367    "#,
1368        );
1369        let mut state = HashMap::new();
1370        let _ = evaluate_python_code(&code, vec![], &mut state).unwrap();
1371        assert_eq!(
1372            state
1373                .get("print_logs")
1374                .unwrap()
1375                .downcast_ref::<Vec<String>>()
1376                .unwrap(),
1377            &vec!["[1, 2, 3, 4]"]
1378        );
1379
1380        let code = textwrap::dedent(
1381            r#"
1382urls = [
1383    "https://www.imdb.com/showtimes/cinema/ES/ci1028808/ES/08520",
1384    "https://en.pathe.nl/bioscoopagenda",
1385    "https://www.filmvandaag.nl/bioscoop?filter=64"
1386]
1387movies = []
1388for url in urls:
1389    page_content = url
1390    movies.append(page_content)
1391
1392print(movies)
1393    "#,
1394        );
1395        let mut state = HashMap::new();
1396        let tools: Vec<Box<dyn AnyTool>> = vec![Box::new(VisitWebsiteTool::new())];
1397        let _ = evaluate_python_code(&code, tools, &mut state).unwrap();
1398        assert_eq!(
1399            state
1400                .get("print_logs")
1401                .unwrap()
1402                .downcast_ref::<Vec<String>>()
1403                .unwrap(),
1404            &vec!["[https://www.imdb.com/showtimes/cinema/ES/ci1028808/ES/08520, https://en.pathe.nl/bioscoopagenda, https://www.filmvandaag.nl/bioscoop?filter=64]"]
1405        );
1406    }
1407
1408    #[test]
1409    fn test_evaluate_python_code_with_error() {
1410        let code = textwrap::dedent(
1411            r#"
1412cycling_paths = [
1413    {
1414        "title": "5x Cycling routes in and around Eindhoven",
1415        "snippet": "One of the well-known cycling routes in and around Eindhoven is Rondje Eindhoven, which means 'around Eindhoven'. This route starts in four different directions. The distances are between 20 and 75 kilometres and take you through the city center, a number of dynamic districts, and beautiful nature.",
1416        "url": "https://www.thisiseindhoven.com/en/see-and-do/fun-things-to-do/cycling-routes-in-and-around-eindhoven"
1417    },
1418    {
1419        "title": "Top 5 Bike Rides and Cycling Routes around Eindhoven - Komoot",
1420        "snippet": "Explore the top cycling routes around Eindhoven to experience more of this area. We bring you the top bike rides around Eindhoven — all you've got to do is pick the one that's right for you.",
1421        "url": "https://www.komoot.com/guide/893152/cycling-around-eindhoven"
1422    },
1423    {
1424        "title": "Cycling routes in Eindhoven - Bikemap",
1425        "snippet": "Find cycle routes in Eindhoven: Round trips | Relaxed routes | Gravel routes | Road bike routes | MTB routes | Trekking routes.",
1426        "url": "https://www.bikemap.net/en/l/2756253/"
1427    }
1428]
1429
1430print("\n".join([f"{path['title']}: {path['snippet']} (More info: {path['url']})" for path in cycling_paths]))
1431
1432
1433    "#,
1434        );
1435        let state = HashMap::new();
1436        let tools: Vec<Box<dyn AnyTool>> = vec![Box::new(VisitWebsiteTool::new())];
1437        let local_python_interpreter = LocalPythonInterpreter::new(tools);
1438        let (_, logs) = local_python_interpreter
1439            .forward(&code, &mut Some(state))
1440            .unwrap();
1441        println!("logs: {:?}", logs);
1442    }
1443}