Prithvigg commited on
Commit
c26004e
·
1 Parent(s): 3f262c8

move dockerfile and app to root

Browse files
Files changed (4) hide show
  1. Dockerfile +80 -0
  2. __init__.py +3 -21
  3. app.py +61 -0
  4. echo_environment.py +196 -0
Dockerfile ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 src/core)
10
+ # - Standalone environments (with openenv-core from pip)
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
+ # Build argument to control whether we're building standalone or in-repo
19
+ ARG BUILD_MODE=in-repo
20
+
21
+ # Copy environment code (always at root of build context)
22
+ COPY . /app/env
23
+
24
+ # For in-repo builds, openenv-core is already in the pyproject.toml dependencies
25
+ # For standalone builds, openenv-core will be installed from pip via pyproject.toml
26
+ WORKDIR /app/env
27
+
28
+ # Ensure uv is available (for local builds where base image lacks it)
29
+ RUN if ! command -v uv >/dev/null 2>&1; then \
30
+ curl -LsSf https://astral.sh/uv/install.sh | sh && \
31
+ mv /root/.local/bin/uv /usr/local/bin/uv && \
32
+ mv /root/.local/bin/uvx /usr/local/bin/uvx; \
33
+ fi
34
+
35
+ # Install git for building from git repos (build-time only)
36
+ RUN apt-get update && apt-get install -y --no-install-recommends \
37
+ git \
38
+ && rm -rf /var/lib/apt/lists/*
39
+
40
+ # Install dependencies using uv sync
41
+ # First pass: install dependencies without the project (for better caching)
42
+ # Second pass: install the project itself
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 using Python (more portable than curl/wget)
75
+ HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
76
+ CMD python -c "import urllib.request; urllib.request.urlopen('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
+ CMD ["sh", "-c", "cd /app/env && uvicorn server.app:app --host 0.0.0.0 --port 8000"]
__init__.py CHANGED
@@ -4,26 +4,8 @@
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
- Echo Environment - A pure MCP environment for testing and demonstration.
9
 
10
- This environment exposes all functionality through MCP tools:
11
- - `echo_message(message)`: Echo back the provided message
12
- - `echo_with_length(message)`: Echo back the message with its length
13
 
14
- Example:
15
- >>> from echo_env import EchoEnv
16
- >>>
17
- >>> with EchoEnv(base_url="http://localhost:8000") as env:
18
- ... env.reset()
19
- ... tools = env.list_tools()
20
- ... result = env.call_tool("echo_message", message="Hello!")
21
- ... print(result) # "Hello!"
22
- """
23
-
24
- # Re-export MCP types for convenience
25
- from openenv.core.env_server.mcp_types import CallToolAction, ListToolsAction
26
-
27
- from .client import EchoEnv
28
-
29
- __all__ = ["EchoEnv", "CallToolAction", "ListToolsAction"]
 
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
+ """Echo environment server components."""
 
8
 
9
+ from .echo_environment import EchoEnvironment
 
 
10
 
11
+ __all__ = ["EchoEnvironment"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app.py ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ """
8
+ FastAPI application for the Echo Environment.
9
+
10
+ This module creates an HTTP server that exposes the EchoEnvironment
11
+ over HTTP and WebSocket endpoints, compatible with MCPToolClient.
12
+
13
+ Usage:
14
+ # Development (with auto-reload):
15
+ uvicorn server.app:app --reload --host 0.0.0.0 --port 8000
16
+
17
+ # Production:
18
+ uvicorn server.app:app --host 0.0.0.0 --port 8000 --workers 4
19
+
20
+ # Or run directly:
21
+ uv run --project . server
22
+ """
23
+
24
+ # Support both in-repo and standalone imports
25
+ try:
26
+ # In-repo imports (when running from OpenEnv repository)
27
+ from openenv.core.env_server.http_server import create_app
28
+ from openenv.core.env_server.mcp_types import CallToolAction, CallToolObservation
29
+
30
+ from .echo_environment import EchoEnvironment
31
+ except ImportError:
32
+ # Standalone imports (when environment is standalone with openenv from pip)
33
+ from openenv.core.env_server.http_server import create_app
34
+ from openenv.core.env_server.mcp_types import CallToolAction, CallToolObservation
35
+ from server.echo_environment import EchoEnvironment
36
+
37
+ # Create the app with web interface and README integration
38
+ # Pass the class (factory) instead of an instance for WebSocket session support
39
+ # Use MCP types for action/observation since this is a pure MCP environment
40
+ app = create_app(
41
+ EchoEnvironment, CallToolAction, CallToolObservation, env_name="echo_env"
42
+ )
43
+
44
+
45
+ def main():
46
+ """
47
+ Entry point for direct execution via uv run or python -m.
48
+
49
+ This function enables running the server without Docker:
50
+ uv run --project . server
51
+ python -m envs.echo_env.server.app
52
+ openenv serve echo_env
53
+
54
+ """
55
+ import uvicorn
56
+
57
+ uvicorn.run(app, host="0.0.0.0", port=8000)
58
+
59
+
60
+ if __name__ == "__main__":
61
+ main()
echo_environment.py ADDED
@@ -0,0 +1,196 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ """
8
+ Echo Environment Implementation.
9
+
10
+ A pure MCP environment that echoes back messages sent to it.
11
+ This demonstrates how to build an MCPEnvironment with inline FastMCP tools.
12
+
13
+ All interactions happen through MCP tools:
14
+ - `echo_message(message)`: Echo back the provided message
15
+ - `echo_with_length(message)`: Echo back the message with its length
16
+
17
+ Example:
18
+ >>> from openenv.core.env_server.mcp_types import ListToolsAction, CallToolAction
19
+ >>> env = EchoEnvironment()
20
+ >>> env.reset()
21
+ >>>
22
+ >>> # List available tools
23
+ >>> obs = env.step(ListToolsAction())
24
+ >>> print([t.name for t in obs.tools]) # ["echo_message", "echo_with_length"]
25
+ >>>
26
+ >>> # Call a tool
27
+ >>> obs = env.step(CallToolAction(tool_name="echo_message", arguments={"message": "Hi!"}))
28
+ >>> print(obs.result) # "Hi!"
29
+ """
30
+
31
+ from typing import Any, Optional
32
+ from uuid import uuid4
33
+
34
+ # Support both in-repo and standalone imports
35
+ try:
36
+ # In-repo imports (when running from OpenEnv repository)
37
+ from openenv.core.env_server.mcp_environment import MCPEnvironment
38
+ from openenv.core.env_server.types import Action, Observation, State
39
+ except ImportError:
40
+ # Standalone imports (when environment is standalone with openenv from pip)
41
+ from openenv.core.env_server.mcp_environment import MCPEnvironment
42
+ from openenv.core.env_server.types import Action, Observation, State
43
+
44
+ from fastmcp import FastMCP
45
+
46
+
47
+ class EchoEnvironment(MCPEnvironment):
48
+ """
49
+ A pure MCP echo environment that echoes back messages.
50
+
51
+ This environment exposes all functionality through MCP tools:
52
+ - `echo_message`: Echo back the provided message
53
+ - `echo_with_length`: Echo back the message with its length
54
+
55
+ The environment inherits MCP support (ListToolsAction, CallToolAction)
56
+ from the MCPEnvironment base class. No legacy action types are supported.
57
+
58
+ Example using MCPToolClient:
59
+ >>> from openenv.core.mcp_client import MCPToolClient
60
+ >>>
61
+ >>> with MCPToolClient(base_url="http://localhost:8000") as env:
62
+ ... env.reset()
63
+ ... tools = env.list_tools()
64
+ ... print([t.name for t in tools])
65
+ ... result = env.call_tool("echo_message", message="Hello!")
66
+ ... print(result)
67
+ """
68
+
69
+ def __init__(self):
70
+ """Initialize the echo environment with MCP server and tools."""
71
+ # Create MCP server and define tools inline
72
+ mcp = FastMCP("echo_env")
73
+
74
+ @mcp.tool
75
+ def echo_message(message: str) -> str:
76
+ """
77
+ Echo back the provided message.
78
+
79
+ Args:
80
+ message: The message to echo back
81
+
82
+ Returns:
83
+ The same message that was provided
84
+ """
85
+ return message
86
+
87
+ @mcp.tool
88
+ def echo_with_length(message: str) -> dict:
89
+ """
90
+ Echo back the message with its length.
91
+
92
+ Args:
93
+ message: The message to echo back
94
+
95
+ Returns:
96
+ Dictionary with the message and its length
97
+ """
98
+ return {"message": message, "length": len(message)}
99
+
100
+ # Pass the MCP server to the base class
101
+ super().__init__(mcp)
102
+ self._state = State(episode_id=str(uuid4()), step_count=0)
103
+ self._reset_count = 0
104
+
105
+ def reset(
106
+ self,
107
+ seed: Optional[int] = None,
108
+ episode_id: Optional[str] = None,
109
+ **kwargs: Any,
110
+ ) -> Observation:
111
+ """
112
+ Reset the environment.
113
+
114
+ Args:
115
+ seed: Optional random seed (unused in echo env)
116
+ episode_id: Optional episode ID to use
117
+ **kwargs: Additional reset options
118
+
119
+ Returns:
120
+ Observation indicating the environment is ready
121
+ """
122
+ self._state = State(
123
+ episode_id=episode_id or str(uuid4()),
124
+ step_count=0,
125
+ )
126
+ self._reset_count += 1
127
+
128
+ return Observation(
129
+ done=False,
130
+ reward=0.0,
131
+ metadata={"status": "ready", "message": "Echo environment ready!"},
132
+ )
133
+
134
+ def _step_impl(
135
+ self,
136
+ action: Action,
137
+ timeout_s: Optional[float] = None,
138
+ **kwargs: Any,
139
+ ) -> Observation:
140
+ """
141
+ Handle non-MCP actions.
142
+
143
+ This environment only supports MCP actions (ListToolsAction, CallToolAction).
144
+ Any other action type returns an error observation.
145
+
146
+ Args:
147
+ action: The action to execute
148
+ timeout_s: Optional timeout (unused)
149
+ **kwargs: Additional arguments
150
+
151
+ Returns:
152
+ Observation with error for unknown action types
153
+ """
154
+ return Observation(
155
+ done=False,
156
+ reward=0.0,
157
+ metadata={
158
+ "error": f"Unknown action type: {type(action).__name__}. "
159
+ "Use ListToolsAction or CallToolAction for MCP interactions."
160
+ },
161
+ )
162
+
163
+ def step(
164
+ self,
165
+ action: Action,
166
+ timeout_s: Optional[float] = None,
167
+ **kwargs: Any,
168
+ ) -> Observation:
169
+ """
170
+ Execute a step in the environment.
171
+
172
+ Delegates to base class for MCP actions. Increments step count for all actions.
173
+
174
+ Args:
175
+ action: The MCP action to execute (ListToolsAction or CallToolAction)
176
+ timeout_s: Optional timeout for the action
177
+ **kwargs: Additional arguments
178
+
179
+ Returns:
180
+ Observation from the action execution
181
+ """
182
+ # Increment step count for all actions
183
+ self._state.step_count += 1
184
+
185
+ # Let the base class handle MCP actions and non-MCP routing
186
+ return super().step(action, timeout_s=timeout_s, **kwargs)
187
+
188
+ @property
189
+ def state(self) -> State:
190
+ """
191
+ Get the current environment state.
192
+
193
+ Returns:
194
+ Current State with episode_id and step_count
195
+ """
196
+ return self._state