| 577 | } |
| 578 | if (fallback.threads <= 0) { |
| 579 | throw std::runtime_error("workflow step threads must be positive"); |
| 580 | } |
| 581 | return fallback; |
| 582 | } |
| 583 | |
| 584 | void run_model_step_impl( |
| 585 | const engine::runtime::ModelRegistry & registry, |
| 586 | const engine::io::json::Value & step, |
| 587 | const WorkflowRunOptions & options, |
| 588 | WorkflowContext & context) { |
| 589 | const std::string id = workflow_string(step, "id"); |
| 590 | const auto request_value = step.find("request"); |
| 591 | if (request_value == nullptr || request_value->is_null()) { |
| 592 | throw std::runtime_error("model step requires request object: " + id); |
| 593 | } |
| 594 | |
| 595 | engine::runtime::ModelLoadRequest load_request; |
| 596 | load_request.model_path = resolve_workflow_path(workflow_string(step, "model"), context); |
| 597 | load_request.model_spec_override = options.model_spec_override; |
| 598 | if (const auto family = workflow_optional_string(step, "family")) { |
| 599 | load_request.family_hint = *family; |
| 600 | } |
| 601 | if (const auto config = workflow_optional_string(step, "config")) { |
| 602 | load_request.config_id = *config; |
| 603 | } |
| 604 | if (const auto weight = workflow_optional_string(step, "weight")) { |
| 605 | load_request.weight_id = *weight; |
| 606 | } |
| 607 | load_request.options = merged_options(options.load_options, step.find("load_options"), context); |
| 608 | |
| 609 | engine::runtime::SessionOptions session_options; |
| 610 | session_options.backend = parse_step_backend(step, options.backend); |
| 611 | session_options.options = merged_options(options.session_options, step.find("session_options"), context); |
| 612 | |
| 613 | const engine::runtime::TaskSpec task_spec{ |
| 614 | engine::runtime::parse_voice_task_kind(workflow_string(step, "task")), |
| 615 | engine::runtime::parse_run_mode(workflow_string_or(step, "mode", "offline")), |
| 616 | }; |
| 617 | if (task_spec.mode != engine::runtime::RunMode::Offline) { |
| 618 | throw std::runtime_error("workflow model steps currently require offline mode: " + id); |
| 619 | } |
| 620 | |
| 621 | auto model = registry.load(load_request); |
| 622 | auto session = model->create_task_session(task_spec, session_options); |
| 623 | auto * offline = dynamic_cast<engine::runtime::IOfflineVoiceTaskSession *>(session.get()); |
| 624 | if (offline == nullptr) { |
| 625 | throw std::runtime_error("workflow model step does not support offline execution: " + id); |
| 626 | } |
| 627 | |
| 628 | const auto expanded_request = expanded_json(*request_value, context); |
| 629 | auto request = minitts::cli::build_request_from_json(expanded_request, context.workflow_dir); |
| 630 | const auto text_out_value = workflow_optional_string(step, "text_out"); |
| 631 | const auto words_out_value = workflow_optional_string(step, "words_out"); |
| 632 | std::optional<std::filesystem::path> text_out; |
| 633 | std::optional<std::filesystem::path> words_out; |
| 634 | if (text_out_value.has_value()) { |
| 635 | text_out = resolve_workflow_path(*text_out_value, context); |
| 636 | } |
no test coverage detected