Skip to content

Commit 9dc600d

Browse files
author
Farhan Nawaz
committed
fix: add support for invoke command
1 parent aaa8d4f commit 9dc600d

14 files changed

Lines changed: 627 additions & 50 deletions

File tree

cli/azd/extensions/azure.ai.rle/README.md

Lines changed: 62 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,20 @@ Install:
1010

1111
- Azure Developer CLI (`azd`): https://learn.microsoft.com/azure/developer/azure-developer-cli/install-azd
1212
- Go: https://go.dev/doc/install
13-
- Git: https://git-scm.com/downloads
13+
- Git, for local development and for fetching the managed Loom recipe: https://git-scm.com/downloads
14+
- Python: https://www.python.org/downloads/
15+
- uv, for running the Loom training recipe: https://docs.astral.sh/uv/getting-started/installation/
1416

1517
Verify:
1618

1719
```powershell
1820
azd version
1921
go version
2022
git --version
23+
python --version
2124
```
2225

23-
`az login` is not required for the current `init`/`deploy` flow because deploy calls the RLE control plane directly. If the control plane later requires auth, set `RLE_BEARER_TOKEN` before deploy.
26+
`az login` is not required for the current `init`/`deploy` flow because deploy calls the RLE control plane directly.
2427

2528
### 2. Check out the branch
2629

@@ -33,12 +36,18 @@ cd cli\azd\extensions\azure.ai.rle
3336
### 3. Configure the RLE control plane
3437

3538
```powershell
36-
$env:RLE_ENDPOINT = "https://rle-controlplane.orangeground-ba9696de.eastus2.azurecontainerapps.io"
37-
$env:RLE_PROJECT_NAME = "demo-3"
39+
$env:RLE_ENDPOINT = "http://localhost:5000"
3840
$env:RLE_ACR_IMAGE = "devrle.azurecr.io/coding_env:latest"
3941
```
4042

41-
To target a local control plane instead, set `RLE_ENDPOINT` to `http://localhost:5000`.
43+
`http://localhost:5000` is also the built-in default, so you can omit `RLE_ENDPOINT` when using a local RLE control plane.
44+
`RLE_ACR_IMAGE` is required by deploy and is expanded from the generated `rle.yaml`.
45+
46+
For `invoke`, provide the Azure AI project endpoint as a parameter:
47+
48+
```powershell
49+
az login
50+
```
4251

4352
### 4. Install the extension into azd
4453

