| 953 | } |
| 954 | |
| 955 | std::string parse_foreach_source(const engine::io::json::Value & step) { |
| 956 | std::string value = workflow_string(step, "foreach"); |
| 957 | if (value.size() >= 3 && value.front() == '$' && value[1] == '{' && value.back() == '}') { |
| 958 | value = value.substr(2, value.size() - 3); |
| 959 | } |
| 960 | const std::string suffix = ".items"; |
| 961 | if (value.size() <= suffix.size() || |
| 962 | value.compare(value.size() - suffix.size(), suffix.size(), suffix) != 0) { |
| 963 | throw std::runtime_error("workflow foreach must reference ${step_id.items}: " + value); |
| 964 | } |
| 965 | return value.substr(0, value.size() - suffix.size()); |
| 966 | } |
| 967 | |
| 968 | void run_model_step_foreach( |
| 969 | const engine::runtime::ModelRegistry & registry, |
| 970 | const engine::io::json::Value & step, |
| 971 | const WorkflowRunOptions & options, |
| 972 | WorkflowContext & context) { |
| 973 | const std::string id = workflow_string(step, "id"); |
| 974 | const std::string source_id = parse_foreach_source(step); |
| 975 | if (source_id == id) { |
| 976 | throw std::runtime_error("workflow foreach cannot reference the current step: " + id); |
| 977 | } |
| 978 | const auto source_it = context.batches.find(source_id); |
| 979 | if (source_it == context.batches.end()) { |
| 980 | throw std::runtime_error("workflow foreach source is not a batch: " + source_id); |
| 981 | } |
| 982 | const auto source_items = source_it->second; |
| 983 | const auto * request_value = step.find("request"); |
| 984 | if (request_value == nullptr || request_value->is_null()) { |
| 985 | throw std::runtime_error("model step requires request object: " + id); |
| 986 | } |
| 987 | |
| 988 | engine::runtime::ModelLoadRequest load_request; |
| 989 | load_request.model_path = resolve_workflow_path(workflow_string(step, "model"), context); |
| 990 | load_request.model_spec_override = options.model_spec_override; |
| 991 | if (const auto family = workflow_optional_string(step, "family")) { |
| 992 | load_request.family_hint = *family; |
| 993 | } |
| 994 | if (const auto config = workflow_optional_string(step, "config")) { |
| 995 | load_request.config_id = *config; |
| 996 | } |
| 997 | if (const auto weight = workflow_optional_string(step, "weight")) { |
| 998 | load_request.weight_id = *weight; |
| 999 | } |
| 1000 | load_request.options = merged_options(options.load_options, step.find("load_options"), context); |
| 1001 | |
| 1002 | engine::runtime::SessionOptions session_options; |
| 1003 | session_options.backend = parse_step_backend(step, options.backend); |
| 1004 | session_options.options = merged_options(options.session_options, step.find("session_options"), context); |
| 1005 | |
| 1006 | const engine::runtime::TaskSpec task_spec{ |
| 1007 | engine::runtime::parse_voice_task_kind(workflow_string(step, "task")), |
| 1008 | engine::runtime::parse_run_mode(workflow_string_or(step, "mode", "offline")), |
| 1009 | }; |
| 1010 | if (task_spec.mode != engine::runtime::RunMode::Offline) { |
| 1011 | throw std::runtime_error("workflow model steps currently require offline mode: " + id); |
| 1012 | } |
no test coverage detected