diff --git a/agents-api/agents_api/activities/task_steps/__init__.py b/agents-api/agents_api/activities/task_steps/__init__.py index 573884629..0b2d04ce3 100644 --- a/agents-api/agents_api/activities/task_steps/__init__.py +++ b/agents-api/agents_api/activities/task_steps/__init__.py @@ -2,18 +2,7 @@ from .base_evaluate import base_evaluate from .cozo_query_step import cozo_query_step -from .evaluate_step import evaluate_step -from .for_each_step import for_each_step from .get_value_step import get_value_step -from .if_else_step import if_else_step -from .log_step import log_step -from .map_reduce_step import map_reduce_step from .prompt_step import prompt_step from .raise_complete_async import raise_complete_async -from .return_step import return_step -from .set_value_step import set_value_step -from .switch_step import switch_step -from .tool_call_step import tool_call_step from .transition_step import transition_step -from .wait_for_input_step import wait_for_input_step -from .yield_step import yield_step diff --git a/agents-api/agents_api/activities/task_steps/base_evaluate.py b/agents-api/agents_api/activities/task_steps/base_evaluate.py index d87b961d3..d07f2cb6d 100644 --- a/agents-api/agents_api/activities/task_steps/base_evaluate.py +++ b/agents-api/agents_api/activities/task_steps/base_evaluate.py @@ -37,10 +37,14 @@ def __init__(self, error, expression, values): # Recursive evaluation helper function -def _recursive_evaluate(expr, evaluator: SimpleEval): +def _recursive_evaluate(expr, evaluator: SimpleEval, eval_prompt_prefix: str = "$ "): if isinstance(expr, str): try: - return evaluator.eval(expr) + result = expr + if expr.startswith(eval_prompt_prefix): + result = evaluator.eval(expr[len(eval_prompt_prefix) :].strip()) + + return result except Exception as e: if activity.in_activity(): evaluate_error = EvaluateError(e, expr, evaluator.names) @@ -69,6 +73,7 @@ async def base_evaluate( exprs: Any, values: dict[str, Any] = {}, extra_lambda_strs: dict[str, str] | None = None, + eval_prompt_prefix: str = "$_", ) -> Any | list[Any] | dict[str, Any]: input_len = 1 if isinstance(exprs, str) else len(exprs) assert input_len > 0, "exprs must be a non-empty string, list or dict" @@ -100,7 +105,9 @@ async def base_evaluate( evaluator: SimpleEval = get_evaluator(names=values, extra_functions=extra_lambdas) # Recursively evaluate the expression - result = _recursive_evaluate(exprs, evaluator) + result = _recursive_evaluate( + exprs, evaluator, eval_prompt_prefix=eval_prompt_prefix + ) return result diff --git a/agents-api/agents_api/activities/task_steps/evaluate_step.py b/agents-api/agents_api/activities/task_steps/evaluate_step.py deleted file mode 100644 index 06d3c7262..000000000 --- a/agents-api/agents_api/activities/task_steps/evaluate_step.py +++ /dev/null @@ -1,44 +0,0 @@ -from typing import Any - -from beartype import beartype -from temporalio import activity - -from ...activities.utils import simple_eval_dict -from ...common.protocol.tasks import StepContext, StepOutcome -from ...common.storage_handler import auto_blob_store -from ...env import testing - - -@auto_blob_store(deep=True) -@beartype -async def evaluate_step( - context: StepContext, - additional_values: dict[str, Any] = {}, - override_expr: dict[str, str] | None = None, -) -> StepOutcome: - try: - expr = ( - override_expr - if override_expr is not None - else context.current_step.evaluate - ) - - values = context.prepare_for_step(include_remote=True) | additional_values - - output = simple_eval_dict(expr, values) - result = StepOutcome(output=output) - - return result - - except BaseException as e: - activity.logger.error(f"Error in evaluate_step: {e}") - return StepOutcome(error=str(e) or repr(e)) - - -# Note: This is here just for clarity. We could have just imported evaluate_step directly -# They do the same thing, so we dont need to mock the evaluate_step function -mock_evaluate_step = evaluate_step - -evaluate_step = activity.defn(name="evaluate_step")( - evaluate_step if not testing else mock_evaluate_step -) diff --git a/agents-api/agents_api/activities/task_steps/for_each_step.py b/agents-api/agents_api/activities/task_steps/for_each_step.py deleted file mode 100644 index 76c74b3d6..000000000 --- a/agents-api/agents_api/activities/task_steps/for_each_step.py +++ /dev/null @@ -1,36 +0,0 @@ -from beartype import beartype -from temporalio import activity - -from ...autogen.openapi_model import ForeachStep -from ...common.protocol.tasks import ( - StepContext, - StepOutcome, -) -from ...common.storage_handler import auto_blob_store -from ...env import testing -from .base_evaluate import base_evaluate - - -@auto_blob_store(deep=True) -@beartype -async def for_each_step(context: StepContext) -> StepOutcome: - try: - assert isinstance(context.current_step, ForeachStep) - - output = await base_evaluate( - context.current_step.foreach.in_, context.prepare_for_step() - ) - return StepOutcome(output=output) - - except BaseException as e: - activity.logger.error(f"Error in for_each_step: {e}") - return StepOutcome(error=str(e)) - - -# Note: This is here just for clarity. We could have just imported if_else_step directly -# They do the same thing, so we dont need to mock the if_else_step function -mock_if_else_step = for_each_step - -for_each_step = activity.defn(name="for_each_step")( - for_each_step if not testing else mock_if_else_step -) diff --git a/agents-api/agents_api/activities/task_steps/if_else_step.py b/agents-api/agents_api/activities/task_steps/if_else_step.py deleted file mode 100644 index 3d0a9739f..000000000 --- a/agents-api/agents_api/activities/task_steps/if_else_step.py +++ /dev/null @@ -1,40 +0,0 @@ -from beartype import beartype -from temporalio import activity - -from ...autogen.openapi_model import IfElseWorkflowStep -from ...common.protocol.tasks import ( - StepContext, - StepOutcome, -) -from ...common.storage_handler import auto_blob_store -from ...env import testing -from .base_evaluate import base_evaluate - - -@auto_blob_store(deep=True) -@beartype -async def if_else_step(context: StepContext) -> StepOutcome: - # NOTE: This activity is only for logging, so we just evaluate the expression - # Hence, it's a local activity and SHOULD NOT fail - try: - assert isinstance(context.current_step, IfElseWorkflowStep) - - expr: str = context.current_step.if_ - output = await base_evaluate(expr, context.prepare_for_step()) - output: bool = bool(output) - - result = StepOutcome(output=output) - return result - - except BaseException as e: - activity.logger.error(f"Error in if_else_step: {e}") - return StepOutcome(error=str(e)) - - -# Note: This is here just for clarity. We could have just imported if_else_step directly -# They do the same thing, so we dont need to mock the if_else_step function -mock_if_else_step = if_else_step - -if_else_step = activity.defn(name="if_else_step")( - if_else_step if not testing else mock_if_else_step -) diff --git a/agents-api/agents_api/activities/task_steps/log_step.py b/agents-api/agents_api/activities/task_steps/log_step.py deleted file mode 100644 index 527a6f45f..000000000 --- a/agents-api/agents_api/activities/task_steps/log_step.py +++ /dev/null @@ -1,41 +0,0 @@ -from beartype import beartype -from temporalio import activity - -from ...autogen.openapi_model import LogStep -from ...common.protocol.tasks import ( - StepContext, - StepOutcome, -) -from ...common.storage_handler import auto_blob_store -from ...common.utils.template import render_template -from ...env import testing - - -@auto_blob_store(deep=True) -@beartype -async def log_step(context: StepContext) -> StepOutcome: - # NOTE: This activity is only for logging, so we just evaluate the expression - # Hence, it's a local activity and SHOULD NOT fail - try: - assert isinstance(context.current_step, LogStep) - - template: str = context.current_step.log - output = await render_template( - template, - context.prepare_for_step(include_remote=True), - skip_vars=["developer_id"], - ) - - result = StepOutcome(output=output) - return result - - except BaseException as e: - activity.logger.error(f"Error in log_step: {e}") - return StepOutcome(error=str(e)) - - -# Note: This is here just for clarity. We could have just imported log_step directly -# They do the same thing, so we dont need to mock the log_step function -mock_log_step = log_step - -log_step = activity.defn(name="log_step")(log_step if not testing else mock_log_step) diff --git a/agents-api/agents_api/activities/task_steps/map_reduce_step.py b/agents-api/agents_api/activities/task_steps/map_reduce_step.py deleted file mode 100644 index 8237e37af..000000000 --- a/agents-api/agents_api/activities/task_steps/map_reduce_step.py +++ /dev/null @@ -1,39 +0,0 @@ -import logging - -from beartype import beartype -from temporalio import activity - -from ...autogen.openapi_model import MapReduceStep -from ...common.protocol.tasks import ( - StepContext, - StepOutcome, -) -from ...common.storage_handler import auto_blob_store -from ...env import testing -from .base_evaluate import base_evaluate - - -@auto_blob_store(deep=True) -@beartype -async def map_reduce_step(context: StepContext) -> StepOutcome: - try: - assert isinstance(context.current_step, MapReduceStep) - - output = await base_evaluate( - context.current_step.over, context.prepare_for_step() - ) - - return StepOutcome(output=output) - - except BaseException as e: - logging.error(f"Error in map_reduce_step: {e}") - return StepOutcome(error=str(e)) - - -# Note: This is here just for clarity. We could have just imported if_else_step directly -# They do the same thing, so we dont need to mock the if_else_step function -mock_if_else_step = map_reduce_step - -map_reduce_step = activity.defn(name="map_reduce_step")( - map_reduce_step if not testing else mock_if_else_step -) diff --git a/agents-api/agents_api/activities/task_steps/return_step.py b/agents-api/agents_api/activities/task_steps/return_step.py deleted file mode 100644 index e00ad6d20..000000000 --- a/agents-api/agents_api/activities/task_steps/return_step.py +++ /dev/null @@ -1,37 +0,0 @@ -from beartype import beartype -from temporalio import activity - -from ...autogen.openapi_model import ReturnStep -from ...common.protocol.tasks import ( - StepContext, - StepOutcome, -) -from ...common.storage_handler import auto_blob_store -from ...env import testing -from .base_evaluate import base_evaluate - - -@auto_blob_store(deep=True) -@beartype -async def return_step(context: StepContext) -> StepOutcome: - try: - assert isinstance(context.current_step, ReturnStep) - - exprs: dict[str, str] = context.current_step.return_ - output = await base_evaluate(exprs, context.prepare_for_step()) - - result = StepOutcome(output=output) - return result - - except BaseException as e: - activity.logger.error(f"Error in log_step: {e}") - return StepOutcome(error=str(e)) - - -# Note: This is here just for clarity. We could have just imported return_step directly -# They do the same thing, so we dont need to mock the return_step function -mock_return_step = return_step - -return_step = activity.defn(name="return_step")( - return_step if not testing else mock_return_step -) diff --git a/agents-api/agents_api/activities/task_steps/set_value_step.py b/agents-api/agents_api/activities/task_steps/set_value_step.py deleted file mode 100644 index dd2553abd..000000000 --- a/agents-api/agents_api/activities/task_steps/set_value_step.py +++ /dev/null @@ -1,41 +0,0 @@ -from typing import Any - -from beartype import beartype -from temporalio import activity - -from ...activities.utils import simple_eval_dict -from ...common.protocol.tasks import StepContext, StepOutcome -from ...common.storage_handler import auto_blob_store -from ...env import testing - - -# TODO: We should use this step to signal to the parent workflow and set the value on the workflow context -# SCRUM-2 -@auto_blob_store(deep=True) -@beartype -async def set_value_step( - context: StepContext, - additional_values: dict[str, Any] = {}, - override_expr: dict[str, str] | None = None, -) -> StepOutcome: - try: - expr = override_expr if override_expr is not None else context.current_step.set - - values = context.prepare_for_step() | additional_values - output = simple_eval_dict(expr, values) - result = StepOutcome(output=output) - - return result - - except BaseException as e: - activity.logger.error(f"Error in set_value_step: {e}") - return StepOutcome(error=str(e) or repr(e)) - - -# Note: This is here just for clarity. We could have just imported set_value_step directly -# They do the same thing, so we dont need to mock the set_value_step function -mock_set_value_step = set_value_step - -set_value_step = activity.defn(name="set_value_step")( - set_value_step if not testing else mock_set_value_step -) diff --git a/agents-api/agents_api/activities/task_steps/switch_step.py b/agents-api/agents_api/activities/task_steps/switch_step.py deleted file mode 100644 index 95c136890..000000000 --- a/agents-api/agents_api/activities/task_steps/switch_step.py +++ /dev/null @@ -1,45 +0,0 @@ -from beartype import beartype -from temporalio import activity - -from ...autogen.openapi_model import SwitchStep -from ...common.protocol.tasks import ( - StepContext, - StepOutcome, -) -from ...common.storage_handler import auto_blob_store -from ...env import testing -from ..utils import get_evaluator - - -@auto_blob_store(deep=True) -@beartype -async def switch_step(context: StepContext) -> StepOutcome: - try: - assert isinstance(context.current_step, SwitchStep) - - # Assume that none of the cases evaluate to truthy - output: int = -1 - cases: list[str] = [c.case for c in context.current_step.switch] - - evaluator = get_evaluator(names=context.prepare_for_step()) - - for i, case in enumerate(cases): - result = evaluator.eval(case) - - if result: - output = i - break - - result = StepOutcome(output=output) - return result - - except BaseException as e: - activity.logger.error(f"Error in switch_step: {e}") - return StepOutcome(error=str(e)) - - -mock_switch_step = switch_step - -switch_step = activity.defn(name="switch_step")( - switch_step if not testing else mock_switch_step -) diff --git a/agents-api/agents_api/activities/task_steps/tool_call_step.py b/agents-api/agents_api/activities/task_steps/tool_call_step.py index 7992de19e..9e0775fe1 100644 --- a/agents-api/agents_api/activities/task_steps/tool_call_step.py +++ b/agents-api/agents_api/activities/task_steps/tool_call_step.py @@ -44,27 +44,3 @@ def construct_tool_call( "id": call_id, "type": tool.type, } - - -@activity.defn -@auto_blob_store(deep=True) -@beartype -async def tool_call_step(context: StepContext) -> StepOutcome: - assert isinstance(context.current_step, ToolCallStep) - - tools: list[Tool] = context.tools - tool_name = context.current_step.tool - - tool = next((t for t in tools if t.name == tool_name), None) - - if tool is None: - raise ApplicationError(f"Tool {tool_name} not found in the toolset") - - arguments = await base_evaluate( - context.current_step.arguments, context.prepare_for_step() - ) - - call_id = generate_call_id() - tool_call = construct_tool_call(tool, arguments, call_id) - - return StepOutcome(output=tool_call) diff --git a/agents-api/agents_api/activities/task_steps/wait_for_input_step.py b/agents-api/agents_api/activities/task_steps/wait_for_input_step.py deleted file mode 100644 index db3e41055..000000000 --- a/agents-api/agents_api/activities/task_steps/wait_for_input_step.py +++ /dev/null @@ -1,32 +0,0 @@ -from beartype import beartype -from temporalio import activity - -from ...autogen.openapi_model import WaitForInputStep -from ...common.protocol.tasks import StepContext, StepOutcome -from ...common.storage_handler import auto_blob_store -from ...env import testing -from .base_evaluate import base_evaluate - - -@auto_blob_store(deep=True) -@beartype -async def wait_for_input_step(context: StepContext) -> StepOutcome: - try: - assert isinstance(context.current_step, WaitForInputStep) - - exprs = context.current_step.wait_for_input.info - output = await base_evaluate(exprs, context.prepare_for_step()) - - result = StepOutcome(output=output) - return result - - except BaseException as e: - activity.logger.error(f"Error in wait_for_input_step: {e}") - return StepOutcome(error=str(e)) - - -mock_wait_for_input_step = wait_for_input_step - -wait_for_input_step = activity.defn(name="wait_for_input_step")( - wait_for_input_step if not testing else mock_wait_for_input_step -) diff --git a/agents-api/agents_api/activities/task_steps/yield_step.py b/agents-api/agents_api/activities/task_steps/yield_step.py deleted file mode 100644 index 8480beb93..000000000 --- a/agents-api/agents_api/activities/task_steps/yield_step.py +++ /dev/null @@ -1,49 +0,0 @@ -from typing import Callable - -from beartype import beartype -from temporalio import activity - -from ...autogen.openapi_model import TransitionTarget, YieldStep -from ...common.protocol.tasks import StepContext, StepOutcome -from ...common.storage_handler import auto_blob_store -from ...env import testing -from .base_evaluate import base_evaluate - - -@auto_blob_store(deep=True) -@beartype -async def yield_step(context: StepContext) -> StepOutcome: - try: - assert isinstance(context.current_step, YieldStep) - - all_workflows = context.execution_input.task.workflows - workflow = context.current_step.workflow - exprs = context.current_step.arguments - - assert workflow in [ - wf.name for wf in all_workflows - ], f"Workflow {workflow} not found in task" - - # Evaluate the expressions in the arguments - arguments = await base_evaluate(exprs, context.prepare_for_step()) - - # Transition to the first step of that workflow - transition_target = TransitionTarget( - workflow=workflow, - step=0, - ) - - return StepOutcome(output=arguments, transition_to=("step", transition_target)) - - except BaseException as e: - activity.logger.error(f"Error in yield_step: {e}") - return StepOutcome(error=str(e)) - - -# Note: This is here just for clarity. We could have just imported yield_step directly -# They do the same thing, so we dont need to mock the yield_step function -mock_yield_step: Callable[[StepContext], StepOutcome] = yield_step - -yield_step: Callable[[StepContext], StepOutcome] = activity.defn(name="yield_step")( - yield_step if not testing else mock_yield_step -) diff --git a/agents-api/agents_api/autogen/Tasks.py b/agents-api/agents_api/autogen/Tasks.py index b9212d8cb..faf045edf 100644 --- a/agents-api/agents_api/autogen/Tasks.py +++ b/agents-api/agents_api/autogen/Tasks.py @@ -236,7 +236,7 @@ class EvaluateStep(BaseModel): """ The label of this step for referencing it from other steps """ - evaluate: dict[str, list[str] | dict[str, str] | list[dict[str, str]] | str] + evaluate: dict[str, Any] """ The expression to evaluate """ @@ -837,10 +837,7 @@ class ReturnStep(BaseModel): """ The label of this step for referencing it from other steps """ - return_: Annotated[ - dict[str, list[str] | dict[str, str] | list[dict[str, str]] | str], - Field(alias="return"), - ] + return_: Annotated[dict[str, Any], Field(alias="return")] """ The value to return """ @@ -860,7 +857,7 @@ class SetStep(BaseModel): """ The label of this step for referencing it from other steps """ - set: dict[str, str] + set: dict[str, Any] """ The value to set """ @@ -1046,25 +1043,7 @@ class ToolCallStep(BaseModel): """ The tool to run """ - arguments: ( - dict[ - str, - dict[str, list[str] | dict[str, str] | list[dict[str, str]] | str] - | list[dict[str, list[str] | dict[str, str] | list[dict[str, str]] | str]] - | str, - ] - | list[ - dict[ - str, - dict[str, list[str] | dict[str, str] | list[dict[str, str]] | str] - | list[ - dict[str, list[str] | dict[str, str] | list[dict[str, str]] | str] - ] - | str, - ] - ] - | Literal["_"] - ) = "_" + arguments: dict[str, Any] | Literal["_"] = "_" """ The input parameters for the tool (defaults to last step output) """ @@ -1168,7 +1147,7 @@ class WaitForInputInfo(BaseModel): model_config = ConfigDict( populate_by_name=True, ) - info: dict[str, list[str] | dict[str, str] | list[dict[str, str]] | str] + info: dict[str, Any] """ Any additional info or data """ @@ -1213,10 +1192,7 @@ class YieldStep(BaseModel): The subworkflow to run. VALIDATION: Should resolve to a defined subworkflow. """ - arguments: ( - dict[str, list[str] | dict[str, str] | list[dict[str, str]] | str] - | Literal["_"] - ) = "_" + arguments: dict[str, Any] | Literal["_"] = "_" """ The input parameters for the subworkflow (defaults to last step output) """ diff --git a/agents-api/agents_api/routers/sessions/chat.py b/agents-api/agents_api/routers/sessions/chat.py index f4cc7420e..fdfd84adb 100644 --- a/agents-api/agents_api/routers/sessions/chat.py +++ b/agents-api/agents_api/routers/sessions/chat.py @@ -163,7 +163,9 @@ async def chat( k: v for k, v in function.model_dump().items() if k not in ["name", "type"] - }, + } + if function is not None + else {}, }, } formatted_tools.append(tool) diff --git a/agents-api/agents_api/workflows/task_execution/__init__.py b/agents-api/agents_api/workflows/task_execution/__init__.py index 5d0300e8f..aefa0e5c5 100644 --- a/agents-api/agents_api/workflows/task_execution/__init__.py +++ b/agents-api/agents_api/workflows/task_execution/__init__.py @@ -16,6 +16,11 @@ from ...activities.execute_integration import execute_integration from ...activities.execute_system import execute_system from ...activities.sync_items_remote import load_inputs_remote, save_inputs_remote + from ...activities.task_steps.base_evaluate import base_evaluate + from ...activities.task_steps.tool_call_step import ( + construct_tool_call, + generate_call_id, + ) from ...autogen.openapi_model import ( ApiCallDef, BaseIntegrationDef, @@ -35,8 +40,10 @@ SleepStep, SwitchStep, SystemDef, + Tool, ToolCallStep, TransitionTarget, + WaitForInputInfo, WaitForInputStep, WorkflowStep, YieldStep, @@ -49,6 +56,8 @@ StepOutcome, ) from ...common.retry_policies import DEFAULT_RETRY_POLICY + from ...common.storage_handler import auto_blob_store + from ...common.utils.template import render_template from ...env import debug, temporal_schedule_to_close_timeout, testing from .helpers import ( continue_as_child, @@ -88,18 +97,6 @@ # Mapping of step types to their corresponding activities STEP_TO_ACTIVITY = { PromptStep: task_steps.prompt_step, - ToolCallStep: task_steps.tool_call_step, - WaitForInputStep: task_steps.wait_for_input_step, - SwitchStep: task_steps.switch_step, - LogStep: task_steps.log_step, - EvaluateStep: task_steps.evaluate_step, - ReturnStep: task_steps.return_step, - YieldStep: task_steps.yield_step, - IfElseWorkflowStep: task_steps.if_else_step, - ForeachStep: task_steps.for_each_step, - MapReduceStep: task_steps.map_reduce_step, - SetStep: task_steps.set_value_step, - # GetStep: task_steps.get_value_step, } @@ -118,6 +115,78 @@ # Probably can be implemented much more efficiently +@auto_blob_store(deep=True) +async def _eval_step_exprs(context: StepContext, step_type: WorkflowStep): + expr, output, transition_to = None, None, None + + match step_type: + case ForeachStep(foreach=ForeachDo(in_=in_)): + expr = in_ + case IfElseWorkflowStep(if_=if_): + expr = if_ + case ReturnStep(return_=return_): + expr = return_ + case WaitForInputStep(wait_for_input=WaitForInputInfo(info=info)): + expr = info + case EvaluateStep(evaluate=evaluate): + expr = evaluate + case MapReduceStep(over=over): + expr = over + case SetStep(set=set): + expr = set + case LogStep(log=log): + output = await render_template( + log, + context.prepare_for_step(include_remote=True), + skip_vars=["developer_id"], + ) + case SwitchStep(switch=switch): + output: int = -1 + cases: list[str] = [c.case for c in switch] + for i, case in enumerate(cases): + result = await base_evaluate(case, context.prepare_for_step()) + + if result: + output = i + break + case ToolCallStep(arguments=arguments): + tools: list[Tool] = context.tools + tool_name = context.current_step.tool + + tool = next((t for t in tools if t.name == tool_name), None) + + if tool is None: + raise ApplicationError(f"Tool {tool_name} not found in the toolset") + + arguments = await base_evaluate(arguments, context.prepare_for_step()) + + call_id = generate_call_id() + output = construct_tool_call(tool, arguments, call_id) + case YieldStep(arguments=arguments, workflow=workflow): + assert isinstance(context.current_step, YieldStep) + + all_workflows = context.execution_input.task.workflows + + assert workflow in [ + wf.name for wf in all_workflows + ], f"Workflow {workflow} not found in task" + + # Evaluate the expressions in the arguments + output = await base_evaluate(arguments, context.prepare_for_step()) + + # Transition to the first step of that workflow + transition_target = TransitionTarget( + workflow=workflow, + step=0, + ) + transition_to = ("step", transition_target) + + if expr is not None: + output = await base_evaluate(expr, context.prepare_for_step()) + + return StepOutcome(output=output, transition_to=transition_to) + + # Main workflow definition @workflow.defn class TaskExecutionWorkflow: @@ -170,8 +239,8 @@ async def run( outcome = None - if activity: - try: + try: + if activity: outcome = await workflow.execute_activity( activity, context, @@ -186,12 +255,17 @@ async def run( workflow.logger.debug( f"Step {context.cursor.step} completed successfully" ) - - except Exception as e: - workflow.logger.error(f"Error in step {context.cursor.step}: {str(e)}") - await transition(context, type="error", output=str(e)) - raise ApplicationError(f"Activity {activity} threw error: {e}") from e - + else: + outcome = await _eval_step_exprs(context, step_type) + except Exception as e: + workflow.logger.error(f"Error in step {context.cursor.step}: {str(e)}") + await transition(context, type="error", output=str(e)) + err_msg = ( + f"Activity {activity} threw error: {e}" + if activity + else f"Step {context.cursor.step} threw error: {e}" + ) + raise ApplicationError(err_msg) from e # --- # 3. Then, based on the outcome and step type, decide what to do next diff --git a/integrations-service/integrations/autogen/Tasks.py b/integrations-service/integrations/autogen/Tasks.py index 8e98caaab..fceded307 100644 --- a/integrations-service/integrations/autogen/Tasks.py +++ b/integrations-service/integrations/autogen/Tasks.py @@ -237,7 +237,7 @@ class EvaluateStep(BaseModel): """ The label of this step for referencing it from other steps """ - evaluate: dict[str, list[str] | dict[str, str] | list[dict[str, str]] | str] + evaluate: dict[str, Any] """ The expression to evaluate """ @@ -838,10 +838,7 @@ class ReturnStep(BaseModel): """ The label of this step for referencing it from other steps """ - return_: Annotated[ - dict[str, list[str] | dict[str, str] | list[dict[str, str]] | str], - Field(alias="return"), - ] + return_: Annotated[dict[str, Any], Field(alias="return")] """ The value to return """ @@ -861,7 +858,7 @@ class SetStep(BaseModel): """ The label of this step for referencing it from other steps """ - set: dict[str, str] + set: dict[str, Any] """ The value to set """ @@ -1047,25 +1044,7 @@ class ToolCallStep(BaseModel): """ The tool to run """ - arguments: ( - dict[ - str, - dict[str, list[str] | dict[str, str] | list[dict[str, str]] | str] - | list[dict[str, list[str] | dict[str, str] | list[dict[str, str]] | str]] - | str, - ] - | list[ - dict[ - str, - dict[str, list[str] | dict[str, str] | list[dict[str, str]] | str] - | list[ - dict[str, list[str] | dict[str, str] | list[dict[str, str]] | str] - ] - | str, - ] - ] - | Literal["_"] - ) = "_" + arguments: dict[str, Any] | Literal["_"] = "_" """ The input parameters for the tool (defaults to last step output) """ @@ -1169,7 +1148,7 @@ class WaitForInputInfo(BaseModel): model_config = ConfigDict( populate_by_name=True, ) - info: dict[str, list[str] | dict[str, str] | list[dict[str, str]] | str] + info: dict[str, Any] """ Any additional info or data """ @@ -1214,10 +1193,7 @@ class YieldStep(BaseModel): The subworkflow to run. VALIDATION: Should resolve to a defined subworkflow. """ - arguments: ( - dict[str, list[str] | dict[str, str] | list[dict[str, str]] | str] - | Literal["_"] - ) = "_" + arguments: dict[str, Any] | Literal["_"] = "_" """ The input parameters for the subworkflow (defaults to last step output) """ diff --git a/typespec/tasks/steps.tsp b/typespec/tasks/steps.tsp index a7c877401..a251638fb 100644 --- a/typespec/tasks/steps.tsp +++ b/typespec/tasks/steps.tsp @@ -28,14 +28,6 @@ alias TypedExpression = PyExpression; /** A python expression that takes an accumulator `results` and an input item `_` and reduces them. */ alias ReduceExpression> = TypedExpression; -/** A string->string object where the values are python expressions that get evaluated to give a final object. */ -alias ExpressionObject = Record | TypedExpression[] | Record> | Record>[]>; - -alias NestedExpression = Record | ExpressionObject | ExpressionObject[]>; - -/** Nested expression object. */ -alias NestedExpressionObject = NestedExpression | NestedExpression[]; - @discriminator("kind_") model BaseWorkflowStep { /** The kind of step */ @@ -88,7 +80,7 @@ model ToolCallStepDef { tool: validPythonIdentifier; /** The input parameters for the tool (defaults to last step output) */ - arguments: NestedExpressionObject | "_" = "_"; + arguments: Record | "_" = "_"; } model PromptStep extends BaseWorkflowStep<"prompt"> { @@ -134,7 +126,7 @@ model EvaluateStep extends BaseWorkflowStep<"evaluate"> { model EvaluateStepDef { /** The expression to evaluate */ - evaluate: ExpressionObject; + evaluate: Record; } model WaitForInputStep extends BaseWorkflowStep<"wait_for_input"> { @@ -146,7 +138,7 @@ model WaitForInputStep extends BaseWorkflowStep<"wait_for_input"> { model WaitForInputInfo { /** Any additional info or data */ - info: ExpressionObject; + info: Record; } model WaitForInputStepDef { @@ -191,7 +183,7 @@ model SetStep extends BaseWorkflowStep<"set"> { model SetStepDef { /** The value to set */ - set: Record>; + set: Record; } /////////////////////// @@ -317,7 +309,7 @@ model YieldStepDef { workflow: string; /** The input parameters for the subworkflow (defaults to last step output) */ - arguments: ExpressionObject | "_" = "_"; + arguments: Record | "_" = "_"; } model ErrorWorkflowStep extends BaseWorkflowStep<"error"> { @@ -375,5 +367,5 @@ model ReturnStep extends BaseWorkflowStep<"return"> { model ReturnStepDef { /** The value to return */ - `return`: ExpressionObject; + `return`: Record; } diff --git a/typespec/tsp-output/@typespec/openapi3/openapi-1.0.0.yaml b/typespec/tsp-output/@typespec/openapi3/openapi-1.0.0.yaml index 5c9a57c4c..0b7971de4 100644 --- a/typespec/tsp-output/@typespec/openapi3/openapi-1.0.0.yaml +++ b/typespec/tsp-output/@typespec/openapi3/openapi-1.0.0.yaml @@ -4629,20 +4629,7 @@ components: readOnly: true evaluate: type: object - additionalProperties: - anyOf: - - $ref: '#/components/schemas/Common.PyExpression' - - type: array - items: - $ref: '#/components/schemas/Common.PyExpression' - - type: object - additionalProperties: - $ref: '#/components/schemas/Common.PyExpression' - - type: array - items: - type: object - additionalProperties: - $ref: '#/components/schemas/Common.PyExpression' + additionalProperties: {} description: The expression to evaluate allOf: - type: object @@ -5608,20 +5595,7 @@ components: readOnly: true return: type: object - additionalProperties: - anyOf: - - $ref: '#/components/schemas/Common.PyExpression' - - type: array - items: - $ref: '#/components/schemas/Common.PyExpression' - - type: object - additionalProperties: - $ref: '#/components/schemas/Common.PyExpression' - - type: array - items: - type: object - additionalProperties: - $ref: '#/components/schemas/Common.PyExpression' + additionalProperties: {} description: The value to return allOf: - type: object @@ -5654,8 +5628,7 @@ components: readOnly: true set: type: object - additionalProperties: - $ref: '#/components/schemas/Common.PyExpression' + additionalProperties: {} description: The value to set allOf: - type: object @@ -6039,79 +6012,7 @@ components: arguments: anyOf: - type: object - additionalProperties: - anyOf: - - $ref: '#/components/schemas/Common.PyExpression' - - type: object - additionalProperties: - anyOf: - - $ref: '#/components/schemas/Common.PyExpression' - - type: array - items: - $ref: '#/components/schemas/Common.PyExpression' - - type: object - additionalProperties: - $ref: '#/components/schemas/Common.PyExpression' - - type: array - items: - type: object - additionalProperties: - $ref: '#/components/schemas/Common.PyExpression' - - type: array - items: - type: object - additionalProperties: - anyOf: - - $ref: '#/components/schemas/Common.PyExpression' - - type: array - items: - $ref: '#/components/schemas/Common.PyExpression' - - type: object - additionalProperties: - $ref: '#/components/schemas/Common.PyExpression' - - type: array - items: - type: object - additionalProperties: - $ref: '#/components/schemas/Common.PyExpression' - - type: array - items: - type: object - additionalProperties: - anyOf: - - $ref: '#/components/schemas/Common.PyExpression' - - type: object - additionalProperties: - anyOf: - - $ref: '#/components/schemas/Common.PyExpression' - - type: array - items: - $ref: '#/components/schemas/Common.PyExpression' - - type: object - additionalProperties: - $ref: '#/components/schemas/Common.PyExpression' - - type: array - items: - type: object - additionalProperties: - $ref: '#/components/schemas/Common.PyExpression' - - type: array - items: - type: object - additionalProperties: - anyOf: - - $ref: '#/components/schemas/Common.PyExpression' - - type: array - items: - $ref: '#/components/schemas/Common.PyExpression' - - type: object - additionalProperties: - $ref: '#/components/schemas/Common.PyExpression' - - type: array - items: - type: object - additionalProperties: - $ref: '#/components/schemas/Common.PyExpression' + additionalProperties: {} - type: string enum: - _ @@ -6354,20 +6255,7 @@ components: properties: info: type: object - additionalProperties: - anyOf: - - $ref: '#/components/schemas/Common.PyExpression' - - type: array - items: - $ref: '#/components/schemas/Common.PyExpression' - - type: object - additionalProperties: - $ref: '#/components/schemas/Common.PyExpression' - - type: array - items: - type: object - additionalProperties: - $ref: '#/components/schemas/Common.PyExpression' + additionalProperties: {} description: Any additional info or data Tasks.WaitForInputStep: type: object @@ -6423,20 +6311,7 @@ components: arguments: anyOf: - type: object - additionalProperties: - anyOf: - - $ref: '#/components/schemas/Common.PyExpression' - - type: array - items: - $ref: '#/components/schemas/Common.PyExpression' - - type: object - additionalProperties: - $ref: '#/components/schemas/Common.PyExpression' - - type: array - items: - type: object - additionalProperties: - $ref: '#/components/schemas/Common.PyExpression' + additionalProperties: {} - type: string enum: - _