Wrap a Python callable `(Json) -> Json` for tool execution intercepts. Supports both sync and async Python callables. If the callable returns a coroutine, it is awaited via the pyo3-async-runtimes bridge.
(
py_fn: Py<PyAny>,
)
| 257 | /// Supports both sync and async Python callables. If the callable returns a |
| 258 | /// coroutine, it is awaited via the pyo3-async-runtimes bridge. |
| 259 | pub fn wrap_py_tool_exec_fn( |
| 260 | py_fn: Py<PyAny>, |
| 261 | ) -> Box<dyn Fn(Json) -> Pin<Box<dyn Future<Output = FlowResult<Json>> + Send>> + Send + Sync> { |
| 262 | let py_fn = std::sync::Arc::new(py_fn); |
| 263 | Box::new(move |args: Json| { |
| 264 | let py_fn = py_fn.clone(); |
| 265 | Box::pin(async move { |
| 266 | // Call the Python function and check if it returns a coroutine |
| 267 | let outcome: FlowResult< |
| 268 | Result<Json, Pin<Box<dyn Future<Output = PyResult<Py<PyAny>>> + Send>>>, |
| 269 | > = Python::attach(|py| { |
| 270 | let py_args = |
| 271 | json_to_py(py, &args).map_err(|e: PyErr| FlowError::Internal(e.to_string()))?; |
| 272 | let result = py_fn |
| 273 | .call1(py, (py_args,)) |
| 274 | .map_err(|e: PyErr| FlowError::Internal(e.to_string()))?; |
| 275 | |
| 276 | // Detect coroutine by checking for __await__ |
| 277 | let bound = result.bind(py); |
| 278 | if bound.getattr("__await__").is_ok() { |
| 279 | let future = pyo3_async_runtimes::tokio::into_future(result.into_bound(py)) |
| 280 | .map_err(|e| FlowError::Internal(e.to_string()))?; |
| 281 | Ok(Err(Box::pin(future) |
| 282 | as Pin< |
| 283 | Box<dyn Future<Output = PyResult<Py<PyAny>>> + Send>, |
| 284 | >)) |
| 285 | } else { |
| 286 | let json = |
| 287 | py_to_json(bound).map_err(|e: PyErr| FlowError::Internal(e.to_string()))?; |
| 288 | Ok(Ok(json)) |
| 289 | } |
| 290 | }); |
| 291 | |
| 292 | match outcome? { |
| 293 | Ok(json) => Ok(json), |
| 294 | Err(future) => { |
| 295 | let py_result = future |
| 296 | .await |
| 297 | .map_err(|e| FlowError::Internal(e.to_string()))?; |
| 298 | Python::attach(|py| { |
| 299 | py_to_json(py_result.bind(py)) |
| 300 | .map_err(|e: PyErr| FlowError::Internal(e.to_string())) |
| 301 | }) |
| 302 | } |
| 303 | } |
| 304 | }) |
| 305 | }) |
| 306 | } |
| 307 | |
| 308 | /// Python-callable wrapper for the Rust `ToolExecutionNextFn`. |
| 309 | /// |