Spaces:
Sleeping
Sleeping
Upload folder using huggingface_hub
Browse files- README.md +13 -14
- inference.py +2 -1
- models.py +1 -1
- server/app.py +16 -0
- server/constraint_env_environment.py +14 -12
- server/gradio_ui.py +7 -1
README.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
| 1 |
---
|
| 2 |
title: Constraint Environment
|
| 3 |
-
emoji:
|
| 4 |
colorFrom: purple
|
| 5 |
colorTo: blue
|
| 6 |
sdk: docker
|
| 7 |
pinned: false
|
| 8 |
license: mit
|
| 9 |
-
short_description: RL training env
|
| 10 |
base_path: /web
|
| 11 |
---
|
| 12 |
|
|
@@ -211,22 +211,21 @@ INFO: 10.16.33.124:5187 - "GET /web HTTP/1.1" 307 Temporary Redirect
|
|
| 211 |
INFO: 10.16.24.44:32462 - "GET /web/ HTTP/1.1" 200 OK
|
| 212 |
```
|
| 213 |
|
| 214 |
-
##
|
| 215 |
|
| 216 |
-
When `ENABLE_WEB_INTERFACE=true` the server mounts a **tabbed Gradio interface** at `/web`:
|
| 217 |
|
| 218 |
| Tab | Description |
|
| 219 |
|-----|-------------|
|
| 220 |
-
| **
|
| 221 |
-
| **
|
| 222 |
-
|
| 223 |
-
### Trying the Compiler
|
| 224 |
-
1.
|
| 225 |
-
2.
|
| 226 |
-
3.
|
| 227 |
-
4.
|
| 228 |
-
|
| 229 |
-
> The custom tab is implemented in `server/gradio_ui.py` via the `gradio_builder` extension point provided by OpenEnv core.
|
| 230 |
|
| 231 |
## Project Structure
|
| 232 |
|
|
|
|
| 1 |
---
|
| 2 |
title: Constraint Environment
|
| 3 |
+
emoji: π¨βπ§
|
| 4 |
colorFrom: purple
|
| 5 |
colorTo: blue
|
| 6 |
sdk: docker
|
| 7 |
pinned: false
|
| 8 |
license: mit
|
| 9 |
+
short_description: RL training env for natural language to constraint AST
|
| 10 |
base_path: /web
|
| 11 |
---
|
| 12 |
|
|
|
|
| 211 |
INFO: 10.16.24.44:32462 - "GET /web/ HTTP/1.1" 200 OK
|
| 212 |
```
|
| 213 |
|
| 214 |
+
## Web UI & Interactive Compiler
|
| 215 |
|
| 216 |
+
When `ENABLE_WEB_INTERFACE=true` (or when deployed to Hugging Face Spaces), the server mounts a **tabbed Gradio interface** at `/web`, natively featuring our custom compiler interface as the primary landing page:
|
| 217 |
|
| 218 |
| Tab | Description |
|
| 219 |
|-----|-------------|
|
| 220 |
+
| **Constraint Compiler** | **(Default First Page)** Our custom built interactive AST environment β featuring a task difficulty selector, full JSON syntax AST code editor, interactive single-click AST sample loaders, node-structure references, and a real-time syntax/logic compiler chatbot. |
|
| 221 |
+
| **Base Playground** | The fallback default OpenEnv UI, displaying stateless Reset / Step / Get State controls for the raw API. |
|
| 222 |
+
|
| 223 |
+
### Trying the Compiler Loop (First Page)
|
| 224 |
+
1. Navigate to `/web` to access the **Constraint Compiler**.
|
| 225 |
+
2. Start by expanding the **π Load Sample ASTs** accordion and clicking one of the sample buttons (`π’ Easy Sample`, `π‘ Medium Sample`, `π΄ Hard Sample`). This instantly populates the compiler code editor with a canonical AST structure avoiding manual entry.
|
| 226 |
+
3. Select a difficulty (`easy / medium / hard`) in the task dropdown and click **Reset / Load Task**.
|
| 227 |
+
4. The prompt will update. Edit the loaded AST in the code editor to attempt to solve the logic.
|
| 228 |
+
5. Click **βΆ Submit to Compiler** β the chatbot will parse the payload and display the reward, error code, and precise compiler traceback feedback natively inline!
|
|
|
|
| 229 |
|
| 230 |
## Project Structure
|
| 231 |
|
inference.py
CHANGED
|
@@ -249,7 +249,8 @@ def _run_task(task_id: str, env_url: str = "http://localhost:8000") -> None:
|
|
| 249 |
messages = getattr(obs_data, "messages", []) if not isinstance(obs_data, dict) else obs_data.get("messages", [])
|
| 250 |
|
| 251 |
if step_count > 0 and messages:
|
| 252 |
-
|
|
|
|
| 253 |
|
| 254 |
# Generate AST from LLM with retry loop
|
| 255 |
raw_output = "{}"
|
|
|
|
| 249 |
messages = getattr(obs_data, "messages", []) if not isinstance(obs_data, dict) else obs_data.get("messages", [])
|
| 250 |
|
| 251 |
if step_count > 0 and messages:
|
| 252 |
+
parsed = [m.get("content", str(m)) if isinstance(m, dict) else str(m) for m in messages]
|
| 253 |
+
prompt_text += "\n\n" + "\n".join(parsed)
|
| 254 |
|
| 255 |
# Generate AST from LLM with retry loop
|
| 256 |
raw_output = "{}"
|
models.py
CHANGED
|
@@ -39,7 +39,7 @@ class ConstraintObservation(Observation):
|
|
| 39 |
done: bool
|
| 40 |
reward: float
|
| 41 |
info: Dict[str, Any]
|
| 42 |
-
messages: list[str] = []
|
| 43 |
|
| 44 |
|
| 45 |
class ConstraintState(State):
|
|
|
|
| 39 |
done: bool
|
| 40 |
reward: float
|
| 41 |
info: Dict[str, Any]
|
| 42 |
+
messages: list[Dict[str, Any]] = []
|
| 43 |
|
| 44 |
|
| 45 |
class ConstraintState(State):
|
server/app.py
CHANGED
|
@@ -66,6 +66,22 @@ except ImportError:
|
|
| 66 |
_gradio_builder = None
|
| 67 |
|
| 68 |
# Create the app β pass the factory so create_app calls _make_env() per session.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
app = create_app(
|
| 70 |
_make_env,
|
| 71 |
ConstraintAction,
|
|
|
|
| 66 |
_gradio_builder = None
|
| 67 |
|
| 68 |
# Create the app β pass the factory so create_app calls _make_env() per session.
|
| 69 |
+
import gradio as gr
|
| 70 |
+
_orig_tabbed = gr.TabbedInterface
|
| 71 |
+
|
| 72 |
+
def _swapped_tabbed(interface_list, tab_names, **kwargs):
|
| 73 |
+
if len(interface_list) == 2 and "Custom" in tab_names:
|
| 74 |
+
idx_custom = tab_names.index("Custom")
|
| 75 |
+
idx_play = tab_names.index("Playground")
|
| 76 |
+
return _orig_tabbed(
|
| 77 |
+
[interface_list[idx_custom], interface_list[idx_play]],
|
| 78 |
+
["Constraint Compiler", "Base Playground"],
|
| 79 |
+
**kwargs
|
| 80 |
+
)
|
| 81 |
+
return _orig_tabbed(interface_list, tab_names, **kwargs)
|
| 82 |
+
|
| 83 |
+
gr.TabbedInterface = _swapped_tabbed
|
| 84 |
+
|
| 85 |
app = create_app(
|
| 86 |
_make_env,
|
| 87 |
ConstraintAction,
|
server/constraint_env_environment.py
CHANGED
|
@@ -158,9 +158,9 @@ class ConstraintEnvironment(Environment):
|
|
| 158 |
except (json.JSONDecodeError, TypeError) as exc:
|
| 159 |
info["error"] = "invalid_json"
|
| 160 |
messages.extend([
|
| 161 |
-
"Your last submitted AST:",
|
| 162 |
-
str(action.ast_output),
|
| 163 |
-
f"Compiler Error: Syntax Error. Invalid JSON β {exc}"
|
| 164 |
])
|
| 165 |
|
| 166 |
# ββ 2. Logic match (ignores "name") ββββββββββββββββββββββββββ
|
|
@@ -171,29 +171,31 @@ class ConstraintEnvironment(Environment):
|
|
| 171 |
else:
|
| 172 |
info["exact_match"] = False
|
| 173 |
|
| 174 |
-
|
| 175 |
-
# ββ 3. Validate structure βββββββββββββββββββββββββββββββββββββ
|
| 176 |
if is_exact_match:
|
| 177 |
is_valid_structure = True
|
| 178 |
elif is_valid_json:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 179 |
valid, msg = self._validate_structure(ast)
|
| 180 |
if valid:
|
| 181 |
is_valid_structure = True
|
| 182 |
# NEW FIX: Provide feedback when structure is valid but logic fails!
|
| 183 |
info["error"] = "logic_mismatch"
|
| 184 |
messages.extend([
|
| 185 |
-
"Your last submitted AST:",
|
| 186 |
-
|
| 187 |
-
"Compiler Error: Syntax is valid, but the logic does not match the prompt's target constraint. Please adjust your logical conditions and resubmit."
|
| 188 |
])
|
| 189 |
else:
|
| 190 |
info["error"] = "bad_structure"
|
| 191 |
messages.extend([
|
| 192 |
-
"Your last submitted AST:",
|
| 193 |
-
|
| 194 |
-
f"Compiler Error: {msg}"
|
| 195 |
])
|
| 196 |
-
|
| 197 |
done = is_exact_match or self._state.step_count >= self._state.max_steps
|
| 198 |
reward = calculate_step_reward(is_valid_json, is_valid_structure, is_exact_match, self._state.step_count)
|
| 199 |
|
|
|
|
| 158 |
except (json.JSONDecodeError, TypeError) as exc:
|
| 159 |
info["error"] = "invalid_json"
|
| 160 |
messages.extend([
|
| 161 |
+
{"role": "assistant", "content": "Your last submitted AST:"},
|
| 162 |
+
{"role": "assistant", "content": str(action.ast_output)},
|
| 163 |
+
{"role": "assistant", "content": f"Compiler Error: Syntax Error. Invalid JSON β {exc}"}
|
| 164 |
])
|
| 165 |
|
| 166 |
# ββ 2. Logic match (ignores "name") ββββββββββββββββββββββββββ
|
|
|
|
| 171 |
else:
|
| 172 |
info["exact_match"] = False
|
| 173 |
|
| 174 |
+
# ββ 3. Validate structure βββββββββββββββββββββββββββββββββββββ
|
|
|
|
| 175 |
if is_exact_match:
|
| 176 |
is_valid_structure = True
|
| 177 |
elif is_valid_json:
|
| 178 |
+
|
| 179 |
+
# THE FIX: Safely convert the AST to a string so Pydantic doesn't crash!
|
| 180 |
+
safe_ast_str = json.dumps(ast) if isinstance(ast, dict) else str(action.ast_output)
|
| 181 |
+
|
| 182 |
valid, msg = self._validate_structure(ast)
|
| 183 |
if valid:
|
| 184 |
is_valid_structure = True
|
| 185 |
# NEW FIX: Provide feedback when structure is valid but logic fails!
|
| 186 |
info["error"] = "logic_mismatch"
|
| 187 |
messages.extend([
|
| 188 |
+
{"role": "assistant", "content": "Your last submitted AST:"},
|
| 189 |
+
{"role": "assistant", "content": safe_ast_str},
|
| 190 |
+
{"role": "assistant", "content": "Compiler Error: Syntax is valid, but the logic does not match the prompt's target constraint. Please adjust your logical conditions and resubmit."}
|
| 191 |
])
|
| 192 |
else:
|
| 193 |
info["error"] = "bad_structure"
|
| 194 |
messages.extend([
|
| 195 |
+
{"role": "assistant", "content": "Your last submitted AST:"},
|
| 196 |
+
{"role": "assistant", "content": safe_ast_str},
|
| 197 |
+
{"role": "assistant", "content": f"Compiler Error: {msg}"}
|
| 198 |
])
|
|
|
|
| 199 |
done = is_exact_match or self._state.step_count >= self._state.max_steps
|
| 200 |
reward = calculate_step_reward(is_valid_json, is_valid_structure, is_exact_match, self._state.step_count)
|
| 201 |
|
server/gradio_ui.py
CHANGED
|
@@ -183,7 +183,13 @@ def build_constraint_gradio_ui(web_manager, action_fields, metadata, is_chat_env
|
|
| 183 |
else:
|
| 184 |
status = f"βΉοΈ {error}"
|
| 185 |
|
| 186 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 187 |
done_badge = " π Episode Done" if done else ""
|
| 188 |
entry = (
|
| 189 |
f"[YOU]\n{ast_text[:400]}\n\n"
|
|
|
|
| 183 |
else:
|
| 184 |
status = f"βΉοΈ {error}"
|
| 185 |
|
| 186 |
+
parsed_msgs = []
|
| 187 |
+
for m in msgs:
|
| 188 |
+
if isinstance(m, dict):
|
| 189 |
+
parsed_msgs.append(m.get("content", str(m)))
|
| 190 |
+
else:
|
| 191 |
+
parsed_msgs.append(str(m))
|
| 192 |
+
compiler_msg = "\n".join(parsed_msgs) if parsed_msgs else ("β
Correct! Episode complete." if exact else "No compiler feedback.")
|
| 193 |
done_badge = " π Episode Done" if done else ""
|
| 194 |
entry = (
|
| 195 |
f"[YOU]\n{ast_text[:400]}\n\n"
|