@@ -78,21 +87,63 @@ azd extension install azure.ai.rle --source local --force
7887
azd ai rle init code_rl
7988
```
8089

81-
Init creates a local session folder named `code_rl`, including an OpenEnv-style FastAPI package, `Dockerfile`, and `rle.yaml`.
90+
Init creates a local session folder named `code_rl`, including an OpenEnv-style FastAPI package, `Dockerfile`, `rle.yaml`, and azd-managed dependencies under `.azd-rle\deps`.
8291

8392
Deploy from the session folder:
8493

8594
```powershell
8695
cd .\code_rl
87-
azd ai rle deploy
96+
azd ai rle deploy --project omi-build-demo-uae
8897
```
8998

90-
Deploy creates or updates the RLE environment and saves the environment id/version locally.
99+
Deploy creates or updates the RLE environment and saves the project plus environment id/version locally in `.azd-rle.json`.
91100

92-
### 6. Training placeholder
101+
### 6. Run Loom training
93102

94103
```powershell
95-
azd ai rle invoke
104+
azd ai rle invoke `
105+
--recipe code_rl_with_rle `
106+
--project-endpoint "https://omi-build-demo-uae.services.ai.azure.com/api/projects/omi-build-demo-uae"
96107
```
97108

98-
This command is a placeholder for now. Later, it will trigger the actual training job for the deployed RLE environment.
109+
Invoke runs the selected Loom recipe's `train_azure.py` entrypoint with values from `.azd-rle.json`.
110+
It passes the deployed RLE environment id, project, and control-plane endpoint to the Loom recipe,
111+
which uses `rle_sdk` to lease sandboxes and call `reset`/`step` during training.
112+
113+
Invoke fetches Loom branch `code_rl_with_rle` into `.azd-rle\recipes\loom`
114+
and uses the RLE SDK wheel copied by `init`. You do not need a separate local Loom checkout
115+
or a separately installed RLE SDK package. The managed Git checkout is shallow, single-branch,
116+
and tagless (`--depth 1 --single-branch --no-tags`). In the future this recipe dependency can
117+
move from Git to a published package.
118+
119+
After fetching Loom, invoke patches only the managed copy of `loom-cookbook\pyproject.toml`
120+
so `uv` resolves `azure-ai-finetuning-sessions` from the fetched Loom checkout and `rle-sdk`
121+
from `.azd-rle\deps`.
122+
123+
The default invoke settings are:
124+
125+
```text
126+
num_tasks=4
127+
model_name=Qwen/Qwen3-32B
128+
renderer_name=qwen3_disable_thinking
129+
max_tokens=1200
130+
lora_rank=32
131+
group_size=4
132+
groups_per_batch=1
133+
max_steps=1
134+
loss_fn=importance_sampling
135+
seed=42
136+
eval_every=999999
137+
save_every=999999
138+
remove_constant_reward_groups=true
139+
```
140+
141+
Override the recipe, task count, or model with flags, for example:
142+
143+
```powershell
144+
azd ai rle invoke `
145+
--recipe code_rl_with_rle `
146+
--project-endpoint "https://omi-build-demo-uae.services.ai.azure.com/api/projects/omi-build-demo-uae" `
147+
--num-tasks 4 `
148+
--model-name "Qwen/Qwen3-32B"
149+
```

cli/azd/extensions/azure.ai.rle/extension.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,5 @@ examples:
2020
description: Create or update the RLE environment.
2121
usage: azd ai rle deploy
2222
- name: invoke
23-
description: Placeholder for triggering an RLE training job.
24-
usage: azd ai rle invoke
23+
description: Run the Loom RLE training recipe.
24+
usage: azd ai rle invoke --recipe code_rl_with_rle
Binary file not shown.

cli/azd/extensions/azure.ai.rle/internal/cmd/client.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -382,10 +382,6 @@ func (c *rleClient) do(ctx context.Context, method string, path string, body any
382382
if body != nil {
383383
req.Header.Set("Content-Type", "application/json")
384384
}
385-
if token := os.Getenv("RLE_BEARER_TOKEN"); token != "" {
386-
req.Header.Set("Authorization", "Bearer "+token)
387-
}
388-
389385
resp, err := c.httpClient.Do(req)
390386
if err != nil {
391387
return fmt.Errorf("call RLE control plane %s: %w", c.baseUrl, err)
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
package cmd
5+
6+
import (
7+
"bytes"
8+
_ "embed"
9+
"os"
10+
"path/filepath"
11+
)
12+
13+
const (
14+
rleManagedDir = ".azd-rle"
15+
rleDepsDir = "deps"
16+
bundledRleSdkWheelName = "rle_sdk-0.1.3-py3-none-any.whl"
17+
defaultLoomRecipeRepo = "https://msdata.visualstudio.com/DefaultCollection/Vienna/_git/loom"
18+
defaultLoomRecipeRef = "code_rl_with_rle"
19+
)
20+
21+
//go:embed assets/rle_sdk-0.1.3-py3-none-any.whl
22+
var bundledRleSdkWheel []byte
23+
24+
func materializeBundledRleSdk(sessionDir string) (string, error) {
25+
depsDir := filepath.Join(sessionDir, rleManagedDir, rleDepsDir)
26+
if err := os.MkdirAll(depsDir, 0700); err != nil {
27+
return "", err
28+
}
29+
30+
targetPath := filepath.Join(depsDir, bundledRleSdkWheelName)
31+
existing, err := os.ReadFile(targetPath)
32+
if err == nil && bytes.Equal(existing, bundledRleSdkWheel) {
33+
return targetPath, nil
34+
}
35+
if err := os.WriteFile(targetPath, bundledRleSdkWheel, 0600); err != nil {
36+
return "", err
37+
}
38+
return targetPath, nil
39+
}
40+
41+
func bundledRleSdkPath(sessionDir string) string {
42+
return filepath.Join(sessionDir, rleManagedDir, rleDepsDir, bundledRleSdkWheelName)
43+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
package cmd
5+
6+
import (
7+
"os"
8+
"path/filepath"
9+
"testing"
10+
)
11+
12+
func TestMaterializeBundledRleSdkWritesWheel(t *testing.T) {
13+
sessionDir := t.TempDir()
14+
15+
wheelPath, err := materializeBundledRleSdk(sessionDir)
16+
if err != nil {
17+
t.Fatal(err)
18+
}
19+
20+
expectedPath := filepath.Join(sessionDir, rleManagedDir, rleDepsDir, bundledRleSdkWheelName)
21+
if wheelPath != expectedPath {
22+
t.Fatalf("expected wheel path %q, got %q", expectedPath, wheelPath)
23+
}
24+
25+
data, err := os.ReadFile(wheelPath)
26+
if err != nil {
27+
t.Fatal(err)
28+
}
29+
if len(data) == 0 {
30+
t.Fatal("expected bundled wheel to be non-empty")
31+
}
32+
}

cli/azd/extensions/azure.ai.rle/internal/cmd/deploy.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,13 @@ import (
1212
"github.com/spf13/cobra"
1313
)
1414

15+
type rleDeployFlags struct {
16+
project string
17+
}
18+
1519
func newDeployCommand() *cobra.Command {
20+
flags := &rleDeployFlags{}
21+
1622
cmd := &cobra.Command{
1723
Use: "deploy",
1824
Short: "Create or update the RLE environment",
@@ -33,17 +39,18 @@ func newDeployCommand() *cobra.Command {
3339
}
3440
state.Name = firstNonEmpty(manifestState.Name, state.Name)
3541
state.Account = firstNonEmpty(manifestState.Account, state.Account)
36-
state.Project = firstNonEmpty(os.Getenv("RLE_PROJECT_NAME"), manifestState.Project, state.Project)
42+
state.Project = firstNonEmpty(manifestState.Project, state.Project)
3743
state.Endpoint = firstNonEmpty(manifestState.Endpoint, state.Endpoint)
3844
state.Image = firstNonEmpty(manifestState.Image, state.Image)
3945
}
46+
state.Project = firstNonEmpty(flags.project, state.Project)
4047

4148
image, err := resolveRecipeImage(state.Recipe, state.Image)
4249
if err != nil {
4350
return err
4451
}
4552
environmentId := firstNonEmpty(state.EnvironmentId, slug(state.Name))
46-
client := newRleClient(resolveControlPlaneEndpoint(state.Endpoint))
53+
client := newRleClient(resolveControlPlaneEndpoint(""))
4754
request := v1EnvironmentRequest{
4855
Name: state.Name,
4956
AcrImagePath: image,
@@ -103,6 +110,7 @@ func newDeployCommand() *cobra.Command {
103110
},
104111
}
105112

113+
cmd.Flags().StringVar(&flags.project, "project", "", "RLE project name. Defaults to the project saved in .azd-rle.json.")
106114
return cmd
107115
}
108116

cli/azd/extensions/azure.ai.rle/internal/cmd/init.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import (
1313

1414
type rleInitFlags struct {
1515
path string
16-
image string
1716
force bool
1817
}
1918

@@ -35,7 +34,7 @@ func newInitCommand() *cobra.Command {
3534
}
3635
}
3736

38-
sessionDir, err := scaffoldRleSession(envName, flags.path, flags.image, flags.force)
37+
sessionDir, err := scaffoldRleSession(envName, flags.path, "${RLE_ACR_IMAGE}", flags.force)
3938
if err != nil {
4039
return err
4140
}
@@ -44,6 +43,9 @@ func newInitCommand() *cobra.Command {
4443
if err := saveRleStateIn(sessionDir, state); err != nil {
4544
return err
4645
}
46+
if _, err := materializeBundledRleSdk(sessionDir); err != nil {
47+
return err
48+
}
4749

4850
displayDir, err := filepath.Abs(sessionDir)
4951
if err != nil {
@@ -60,7 +62,6 @@ func newInitCommand() *cobra.Command {
6062
}
6163

6264
cmd.Flags().StringVar(&flags.path, "path", ".", "Directory where the RLE session folder is created")
63-
cmd.Flags().StringVar(&flags.image, "image", "${RLE_ACR_IMAGE}", "Image reference written to rle.yaml")
6465
cmd.Flags().BoolVar(&flags.force, "force", false, "Overwrite local RLE state if it already exists")
6566
return cmd
6667
}

0 commit comments

Comments
 (0)