Spaces:
Sleeping
Sleeping
Upload folder using huggingface_hub
Browse files- Dockerfile +81 -0
- README.md +0 -0
- __init__.py +16 -0
- client.py +54 -0
- inference.py +66 -0
- inference_output.txt +0 -0
- models.py +23 -0
- openenv.yaml +11 -0
- openenv_openenv_jayesh.egg-info/PKG-INFO +9 -0
- openenv_openenv_jayesh.egg-info/SOURCES.txt +14 -0
- openenv_openenv_jayesh.egg-info/dependency_links.txt +1 -0
- openenv_openenv_jayesh.egg-info/entry_points.txt +2 -0
- openenv_openenv_jayesh.egg-info/requires.txt +5 -0
- openenv_openenv_jayesh.egg-info/top_level.txt +1 -0
- out.py +201 -0
- out_utf8.txt +23 -0
- pyproject.toml +45 -0
- server/__init__.py +11 -0
- server/app.py +42 -0
- server/openenv_jayesh_environment.py +185 -0
- server/requirements.txt +3 -0
- uv.lock +0 -0
Dockerfile
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
# Ensure git is available (required for installing dependencies from VCS)
|
| 19 |
+
RUN apt-get update && \
|
| 20 |
+
apt-get install -y --no-install-recommends git && \
|
| 21 |
+
rm -rf /var/lib/apt/lists/*
|
| 22 |
+
|
| 23 |
+
# Build argument to control whether we're building standalone or in-repo
|
| 24 |
+
ARG BUILD_MODE=in-repo
|
| 25 |
+
ARG ENV_NAME=openenv_jayesh
|
| 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 |
+
# Ensure uv is available (for local builds where base image lacks it)
|
| 35 |
+
RUN if ! command -v uv >/dev/null 2>&1; then \
|
| 36 |
+
curl -LsSf https://astral.sh/uv/install.sh | sh && \
|
| 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 |
+
if [ -f uv.lock ]; then \
|
| 52 |
+
uv sync --frozen --no-editable; \
|
| 53 |
+
else \
|
| 54 |
+
uv sync --no-editable; \
|
| 55 |
+
fi
|
| 56 |
+
|
| 57 |
+
# Final runtime stage
|
| 58 |
+
FROM ${BASE_IMAGE}
|
| 59 |
+
|
| 60 |
+
WORKDIR /app
|
| 61 |
+
|
| 62 |
+
# Copy the virtual environment from builder
|
| 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 |
+
# Set PYTHONPATH so imports work correctly
|
| 72 |
+
ENV PYTHONPATH="/app/env:$PYTHONPATH"
|
| 73 |
+
|
| 74 |
+
# Health check
|
| 75 |
+
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
| 76 |
+
CMD curl -f http://localhost:8000/health || exit 1
|
| 77 |
+
|
| 78 |
+
# Run the FastAPI server
|
| 79 |
+
# The module path is constructed to work with the /app/env structure
|
| 80 |
+
ENV ENABLE_WEB_INTERFACE=true
|
| 81 |
+
CMD ["sh", "-c", "cd /app/env && uvicorn server.app:app --host 0.0.0.0 --port 8000"]
|
README.md
CHANGED
|
Binary files a/README.md and b/README.md differ
|
|
|
__init__.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
"""Openenv Jayesh Environment."""
|
| 8 |
+
|
| 9 |
+
from .client import OpenenvJayeshEnv
|
| 10 |
+
from .models import OpenenvJayeshAction, OpenenvJayeshObservation
|
| 11 |
+
|
| 12 |
+
__all__ = [
|
| 13 |
+
"OpenenvJayeshAction",
|
| 14 |
+
"OpenenvJayeshObservation",
|
| 15 |
+
"OpenenvJayeshEnv",
|
| 16 |
+
]
|
client.py
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
|
| 4 |
+
"""Openenv Jayesh Environment Client."""
|
| 5 |
+
|
| 6 |
+
from typing import Dict
|
| 7 |
+
|
| 8 |
+
from openenv.core import EnvClient
|
| 9 |
+
from openenv.core.client_types import StepResult
|
| 10 |
+
from openenv.core.env_server.types import State
|
| 11 |
+
|
| 12 |
+
try:
|
| 13 |
+
from .models import TaskManagerAction, TaskManagerObservation
|
| 14 |
+
except ImportError:
|
| 15 |
+
from models import TaskManagerAction, TaskManagerObservation
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
class OpenenvJayeshEnv(
|
| 19 |
+
EnvClient[TaskManagerAction, TaskManagerObservation, State]
|
| 20 |
+
):
|
| 21 |
+
"""Client for the Task Manager Environment."""
|
| 22 |
+
|
| 23 |
+
def _step_payload(self, action: TaskManagerAction) -> Dict:
|
| 24 |
+
"""Convert TaskManagerAction to JSON payload."""
|
| 25 |
+
return {
|
| 26 |
+
"command": action.command,
|
| 27 |
+
"title": action.title,
|
| 28 |
+
"priority": action.priority,
|
| 29 |
+
"deadline": action.deadline
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
def _parse_result(self, payload: Dict) -> StepResult[TaskManagerObservation]:
|
| 33 |
+
"""Parse server response."""
|
| 34 |
+
obs_data = payload.get("observation", {})
|
| 35 |
+
observation = TaskManagerObservation(
|
| 36 |
+
success=obs_data.get("success", True),
|
| 37 |
+
message=obs_data.get("message", ""),
|
| 38 |
+
tasks=obs_data.get("tasks", []),
|
| 39 |
+
done=payload.get("done", False),
|
| 40 |
+
reward=payload.get("reward", 0.0),
|
| 41 |
+
metadata=obs_data.get("metadata", {}),
|
| 42 |
+
)
|
| 43 |
+
|
| 44 |
+
return StepResult(
|
| 45 |
+
observation=observation,
|
| 46 |
+
reward=payload.get("reward", 0.0),
|
| 47 |
+
done=payload.get("done", False),
|
| 48 |
+
)
|
| 49 |
+
|
| 50 |
+
def _parse_state(self, payload: Dict) -> State:
|
| 51 |
+
return State(
|
| 52 |
+
episode_id=payload.get("episode_id"),
|
| 53 |
+
step_count=payload.get("step_count", 0),
|
| 54 |
+
)
|
inference.py
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sys
|
| 2 |
+
import os
|
| 3 |
+
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
| 4 |
+
|
| 5 |
+
from client import OpenenvJayeshEnv
|
| 6 |
+
from models import TaskManagerAction
|
| 7 |
+
|
| 8 |
+
def run_easy_task(client):
|
| 9 |
+
print("\n=== Scenario: Easy ===")
|
| 10 |
+
res = client.reset()
|
| 11 |
+
default_msg = res.observation.message if hasattr(res.observation, 'message') else "No message"
|
| 12 |
+
print(f"Goal: {default_msg}")
|
| 13 |
+
|
| 14 |
+
res = client.step(TaskManagerAction(command="add", title="Buy groceries", priority="Normal"))
|
| 15 |
+
print(f"Action 'add 1': {res.observation.message} | Score: {res.reward}")
|
| 16 |
+
res = client.step(TaskManagerAction(command="add", title="Do laundry", priority="Low"))
|
| 17 |
+
print(f"Action 'add 2': {res.observation.message} | Score: {res.reward}")
|
| 18 |
+
res = client.step(TaskManagerAction(command="list"))
|
| 19 |
+
print(f"Action 'list': {res.observation.message} | Score: {res.reward} | Done: {res.done}")
|
| 20 |
+
|
| 21 |
+
def run_medium_task(client):
|
| 22 |
+
print("\n=== Scenario: Medium ===")
|
| 23 |
+
res = client.reset()
|
| 24 |
+
default_msg = res.observation.message if hasattr(res.observation, 'message') else "No message"
|
| 25 |
+
print(f"Goal: {default_msg}")
|
| 26 |
+
|
| 27 |
+
res = client.step(TaskManagerAction(command="add", title="Write code", priority="Normal"))
|
| 28 |
+
print(f"Action 'add Normal': {res.observation.message} | Score: {res.reward}")
|
| 29 |
+
res = client.step(TaskManagerAction(command="add", title="Review PR", priority="High"))
|
| 30 |
+
print(f"Action 'add High': {res.observation.message} | Score: {res.reward}")
|
| 31 |
+
res = client.step(TaskManagerAction(command="add", title="Check emails", priority="Low"))
|
| 32 |
+
print(f"Action 'add Low': {res.observation.message} | Score: {res.reward}")
|
| 33 |
+
|
| 34 |
+
res = client.step(TaskManagerAction(command="complete", title="Review PR"))
|
| 35 |
+
print(f"Action 'complete High': {res.observation.message} | Score: {res.reward} | Done: {res.done}")
|
| 36 |
+
|
| 37 |
+
def run_hard_task(client):
|
| 38 |
+
print("\n=== Scenario: Hard ===")
|
| 39 |
+
res = client.reset()
|
| 40 |
+
default_msg = res.observation.message if hasattr(res.observation, 'message') else "No message"
|
| 41 |
+
print(f"Goal: {default_msg}")
|
| 42 |
+
|
| 43 |
+
res = client.step(TaskManagerAction(command="add", title="Fix prod bug", priority="High"))
|
| 44 |
+
print(f"Action 'add High 1': {res.observation.message} | Score: {res.reward}")
|
| 45 |
+
res = client.step(TaskManagerAction(command="add", title="Write incident report", priority="High"))
|
| 46 |
+
print(f"Action 'add High 2': {res.observation.message} | Score: {res.reward}")
|
| 47 |
+
res = client.step(TaskManagerAction(command="add", title="Refactor", priority="Normal"))
|
| 48 |
+
print(f"Action 'add Normal': {res.observation.message} | Score: {res.reward}")
|
| 49 |
+
res = client.step(TaskManagerAction(command="add", title="Update docs", priority="Low"))
|
| 50 |
+
print(f"Action 'add Low': {res.observation.message} | Score: {res.reward}")
|
| 51 |
+
|
| 52 |
+
res = client.step(TaskManagerAction(command="complete", title="Fix prod bug"))
|
| 53 |
+
print(f"Action 'complete High 1': {res.observation.message} | Score: {res.reward}")
|
| 54 |
+
res = client.step(TaskManagerAction(command="complete", title="Write incident report"))
|
| 55 |
+
print(f"Action 'complete High 2': {res.observation.message} | Score: {res.reward} | Done: {res.done}")
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
if __name__ == "__main__":
|
| 59 |
+
print("Connecting to OpenEnv Task Manager Server on http://127.0.0.1:8000...")
|
| 60 |
+
try:
|
| 61 |
+
with OpenenvJayeshEnv(base_url="http://127.0.0.1:8000").sync() as client:
|
| 62 |
+
run_easy_task(client)
|
| 63 |
+
run_medium_task(client)
|
| 64 |
+
run_hard_task(client)
|
| 65 |
+
except Exception as e:
|
| 66 |
+
print(f"Error connecting to server. Is it running? {e}")
|
inference_output.txt
ADDED
|
Binary file (3.24 kB). View file
|
|
|
models.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
|
| 4 |
+
"""
|
| 5 |
+
Data models for the Openenv Jayesh Task Manager Environment.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
from typing import Optional, List, Dict, Any
|
| 9 |
+
from openenv.core.env_server.types import Action, Observation
|
| 10 |
+
from pydantic import Field
|
| 11 |
+
|
| 12 |
+
class TaskManagerAction(Action):
|
| 13 |
+
"""Action for the Task Manager."""
|
| 14 |
+
command: str = Field(..., description="The command to execute: 'add', 'complete', 'list'")
|
| 15 |
+
title: Optional[str] = Field(default=None, description="Task title (for 'add' or 'complete')")
|
| 16 |
+
priority: Optional[str] = Field(default="Normal", description="Task priority (for 'add')")
|
| 17 |
+
deadline: Optional[str] = Field(default=None, description="Task deadline (for 'add')")
|
| 18 |
+
|
| 19 |
+
class TaskManagerObservation(Observation):
|
| 20 |
+
"""Observation from the Task Manager."""
|
| 21 |
+
success: bool = Field(default=True, description="Whether the action succeeded")
|
| 22 |
+
message: str = Field(default="", description="System message or error")
|
| 23 |
+
tasks: List[Dict[str, Any]] = Field(default_factory=list, description="List of tasks in the system")
|
openenv.yaml
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
spec_version: 1
|
| 2 |
+
name: task-manager-openenv
|
| 3 |
+
type: space
|
| 4 |
+
runtime: fastapi
|
| 5 |
+
app: server.app:app
|
| 6 |
+
port: 8000
|
| 7 |
+
description: "OpenEnv Hackathon Task Manager: AI agent environment tracking task states through Easy, Medium, and Hard dynamic constraints."
|
| 8 |
+
tags:
|
| 9 |
+
- task-manager
|
| 10 |
+
- productivity
|
| 11 |
+
- openenv
|
openenv_openenv_jayesh.egg-info/PKG-INFO
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Metadata-Version: 2.4
|
| 2 |
+
Name: openenv-openenv_jayesh
|
| 3 |
+
Version: 0.1.0
|
| 4 |
+
Summary: Openenv Jayesh environment for OpenEnv
|
| 5 |
+
Requires-Python: >=3.10
|
| 6 |
+
Requires-Dist: openenv-core[core]>=0.2.1
|
| 7 |
+
Provides-Extra: dev
|
| 8 |
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
| 9 |
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
openenv_openenv_jayesh.egg-info/SOURCES.txt
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
README.md
|
| 2 |
+
pyproject.toml
|
| 3 |
+
./__init__.py
|
| 4 |
+
./client.py
|
| 5 |
+
./models.py
|
| 6 |
+
openenv_openenv_jayesh.egg-info/PKG-INFO
|
| 7 |
+
openenv_openenv_jayesh.egg-info/SOURCES.txt
|
| 8 |
+
openenv_openenv_jayesh.egg-info/dependency_links.txt
|
| 9 |
+
openenv_openenv_jayesh.egg-info/entry_points.txt
|
| 10 |
+
openenv_openenv_jayesh.egg-info/requires.txt
|
| 11 |
+
openenv_openenv_jayesh.egg-info/top_level.txt
|
| 12 |
+
server/__init__.py
|
| 13 |
+
server/app.py
|
| 14 |
+
server/openenv_jayesh_environment.py
|
openenv_openenv_jayesh.egg-info/dependency_links.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
|
openenv_openenv_jayesh.egg-info/entry_points.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[console_scripts]
|
| 2 |
+
server = openenv_jayesh.server.app:main
|
openenv_openenv_jayesh.egg-info/requires.txt
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
openenv-core[core]>=0.2.1
|
| 2 |
+
|
| 3 |
+
[dev]
|
| 4 |
+
pytest>=8.0.0
|
| 5 |
+
pytest-cov>=4.0.0
|
openenv_openenv_jayesh.egg-info/top_level.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
openenv_jayesh
|
out.py
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
class Environment(ABC, Generic[ActT, ObsT, StateT]):
|
| 2 |
+
"""Base class for all environment servers following Gym/Gymnasium API.
|
| 3 |
+
|
| 4 |
+
Args:
|
| 5 |
+
transform: Optional transform to apply to observations
|
| 6 |
+
rubric: Optional rubric for reward computation. When provided, the
|
| 7 |
+
rubric's output can be used to set the observation's reward in step().
|
| 8 |
+
|
| 9 |
+
Class Attributes:
|
| 10 |
+
SUPPORTS_CONCURRENT_SESSIONS: Whether this environment supports concurrent sessions.
|
| 11 |
+
When True, multiple WebSocket connections can each have their own
|
| 12 |
+
environment instance (up to max_concurrent_envs). When False (default),
|
| 13 |
+
the environment should only be used with a single session at a time.
|
| 14 |
+
|
| 15 |
+
Set this to True in your Environment subclass if:
|
| 16 |
+
- The environment uses proper session isolation (e.g., unique working dirs)
|
| 17 |
+
- No shared mutable state exists between instances
|
| 18 |
+
- External resources (databases, APIs) can handle concurrent access
|
| 19 |
+
|
| 20 |
+
Attributes:
|
| 21 |
+
rubric: Optional rubric for computing rewards. Environments can set this
|
| 22 |
+
in __init__ and use it in step() to compute observation rewards.
|
| 23 |
+
Training infrastructure can access it for introspection:
|
| 24 |
+
for name, r in env.rubric.named_rubrics():
|
| 25 |
+
print(f"{name}: {r.last_score}")
|
| 26 |
+
|
| 27 |
+
See RFC 004 for rubric design: rfcs/004-rubrics.md
|
| 28 |
+
"""
|
| 29 |
+
|
| 30 |
+
# Class-level flag indicating whether this environment supports concurrent sessions
|
| 31 |
+
SUPPORTS_CONCURRENT_SESSIONS: bool = False
|
| 32 |
+
|
| 33 |
+
# Optional rubric for reward computation
|
| 34 |
+
rubric: Optional["Rubric"]
|
| 35 |
+
|
| 36 |
+
def __init__(
|
| 37 |
+
self,
|
| 38 |
+
transform: Optional[Transform[ObsT]] = None,
|
| 39 |
+
rubric: Optional["Rubric"] = None,
|
| 40 |
+
):
|
| 41 |
+
self.transform = transform
|
| 42 |
+
self.rubric = rubric
|
| 43 |
+
|
| 44 |
+
@abstractmethod
|
| 45 |
+
def reset(
|
| 46 |
+
self,
|
| 47 |
+
seed: Optional[int] = None,
|
| 48 |
+
episode_id: Optional[str] = None,
|
| 49 |
+
**kwargs: Any,
|
| 50 |
+
) -> ObsT:
|
| 51 |
+
"""Reset the environment and return initial observation."""
|
| 52 |
+
pass
|
| 53 |
+
|
| 54 |
+
async def reset_async(
|
| 55 |
+
self,
|
| 56 |
+
seed: Optional[int] = None,
|
| 57 |
+
episode_id: Optional[str] = None,
|
| 58 |
+
**kwargs: Any,
|
| 59 |
+
) -> ObsT:
|
| 60 |
+
"""Async version of reset. Default implementation calls sync reset.
|
| 61 |
+
|
| 62 |
+
Override to provide true async implementation.
|
| 63 |
+
"""
|
| 64 |
+
return self.reset(seed=seed, episode_id=episode_id, **kwargs)
|
| 65 |
+
|
| 66 |
+
@abstractmethod
|
| 67 |
+
def step(
|
| 68 |
+
self,
|
| 69 |
+
action: ActT,
|
| 70 |
+
timeout_s: Optional[float] = None,
|
| 71 |
+
**kwargs: Any,
|
| 72 |
+
) -> ObsT:
|
| 73 |
+
"""Take a step in the environment."""
|
| 74 |
+
pass
|
| 75 |
+
|
| 76 |
+
async def step_async(
|
| 77 |
+
self,
|
| 78 |
+
action: ActT,
|
| 79 |
+
timeout_s: Optional[float] = None,
|
| 80 |
+
**kwargs: Any,
|
| 81 |
+
) -> ObsT:
|
| 82 |
+
"""Async version of step. Default implementation calls sync step.
|
| 83 |
+
|
| 84 |
+
Override to provide true async implementation.
|
| 85 |
+
"""
|
| 86 |
+
return self.step(action, timeout_s=timeout_s, **kwargs)
|
| 87 |
+
|
| 88 |
+
@property
|
| 89 |
+
@abstractmethod
|
| 90 |
+
def state(self) -> StateT:
|
| 91 |
+
"""Get the current environment state."""
|
| 92 |
+
pass
|
| 93 |
+
|
| 94 |
+
def get_metadata(self) -> EnvironmentMetadata:
|
| 95 |
+
"""
|
| 96 |
+
Get metadata about this environment.
|
| 97 |
+
|
| 98 |
+
Override this method to provide custom metadata for the environment.
|
| 99 |
+
Default implementation returns basic metadata derived from class name.
|
| 100 |
+
|
| 101 |
+
Returns:
|
| 102 |
+
EnvironmentMetadata with environment information
|
| 103 |
+
"""
|
| 104 |
+
return EnvironmentMetadata(
|
| 105 |
+
name=self.__class__.__name__,
|
| 106 |
+
description=f"{self.__class__.__name__} environment",
|
| 107 |
+
version="1.0.0",
|
| 108 |
+
)
|
| 109 |
+
|
| 110 |
+
def _apply_transform(self, observation: ObsT) -> ObsT:
|
| 111 |
+
"""Apply transform if one is provided."""
|
| 112 |
+
if self.transform is not None:
|
| 113 |
+
return self.transform(observation)
|
| 114 |
+
return observation
|
| 115 |
+
|
| 116 |
+
def _apply_rubric(self, action: ActT, observation: ObsT) -> float:
|
| 117 |
+
"""Apply rubric if one is provided.
|
| 118 |
+
|
| 119 |
+
Args:
|
| 120 |
+
action: The action taken by the agent.
|
| 121 |
+
observation: The resulting observation.
|
| 122 |
+
|
| 123 |
+
Returns:
|
| 124 |
+
Reward value from the rubric, or 0.0 if no rubric is set.
|
| 125 |
+
|
| 126 |
+
Usage in step():
|
| 127 |
+
def step(self, action: MyAction, ...) -> MyObservation:
|
| 128 |
+
# ... execute action and create observation ...
|
| 129 |
+
observation.reward = self._apply_rubric(action, observation)
|
| 130 |
+
return observation
|
| 131 |
+
"""
|
| 132 |
+
if self.rubric is not None:
|
| 133 |
+
return self.rubric(action, observation)
|
| 134 |
+
return 0.0
|
| 135 |
+
|
| 136 |
+
async def _apply_rubric_async(self, action: ActT, observation: ObsT) -> float:
|
| 137 |
+
"""Apply rubric asynchronously if one is provided.
|
| 138 |
+
|
| 139 |
+
Args:
|
| 140 |
+
action: The action taken by the agent.
|
| 141 |
+
observation: The resulting observation.
|
| 142 |
+
|
| 143 |
+
Returns:
|
| 144 |
+
Reward value from the rubric, or 0.0 if no rubric is set.
|
| 145 |
+
|
| 146 |
+
Usage in step_async():
|
| 147 |
+
async def step_async(self, action: MyAction, ...) -> MyObservation:
|
| 148 |
+
# ... execute action and create observation ...
|
| 149 |
+
observation.reward = await self._apply_rubric_async(action, observation)
|
| 150 |
+
return observation
|
| 151 |
+
"""
|
| 152 |
+
if self.rubric is not None:
|
| 153 |
+
result = self.rubric(action, observation)
|
| 154 |
+
# If rubric returns a coroutine, await it
|
| 155 |
+
if inspect.iscoroutine(result):
|
| 156 |
+
return await result
|
| 157 |
+
return result
|
| 158 |
+
return 0.0
|
| 159 |
+
|
| 160 |
+
def _reset_rubric(self) -> None:
|
| 161 |
+
"""Reset the rubric state if one is provided.
|
| 162 |
+
|
| 163 |
+
Call this in reset() to clear any trajectory state in the rubric.
|
| 164 |
+
|
| 165 |
+
Usage in reset():
|
| 166 |
+
def reset(self, ...) -> MyObservation:
|
| 167 |
+
self._reset_rubric()
|
| 168 |
+
# ... create initial observation ...
|
| 169 |
+
return observation
|
| 170 |
+
"""
|
| 171 |
+
if self.rubric is not None:
|
| 172 |
+
self.rubric.reset()
|
| 173 |
+
|
| 174 |
+
async def _reset_rubric_async(self) -> None:
|
| 175 |
+
"""Reset the rubric state asynchronously if one is provided.
|
| 176 |
+
|
| 177 |
+
Call this in reset_async() to clear any trajectory state in the rubric.
|
| 178 |
+
|
| 179 |
+
Usage in reset_async():
|
| 180 |
+
async def reset_async(self, ...) -> MyObservation:
|
| 181 |
+
await self._reset_rubric_async()
|
| 182 |
+
# ... create initial observation ...
|
| 183 |
+
return observation
|
| 184 |
+
"""
|
| 185 |
+
if self.rubric is not None:
|
| 186 |
+
# Check if rubric has async reset method
|
| 187 |
+
if hasattr(self.rubric, "reset_async"):
|
| 188 |
+
result = self.rubric.reset_async()
|
| 189 |
+
if inspect.iscoroutine(result):
|
| 190 |
+
await result
|
| 191 |
+
else:
|
| 192 |
+
self.rubric.reset()
|
| 193 |
+
|
| 194 |
+
def close(self) -> None:
|
| 195 |
+
"""Clean up resources used by the environment.
|
| 196 |
+
|
| 197 |
+
Override this method to implement custom cleanup logic.
|
| 198 |
+
Called when the environment is being destroyed or reset.
|
| 199 |
+
"""
|
| 200 |
+
pass
|
| 201 |
+
|
out_utf8.txt
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Connecting to OpenEnv Task Manager Server on http://127.0.0.1:8000...
|
| 2 |
+
|
| 3 |
+
=== Scenario: Easy ===
|
| 4 |
+
Goal: Task Manager started in Easy mode. Goal: Add 2 tasks and list them.
|
| 5 |
+
Action 'add 1': Task 'Buy groceries' added successfully. (Priority: Normal) | Score: 0.4
|
| 6 |
+
Action 'add 2': Task 'Do laundry' added successfully. (Priority: Low) | Score: 0.8
|
| 7 |
+
Action 'list': Listed 2 current tasks. | Score: 1.0 | Done: True
|
| 8 |
+
|
| 9 |
+
=== Scenario: Medium ===
|
| 10 |
+
Goal: Task Manager started in Medium mode. Goal: Add 3 tasks (mixed priorities) and complete all High priority ones.
|
| 11 |
+
Action 'add Normal': Task 'Write code' added successfully. (Priority: Normal) | Score: 0.2
|
| 12 |
+
Action 'add High': Task 'Review PR' added successfully. (Priority: High) | Score: 0.5
|
| 13 |
+
Action 'add Low': Task 'Check emails' added successfully. (Priority: Low) | Score: 0.7
|
| 14 |
+
Action 'complete High': Task 'Review PR' marked as completed. | Score: 1.0 | Done: True
|
| 15 |
+
|
| 16 |
+
=== Scenario: Hard ===
|
| 17 |
+
Goal: Task Manager started in Hard mode. Goal: Add 4 tasks (at least 2 High) and complete at least 2 High priority tasks.
|
| 18 |
+
Action 'add High 1': Task 'Fix prod bug' added successfully. (Priority: High) | Score: 0.2
|
| 19 |
+
Action 'add High 2': Task 'Write incident report' added successfully. (Priority: High) | Score: 0.4
|
| 20 |
+
Action 'add Normal': Task 'Refactor' added successfully. (Priority: Normal) | Score: 0.5
|
| 21 |
+
Action 'add Low': Task 'Update docs' added successfully. (Priority: Low) | Score: 0.6
|
| 22 |
+
Action 'complete High 1': Task 'Fix prod bug' marked as completed. | Score: 0.8
|
| 23 |
+
Action 'complete High 2': Task 'Write incident report' marked as completed. | Score: 1.0 | Done: True
|
pyproject.toml
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
[build-system]
|
| 8 |
+
requires = ["setuptools>=45", "wheel"]
|
| 9 |
+
build-backend = "setuptools.build_meta"
|
| 10 |
+
|
| 11 |
+
[project]
|
| 12 |
+
name = "openenv-openenv_jayesh"
|
| 13 |
+
version = "0.1.0"
|
| 14 |
+
description = "Openenv Jayesh environment for OpenEnv"
|
| 15 |
+
requires-python = ">=3.10"
|
| 16 |
+
dependencies = [
|
| 17 |
+
# Core OpenEnv runtime (provides FastAPI server + HTTP client types)
|
| 18 |
+
# install from github
|
| 19 |
+
# "openenv-core[core] @ git+https://github.com/meta-pytorch/OpenEnv.git",
|
| 20 |
+
"openenv-core[core]>=0.2.1",
|
| 21 |
+
# Environment-specific dependencies
|
| 22 |
+
# Add all dependencies needed for your environment here
|
| 23 |
+
# Examples:
|
| 24 |
+
# "numpy>=1.19.0",
|
| 25 |
+
# "torch>=2.0.0",
|
| 26 |
+
# "gymnasium>=0.29.0",
|
| 27 |
+
# "openspiel>=1.0.0",
|
| 28 |
+
# "smolagents>=1.22.0,<2",
|
| 29 |
+
]
|
| 30 |
+
|
| 31 |
+
[project.optional-dependencies]
|
| 32 |
+
dev = [
|
| 33 |
+
"pytest>=8.0.0",
|
| 34 |
+
"pytest-cov>=4.0.0",
|
| 35 |
+
]
|
| 36 |
+
|
| 37 |
+
[project.scripts]
|
| 38 |
+
# Server entry point - enables running via: uv run --project . server
|
| 39 |
+
# or: python -m openenv_jayesh.server.app
|
| 40 |
+
server = "openenv_jayesh.server.app:main"
|
| 41 |
+
|
| 42 |
+
[tool.setuptools]
|
| 43 |
+
include-package-data = true
|
| 44 |
+
packages = ["openenv_jayesh", "openenv_jayesh.server"]
|
| 45 |
+
package-dir = { "openenv_jayesh" = ".", "openenv_jayesh.server" = "server" }
|
server/__init__.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
"""Openenv Jayesh environment server components."""
|
| 8 |
+
|
| 9 |
+
from .openenv_jayesh_environment import OpenenvJayeshEnvironment
|
| 10 |
+
|
| 11 |
+
__all__ = ["OpenenvJayeshEnvironment"]
|
server/app.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
|
| 4 |
+
"""
|
| 5 |
+
FastAPI application for the Openenv Jayesh Environment.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
try:
|
| 9 |
+
from openenv.core.env_server.http_server import create_app
|
| 10 |
+
except Exception as e: # pragma: no cover
|
| 11 |
+
raise ImportError(
|
| 12 |
+
"openenv is required for the web interface. Install dependencies with '\n uv sync\n'"
|
| 13 |
+
) from e
|
| 14 |
+
|
| 15 |
+
try:
|
| 16 |
+
from ..models import TaskManagerAction, TaskManagerObservation
|
| 17 |
+
from .openenv_jayesh_environment import OpenenvJayeshEnvironment
|
| 18 |
+
except (ModuleNotFoundError, ImportError):
|
| 19 |
+
from models import TaskManagerAction, TaskManagerObservation
|
| 20 |
+
from server.openenv_jayesh_environment import OpenenvJayeshEnvironment
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
app = create_app(
|
| 24 |
+
OpenenvJayeshEnvironment,
|
| 25 |
+
TaskManagerAction,
|
| 26 |
+
TaskManagerObservation,
|
| 27 |
+
env_name="openenv_jayesh",
|
| 28 |
+
max_concurrent_envs=1,
|
| 29 |
+
)
|
| 30 |
+
|
| 31 |
+
def main(host: str = "0.0.0.0", port: int = 8000):
|
| 32 |
+
import uvicorn
|
| 33 |
+
|
| 34 |
+
uvicorn.run(app, host=host, port=port)
|
| 35 |
+
|
| 36 |
+
if __name__ == "__main__":
|
| 37 |
+
import argparse
|
| 38 |
+
|
| 39 |
+
parser = argparse.ArgumentParser()
|
| 40 |
+
parser.add_argument("--port", type=int, default=8000)
|
| 41 |
+
args = parser.parse_args()
|
| 42 |
+
main(port=args.port)
|
server/openenv_jayesh_environment.py
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
|
| 4 |
+
"""
|
| 5 |
+
Openenv Jayesh Environment Implementation.
|
| 6 |
+
A Simple Task Manager Environment.
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
from typing import Optional, Any
|
| 10 |
+
from uuid import uuid4
|
| 11 |
+
|
| 12 |
+
from openenv.core.env_server.interfaces import Environment
|
| 13 |
+
from openenv.core.env_server.types import State
|
| 14 |
+
|
| 15 |
+
try:
|
| 16 |
+
from ..models import TaskManagerAction, TaskManagerObservation
|
| 17 |
+
except (ModuleNotFoundError, ImportError):
|
| 18 |
+
from models import TaskManagerAction, TaskManagerObservation
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
class OpenenvJayeshEnvironment(Environment):
|
| 22 |
+
"""A Task Manager environment with 3 difficulty modes."""
|
| 23 |
+
|
| 24 |
+
SUPPORTS_CONCURRENT_SESSIONS: bool = True
|
| 25 |
+
|
| 26 |
+
def __init__(self):
|
| 27 |
+
super().__init__()
|
| 28 |
+
self._state = State(episode_id=str(uuid4()), step_count=0)
|
| 29 |
+
self._reset_count = 0
|
| 30 |
+
self._scenarios = ["Easy", "Medium", "Hard"]
|
| 31 |
+
|
| 32 |
+
# Internal state
|
| 33 |
+
self.tasks = []
|
| 34 |
+
self.difficulty = "Easy"
|
| 35 |
+
self.goal_completed = False
|
| 36 |
+
self.tasks_added = 0
|
| 37 |
+
self.high_priority_added = 0
|
| 38 |
+
self.high_priority_completed = 0
|
| 39 |
+
self.list_called = False
|
| 40 |
+
|
| 41 |
+
def reset(
|
| 42 |
+
self,
|
| 43 |
+
seed: Optional[int] = None,
|
| 44 |
+
episode_id: Optional[str] = None,
|
| 45 |
+
**kwargs: Any,
|
| 46 |
+
) -> TaskManagerObservation:
|
| 47 |
+
"""Reset the environment."""
|
| 48 |
+
self._state = State(episode_id=episode_id or str(uuid4()), step_count=0)
|
| 49 |
+
|
| 50 |
+
idx = self._reset_count % len(self._scenarios)
|
| 51 |
+
self.difficulty = self._scenarios[idx]
|
| 52 |
+
self._reset_count += 1
|
| 53 |
+
|
| 54 |
+
self.tasks = []
|
| 55 |
+
self.goal_completed = False
|
| 56 |
+
self.tasks_added = 0
|
| 57 |
+
self.high_priority_added = 0
|
| 58 |
+
self.high_priority_completed = 0
|
| 59 |
+
self.list_called = False
|
| 60 |
+
|
| 61 |
+
message = f"Task Manager started in {self.difficulty} mode. "
|
| 62 |
+
if self.difficulty == "Easy":
|
| 63 |
+
message += "Goal: Add 2 tasks and list them."
|
| 64 |
+
elif self.difficulty == "Medium":
|
| 65 |
+
message += "Goal: Add 3 tasks (mixed priorities) and complete all High priority ones."
|
| 66 |
+
elif self.difficulty == "Hard":
|
| 67 |
+
message += "Goal: Add 4 tasks (at least 2 High) and complete at least 2 High priority tasks."
|
| 68 |
+
|
| 69 |
+
return TaskManagerObservation(
|
| 70 |
+
success=True,
|
| 71 |
+
message=message,
|
| 72 |
+
tasks=self.tasks,
|
| 73 |
+
done=False,
|
| 74 |
+
reward=0.0
|
| 75 |
+
)
|
| 76 |
+
|
| 77 |
+
def step(
|
| 78 |
+
self,
|
| 79 |
+
action: TaskManagerAction,
|
| 80 |
+
timeout_s: Optional[float] = None,
|
| 81 |
+
**kwargs: Any,
|
| 82 |
+
) -> TaskManagerObservation: # type: ignore[override]
|
| 83 |
+
"""Execute a step."""
|
| 84 |
+
self._state.step_count += 1
|
| 85 |
+
|
| 86 |
+
cmd = action.command.lower()
|
| 87 |
+
success = True
|
| 88 |
+
message = ""
|
| 89 |
+
|
| 90 |
+
if cmd == "add":
|
| 91 |
+
if not action.title:
|
| 92 |
+
success = False
|
| 93 |
+
message = "Title is required to add a task."
|
| 94 |
+
else:
|
| 95 |
+
prio = action.priority or "Normal"
|
| 96 |
+
new_task = {
|
| 97 |
+
"title": action.title,
|
| 98 |
+
"priority": prio,
|
| 99 |
+
"deadline": action.deadline or "N/A",
|
| 100 |
+
"completed": False
|
| 101 |
+
}
|
| 102 |
+
self.tasks.append(new_task)
|
| 103 |
+
self.tasks_added += 1
|
| 104 |
+
if prio == "High":
|
| 105 |
+
self.high_priority_added += 1
|
| 106 |
+
message = f"Task '{action.title}' added successfully. (Priority: {prio})"
|
| 107 |
+
elif cmd == "complete":
|
| 108 |
+
if not action.title:
|
| 109 |
+
success = False
|
| 110 |
+
message = "Title is required to complete a task."
|
| 111 |
+
else:
|
| 112 |
+
found = False
|
| 113 |
+
for t in self.tasks:
|
| 114 |
+
if t["title"] == action.title:
|
| 115 |
+
if not t["completed"]:
|
| 116 |
+
t["completed"] = True
|
| 117 |
+
if t["priority"] == "High":
|
| 118 |
+
self.high_priority_completed += 1
|
| 119 |
+
found = True
|
| 120 |
+
message = f"Task '{action.title}' marked as completed."
|
| 121 |
+
break
|
| 122 |
+
if not found:
|
| 123 |
+
success = False
|
| 124 |
+
message = f"Task '{action.title}' not found."
|
| 125 |
+
elif cmd == "list":
|
| 126 |
+
self.list_called = True
|
| 127 |
+
message = f"Listed {len(self.tasks)} current tasks."
|
| 128 |
+
else:
|
| 129 |
+
success = False
|
| 130 |
+
message = f"Unknown command: '{cmd}'"
|
| 131 |
+
|
| 132 |
+
reward = self._calculate_reward()
|
| 133 |
+
done = self.goal_completed
|
| 134 |
+
|
| 135 |
+
return TaskManagerObservation(
|
| 136 |
+
success=success,
|
| 137 |
+
message=message,
|
| 138 |
+
tasks=self.tasks,
|
| 139 |
+
done=done,
|
| 140 |
+
reward=reward,
|
| 141 |
+
metadata={"difficulty": self.difficulty, "step": self._state.step_count}
|
| 142 |
+
)
|
| 143 |
+
|
| 144 |
+
def _calculate_reward(self) -> float:
|
| 145 |
+
reward = 0.0
|
| 146 |
+
self.goal_completed = False
|
| 147 |
+
|
| 148 |
+
if self.difficulty == "Easy":
|
| 149 |
+
# Add 2 tasks (0.4 each) + list them (0.2)
|
| 150 |
+
reward += min(0.8, self.tasks_added * 0.4)
|
| 151 |
+
if self.list_called:
|
| 152 |
+
reward += 0.2
|
| 153 |
+
|
| 154 |
+
if self.tasks_added >= 2 and self.list_called:
|
| 155 |
+
reward = 1.0
|
| 156 |
+
self.goal_completed = True
|
| 157 |
+
|
| 158 |
+
elif self.difficulty == "Medium":
|
| 159 |
+
# Add 3 tasks (0.2 each) + Add High (0.1) + Complete High (0.3 ratio)
|
| 160 |
+
reward += min(0.6, self.tasks_added * 0.2)
|
| 161 |
+
if self.high_priority_added >= 1:
|
| 162 |
+
reward += 0.1
|
| 163 |
+
completion_ratio = self.high_priority_completed / self.high_priority_added
|
| 164 |
+
reward += min(0.3, completion_ratio * 0.3)
|
| 165 |
+
|
| 166 |
+
if self.tasks_added >= 3 and self.high_priority_added >= 1:
|
| 167 |
+
if self.high_priority_completed >= self.high_priority_added:
|
| 168 |
+
reward = 1.0
|
| 169 |
+
self.goal_completed = True
|
| 170 |
+
|
| 171 |
+
elif self.difficulty == "Hard":
|
| 172 |
+
# Add 4 tasks (0.1 each) + Add 2 High (0.1 each) + Complete 2 High (0.2 each)
|
| 173 |
+
reward += min(0.4, self.tasks_added * 0.1)
|
| 174 |
+
reward += min(0.2, self.high_priority_added * 0.1)
|
| 175 |
+
reward += min(0.4, self.high_priority_completed * 0.2)
|
| 176 |
+
|
| 177 |
+
if self.tasks_added >= 4 and self.high_priority_added >= 2 and self.high_priority_completed >= 2:
|
| 178 |
+
reward = 1.0
|
| 179 |
+
self.goal_completed = True
|
| 180 |
+
|
| 181 |
+
return round(min(1.0, reward), 2)
|
| 182 |
+
|
| 183 |
+
@property
|
| 184 |
+
def state(self) -> State:
|
| 185 |
+
return self._state
|
server/requirements.txt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
openenv-core[core]>=0.2.1
|
| 2 |
+
fastapi>=0.115.0
|
| 3 |
+
uvicorn>=0.24.0
|
uv.lock
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|