diff --git a/experimental/air/cmd/runupload.go b/experimental/air/cmd/runupload.go index fb9ca00b98..b02077c4f2 100644 --- a/experimental/air/cmd/runupload.go +++ b/experimental/air/cmd/runupload.go @@ -72,6 +72,9 @@ func buildArtifacts(cfg *runConfig, configPath string) ([]uploadItem, error) { {commandScriptName, []byte(*cfg.Command)}, } + // The AI Runtime launcher always reads /requirements.yaml, so always + // upload one: the declared file, a synthesized one from inline dependencies, or + // an empty doc. Without it the task fails at launch. switch reqPath, ok := cfg.requirementsFile(); { case ok: // Resolve a relative requirements path against the config's directory. @@ -84,14 +87,15 @@ func buildArtifacts(cfg *runConfig, configPath string) ([]uploadItem, error) { } items = append(items, uploadItem{requirementsName, data}) default: - if deps, ok := cfg.inlineDependencies(); ok { - version, _ := cfg.runtimeVersion() - data, err := yaml.Marshal(requirementsDoc{Version: version, Dependencies: deps}) - if err != nil { - return nil, fmt.Errorf("failed to synthesize requirements.yaml: %w", err) - } - items = append(items, uploadItem{requirementsName, data}) + // deps is nil when no inline dependencies are set, which marshals to an + // empty dependency list. + deps, _ := cfg.inlineDependencies() + version, _ := cfg.runtimeVersion() + data, err := yaml.Marshal(requirementsDoc{Version: version, Dependencies: deps}) + if err != nil { + return nil, fmt.Errorf("failed to synthesize requirements.yaml: %w", err) } + items = append(items, uploadItem{requirementsName, data}) } if len(cfg.Parameters) > 0 { diff --git a/experimental/air/cmd/runupload_test.go b/experimental/air/cmd/runupload_test.go index 0c87524735..eeb1146099 100644 --- a/experimental/air/cmd/runupload_test.go +++ b/experimental/air/cmd/runupload_test.go @@ -52,9 +52,11 @@ func TestBuildArtifacts_CommandAndConfig(t *testing.T) { items, err := buildArtifacts(cfg, path) require.NoError(t, err) - assert.Equal(t, []string{trainingConfigName, commandScriptName}, itemNames(items)) + // requirements.yaml is always uploaded; with no dependencies it is an empty doc. + assert.Equal(t, []string{trainingConfigName, commandScriptName, requirementsName}, itemNames(items)) assert.Equal(t, minimalConfig, string(items[0].data)) assert.Equal(t, "python train.py", string(items[1].data)) + assert.Equal(t, "dependencies: []\n", string(items[2].data)) } func TestBuildArtifacts_InlineRequirementsAndParameters(t *testing.T) {