diff --git a/velvetflow/bindings.py b/velvetflow/bindings.py index 72ce852e..36e5f2cf 100644 --- a/velvetflow/bindings.py +++ b/velvetflow/bindings.py @@ -1037,7 +1037,14 @@ def _resolve(value: Any, path: str = "") -> Any: if stripped_template.startswith("{{") and stripped_template.endswith("}}"): inner = stripped_template[2:-2].strip() try: - return eval_jinja_expression(inner, ctx.build_jinja_context()) + jinja_result = eval_jinja_expression(inner, ctx.build_jinja_context()) + try: + from jinja2.runtime import Undefined # type: ignore[import] + except Exception: # pragma: no cover - fallback when jinja is absent + Undefined = () # type: ignore[assignment] + + if jinja_result is not None and not isinstance(jinja_result, Undefined): + return jinja_result except Exception: pass rendered_inline = _render_json_bindings(normalized_templates) diff --git a/velvetflow/executor/loops.py b/velvetflow/executor/loops.py index fe3d1f14..5ffa73fa 100644 --- a/velvetflow/executor/loops.py +++ b/velvetflow/executor/loops.py @@ -33,10 +33,13 @@ def _apply_loop_exports( aggregates_output: Dict[str, Any], avg_state: Dict[str, Dict[str, float]], default_from_node: Optional[str], - extra_exports: Optional[Mapping[str, Any]], - extra_output: Dict[str, Any], - loop_binding: Optional[BindingContext], + extra_exports: Optional[Mapping[str, Any]] = None, + extra_output: Optional[Dict[str, Any]] = None, + loop_binding: Optional[BindingContext] = None, ) -> None: + if extra_output is None: + extra_output = {} + normalized_items_spec: Optional[Mapping[str, Any]] = None if isinstance(items_spec, list): diff --git a/velvetflow/planner/agent_runtime.py b/velvetflow/planner/agent_runtime.py index 1fa39d4b..d92af37f 100644 --- a/velvetflow/planner/agent_runtime.py +++ b/velvetflow/planner/agent_runtime.py @@ -23,4 +23,51 @@ from openai import OpenAI from pydantic import create_model -from agents import Agent, Runner, function_tool +try: # Prefer the official Agents SDK when available + from agents import Agent, Runner, function_tool +except Exception as import_error: # pragma: no cover - exercised in CI without agents + import functools + import warnings + + warnings.warn( + "Falling back to the built-in Agents shim because the optional " + "'agents' package is missing or incompatible. Install the official " + "package alongside openai>=1.30.0 to enable full agent execution.", + RuntimeWarning, + ) + + class Agent: + def __init__(self, *, name: str, instructions: str, tools: Sequence[Callable[..., Any]], model: str, **_: Any): + self.name = name + self.instructions = instructions + self.tools = list(tools) + self.model = model + + def function_tool(strict_mode: bool = True, **decorator_kwargs: Any) -> Callable[[Callable[..., Any]], Callable[..., Any]]: + def _decorator(func: Callable[..., Any]) -> Callable[..., Any]: + @functools.wraps(func) + def _wrapper(*args: Any, **kwargs: Any) -> Any: + return func(*args, **kwargs) + + # Preserve metadata that downstream code may introspect + _wrapper.__original_function__ = func # type: ignore[attr-defined] + _wrapper.__function_tool_strict_mode__ = strict_mode # type: ignore[attr-defined] + _wrapper.__function_tool_kwargs__ = decorator_kwargs # type: ignore[attr-defined] + return _wrapper + + return _decorator + + class Runner: + @staticmethod + def run_sync(agent: Agent, run_input: Any, *, max_turns: int | None = None) -> None: + raise RuntimeError( + "agents shim does not implement execution. Install the 'agents' package " + "compatible with openai>=1.30.0 to run agents." + ) from import_error + + @staticmethod + async def run(agent: Agent, run_input: Any, *, max_turns: int | None = None) -> None: + raise RuntimeError( + "agents shim does not implement execution. Install the 'agents' package " + "compatible with openai>=1.30.0 to run agents." + ) from import_error