krishnah27 commited on
Commit
21d2feb
·
verified ·
1 Parent(s): 54092f4

Harden deployment and Space compatibility

Browse files
.dockerignore CHANGED
@@ -1,8 +1,15 @@
1
  __pycache__
2
  .git
 
3
  .venv
4
  .pytest_cache
5
  dist
6
  build
7
  outputs
8
- tests
 
 
 
 
 
 
 
1
  __pycache__
2
  .git
3
+ .gitattributes
4
  .venv
5
  .pytest_cache
6
  dist
7
  build
8
  outputs
9
+ tests
10
+ .env
11
+ .env.*
12
+ *.pem
13
+ *.key
14
+ .vscode
15
+ .idea
.env.example ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ HF_TOKEN=hf_your_write_or_inference_token_here
2
+ API_BASE_URL=https://router.huggingface.co/v1
3
+ MODEL_NAME=Qwen/Qwen2.5-72B-Instruct
4
+ BENCHMARK_NAME=model_release_env
5
+ MODEL_RELEASE_MAX_STEPS=8
6
+ MODEL_RELEASE_SUCCESS_THRESHOLD=0.75
7
+ MODEL_RELEASE_TASKS=card_completion_easy,policy_alignment_medium,launch_gate_hard
8
+ LOCAL_IMAGE_NAME=model-release-env:latest
9
+ ENV_BASE_URL=http://localhost:8000
.gitignore CHANGED
@@ -2,10 +2,19 @@
2
  __pycache__/
3
  .pytest_cache/
4
  *.pyc
 
 
5
  *.egg-info/
6
  dist/
7
  build/
