Spaces:
Sleeping
Sleeping
Upload folder using huggingface_hub
Browse files- Dockerfile +30 -54
- server/lint_coding_agent_environment.py +40 -26
Dockerfile
CHANGED
|
@@ -1,83 +1,59 @@
|
|
| 1 |
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
# All rights reserved.
|
| 3 |
-
#
|
| 4 |
-
# This source code is licensed under the BSD-style license found in the
|
| 5 |
-
# LICENSE file in the root directory of this source tree.
|
| 6 |
-
|
| 7 |
-
# Multi-stage build using openenv-base
|
| 8 |
-
# This Dockerfile is flexible and works for both:
|
| 9 |
-
# - In-repo environments (with local OpenEnv sources)
|
| 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 |
|
| 18 |
-
#
|
| 19 |
RUN apt-get update && \
|
| 20 |
-
apt-get install -y --no-install-recommends git && \
|
| 21 |
rm -rf /var/lib/apt/lists/*
|
| 22 |
|
| 23 |
-
#
|
| 24 |
-
ARG BUILD_MODE=in-repo
|
| 25 |
-
ARG ENV_NAME=lint_coding_agent
|
| 26 |
-
|
| 27 |
-
# Copy environment code (always at root of build context)
|
| 28 |
COPY . /app/env
|
| 29 |
|
| 30 |
-
# For in-repo builds, openenv is already vendored in the build context
|
| 31 |
-
# For standalone builds, openenv will be installed via pyproject.toml
|
| 32 |
WORKDIR /app/env
|
| 33 |
|
| 34 |
-
#
|
| 35 |
-
RUN
|
| 36 |
-
|
| 37 |
-
mv /root/.local/bin/uv /usr/local/bin/uv && \
|
| 38 |
-
mv /root/.local/bin/uvx /usr/local/bin/uvx; \
|
| 39 |
-
fi
|
| 40 |
-
|
| 41 |
-
# Install dependencies using uv sync
|
| 42 |
-
# If uv.lock exists, use it; otherwise resolve on the fly
|
| 43 |
-
RUN --mount=type=cache,target=/root/.cache/uv \
|
| 44 |
-
if [ -f uv.lock ]; then \
|
| 45 |
-
uv sync --frozen --no-install-project --no-editable; \
|
| 46 |
-
else \
|
| 47 |
-
uv sync --no-install-project --no-editable; \
|
| 48 |
-
fi
|
| 49 |
|
|
|
|
| 50 |
RUN --mount=type=cache,target=/root/.cache/uv \
|
| 51 |
-
|
| 52 |
-
uv sync --frozen --no-editable; \
|
| 53 |
-
else \
|
| 54 |
-
uv sync --no-editable; \
|
| 55 |
-
fi
|
| 56 |
|
| 57 |
-
#
|
| 58 |
FROM ${BASE_IMAGE}
|
| 59 |
|
| 60 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
|
| 62 |
-
|
| 63 |
-
COPY --from=builder /app/env/.venv /app/.venv
|
| 64 |
|
| 65 |
-
# Copy the environment code
|
|
|
|
| 66 |
COPY --from=builder /app/env /app/env
|
| 67 |
|
| 68 |
# Set PATH to use the virtual environment
|
| 69 |
-
ENV PATH="/app/.venv/bin:$PATH"
|
|
|
|
|
|
|
| 70 |
|
| 71 |
-
#
|
| 72 |
-
|
| 73 |
|
| 74 |
-
#
|
| 75 |
-
|
| 76 |
-
CMD curl -f http://localhost:8000/health || exit 1
|
| 77 |
|
| 78 |
-
#
|
| 79 |
-
|
| 80 |
-
CMD
|
| 81 |
|
| 82 |
-
#
|
| 83 |
-
|
|
|
|
|
|
| 1 |
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
# All rights reserved.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
|
| 4 |
ARG BASE_IMAGE=ghcr.io/meta-pytorch/openenv-base:latest
|
| 5 |
FROM ${BASE_IMAGE} AS builder
|
| 6 |
|
| 7 |
WORKDIR /app
|
| 8 |
|
| 9 |
+
# Install system dependencies
|
| 10 |
RUN apt-get update && \
|
| 11 |
+
apt-get install -y --no-install-recommends git curl && \
|
| 12 |
rm -rf /var/lib/apt/lists/*
|
| 13 |
|
| 14 |
+
# Copy the entire project
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
COPY . /app/env
|
| 16 |
|
|
|
|
|
|
|
| 17 |
WORKDIR /app/env
|
| 18 |
|
| 19 |
+
# Install 'uv' for high-performance dependency management
|
| 20 |
+
RUN curl -LsSf https://astral.sh/uv/install.sh | sh && \
|
| 21 |
+
mv /root/.local/bin/uv /usr/local/bin/uv
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
|
| 23 |
+
# Sync dependencies into a virtual environment
|
| 24 |
RUN --mount=type=cache,target=/root/.cache/uv \
|
| 25 |
+
uv sync --no-install-project --no-editable
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
|
| 27 |
+
# --- Runtime Stage ---
|
| 28 |
FROM ${BASE_IMAGE}
|
| 29 |
|
| 30 |
+
# Set Hugging Face required environment variables
|
| 31 |
+
ENV PYTHONUNBUFFERED=1 \
|
| 32 |
+
PYTHONDONTWRITEBYTECODE=1 \
|
| 33 |
+
PORT=7860 \
|
| 34 |
+
ENABLE_WEB_INTERFACE=true
|
| 35 |
|
| 36 |
+
WORKDIR /app/env
|
|
|
|
| 37 |
|
| 38 |
+
# Copy the virtual environment and code from builder
|
| 39 |
+
COPY --from=builder /app/env/.venv /app/env/.venv
|
| 40 |
COPY --from=builder /app/env /app/env
|
| 41 |
|
| 42 |
# Set PATH to use the virtual environment
|
| 43 |
+
ENV PATH="/app/env/.venv/bin:$PATH"
|
| 44 |
+
# Ensure the root of the project is in PYTHONPATH
|
| 45 |
+
ENV PYTHONPATH="/app/env:/app/env/server:$PYTHONPATH"
|
| 46 |
|
| 47 |
+
# Hugging Face Spaces run with a non-root user (1000). Ensure permissions are correct.
|
| 48 |
+
RUN chmod -R 777 /app/env
|
| 49 |
|
| 50 |
+
# Standard HF Port exposure
|
| 51 |
+
EXPOSE 7860
|
|
|
|
| 52 |
|
| 53 |
+
# Updated Healthcheck to use the correct port
|
| 54 |
+
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
| 55 |
+
CMD curl -f http://localhost:7860/health || exit 1
|
| 56 |
|
| 57 |
+
# Start the server on 7860 with proxy support
|
| 58 |
+
# 'proxy_headers' is vital for HF reverse proxies to handle WebSockets correctly
|
| 59 |
+
CMD ["uvicorn", "server.app:app", "--host", "0.0.0.0", "--port", "7860", "--proxy-headers", "--forwarded-allow-ips", "*"]
|
server/lint_coding_agent_environment.py
CHANGED
|
@@ -4,13 +4,6 @@
|
|
| 4 |
# This source code is licensed under the BSD-style license found in the
|
| 5 |
# LICENSE file in the root directory of this source tree.
|
| 6 |
|
| 7 |
-
"""
|
| 8 |
-
Lint Coding Agent Environment Implementation.
|
| 9 |
-
|
| 10 |
-
A simple test environment that echoes back messages sent to it.
|
| 11 |
-
Perfect for testing HTTP server infrastructure.
|
| 12 |
-
"""
|
| 13 |
-
|
| 14 |
import json
|
| 15 |
import os
|
| 16 |
import ast
|
|
@@ -22,7 +15,8 @@ from typing import Tuple, Dict, Any, List
|
|
| 22 |
from openenv.core.env_server.interfaces import Environment
|
| 23 |
from openenv.core.env_server.types import State
|
| 24 |
|
| 25 |
-
# Setup Logging
|
|
|
|
| 26 |
logger = logging.getLogger("ArchitectEnvironment")
|
| 27 |
|
| 28 |
# --- ABSOLUTE IMPORT FIX ---
|
|
@@ -34,7 +28,7 @@ except ImportError:
|
|
| 34 |
class LintCodingAgentEnvironment(Environment):
|
| 35 |
"""
|
| 36 |
Advanced Repository Sandbox.
|
| 37 |
-
|
| 38 |
"""
|
| 39 |
SUPPORTS_CONCURRENT_SESSIONS: bool = True
|
| 40 |
|
|
@@ -44,25 +38,46 @@ class LintCodingAgentEnvironment(Environment):
|
|
| 44 |
self.failed_queue = []
|
| 45 |
self.vfs: Dict[str, str] = {}
|
| 46 |
|
| 47 |
-
# ---
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
|
| 52 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
|
| 64 |
self.max_levels = int(self.all_levels[-1]) if self.all_levels else 1
|
| 65 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
def reset(self) -> LintCodingAgentObservation:
|
| 67 |
"""Full state reset for Phase 2 scoring."""
|
| 68 |
logger.info("π Resetting Environment State...")
|
|
@@ -138,18 +153,17 @@ class LintCodingAgentEnvironment(Environment):
|
|
| 138 |
feedback = f"Transition Failed: {error_msg}"
|
| 139 |
logger.info(f"β Syntax Invalid: {error_msg}")
|
| 140 |
else:
|
| 141 |
-
# Check logic against the 'ans' substring in manifest
|
| 142 |
logic_ok = task_data["ans"].lower() in action.code_solution.lower()
|
| 143 |
|
| 144 |
if logic_ok:
|
| 145 |
reward = 1.0
|
| 146 |
feedback = f"Level {self.level} Success."
|
| 147 |
-
logger.info(f"β
Success! Solution matches
|
| 148 |
self.level += 1
|
| 149 |
else:
|
| 150 |
reward = 0.1
|
| 151 |
feedback = f"Syntax OK | Logic Missing: {task_data['task']}"
|
| 152 |
-
logger.info(f"β οΈ Logic Mismatch.
|
| 153 |
if str(self.level) not in self.failed_queue:
|
| 154 |
self.failed_queue.append(str(self.level))
|
| 155 |
|
|
|
|
| 4 |
# This source code is licensed under the BSD-style license found in the
|
| 5 |
# LICENSE file in the root directory of this source tree.
|
| 6 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
import json
|
| 8 |
import os
|
| 9 |
import ast
|
|
|
|
| 15 |
from openenv.core.env_server.interfaces import Environment
|
| 16 |
from openenv.core.env_server.types import State
|
| 17 |
|
| 18 |
+
# Setup Logging
|
| 19 |
+
logging.basicConfig(level=logging.INFO)
|
| 20 |
logger = logging.getLogger("ArchitectEnvironment")
|
| 21 |
|
| 22 |
# --- ABSOLUTE IMPORT FIX ---
|
|
|
|
| 28 |
class LintCodingAgentEnvironment(Environment):
|
| 29 |
"""
|
| 30 |
Advanced Repository Sandbox.
|
| 31 |
+
Fixed for Docker/Hugging Face pathing with robust resource discovery.
|
| 32 |
"""
|
| 33 |
SUPPORTS_CONCURRENT_SESSIONS: bool = True
|
| 34 |
|
|
|
|
| 38 |
self.failed_queue = []
|
| 39 |
self.vfs: Dict[str, str] = {}
|
| 40 |
|
| 41 |
+
# --- ROBUST PATH DISCOVERY ---
|
| 42 |
+
# 1. Try directory relative to this file
|
| 43 |
+
current_file_dir = os.path.dirname(os.path.abspath(__file__))
|
| 44 |
+
|
| 45 |
+
# Candidate paths for QUESTIONS.json
|
| 46 |
+
candidates = [
|
| 47 |
+
os.path.join(current_file_dir, "QUESTIONS.json"),
|
| 48 |
+
os.path.join(os.getcwd(), "server", "QUESTIONS.json"),
|
| 49 |
+
os.path.join(os.getcwd(), "QUESTIONS.json"),
|
| 50 |
+
"/app/server/QUESTIONS.json"
|
| 51 |
+
]
|
| 52 |
|
| 53 |
+
json_path = None
|
| 54 |
+
for path in candidates:
|
| 55 |
+
if os.path.exists(path):
|
| 56 |
+
json_path = path
|
| 57 |
+
self.templates_base = os.path.join(os.path.dirname(path), "templates")
|
| 58 |
+
break
|
| 59 |
|
| 60 |
+
if json_path:
|
| 61 |
+
logger.info(f"ποΈ Environment Resources Found: {json_path}")
|
| 62 |
+
try:
|
| 63 |
+
with open(json_path, "r") as f:
|
| 64 |
+
self.curriculum = json.load(f)
|
| 65 |
+
self.all_levels = sorted(list(self.curriculum.keys()), key=int)
|
| 66 |
+
logger.info(f"π Curriculum Loaded: {len(self.all_levels)} levels.")
|
| 67 |
+
except Exception as e:
|
| 68 |
+
logger.error(f"β Error reading QUESTIONS.json: {e}")
|
| 69 |
+
self._set_fallback_curriculum()
|
| 70 |
+
else:
|
| 71 |
+
logger.error(f"β CRITICAL: QUESTIONS.json not found in any candidate path: {candidates}")
|
| 72 |
+
self._set_fallback_curriculum()
|
| 73 |
|
| 74 |
self.max_levels = int(self.all_levels[-1]) if self.all_levels else 1
|
| 75 |
|
| 76 |
+
def _set_fallback_curriculum(self):
|
| 77 |
+
self.curriculum = {"1": {"lang": "Python", "task": "Fallback: Init", "template_dir": "level_1", "ans": "print"}}
|
| 78 |
+
self.all_levels = ["1"]
|
| 79 |
+
self.templates_base = os.path.join(os.path.dirname(os.path.abspath(__file__)), "templates")
|
| 80 |
+
|
| 81 |
def reset(self) -> LintCodingAgentObservation:
|
| 82 |
"""Full state reset for Phase 2 scoring."""
|
| 83 |
logger.info("π Resetting Environment State...")
|
|
|
|
| 153 |
feedback = f"Transition Failed: {error_msg}"
|
| 154 |
logger.info(f"β Syntax Invalid: {error_msg}")
|
| 155 |
else:
|
|
|
|
| 156 |
logic_ok = task_data["ans"].lower() in action.code_solution.lower()
|
| 157 |
|
| 158 |
if logic_ok:
|
| 159 |
reward = 1.0
|
| 160 |
feedback = f"Level {self.level} Success."
|
| 161 |
+
logger.info(f"β
Success! Solution matches logic check.")
|
| 162 |
self.level += 1
|
| 163 |
else:
|
| 164 |
reward = 0.1
|
| 165 |
feedback = f"Syntax OK | Logic Missing: {task_data['task']}"
|
| 166 |
+
logger.info(f"β οΈ Logic Mismatch.")
|
| 167 |
if str(self.level) not in self.failed_queue:
|
| 168 |
self.failed_queue.append(str(self.level))
|
| 169 |
|