Krishna1107 commited on
Commit
498f684
ยท
1 Parent(s): a7caaff

ready for first submission, fixed openenv and multi mode deployment errors

Browse files
.gitignore CHANGED
@@ -42,4 +42,3 @@ Thumbs.db
42
  *.zip
43
 
44
  context/
45
- tutorial_references/
 
42
  *.zip
43
 
44
  context/
 
Dockerfile CHANGED
@@ -19,4 +19,4 @@ EXPOSE 8000
19
  HEALTHCHECK --interval=30s --timeout=10s --retries=3 \
20
  CMD python -c "import requests; requests.get('http://localhost:8000/')" || exit 1
21
 
22
- CMD ["python", "-m", "uvicorn", "server.main:app", "--host", "0.0.0.0", "--port", "8000"]
 
19
  HEALTHCHECK --interval=30s --timeout=10s --retries=3 \
20
  CMD python -c "import requests; requests.get('http://localhost:8000/')" || exit 1
21
 
22
+ CMD ["python", "-m", "uvicorn", "server.app:app", "--host", "0.0.0.0", "--port", "8000"]
inference.py CHANGED
@@ -297,7 +297,7 @@ def main():
297
  print(f"\nERROR: Cannot connect to environment at {ENV_URL}")
298
  print(f" {e}")
299
  print("\nStart the server first:")
300
- print(" python -m uvicorn server.main:app --host 0.0.0.0 --port 8000")
301
  sys.exit(1)
302
 
303
  client = create_client()
 
297
  print(f"\nERROR: Cannot connect to environment at {ENV_URL}")
298
  print(f" {e}")
299
  print("\nStart the server first:")
300
+ print(" python -m uvicorn server.app:app --host 0.0.0.0 --port 8000")
301
  sys.exit(1)
302
 
303
  client = create_client()