8
- outputs/logs/*
9
- outputs/evals/*
10
- !outputs/logs/.gitkeep
11
- !outputs/evals/.gitkeep
 
 
 
 
 
 
 
 
2
  __pycache__/
3
  .pytest_cache/
4
  *.pyc
5
+ *.pyo
6
+ *.pyd
7
  *.egg-info/
8
  dist/
9
  build/
10
+ outputs/
11
+ .env
12
+ .env.*
13
+ !.env.example
14
+ *.pem
15
+ *.key
16
+ *.p12
17
+ .vscode/
18
+ .idea/
19
+ *.swp
20
+ *.swo
Dockerfile ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM ghcr.io/meta-pytorch/openenv-base:latest
2
+
3
+ WORKDIR /app/env
4
+
5
+ COPY . /app/env
6
+
7
+ RUN pip install --no-cache-dir .
8
+
9
+ EXPOSE 8000
10
+
11
+ CMD ["uvicorn", "server.app:app", "--host", "0.0.0.0", "--port", "8000"]
README.md CHANGED
@@ -16,6 +16,8 @@ Model Release Env is an OpenEnv environment for release-readiness decisions arou
16
 
17
  The current offline heuristic smoke baseline scores `1.00` on all three tasks, for an average score of `1.00`.
18
 
 
 
19
  ## Why this is a real environment
20
 
21
  This is modeled on a real workflow used before shipping model checkpoints: confirm what can be published, align the release card with approved evidence, and block unsafe launches when compliance or safety signals fail. The environment is deterministic, fast to evaluate, and shaped for RL because every intermediate edit changes a measurable checklist score.
@@ -40,6 +42,8 @@ Each task exposes three evidence documents, a structured release package, and a
40
 
41
  `submit`: finish the episode and receive the final score.
42
 
 
 
43
  ## Reward Design
44
 
45
  The score is a weighted checklist in `[0, 1]`.
@@ -68,12 +72,16 @@ uv run server
68
 
69
  Open the local server at `http://localhost:8000/web`.
70
 
 
 
71
  ## Docker
72
 
73
  ```bash
74
  docker build -t model-release-env:latest -f server/Dockerfile .
75
  ```
76
 
 
 
77
  ## Baseline Runner
78
 
79
  The hackathon runner expects a root-level `inference.py`.
@@ -94,6 +102,41 @@ If `HF_TOKEN` is missing, `inference.py` uses a deterministic heuristic fallback
94
 
95
  On machines with a polluted user-site Python installation, prefix local runs with `env -u PYTHONPATH` to prevent incompatible global packages from overriding the repo environment.
96
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
  ## Example Client Usage
98
 
99
  ```python
 
16
 
17
  The current offline heuristic smoke baseline scores `1.00` on all three tasks, for an average score of `1.00`.
18
 
19
+ The environment now exposes `critical_gaps` in each observation so an agent can see which launch checks are still failing without reverse-engineering the full checklist.
20
+
21
  ## Why this is a real environment
22
 
23
  This is modeled on a real workflow used before shipping model checkpoints: confirm what can be published, align the release card with approved evidence, and block unsafe launches when compliance or safety signals fail. The environment is deterministic, fast to evaluate, and shaped for RL because every intermediate edit changes a measurable checklist score.
 
42
 
43
  `submit`: finish the episode and receive the final score.
44
 
45
+ Observations also include `critical_gaps`, a prioritized list of currently unsatisfied checks. This improves agent ergonomics and makes the environment more useful for RL agents that need dense tactical feedback.
46
+
47
  ## Reward Design
48
 
49
  The score is a weighted checklist in `[0, 1]`.
 
72
 
73
  Open the local server at `http://localhost:8000/web`.
74
 
75
+ If you prefer environment files, copy `.env.example` to `.env` locally. `.env` is ignored by git and docker to avoid leaking tokens.
76
+
77
  ## Docker
78
 
79
  ```bash
80
  docker build -t model-release-env:latest -f server/Dockerfile .
81
  ```
82
 
83
+ For Hugging Face Docker Spaces, the repository also includes a root `Dockerfile` because Spaces looks for Docker entrypoints at repo root.
84
+
85
  ## Baseline Runner
86
 
87
  The hackathon runner expects a root-level `inference.py`.
 
102
 
103
  On machines with a polluted user-site Python installation, prefix local runs with `env -u PYTHONPATH` to prevent incompatible global packages from overriding the repo environment.
104
 
105
+ The baseline runner redacts token-like strings from fallback error messages so model or transport failures do not echo secrets into logs.
106
+
107
+ ## Security
108
+
109
+ - Keep tokens only in environment variables or an untracked `.env` file.
110
+ - Never commit credentials. `.env*`, key files, editor metadata, and local build caches are ignored.
111
+ - The Space upload contains only tracked project files; no local secrets or virtualenv artifacts are uploaded.
112
+ - The baseline runner redacts token-like values in fallback logs.
113
+
114
+ ## Hugging Face Space
115
+
116
+ Space URL: `https://huggingface.co/spaces/krishnah27/openenv-model-release-env`
117
+
118
+ Public clone URL:
119
+
120
+ ```bash
121
+ git clone https://huggingface.co/spaces/krishnah27/openenv-model-release-env
122
+ ```
123
+
124
+ If you need authenticated push access from another machine, use the same clone URL and provide a Hugging Face write token as the git password when prompted.
125
+
126
+ ## Submission
127
+
128
+ Submit these two URLs in the hackathon form:
129
+
130
+ 1. GitHub repository: `https://github.com/krishnakumarbhat/openenv-model-release-env`
131
+ 2. Hugging Face Space: `https://huggingface.co/spaces/krishnah27/openenv-model-release-env`
132
+
133
+ Recommended final checklist before clicking submit:
134
+
135
+ 1. Open the GitHub repo and confirm the latest commit is present.
136
+ 2. Open the Hugging Face Space and confirm the Docker build starts or finishes successfully.
137
+ 3. Verify the root `inference.py` exists in both places.
138
+ 4. Submit the GitHub URL and Hugging Face Space URL.
139
+
140
  ## Example Client Usage
141
 
142
  ```python
inference.py CHANGED
@@ -32,12 +32,27 @@ except ImportError:
32
 
33
  API_BASE_URL = os.getenv("API_BASE_URL", "https://router.huggingface.co/v1")
34
  MODEL_NAME = os.getenv("MODEL_NAME", "Qwen/Qwen2.5-72B-Instruct")
35
- HF_TOKEN = os.getenv("HF_TOKEN")
36
  LOCAL_IMAGE_NAME = os.getenv("LOCAL_IMAGE_NAME", "model-release-env:latest")
37
  ENV_BASE_URL = os.getenv("ENV_BASE_URL")
38
  BENCHMARK = os.getenv("BENCHMARK_NAME", "model_release_env")
39
- MAX_STEPS = int(os.getenv("MODEL_RELEASE_MAX_STEPS", "8"))
40
- SUCCESS_THRESHOLD = float(os.getenv("MODEL_RELEASE_SUCCESS_THRESHOLD", "0.75"))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
 
42
  DEFAULT_TASKS = [
43
  "card_completion_easy",
@@ -137,10 +152,18 @@ def _stderr(message: str) -> None:
137
  print(message, file=sys.stderr)
138
 
139
 
 
 
 
 
 
 
 
140
  def _llm_client() -> Optional[OpenAI]:
141
- if not HF_TOKEN:
 
142
  return None
143
- return OpenAI(base_url=API_BASE_URL, api_key=HF_TOKEN)
144
 
145
 
146
  def _extract_json_block(content: str) -> Dict[str, Any]:
@@ -158,6 +181,7 @@ def _observation_prompt(observation: Any) -> str:
158
  "visible_documents": observation.visible_documents,
159
  "package_snapshot": observation.package_snapshot,
160
  "checklist_status": observation.checklist_status,
 
161
  "available_fields": observation.available_fields,
162
  "available_decisions": observation.available_decisions,
163
  "inspected_documents": observation.inspected_documents,
@@ -201,6 +225,8 @@ def _model_action(
201
  {"role": "user", "content": user_prompt},
202
  ],
203
  )
 
 
204
  content = response.choices[0].message.content or ""
205
  return ModelReleaseAction(**_extract_json_block(content))
206
 
@@ -223,7 +249,9 @@ async def _run_task(env: ModelReleaseEnv, task_name: str, llm: Optional[OpenAI])
223
  else:
224
  action = _model_action(llm, task_name, result.observation)
225
  except Exception as exc:
226
- _stderr(f"planner fallback for {task_name}: {exc}")
 
 
227
  action = _heuristic_action(task_name, step_index)
228
 
229
  result = await env.step(action)
 
32
 
33
  API_BASE_URL = os.getenv("API_BASE_URL", "https://router.huggingface.co/v1")
34
  MODEL_NAME = os.getenv("MODEL_NAME", "Qwen/Qwen2.5-72B-Instruct")
 
35
  LOCAL_IMAGE_NAME = os.getenv("LOCAL_IMAGE_NAME", "model-release-env:latest")
36
  ENV_BASE_URL = os.getenv("ENV_BASE_URL")
37
  BENCHMARK = os.getenv("BENCHMARK_NAME", "model_release_env")
38
+
39
+
40
+ def _get_env_int(name: str, default: str) -> int:
41
+ try:
42
+ return int(os.getenv(name, default))
43
+ except ValueError as exc:
44
+ raise SystemExit(f"Invalid integer for {name}: {exc}") from exc
45
+
46
+
47
+ def _get_env_float(name: str, default: str) -> float:
48
+ try:
49
+ return float(os.getenv(name, default))
50
+ except ValueError as exc:
51
+ raise SystemExit(f"Invalid float for {name}: {exc}") from exc
52
+
53
+
54
+ MAX_STEPS = _get_env_int("MODEL_RELEASE_MAX_STEPS", "8")
55
+ SUCCESS_THRESHOLD = _get_env_float("MODEL_RELEASE_SUCCESS_THRESHOLD", "0.75")
56
 
57
  DEFAULT_TASKS = [
58
  "card_completion_easy",
 
152
  print(message, file=sys.stderr)
153
 
154
 
155
+ def _redact_message(message: str) -> str:
156
+ redacted = re.sub(r"hf_[A-Za-z0-9]+", "hf_[REDACTED]", message)
157
+ redacted = re.sub(r"sk_[A-Za-z0-9]+", "sk_[REDACTED]", redacted)
158
+ redacted = re.sub(r"https://[^\s:@]+:[^\s@]+@", "https://[REDACTED]@", redacted)
159
+ return redacted
160
+
161
+
162
  def _llm_client() -> Optional[OpenAI]:
163
+ token = os.getenv("HF_TOKEN")
164
+ if not token:
165
  return None
166
+ return OpenAI(base_url=API_BASE_URL, api_key=token)
167
 
168
 
169
  def _extract_json_block(content: str) -> Dict[str, Any]:
 
181
  "visible_documents": observation.visible_documents,
182
  "package_snapshot": observation.package_snapshot,
183
  "checklist_status": observation.checklist_status,
184
+ "critical_gaps": observation.critical_gaps,
185
  "available_fields": observation.available_fields,
186
  "available_decisions": observation.available_decisions,
187
  "inspected_documents": observation.inspected_documents,
 
225
  {"role": "user", "content": user_prompt},
226
  ],
227
  )
228
+ if not response.choices:
229
+ raise ValueError("Model returned no choices")
230
  content = response.choices[0].message.content or ""
231
  return ModelReleaseAction(**_extract_json_block(content))
232
 
 
249
  else:
250
  action = _model_action(llm, task_name, result.observation)
251
  except Exception as exc:
252
+ _stderr(
253
+ f"planner fallback for {task_name}: {_redact_message(str(exc))}"
254
+ )
255
  action = _heuristic_action(task_name, step_index)
256
 
257
  result = await env.step(action)
models.py CHANGED
@@ -52,6 +52,10 @@ class ModelReleaseObservation(Observation):
52
  default_factory=dict,
53
  description="Progress on deterministic grader checks",
54
  )
 
 
 
 
55
  available_fields: List[str] = Field(
56
  default_factory=list,
57
  description="Fields the agent may edit with set_field",
 
52
  default_factory=dict,
53
  description="Progress on deterministic grader checks",
54
  )
55
+ critical_gaps: List[str] = Field(
56
+ default_factory=list,
57
+ description="Unsatisfied checks in priority order",
58
+ )
59
  available_fields: List[str] = Field(
60
  default_factory=list,
61
  description="Fields the agent may edit with set_field",
server/model_release_env_environment.py CHANGED
@@ -389,6 +389,9 @@ class ModelReleaseEnvironment(
389
  return [name for name, passed in self._score_by_check.items() if passed]
390
 
391
  def _build_observation(self, reward: float, done: bool) -> ModelReleaseObservation:
 
 
 
392
  return ModelReleaseObservation(
393
  task_name=self._task_name,
394
  difficulty=self._task_spec["difficulty"],
@@ -397,6 +400,7 @@ class ModelReleaseEnvironment(
397
  visible_documents=deepcopy(self._visible_documents),
398
  package_snapshot=deepcopy(self._package),
399
  checklist_status=deepcopy(self._score_by_check),
 
400
  available_fields=list(self._task_spec["editable_fields"]),
401
  available_decisions=list(self._task_spec["allowed_decisions"]),
402
  inspected_documents=sorted(self._inspected_documents),
 
389
  return [name for name, passed in self._score_by_check.items() if passed]
390
 
391
  def _build_observation(self, reward: float, done: bool) -> ModelReleaseObservation:
392
+ critical_gaps = [
393
+ name for name, satisfied in self._score_by_check.items() if not satisfied
394
+ ]
395
  return ModelReleaseObservation(
396
  task_name=self._task_name,
397
  difficulty=self._task_spec["difficulty"],
 
400
  visible_documents=deepcopy(self._visible_documents),
401
  package_snapshot=deepcopy(self._package),
402
  checklist_status=deepcopy(self._score_by_check),
403
+ critical_gaps=critical_gaps,
404
  available_fields=list(self._task_spec["editable_fields"]),
405
  available_decisions=list(self._task_spec["allowed_decisions"]),
406
  inspected_documents=sorted(self._inspected_documents),