seatyyy commited on
Commit
9a6cc06
·
1 Parent(s): ef6eadd

deploy skillforge

Browse files
Files changed (4) hide show
  1. README.md +2 -251
  2. models.py +5 -3
  3. openenv.yaml +5 -0
  4. server/Dockerfile +2 -3
README.md CHANGED
@@ -1,255 +1,6 @@
1
  ---
2
- title: Skill Forge Environment Server
3
- emoji: 🎧
4
- colorFrom: yellow
5
- colorTo: red
6
  sdk: docker
7
  pinned: false
8
- app_port: 8000
9
- base_path: /web
10
- tags:
11
- - openenv
12
  ---
13
-
14
- # Skill Forge Environment
15
-
16
- A simple test environment that echoes back messages. Perfect for testing the env APIs as well as demonstrating environment usage patterns.
17
-
18
- ## Quick Start
19
-
20
- The simplest way to use the Skill Forge environment is through the `SkillForgeEnv` class:
21
-
22
- ```python
23
- from skill_forge import SkillForgeAction, SkillForgeEnv
24
-
25
- try:
26
- # Create environment from Docker image
27
- skill_forgeenv = SkillForgeEnv.from_docker_image("skill_forge-env:latest")
28
-
29
- # Reset
30
- result = skill_forgeenv.reset()
31
- print(f"Reset: {result.observation.echoed_message}")
32
-
33
- # Send multiple messages
34
- messages = ["Hello, World!", "Testing echo", "Final message"]
35
-
36
- for msg in messages:
37
- result = skill_forgeenv.step(SkillForgeAction(message=msg))
38
- print(f"Sent: '{msg}'")
39
- print(f" → Echoed: '{result.observation.echoed_message}'")
40
- print(f" → Length: {result.observation.message_length}")
41
- print(f" → Reward: {result.reward}")
42
-
43
- finally:
44
- # Always clean up
45
- skill_forgeenv.close()
46
- ```
47
-
48
- That's it! The `SkillForgeEnv.from_docker_image()` method handles:
49
- - Starting the Docker container
50
- - Waiting for the server to be ready
51
- - Connecting to the environment
52
- - Container cleanup when you call `close()`
53
-
54
- ## Building the Docker Image
55
-
56
- Before using the environment, you need to build the Docker image:
57
-
58
- ```bash
59
- # From project root
60
- docker build -t skill_forge-env:latest -f server/Dockerfile .
61
- ```
62
-
63
- ## Deploying to Hugging Face Spaces
64
-
65
- You can easily deploy your OpenEnv environment to Hugging Face Spaces using the `openenv push` command:
66
-
67
- ```bash
68
- # From the environment directory (where openenv.yaml is located)
69
- openenv push
70
-
71
- # Or specify options
72
- openenv push --namespace my-org --private
73
- ```
74
-
75
- The `openenv push` command will:
76
- 1. Validate that the directory is an OpenEnv environment (checks for `openenv.yaml`)
77
- 2. Prepare a custom build for Hugging Face Docker space (enables web interface)
78
- 3. Upload to Hugging Face (ensuring you're logged in)
79
-
80
- ### Prerequisites
81
-
82
- - Authenticate with Hugging Face: The command will prompt for login if not already authenticated
83
-
84
- ### Options
85
-
86
- - `--directory`, `-d`: Directory containing the OpenEnv environment (defaults to current directory)
87
- - `--repo-id`, `-r`: Repository ID in format 'username/repo-name' (defaults to 'username/env-name' from openenv.yaml)
88
- - `--base-image`, `-b`: Base Docker image to use (overrides Dockerfile FROM)
89
- - `--private`: Deploy the space as private (default: public)
90
-
91
- ### Examples
92
-
93
- ```bash
94
- # Push to your personal namespace (defaults to username/env-name from openenv.yaml)
95
- openenv push
96
-
97
- # Push to a specific repository
98
- openenv push --repo-id my-org/my-env
99
-
100
- # Push with a custom base image
101
- openenv push --base-image ghcr.io/meta-pytorch/openenv-base:latest
102
-
103
- # Push as a private space
104
- openenv push --private
105
-
106
- # Combine options
107
- openenv push --repo-id my-org/my-env --base-image custom-base:latest --private
108
- ```
109
-
110
- After deployment, your space will be available at:
111
- `https://huggingface.co/spaces/<repo-id>`
112
-
113
- The deployed space includes:
114
- - **Web Interface** at `/web` - Interactive UI for exploring the environment
115
- - **API Documentation** at `/docs` - Full OpenAPI/Swagger interface
116
- - **Health Check** at `/health` - Container health monitoring
117
- - **WebSocket** at `/ws` - Persistent session endpoint for low-latency interactions
118
-
119
- ## Environment Details
120
-
121
- ### Action
122
- **SkillForgeAction**: Contains a single field
123
- - `message` (str) - The message to echo back
124
-
125
- ### Observation
126
- **SkillForgeObservation**: Contains the echo response and metadata
127
- - `echoed_message` (str) - The message echoed back
128
- - `message_length` (int) - Length of the message
129
- - `reward` (float) - Reward based on message length (length × 0.1)
130
- - `done` (bool) - Always False for echo environment
131
- - `metadata` (dict) - Additional info like step count
132
-
133
- ### Reward
134
- The reward is calculated as: `message_length × 0.1`
135
- - "Hi" → reward: 0.2
136
- - "Hello, World!" → reward: 1.3
137
- - Empty message → reward: 0.0
138
-
139
- ## Advanced Usage
140
-
141
- ### Connecting to an Existing Server
142
-
143
- If you already have a Skill Forge environment server running, you can connect directly:
144
-
145
- ```python
146
- from skill_forge import SkillForgeEnv
147
-
148
- # Connect to existing server
149
- skill_forgeenv = SkillForgeEnv(base_url="<ENV_HTTP_URL_HERE>")
150
-
151
- # Use as normal
152
- result = skill_forgeenv.reset()
153
- result = skill_forgeenv.step(SkillForgeAction(message="Hello!"))
154
- ```
155
-
156
- Note: When connecting to an existing server, `skill_forgeenv.close()` will NOT stop the server.
157
-
158
- ### Using the Context Manager
159
-
160
- The client supports context manager usage for automatic connection management:
161
-
162
- ```python
163
- from skill_forge import SkillForgeAction, SkillForgeEnv
164
-
165
- # Connect with context manager (auto-connects and closes)
166
- with SkillForgeEnv(base_url="http://localhost:8000") as env:
167
- result = env.reset()
168
- print(f"Reset: {result.observation.echoed_message}")
169
- # Multiple steps with low latency
170
- for msg in ["Hello", "World", "!"]:
171
- result = env.step(SkillForgeAction(message=msg))
172
- print(f"Echoed: {result.observation.echoed_message}")
173
- ```
174
-
175
- The client uses WebSocket connections for:
176
- - **Lower latency**: No HTTP connection overhead per request
177
- - **Persistent session**: Server maintains your environment state
178
- - **Efficient for episodes**: Better for many sequential steps
179
-
180
- ### Concurrent WebSocket Sessions
181
-
182
- The server supports multiple concurrent WebSocket connections. To enable this,
183
- modify `server/app.py` to use factory mode:
184
-
185
- ```python
186
- # In server/app.py - use factory mode for concurrent sessions
187
- app = create_app(
188
- SkillForgeEnvironment, # Pass class, not instance
189
- SkillForgeAction,
190
- SkillForgeObservation,
191
- max_concurrent_envs=4, # Allow 4 concurrent sessions
192
- )
193
- ```
194
-
195
- Then multiple clients can connect simultaneously:
196
-
197
- ```python
198
- from skill_forge import SkillForgeAction, SkillForgeEnv
199
- from concurrent.futures import ThreadPoolExecutor
200
-
201
- def run_episode(client_id: int):
202
- with SkillForgeEnv(base_url="http://localhost:8000") as env:
203
- result = env.reset()
204
- for i in range(10):
205
- result = env.step(SkillForgeAction(message=f"Client {client_id}, step {i}"))
206
- return client_id, result.observation.message_length
207
-
208
- # Run 4 episodes concurrently
209
- with ThreadPoolExecutor(max_workers=4) as executor:
210
- results = list(executor.map(run_episode, range(4)))
211
- ```
212
-
213
- ## Development & Testing
214
-
215
- ### Direct Environment Testing
216
-
217
- Test the environment logic directly without starting the HTTP server:
218
-
219
- ```bash
220
- # From the server directory
221
- python3 server/skill_forge_environment.py
222
- ```
223
-
224
- This verifies that:
225
- - Environment resets correctly
226
- - Step executes actions properly
227
- - State tracking works
228
- - Rewards are calculated correctly
229
-
230
- ### Running Locally
231
-
232
- Run the server locally for development:
233
-
234
- ```bash
235
- uvicorn server.app:app --reload
236
- ```
237
-
238
- ## Project Structure
239
-
240
- ```
241
- skill_forge/
242
- ├── .dockerignore # Docker build exclusions
243
- ├── __init__.py # Module exports
244
- ├── README.md # This file
245
- ├── openenv.yaml # OpenEnv manifest
246
- ├── pyproject.toml # Project metadata and dependencies
247
- ├── uv.lock # Locked dependencies (generated)
248
- ├── client.py # SkillForgeEnv client
249
- ├── models.py # Action and Observation models
250
- └── server/
251
- ├── __init__.py # Server module exports
252
- ├── skill_forge_environment.py # Core environment logic
253
- ├── app.py # FastAPI application (HTTP + WebSocket endpoints)
254
- └── Dockerfile # Container image definition
255
- ```
 
1
  ---
2
+ title: SkillForge
3
+ emoji: 🔨
 
 
4
  sdk: docker
5
  pinned: false
 
 
 
 
6
  ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
models.py CHANGED
@@ -19,9 +19,9 @@ class SkillForgeAction(Action):
19
  """Action for the Skill Forge environment"""
20
  action_type: Literal["create_skill", "use_skill", "raw_code"]
21
  content: str = Field(description="The content of the action. For create_skill, it is the template. For use_skill, it is the skill id. For raw_code, it is the code.")
22
- skill_name: Optional[str] = None # only for create_skill
23
  reasoning: str = ""
24
- params: Optional[dict] = None
25
 
26
 
27
  class SkillForgeObservation(Observation):
@@ -30,10 +30,12 @@ class SkillForgeObservation(Observation):
30
  task_description: str
31
  snapshot_data: str #df.head(5).to_string()
32
  skill_library: dict
33
- context: str
34
  result_correct: bool
35
  result_output: str
36
  expected_output: str
37
  step_count: int
38
  total_tokens: int
 
 
39
 
 
19
  """Action for the Skill Forge environment"""
20
  action_type: Literal["create_skill", "use_skill", "raw_code"]
21
  content: str = Field(description="The content of the action. For create_skill, it is the template. For use_skill, it is the skill id. For raw_code, it is the code.")
22
+ skill_name: str = "" # only for create_skill
23
  reasoning: str = ""
24
+ params: dict = Field(default_factory=dict, description="Template slot values for use_skill")
25
 
26
 
27
  class SkillForgeObservation(Observation):
 
30
  task_description: str
31
  snapshot_data: str #df.head(5).to_string()
32
  skill_library: dict
33
+ context: str
34
  result_correct: bool
35
  result_output: str
36
  expected_output: str
37
  step_count: int
38
  total_tokens: int
39
+ reward: Optional[float] = Field(default=None, description="Reward signal from the last action")
40
+ done: bool = Field(default=False, description="Whether the episode has terminated")
41
 
openenv.yaml CHANGED
@@ -4,4 +4,9 @@ type: space
4
  runtime: fastapi
5
  app: server.app:app
6
  port: 8000
 
 
 
 
 
7
 
 
4
  runtime: fastapi
5
  app: server.app:app
6
  port: 8000
7
+ hf_space:
8
+ sdk: docker
9
+ hardware: cpu-basic
10
+ env_vars:
11
+ ENABLE_WEB_INTERFACE: "true"
12
 
server/Dockerfile CHANGED
@@ -10,8 +10,7 @@
10
  # - Standalone environments (with openenv from PyPI/Git)
11
  # The build script (openenv build) handles context detection and sets appropriate build args.
12
 
13
- ARG BASE_IMAGE=ghcr.io/meta-pytorch/openenv-base:latest
14
- FROM ${BASE_IMAGE} AS builder
15
 
16
  WORKDIR /app
17
 
@@ -55,7 +54,7 @@ RUN --mount=type=cache,target=/root/.cache/uv \
55
  fi
56
 
57
  # Final runtime stage
58
- FROM ${BASE_IMAGE}
59
 
60
  WORKDIR /app
61
 
 
10
  # - Standalone environments (with openenv from PyPI/Git)
11
  # The build script (openenv build) handles context detection and sets appropriate build args.
12
 
13
+ FROM ghcr.io/meta-pytorch/openenv-base:latest AS builder
 
14
 
15
  WORKDIR /app
16
 
 
54
  fi
55
 
56
  # Final runtime stage
57
+ FROM ghcr.io/meta-pytorch/openenv-base:latest
58
 
59
  WORKDIR /app
60