pyproject.toml ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [build-system]
2
+ requires = ["setuptools>=68.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "cicd-docker-env"
7
+ version = "1.0.0"
8
+ description = "OpenEnv environment for debugging CI/CD infrastructure โ€” GitHub Actions workflows and Dockerfiles."
9
+ readme = "README.md"
10
+ license = {text = "MIT"}
11
+ requires-python = ">=3.10"
12
+ authors = [
13
+ {name = "Krishna"},
14
+ ]
15
+ keywords = ["openenv", "cicd", "docker", "github-actions", "debugging"]
16
+ classifiers = [
17
+ "Programming Language :: Python :: 3",
18
+ "License :: OSI Approved :: MIT License",
19
+ "Operating System :: OS Independent",
20
+ ]
21
+
22
+ dependencies = [
23
+ "fastapi",
24
+ "uvicorn",
25
+ "pydantic",
26
+ "pydantic-settings",
27
+ "PyYAML",
28
+ "requests",
29
+ "httpx",
30
+ "aiofiles",
31
+ "openenv-core>=0.2.0",
32
+ ]
33
+
34
+ [project.optional-dependencies]
35
+ dev = [
36
+ "pytest",
37
+ ]
38
+ inference = [
39
+ "openai",
40
+ "huggingface_hub",
41
+ ]
42
+
43
+ [project.scripts]
44
+ server = "server.app:main"
45
+
46
+ [project.urls]
47
+ Homepage = "https://huggingface.co/spaces/jester1177/cicd-docker-env"
48
+ Repository = "https://github.com/melohub-xbit/GitHubActions-Docker-OpenEnv"
49
+
50
+ [tool.setuptools.packages.find]
51
+ include = ["server*"]
52
+
53
+ [tool.setuptools.package-data]
54
+ server = ["static/*"]
requirements.txt CHANGED
Binary files a/requirements.txt and b/requirements.txt differ
 
server/{main.py โ†’ app.py} RENAMED
@@ -233,5 +233,9 @@ async def run_baseline(request: Optional[BaselineRequest] = None):
233
  return BaselineResponse(results=results, aggregate_score=aggregate)
234
 
235
 
236
- if __name__ == "__main__":
237
  uvicorn.run(app, host="0.0.0.0", port=8000)
 
 
 
 
 
233
  return BaselineResponse(results=results, aggregate_score=aggregate)
234
 
235
 
236
+ def main():
237
  uvicorn.run(app, host="0.0.0.0", port=8000)
238
+
239
+
240
+ if __name__ == "__main__":
241
+ main()
smoke_test.py CHANGED
@@ -35,7 +35,7 @@ class EndpointClient:
35
  class InProcessClient(EndpointClient):
36
  def __init__(self):
37
  from fastapi.testclient import TestClient
38
- from server.main import app
39
 
40
  self._client = TestClient(app)
41
 
 
35
  class InProcessClient(EndpointClient):
36
  def __init__(self):
37
  from fastapi.testclient import TestClient
38
+ from server.app import app
39
 
40
  self._client = TestClient(app)
41
 
tests/test_endpoints.py CHANGED
@@ -2,7 +2,7 @@
2
 
3
  from fastapi.testclient import TestClient
4
 
5
- from server.main import app
6
 
7
  client = TestClient(app)
8
 
 
2
 
3
  from fastapi.testclient import TestClient
4
 
5
+ from server.app import app
6
 
7
  client = TestClient(app)
8
 
tutorial_references/02-deployment.md ADDED
@@ -0,0 +1,427 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 2. Deploying an OpenEnv environment
2
+
3
+ This section covers deploying OpenEnv environments locally, on clusters, and on Hugging Face Spaces.
4
+
5
+ **Contents:**
6
+ - [Local Development with Uvicorn](#local-development-with-uvicorn)
7
+ - [Docker Deployment](#docker-deployment)
8
+ - [Hugging Face Spaces](#hugging-face-spaces)
9
+ - [Best Practices](#best-practices)
10
+
11
+ ## HF Spaces are the infrastructure for OpenEnv environments
12
+
13
+ Every HF Space provides three things that OpenEnv environments need:
14
+
15
+ | Component | What it provides | How to access | Used as |
16
+ |-----------|------------------|---------------|-----------|
17
+ | **Server** | Running environment endpoint | `https://<username>-<space-name>.hf.space` | Agent and Public API |
18
+ | **Repository** | Installable Python package | `pip install git+https://huggingface.co/spaces/<username>-<space-name>` | Code and client |
19
+ | **Registry** | Docker container image | `docker pull registry.hf.space/<username>-<space-name>:latest` | Deployment |
20
+
21
+ This means a single Space deployment gives you all the components you need to use an environment in training.
22
+
23
+ ### 1. Server: A running environment endpoint
24
+
25
+ When you deploy to HF Spaces, your environment runs as a server. The client connects via **WebSocket** (`/ws`) for a persistent session:
26
+
27
+ ```python
28
+ from echo_env import EchoEnv, EchoAction
29
+
30
+ # Connect directly to the running Space (WebSocket under the hood)
31
+ # Async (recommended):
32
+ async with EchoEnv(base_url="https://openenv-echo-env.hf.space") as client:
33
+ result = await client.reset()
34
+ result = await client.step(EchoAction(message="Hello"))
35
+
36
+ # Sync (using .sync() wrapper):
37
+ with EchoEnv(base_url="https://openenv-echo-env.hf.space").sync() as client:
38
+ result = client.reset()
39
+ result = client.step(EchoAction(message="Hello"))
40
+ ```
41
+
42
+ **Endpoints available:**
43
+
44
+ | Endpoint | Protocol | Description |
45
+ |----------|----------|-------------|
46
+ | `/ws` | **WebSocket** | Persistent session (used by client) |
47
+ | `/health` | HTTP GET | Health check |
48
+ | `/reset` | HTTP POST | Reset environment (stateless) |
49
+ | `/step` | HTTP POST | Execute action (stateless) |
50
+ | `/state` | HTTP GET | Get current state |
51
+ | `/docs` | HTTP GET | OpenAPI documentation |
52
+ | `/web` | HTTP GET | Interactive web UI |
53
+
54
+ > **Note:** The Python client uses the `/ws` WebSocket endpoint by default. HTTP endpoints are available for debugging or stateless use cases.
55
+
56
+ **Example: Check if a Space is running**
57
+
58
+ ```bash
59
+ curl https://openenv-echo-env.hf.space/health
60
+ # {"status": "healthy"}
61
+ ```
62
+
63
+ ### 2. Repository: Installable Python package
64
+
65
+ Every Space is a Git repository. OpenEnv environments include a `pyproject.toml`, making them pip-installable directly from the Space URL.
66
+
67
+ ```bash
68
+ # Install client package from Space
69
+ pip install git+https://huggingface.co/spaces/openenv/echo-env
70
+ ```
71
+
72
+ This installs:
73
+ - **Client class** (`EchoEnv`) โ€” Handles HTTP/WebSocket communication
74
+ - **Models** (`EchoAction`, `EchoObservation`) โ€” Typed action and observation classes
75
+ - **Utilities** โ€” Any helper functions the environment provides
76
+
77
+ **After installation:**
78
+
79
+ ```python
80
+ from envs.echo_env import EchoEnv, EchoAction, EchoObservation
81
+
82
+ # Now you have typed classes for the environment
83
+ action = EchoAction(message="Hello")
84
+ ```
85
+
86
+ ### 3. Registry: Docker container image
87
+
88
+ Every Docker-based Space has a container registry. You can pull and run the environment locally.
89
+
90
+ ```bash
91
+ # Pull the image
92
+ docker pull registry.hf.space/openenv-echo-env:latest
93
+
94
+ # Run locally on port 8001
95
+ docker run -d -p 8001:8000 registry.hf.space/openenv-echo-env:latest
96
+ ```
97
+
98
+ **Find the registry URL for any Space:**
99
+
100
+ 1. Go to the Space page (e.g., [openenv/echo-env](https://huggingface.co/spaces/openenv/echo-env))
101
+ 2. Click **โ‹ฎ** (three dots) โ†’ **"Run locally"**
102
+ 3. Copy the `docker run` command
103
+
104
+ ### Choosing an access method
105
+
106
+ | Method | Use when | Pros | Cons |
107
+ |--------|----------|------|------|
108
+ | **Server** | Quick testing, low volume | Zero setup | Network latency, rate limits |
109
+ | **Repository** | Need typed classes | Type safety, IDE support | Still need a server |
110
+ | **Docker** | Local dev, high throughput | Full control, no network | Requires Docker |
111
+
112
+ **Typical workflow:**
113
+
114
+ ```python
115
+ import asyncio
116
+ from echo_env import EchoEnv, EchoAction
117
+
118
+ async def main():
119
+ # Development: connect to remote Space
120
+ async with EchoEnv(base_url="https://openenv-echo-env.hf.space") as client:
121
+ result = await client.reset()
122
+
123
+ # Production: run locally for speed
124
+ # docker run -d -p 8001:8000 registry.hf.space/openenv-echo-env:latest
125
+ async with EchoEnv(base_url="http://localhost:8001") as client:
126
+ result = await client.reset()
127
+
128
+ # Or let the client manage Docker for you
129
+ client = await EchoEnv.from_env("openenv/echo-env") # Auto-pulls and runs
130
+ async with client:
131
+ result = await client.reset()
132
+
133
+ asyncio.run(main())
134
+
135
+ # For sync usage, use the .sync() wrapper:
136
+ with EchoEnv(base_url="http://localhost:8001").sync() as client:
137
+ result = client.reset()
138
+ ```
139
+
140
+ > **Reference:** [HF Spaces Documentation](https://huggingface.co/docs/hub/spaces) | [Environment Hub Collection](https://huggingface.co/collections/openenv/environment-hub)
141
+
142
+
143
+ ## Local Development with Uvicorn
144
+
145
+ The fastest way to iterate on environment logic is running directly with Uvicorn.
146
+
147
+ ## Clone and run the environment locally
148
+
149
+ ```bash
150
+ # Clone from HF Space
151
+ git clone https://huggingface.co/spaces/burtenshaw/openenv-benchmark
152
+ cd openenv-benchmark
153
+
154
+ # Install in editable mode
155
+ uv sync
156
+
157
+ # Start server
158
+ uv run server
159
+
160
+ # Run isolated from remote Space
161
+ uv run --isolated --project https://huggingface.co/spaces/burtenshaw/openenv-benchmark server
162
+ ```
163
+
164
+ ## Uvicorn directly in python
165
+
166
+ ```bash
167
+ # Full control over uvicorn options
168
+ uvicorn benchmark.server.app:app --host "$HOST" --port "$PORT" --workers "$WORKERS"
169
+
170
+ # With reload for development
171
+ uvicorn benchmark.server.app:app --host 0.0.0.0 --port 8000 --reload
172
+
173
+ # Multi-Worker Mode For better concurrency:
174
+ uvicorn benchmark.server.app:app --host 0.0.0.0 --port 8000 --workers 4
175
+ ```
176
+
177
+ | Flag | Purpose |
178
+ |------|---------|
179
+ | `--reload` | Auto-restart on code changes |
180
+ | `--workers N` | Run N worker processes |
181
+ | `--log-level debug` | Verbose logging |
182
+
183
+ ## Docker Deployment
184
+
185
+ Docker provides isolation and reproducibility for production use.
186
+
187
+ ### Run the environment locally from the space
188
+
189
+ ```bash
190
+ # Run the environment locally from the space
191
+ docker run -d -p 8000:8000 registry.hf.space/openenv-echo-env:latest
192
+ ```
193
+
194
+ ### Build Image
195
+
196
+ ```bash
197
+ # Clone from HF Space
198
+ git clone https://huggingface.co/spaces/burtenshaw/openenv-benchmark
199
+ cd openenv-benchmark
200
+
201
+ # Using OpenEnv CLI (recommended)
202
+ openenv build -t openenv-benchmark:latest
203
+
204
+ # Or with Docker directly
205
+ docker build -t openenv-benchmark:latest -f server/Dockerfile .
206
+ ```
207
+
208
+ ### Run Container
209
+
210
+ ```bash
211
+ # Basic run
212
+ docker run -d -p 8000:8000 my-env:latest
213
+
214
+ # With environment variables
215
+ docker run -d -p 8000:8000 \
216
+ -e WORKERS=4 \
217
+ -e MAX_CONCURRENT_ENVS=100 \
218
+ my-env:latest
219
+
220
+ # Named container for easy management
221
+ docker run -d --name my-env -p 8000:8000 my-env:latest
222
+ ```
223
+
224
+ ### Connect from Python
225
+
226
+ ```python
227
+ import asyncio
228
+ from echo_env import EchoEnv, EchoAction
229
+
230
+ async def main():
231
+ # Async usage (recommended)
232
+ async with EchoEnv(base_url="http://localhost:8000") as client:
233
+ result = await client.reset()
234
+ result = await client.step(EchoAction(message="Hello"))
235
+ print(result.observation)
236
+
237
+ # From Docker image
238
+ client = await EchoEnv.from_docker_image("<local_docker_image>")
239
+ async with client:
240
+ result = await client.reset()
241
+ print(result.observation)
242
+
243
+ asyncio.run(main())
244
+
245
+ # Sync usage (using .sync() wrapper)
246
+ with EchoEnv(base_url="http://localhost:8000").sync() as client:
247
+ result = client.reset()
248
+ result = client.step(EchoAction(message="Hello"))
249
+ print(result.observation)
250
+ ```
251
+
252
+ ### Container Lifecycle
253
+
254
+ | Method | Container | WebSocket | On `close()` |
255
+ |--------|-----------|-----------|--------------|
256
+ | `from_hub(repo_id)` | Starts | Connects | Stops container |
257
+ | `from_hub(repo_id, use_docker=False)` | None (UV) | Connects | Stops UV server |
258
+ | `from_docker_image(image)` | Starts | Connects | Stops container |
259
+ | `MyEnv(base_url=...)` | None | Connects | Disconnects only |
260
+
261
+ Find Docker Commands for Any Space
262
+
263
+ 1. Open the Space on HuggingFace Hub
264
+ 2. Click **โ‹ฎ (three dots)** menu
265
+ 3. Select **"Run locally"**
266
+ 4. Copy the provided `docker run` command
267
+
268
+ ## Deploy with CLI
269
+
270
+ ```bash
271
+ cd my_env
272
+
273
+ # Deploy to your namespace
274
+ openenv push
275
+
276
+ # Deploy to specific repo
277
+ openenv push --repo-id username/my-env
278
+
279
+ # Deploy as private
280
+ openenv push --repo-id username/my-env --private
281
+ ```
282
+
283
+ ### Space Configuration
284
+
285
+ The `openenv.yaml` manifest controls Space settings:
286
+
287
+ ```yaml
288
+ # openenv.yaml
289
+ name: my_env
290
+ version: "1.0.0"
291
+ description: My custom environment
292
+ ```
293
+
294
+ Hardware Options:
295
+
296
+ | Tier | vCPU | RAM | Cost |
297
+ |------|------|-----|------|
298
+ | CPU Basic (Free) | 2 | 16GB | Free |
299
+ | CPU Upgrade | 8 | 32GB | $0.03/hr |
300
+
301
+ OpenEnv environments support configuration via environment variables.
302
+
303
+ | Variable | Default | Description |
304
+ |----------|---------|-------------|
305
+ | `WORKERS` | 4 | Uvicorn worker processes |
306
+ | `PORT` | 8000 | Server port |
307
+ | `HOST` | 0.0.0.0 | Bind address |
308
+ | `MAX_CONCURRENT_ENVS` | 100 | Max WebSocket sessions |
309
+ | `ENABLE_WEB_INTERFACE` | Auto | Enable web UI |
310
+
311
+ ### Environment-Specific Variables
312
+
313
+ Some environments have custom variables:
314
+
315
+ **TextArena:**
316
+ ```bash
317
+ TEXTARENA_ENV_ID=Wordle-v0
318
+ TEXTARENA_NUM_PLAYERS=1
319
+ TEXTARENA_MAX_TURNS=6
320
+ ```
321
+
322
+ **Coding Environment:**
323
+ ```bash
324
+ SANDBOX_TIMEOUT=30
325
+ MAX_OUTPUT_LENGTH=10000
326
+ ```
327
+
328
+ # DEMO: Deploying to Hugging Face Spaces
329
+
330
+ This demo walks through the full workflow: create an environment, test locally, deploy to HF Spaces, and use it.
331
+
332
+ ## Step 1: Initialize a new environment
333
+
334
+ ```bash
335
+ openenv init my_env
336
+ cd my_env
337
+ ```
338
+
339
+ This creates the standard OpenEnv structure:
340
+
341
+ ```
342
+ my_env/
343
+ โ”œโ”€โ”€ server/
344
+ โ”‚ โ”œโ”€โ”€ app.py # FastAPI server
345
+ โ”‚ โ”œโ”€โ”€ environment.py # Your environment logic
346
+ โ”‚ โ””โ”€โ”€ Dockerfile
347
+ โ”œโ”€โ”€ models.py # Action/Observation types
348
+ โ”œโ”€โ”€ client.py # HTTP client
349
+ โ”œโ”€โ”€ openenv.yaml # Manifest
350
+ โ””โ”€โ”€ pyproject.toml
351
+ ```
352
+
353
+ ## Step 2: Run locally
354
+
355
+ ```bash
356
+ # Start the server
357
+ uv run server
358
+
359
+ # Or with uvicorn directly
360
+ uvicorn server.app:app --host 0.0.0.0 --port 8000 --reload
361
+ ```
362
+
363
+ Test the health endpoint:
364
+
365
+ ```bash
366
+ curl http://localhost:8000/health
367
+ # {"status": "healthy"}
368
+ ```
369
+
370
+ ## Step 3: Deploy to HF Spaces
371
+
372
+ ```bash
373
+ openenv push --repo-id username/my-env
374
+ ```
375
+
376
+ Your environment is now live at:
377
+ - Web UI: https://username-my-env.hf.space/web
378
+ - API Docs: https://username-my-env.hf.space/docs
379
+ - Health: https://username-my-env.hf.space/health
380
+
381
+ ```bash
382
+ curl https://openenv-echo-env.hf.space/health
383
+ # {"status": "healthy"}
384
+ ```
385
+
386
+ ## Step 4: install the environment
387
+
388
+ ```bash
389
+ uv pip install git+https://huggingface.co/spaces/openenv/echo_env
390
+ ```
391
+
392
+ ## Step 5: Run locally via Docker (optional)
393
+
394
+ Pull and run the container from the HF registry, or open the [browser](https://huggingface.co/spaces/openenv/echo_env?docker=true):
395
+
396
+ ```bash
397
+ # Pull from HF Spaces registry
398
+ docker pull registry.hf.space/openenv-echo-env:latest
399
+
400
+ # Run locally
401
+ docker run -it -p 7860:7860 --platform=linux/amd64 \
402
+ registry.hf.space/openenv-echo-env:latest
403
+ ```
404
+
405
+ Now connect to your local instance:
406
+
407
+ ```python
408
+ import asyncio
409
+ from echo_env import EchoEnv, EchoAction
410
+
411
+ # Async (recommended)
412
+ async def main():
413
+ async with EchoEnv(base_url="http://localhost:8000") as env:
414
+ result = await env.reset()
415
+ print(result.observation)
416
+ result = await env.step(EchoAction(message="Hello"))
417
+ print(result.observation)
418
+
419
+ asyncio.run(main())
420
+
421
+ # Sync (using .sync() wrapper)
422
+ with EchoEnv(base_url="http://localhost:8000").sync() as env:
423
+ result = env.reset()
424
+ print(result.observation)
425
+ result = env.step(EchoAction(message="Hello"))
426
+ print(result.observation)
427
+ ```
uv.lock ADDED
The diff for this file is too large to render. See raw diff