shrads78 commited on
Commit
6d0cebc
·
verified ·
1 Parent(s): 840868e

Upload folder using huggingface_hub

Browse files
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=vc_gemini_v0
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
@@ -1,10 +1,255 @@
1
  ---
2
- title: Vc Gemini V0
3
- emoji: 🏆
4
- colorFrom: gray
5
- colorTo: gray
6
  sdk: docker
7
  pinned: false
 
 
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: Vc Gemini V0 Environment Server
3
+ emoji: 🎵
4
+ colorFrom: indigo
5
+ colorTo: blue
6
  sdk: docker
7
  pinned: false
8
+ app_port: 8000
9
+ base_path: /web
10
+ tags:
11
+ - openenv
12
  ---
13
 
14
+ # Vc Gemini V0 Environment
15
+
16
+ A simple test environment that echoes back messages. Perfect for testing the env APIs as well as demonstrating environment usage patterns.
17
+
18
+ ## Quick Start
19
+
20
+ The simplest way to use the Vc Gemini V0 environment is through the `VcGeminiV0Env` class:
21
+
22
+ ```python
23
+ from vc_gemini_v0 import VcGeminiV0Action, VcGeminiV0Env
24
+
25
+ try:
26
+ # Create environment from Docker image
27
+ vc_gemini_v0env = VcGeminiV0Env.from_docker_image("vc_gemini_v0-env:latest")
28
+
29
+ # Reset
30
+ result = vc_gemini_v0env.reset()
31
+ print(f"Reset: {result.observation.echoed_message}")
32
+
33
+ # Send multiple messages
34
+ messages = ["Hello, World!", "Testing echo", "Final message"]
35
+
36
+ for msg in messages:
37
+ result = vc_gemini_v0env.step(VcGeminiV0Action(message=msg))
38
+ print(f"Sent: '{msg}'")
39
+ print(f" → Echoed: '{result.observation.echoed_message}'")
40
+ print(f" → Length: {result.observation.message_length}")
41
+ print(f" → Reward: {result.reward}")
42
+
43
+ finally:
44
+ # Always clean up
45
+ vc_gemini_v0env.close()
46
+ ```
47
+
48
+ That's it! The `VcGeminiV0Env.from_docker_image()` method handles:
49
+ - Starting the Docker container
50
+ - Waiting for the server to be ready
51
+ - Connecting to the environment
52
+ - Container cleanup when you call `close()`
53
+
54
+ ## Building the Docker Image
55
+
56
+ Before using the environment, you need to build the Docker image:
57
+
58
+ ```bash
59
+ # From project root
60
+ docker build -t vc_gemini_v0-env:latest -f server/Dockerfile .
61
+ ```
62
+
63
+ ## Deploying to Hugging Face Spaces
64
+
65
+ You can easily deploy your OpenEnv environment to Hugging Face Spaces using the `openenv push` command:
66
+
67
+ ```bash
68
+ # From the environment directory (where openenv.yaml is located)
69
+ openenv push
70
+
71
+ # Or specify options
72
+ openenv push --namespace my-org --private
73
+ ```
74
+
75
+ The `openenv push` command will:
76
+ 1. Validate that the directory is an OpenEnv environment (checks for `openenv.yaml`)
77
+ 2. Prepare a custom build for Hugging Face Docker space (enables web interface)
78
+ 3. Upload to Hugging Face (ensuring you're logged in)
79
+
80
+ ### Prerequisites
81
+
82
+ - Authenticate with Hugging Face: The command will prompt for login if not already authenticated
83
+
84
+ ### Options
85
+
86
+ - `--directory`, `-d`: Directory containing the OpenEnv environment (defaults to current directory)
87
+ - `--repo-id`, `-r`: Repository ID in format 'username/repo-name' (defaults to 'username/env-name' from openenv.yaml)
88
+ - `--base-image`, `-b`: Base Docker image to use (overrides Dockerfile FROM)
89
+ - `--private`: Deploy the space as private (default: public)
90
+
91
+ ### Examples
92
+
93
+ ```bash
94
+ # Push to your personal namespace (defaults to username/env-name from openenv.yaml)
95
+ openenv push
96
+
97
+ # Push to a specific repository
98
+ openenv push --repo-id my-org/my-env
99
+
100
+ # Push with a custom base image
101
+ openenv push --base-image ghcr.io/meta-pytorch/openenv-base:latest
102
+
103
+ # Push as a private space
104
+ openenv push --private
105
+
106
+ # Combine options
107
+ openenv push --repo-id my-org/my-env --base-image custom-base:latest --private
108
+ ```
109
+
110
+ After deployment, your space will be available at:
111
+ `https://huggingface.co/spaces/<repo-id>`
112
+
113
+ The deployed space includes:
114
+ - **Web Interface** at `/web` - Interactive UI for exploring the environment
115
+ - **API Documentation** at `/docs` - Full OpenAPI/Swagger interface
116
+ - **Health Check** at `/health` - Container health monitoring
117
+ - **WebSocket** at `/ws` - Persistent session endpoint for low-latency interactions
118
+
119
+ ## Environment Details
120
+
121
+ ### Action
122
+ **VcGeminiV0Action**: Contains a single field
123
+ - `message` (str) - The message to echo back
124
+
125
+ ### Observation
126
+ **VcGeminiV0Observation**: Contains the echo response and metadata
127
+ - `echoed_message` (str) - The message echoed back
128
+ - `message_length` (int) - Length of the message
129
+ - `reward` (float) - Reward based on message length (length × 0.1)
130
+ - `done` (bool) - Always False for echo environment
131
+ - `metadata` (dict) - Additional info like step count
132
+
133
+ ### Reward
134
+ The reward is calculated as: `message_length × 0.1`
135
+ - "Hi" → reward: 0.2
136
+ - "Hello, World!" → reward: 1.3
137
+ - Empty message → reward: 0.0
138
+
139
+ ## Advanced Usage
140
+
141
+ ### Connecting to an Existing Server
142
+
143
+ If you already have a Vc Gemini V0 environment server running, you can connect directly:
144
+
145
+ ```python
146
+ from vc_gemini_v0 import VcGeminiV0Env
147
+
148
+ # Connect to existing server
149
+ vc_gemini_v0env = VcGeminiV0Env(base_url="<ENV_HTTP_URL_HERE>")
150
+
151
+ # Use as normal
152
+ result = vc_gemini_v0env.reset()
153
+ result = vc_gemini_v0env.step(VcGeminiV0Action(message="Hello!"))
154
+ ```
155
+
156
+ Note: When connecting to an existing server, `vc_gemini_v0env.close()` will NOT stop the server.
157
+
158
+ ### Using the Context Manager
159
+
160
+ The client supports context manager usage for automatic connection management:
161
+
162
+ ```python
163
+ from vc_gemini_v0 import VcGeminiV0Action, VcGeminiV0Env
164
+
165
+ # Connect with context manager (auto-connects and closes)
166
+ with VcGeminiV0Env(base_url="http://localhost:8000") as env:
167
+ result = env.reset()
168
+ print(f"Reset: {result.observation.echoed_message}")
169
+ # Multiple steps with low latency
170
+ for msg in ["Hello", "World", "!"]:
171
+ result = env.step(VcGeminiV0Action(message=msg))
172
+ print(f"Echoed: {result.observation.echoed_message}")
173
+ ```
174
+
175
+ The client uses WebSocket connections for:
176
+ - **Lower latency**: No HTTP connection overhead per request
177
+ - **Persistent session**: Server maintains your environment state
178
+ - **Efficient for episodes**: Better for many sequential steps
179
+
180
+ ### Concurrent WebSocket Sessions
181
+
182
+ The server supports multiple concurrent WebSocket connections. To enable this,
183
+ modify `server/app.py` to use factory mode:
184
+
185
+ ```python
186
+ # In server/app.py - use factory mode for concurrent sessions
187
+ app = create_app(
188
+ VcGeminiV0Environment, # Pass class, not instance
189
+ VcGeminiV0Action,
190
+ VcGeminiV0Observation,
191
+ max_concurrent_envs=4, # Allow 4 concurrent sessions
192
+ )
193
+ ```
194
+
195
+ Then multiple clients can connect simultaneously:
196
+
197
+ ```python
198
+ from vc_gemini_v0 import VcGeminiV0Action, VcGeminiV0Env
199
+ from concurrent.futures import ThreadPoolExecutor
200
+
201
+ def run_episode(client_id: int):
202
+ with VcGeminiV0Env(base_url="http://localhost:8000") as env:
203
+ result = env.reset()
204
+ for i in range(10):
205
+ result = env.step(VcGeminiV0Action(message=f"Client {client_id}, step {i}"))
206
+ return client_id, result.observation.message_length
207
+
208
+ # Run 4 episodes concurrently
209
+ with ThreadPoolExecutor(max_workers=4) as executor:
210
+ results = list(executor.map(run_episode, range(4)))
211
+ ```
212
+
213
+ ## Development & Testing
214
+
215
+ ### Direct Environment Testing
216
+
217
+ Test the environment logic directly without starting the HTTP server:
218
+
219
+ ```bash
220
+ # From the server directory
221
+ python3 server/vc_gemini_v0_environment.py
222
+ ```
223
+
224
+ This verifies that:
225
+ - Environment resets correctly
226
+ - Step executes actions properly
227
+ - State tracking works
228
+ - Rewards are calculated correctly
229
+
230
+ ### Running Locally
231
+
232
+ Run the server locally for development:
233
+
234
+ ```bash
235
+ uvicorn server.app:app --reload
236
+ ```
237
+
238
+ ## Project Structure
239
+
240
+ ```
241
+ vc_gemini_v0/
242
+ ├── .dockerignore # Docker build exclusions
243
+ ├── __init__.py # Module exports
244
+ ├── README.md # This file
245
+ ├── openenv.yaml # OpenEnv manifest
246
+ ├── pyproject.toml # Project metadata and dependencies
247
+ ├── uv.lock # Locked dependencies (generated)
248
+ ├── client.py # VcGeminiV0Env client
249
+ ├── models.py # Action and Observation models
250
+ └── server/
251
+ ├── __init__.py # Server module exports
252
+ ├── vc_gemini_v0_environment.py # Core environment logic
253
+ ├── app.py # FastAPI application (HTTP + WebSocket endpoints)
254
+ └── Dockerfile # Container image definition
255
+ ```
__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
+ """Vc Gemini V0 Environment."""
8
+
9
+ from .client import VcGeminiV0Env
10
+ from .models import VcGeminiV0Action, VcGeminiV0Observation
11
+
12
+ __all__ = [
13
+ "VcGeminiV0Action",
14
+ "VcGeminiV0Observation",
15
+ "VcGeminiV0Env",
16
+ ]
client.py ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ """Vc Gemini V0 Environment Client."""
8
+
9
+ from typing import Dict
10
+
11
+ from openenv.core.client_types import StepResult
12
+ from openenv.core.env_server.types import State
13
+ from openenv.core import EnvClient
14
+
15
+ from .models import VcGeminiV0Action, VcGeminiV0Observation
16
+
17
+
18
+ class VcGeminiV0Env(
19
+ EnvClient[VcGeminiV0Action, VcGeminiV0Observation]
20
+ ):
21
+ """
22
+ Client for the Vc Gemini V0 Environment.
23
+
24
+ This client maintains a persistent WebSocket connection to the environment server,
25
+ enabling efficient multi-step interactions with lower latency.
26
+ Each client instance has its own dedicated environment session on the server.
27
+
28
+ Example:
29
+ >>> # Connect to a running server
30
+ >>> with VcGeminiV0Env(base_url="http://localhost:8000") as client:
31
+ ... result = client.reset()
32
+ ... print(result.observation.echoed_message)
33
+ ...
34
+ ... result = client.step(VcGeminiV0Action(message="Hello!"))
35
+ ... print(result.observation.echoed_message)
36
+
37
+ Example with Docker:
38
+ >>> # Automatically start container and connect
39
+ >>> client = VcGeminiV0Env.from_docker_image("vc_gemini_v0-env:latest")
40
+ >>> try:
41
+ ... result = client.reset()
42
+ ... result = client.step(VcGeminiV0Action(message="Test"))
43
+ ... finally:
44
+ ... client.close()
45
+ """
46
+
47
+ def _step_payload(self, action: VcGeminiV0Action) -> Dict:
48
+ """
49
+ Convert VcGeminiV0Action to JSON payload for step message.
50
+ """
51
+ return {
52
+ "action_type": action.action_type,
53
+ "parameters": action.parameters,
54
+ }
55
+
56
+ def _parse_result(self, payload: Dict) -> StepResult[VcGeminiV0Observation]:
57
+ """
58
+ Parse server response into StepResult[VcGeminiV0Observation].
59
+ """
60
+ obs_data = payload.get("observation", {})
61
+ observation = VcGeminiV0Observation(
62
+ observation_text=obs_data.get("observation_text", ""),
63
+ inbox=obs_data.get("inbox", []),
64
+ data=obs_data.get("data", {}),
65
+ done=payload.get("done", False),
66
+ reward=payload.get("reward"),
67
+ metadata=obs_data.get("metadata", {}),
68
+ )
69
+
70
+ return StepResult(
71
+ observation=observation,
72
+ reward=payload.get("reward"),
73
+ done=payload.get("done", False),
74
+ )
75
+
76
+ def _parse_state(self, payload: Dict) -> State:
77
+ """
78
+ Parse server response into State object.
79
+
80
+ Args:
81
+ payload: JSON response from state request
82
+
83
+ Returns:
84
+ State object with episode_id and step_count
85
+ """
86
+ return State(
87
+ episode_id=payload.get("episode_id"),
88
+ step_count=payload.get("step_count", 0),
89
+ )
competitors.json ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "id": "sequoia_bot",
4
+ "name": "Sequoia Capital",
5
+ "speed_modifier": 0.9,
6
+ "valuation_cap": 1.5,
7
+ "primary_sectors": [
8
+ "Enterprise SaaS"
9
+ ],
10
+ "secondary_sectors": [
11
+ "Consumer Social"
12
+ ]
13
+ },
14
+ {
15
+ "id": "a16z_bot",
16
+ "name": "Andreessen Horowitz",
17
+ "speed_modifier": 0.8,
18
+ "valuation_cap": 2.0,
19
+ "primary_sectors": [
20
+ "FinTech / Web3"
21
+ ],
22
+ "secondary_sectors": [
23
+ "Enterprise SaaS"
24
+ ]
25
+ },
26
+ {
27
+ "id": "benchmark_bot",
28
+ "name": "Benchmark",
29
+ "speed_modifier": 1.5,
30
+ "valuation_cap": 1.1,
31
+ "primary_sectors": [
32
+ "Consumer Social"
33
+ ],
34
+ "secondary_sectors": [
35
+ "FinTech / Web3"
36
+ ]
37
+ }
38
+ ]
evaluate_v0_models.py ADDED
@@ -0,0 +1,138 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import time
3
+ from openai import OpenAI
4
+
5
+ from test_reward_v0 import strategy_succeeds, env
6
+
7
+ # ===================================================================
8
+ # Hugging Face Router API Configuration
9
+ # ===================================================================
10
+ BASE_URL = "https://router.huggingface.co/v1"
11
+ API_KEY = os.environ.get("HF_TOKEN", "")
12
+
13
+ # Testing large models on the V0 environment
14
+ MODELS_TO_TEST = [
15
+ "Qwen/Qwen3.5-397B-A17B:novita",
16
+ "moonshotai/Kimi-K2.5:novita",
17
+ "meta-llama/Llama-3.1-70B-Instruct:scaleway",
18
+ ]
19
+
20
+ # Initialize OpenAI compatible client
21
+ client = OpenAI(
22
+ base_url=BASE_URL,
23
+ api_key=API_KEY
24
+ )
25
+
26
+ # Reference Prompt adapted for V0
27
+ PROMPT = """Create a short Python VC strategy.
28
+
29
+ You have to plan for 4 quarters, and each quarter you have 5 turns.
30
+ You must maximize your TVPI (Total Value to Paid In Capital) by Quarter 4.
31
+ If your TVPI goes below 1.20x at the end, you get fired (penalty of -10.0 reward).
32
+
33
+ There will be 12 startups arriving in your inbox over 4 quarters (3 per quarter, drawn from 10 sectors like AI/ML, BioTech, DeepTech, Robotics, etc.).
34
+ There is 1 competitive VC actively playing against you.
35
+
36
+ At each turn, a valid action looks exactly like one of these JSON dictionaries:
37
+
38
+ 1. READ FILE (Consume 1 turn to diligence):
39
+ {"action_type": "read_file", "parameters": {"path": "pitch_deck.txt", "startup_name": "Aura Labs"}}
40
+
41
+ 2. EMAIL FOUNDER (Consume 1 turn to ask for hints on valuation/board seats):
42
+ {"action_type": "email_founder", "parameters": {"startup_name": "Aura Labs", "body": "What valuation are you looking for?"}}
43
+
44
+ 3. PASS ON DEAL (Reject a startup, saves capital):
45
+ {"action_type": "pass_on_deal", "parameters": {"startup_name": "Aura Labs"}}
46
+
47
+ 4. SUBMIT TERM SHEET (Invest your capital):
48
+ {"action_type": "submit_term_sheet", "parameters": {"startup_name": "Aura Labs", "valuation": 15000000.0, "amount": 5000000.0, "board_seats": 1}}
49
+
50
+ 5. WAIT (Do nothing this turn):
51
+ {"action_type": "wait", "parameters": {}}
52
+
53
+ Your task is write a python function `vc_strategy(observation)` to outline your strategy.
54
+
55
+ IMPORTANT FORMATTING RULES:
56
+ 1. All your helper functions must be defined *inside* the `def vc_strategy(observation):` block.
57
+ 2. The function must return a valid action dictionary.
58
+ 3. You must output the code block wrapped in ```python backticks.
59
+ 4. You MUST extract the actual target names from `observation.get("observation_text")`.
60
+
61
+ Here is an example policy:
62
+ ```python
63
+ def vc_strategy(observation):
64
+ text = observation.get("observation_text", "")
65
+ data = observation.get("data", {})
66
+
67
+ # Save capital if budget is low
68
+ if data.get("budget", 100000000) < 5000000:
69
+ return {"action_type": "wait", "parameters": {}}
70
+
71
+ safe_sectors = ["AI/ML Foundation Models", "Agentic AI / Automation", "Robotics / Automation"]
72
+ for sector in safe_sectors:
73
+ if sector in text:
74
+ # Dynamically extract the startup name from "New Pitches in Inbox:\n- StartupName (Sector)"
75
+ lines = text.split("\\n")
76
+ for line in lines:
77
+ if sector in line and line.startswith("- "):
78
+ actual_name = line[2:line.find("(")].strip()
79
+ # Make a reasonable term sheet offer
80
+ return {"action_type": "submit_term_sheet", "parameters": {"startup_name": actual_name, "valuation": 20000000.0, "amount": 10000000.0, "board_seats": 1}}
81
+
82
+ return {"action_type": "wait", "parameters": {}}
83
+ ```
84
+
85
+ Only output the short function `vc_strategy`!
86
+ """
87
+
88
+ def evaluate_model(model_name):
89
+ print(f"\n{'='*80}")
90
+ print(f"EVALUATING MODEL ON VC_GEMINI_V0: {model_name}")
91
+ print(f"{'='*80}")
92
+
93
+ try:
94
+ response = client.chat.completions.create(
95
+ model=model_name,
96
+ messages=[{"role": "user", "content": PROMPT}],
97
+ temperature=0.7,
98
+ max_tokens=4096,
99
+ )
100
+
101
+ generated_text = response.choices[0].message.content
102
+ print("\n--- GENERATED CODE/RESPONSE ---")
103
+ print(generated_text)
104
+ print("-------------------------------\n")
105
+
106
+ mock_completion = [[{"content": generated_text}]]
107
+ print("Executing generated strategy locally...")
108
+ scores = strategy_succeeds(mock_completion)
109
+
110
+ final_score = scores[0] if scores else -4.0
111
+ print(f"Final Reward Score for {model_name}: {final_score}")
112
+ return final_score
113
+
114
+ except Exception as e:
115
+ print(f"Failed to evaluate {model_name}: {e}")
116
+ return -5.0
117
+
118
+ def main():
119
+ if not API_KEY:
120
+ print("WARNING: HF_TOKEN is not set.")
121
+ time.sleep(2)
122
+
123
+ results = {}
124
+ for model in MODELS_TO_TEST:
125
+ score = evaluate_model(model)
126
+ results[model] = score
127
+ time.sleep(3)
128
+
129
+ print("\n\n" + "#"*60)
130
+ print("V0 MODEL EVALUATION LEADERBOARD")
131
+ print("#"*60)
132
+
133
+ sorted_results = sorted(results.items(), key=lambda x: x[1], reverse=True)
134
+ for i, (model, score) in enumerate(sorted_results, 1):
135
+ print(f"{i}. {model:<40} | Score: {score}")
136
+
137
+ if __name__ == "__main__":
138
+ main()
fund_scenarios.json ADDED
The diff for this file is too large to render. See raw diff
 
models.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Dict, Any
2
+
3
+ from pydantic import Field
4
+
5
+ from openenv.core.env_server.types import Action, Observation
6
+
7
+
8
+ class VcGeminiV0Action(Action):
9
+ """Action for the Multi-Agent VC Negotiator (V0 Curriculum)."""
10
+ action_type: str = Field(..., description="Type of action: 'read_file', 'email_founder', 'submit_term_sheet', 'pass_on_deal', 'wait'")
11
+ parameters: Dict[str, Any] = Field(default_factory=dict, description="Parameters for the specific action type.")
12
+
13
+
14
+ class VcGeminiV0Observation(Observation):
15
+ """Observation from the VC Environment (V0 Curriculum)."""
16
+ observation_text: str = Field(..., description="The immediate result or narrative of your action.")
17
+ inbox: list = Field(default_factory=list, description="Any new emails or messages received in your inbox this turn.")
18
+ data: Dict[str, Any] = Field(default_factory=dict, description="Structured data returned (e.g. file contents).")
openenv.yaml ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ spec_version: 1
2
+ name: vc_gemini_v0
3
+ type: space
4
+ runtime: fastapi
5
+ app: server.app:app
6
+ port: 8000
7
+
openenv_vc_gemini_v0.egg-info/PKG-INFO ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ Metadata-Version: 2.4
2
+ Name: openenv-vc_gemini_v0
3
+ Version: 0.1.0
4
+ Summary: Vc Gemini V0 environment for OpenEnv
5
+ Requires-Python: >=3.10
6
+ Requires-Dist: openenv-core[core]>=0.2.0
7
+ Provides-Extra: dev
8
+ Requires-Dist: pytest>=8.0.0; extra == "dev"
9
+ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
openenv_vc_gemini_v0.egg-info/SOURCES.txt ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ README.md
2
+ pyproject.toml
3
+ ./__init__.py
4
+ ./client.py
5
+ ./evaluate_v0_models.py
6
+ ./models.py
7
+ ./test_reward_v0.py
8
+ openenv_vc_gemini_v0.egg-info/PKG-INFO
9
+ openenv_vc_gemini_v0.egg-info/SOURCES.txt
10
+ openenv_vc_gemini_v0.egg-info/dependency_links.txt
11
+ openenv_vc_gemini_v0.egg-info/entry_points.txt
12
+ openenv_vc_gemini_v0.egg-info/requires.txt
13
+ openenv_vc_gemini_v0.egg-info/top_level.txt
14
+ server/__init__.py
15
+ server/app.py
16
+ server/generate_dataset.py
17
+ server/vc_gemini_v0_environment.py
openenv_vc_gemini_v0.egg-info/dependency_links.txt ADDED
@@ -0,0 +1 @@
 
 
1
+
openenv_vc_gemini_v0.egg-info/entry_points.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ [console_scripts]
2
+ server = vc_gemini_v0.server.app:main
openenv_vc_gemini_v0.egg-info/requires.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ openenv-core[core]>=0.2.0
2
+
3
+ [dev]
4
+ pytest>=8.0.0
5
+ pytest-cov>=4.0.0
openenv_vc_gemini_v0.egg-info/top_level.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ vc_gemini_v0
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-vc_gemini_v0"
13
+ version = "0.1.0"
14
+ description = "Vc Gemini V0 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.0",
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 vc_gemini_v0.server.app
40
+ server = "vc_gemini_v0.server.app:main"
41
+
42
+ [tool.setuptools]
43
+ include-package-data = true
44
+ packages = ["vc_gemini_v0", "vc_gemini_v0.server"]
45
+ package-dir = { "vc_gemini_v0" = ".", "vc_gemini_v0.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
+ """Vc Gemini V0 environment server components."""
8
+
9
+ from .vc_gemini_v0_environment import VcGeminiV0Environment
10
+
11
+ __all__ = ["VcGeminiV0Environment"]
server/app.py 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
+ """
8
+ FastAPI application for the Vc Gemini V0 Environment.
9
+
10
+ This module creates an HTTP server that exposes the VcGeminiV0Environment
11
+ over HTTP and WebSocket endpoints, compatible with EnvClient.
12
+
13
+ Endpoints:
14
+ - POST /reset: Reset the environment
15
+ - POST /step: Execute an action
16
+ - GET /state: Get current environment state
17
+ - GET /schema: Get action/observation schemas
18
+ - WS /ws: WebSocket endpoint for persistent sessions
19
+
20
+ Usage:
21
+ # Development (with auto-reload):
22
+ uvicorn server.app:app --reload --host 0.0.0.0 --port 8000
23
+
24
+ # Production:
25
+ uvicorn server.app:app --host 0.0.0.0 --port 8000 --workers 4
26
+
27
+ # Or run directly:
28
+ python -m server.app
29
+ """
30
+
31
+ try:
32
+ from openenv.core.env_server.http_server import create_app
33
+ except Exception as e: # pragma: no cover
34
+ raise ImportError(
35
+ "openenv is required for the web interface. Install dependencies with '\n uv sync\n'"
36
+ ) from e
37
+
38
+ # Import from local models.py (PYTHONPATH includes /app/env in Docker)
39
+ from models import VcGeminiV0Action, VcGeminiV0Observation
40
+ from .vc_gemini_v0_environment import VcGeminiV0Environment
41
+
42
+
43
+ # Create the app with web interface and README integration
44
+ app = create_app(
45
+ VcGeminiV0Environment,
46
+ VcGeminiV0Action,
47
+ VcGeminiV0Observation,
48
+ env_name="vc_gemini_v0",
49
+ max_concurrent_envs=1, # increase this number to allow more concurrent WebSocket sessions
50
+ )
51
+
52
+
53
+ def main(host: str = "0.0.0.0", port: int = 8000):
54
+ """
55
+ Entry point for direct execution via uv run or python -m.
56
+
57
+ This function enables running the server without Docker:
58
+ uv run --project . server
59
+ uv run --project . server --port 8001
60
+ python -m vc_gemini_v0.server.app
61
+
62
+ Args:
63
+ host: Host address to bind to (default: "0.0.0.0")
64
+ port: Port number to listen on (default: 8000)
65
+
66
+ For production deployments, consider using uvicorn directly with
67
+ multiple workers:
68
+ uvicorn vc_gemini_v0.server.app:app --workers 4
69
+ """
70
+ import uvicorn
71
+
72
+ uvicorn.run(app, host=host, port=port)
73
+
74
+
75
+ if __name__ == "__main__":
76
+ import argparse
77
+
78
+ parser = argparse.ArgumentParser()
79
+ parser.add_argument("--port", type=int, default=8000)
80
+ args = parser.parse_args()
81
+ main(port=args.port)
server/generate_dataset.py ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import random
3
+ import uuid
4
+
5
+ def generate_competitors():
6
+ competitors = [
7
+ {"id": "sequoia_bot", "name": "Sequoia Capital", "speed_modifier": 0.9, "valuation_cap": 1.5, "primary_sectors": ["Enterprise SaaS"], "secondary_sectors": ["Consumer Social"]},
8
+ {"id": "a16z_bot", "name": "Andreessen Horowitz", "speed_modifier": 0.8, "valuation_cap": 2.0, "primary_sectors": ["FinTech / Web3"], "secondary_sectors": ["Enterprise SaaS"]},
9
+ {"id": "benchmark_bot", "name": "Benchmark", "speed_modifier": 1.5, "valuation_cap": 1.1, "primary_sectors": ["Consumer Social"], "secondary_sectors": ["FinTech / Web3"]}
10
+ ]
11
+ with open("competitors.json", "w") as f:
12
+ json.dump(competitors, f, indent=2)
13
+ print(f"Generated {len(competitors)} competitors.")
14
+
15
+ def generate_fund_scenarios(count=100):
16
+ sectors = [
17
+ "Enterprise SaaS",
18
+ "Consumer Social",
19
+ "FinTech / Web3",
20
+ ]
21
+
22
+ first_names = ["Alice", "Bob", "Charlie", "Diana", "Ethan", "Faye", "George", "Hannah", "Ian", "Jane", "Kevin", "Laura", "Mike", "Nina", "Oscar", "Peggy", "Uma", "Viktor", "Wendy", "Zane"]
23
+ company_prefixes = ["Quantum", "Aura", "Nebula", "Apex", "Vanguard", "Zenith", "Nova", "Stratos", "Omni", "Nex", "Data", "Bio", "Cranium", "Cyber", "Astro", "Alpha", "Flux", "Core", "Grid", "Pioneer"]
24
+ company_suffixes = ["AI", "Dynamics", "Systems", "Labs", "Networks", "Technologies", "Solutions", "Health", "Space", "Genomics", "Robotics", "Analytics", "Agents", "Finance", "Energy", "Chip"]
25
+
26
+ scenarios = []
27
+
28
+ for _ in range(count):
29
+ sector = random.choice(sectors)
30
+
31
+ # Valuation logic based on sector
32
+ if sector in ["Enterprise SaaS"]:
33
+ raise_amount = random.randint(1, 4) * 1000000
34
+ fair_eval = raise_amount * random.uniform(3.0, 6.0)
35
+ true_potential_mult = random.uniform(0.5, 4.0) # Moderate variance
36
+ elif sector in ["Consumer Social", "FinTech / Web3"]:
37
+ raise_amount = random.randint(1, 5) * 1000000
38
+ fair_eval = raise_amount * random.uniform(2.0, 6.0)
39
+ true_potential_mult = random.choice([0.0, 0.0, 0.0, 0.5, 1.0, 10.0])
40
+
41
+ fair_eval = round(fair_eval, -5) # Round to nearest 100k
42
+
43
+ founder_name = random.choice(first_names)
44
+ company_name = f"{random.choice(company_prefixes)} {random.choice(company_suffixes)}"
45
+
46
+ # Founder Priorities
47
+ priority_roll = random.random()
48
+ if priority_roll < 0.33:
49
+ p_val, p_board, p_speed = True, False, False
50
+ hint = f"I'm looking for a premium valuation. We won't take less than ${int((fair_eval * 0.9)/1000000)}M pre-money."
51
+ elif priority_roll < 0.66:
52
+ p_val, p_board, p_speed = False, True, False
53
+ hint = "We aren't optimizing for every last dollar of valuation. We just want to retain 2 board seats so we control our destiny."
54
+ else:
55
+ p_val, p_board, p_speed = False, False, True
56
+ hint = "We are running low on runway. We need to close this round extremely fast. We will take a fair price if you move now."
57
+
58
+ # Cap Table logic (30% chance of messy options trap)
59
+ cap_table = [
60
+ {"Shareholder": f"Founder {founder_name}", "Shares": random.randint(4000000, 8000000), "Type": "Common"}
61
+ ]
62
+ if random.random() > 0.5:
63
+ cap_table.append({"Shareholder": "Seed Investors", "Shares": random.randint(1000000, 3000000), "Type": "Preferred"})
64
+
65
+ if random.random() > 0.7:
66
+ cap_table.append({"Shareholder": "Unissued Option Pool", "Shares": random.randint(1000000, 2500000), "Type": "Options"})
67
+ else:
68
+ cap_table.append({"Shareholder": "Employee Option Pool", "Shares": random.randint(500000, 1000000), "Type": "Options"})
69
+
70
+ scenario = {
71
+ "startup_id": str(uuid.uuid4())[:8],
72
+ "startup_name": company_name,
73
+ "sector": sector,
74
+ "raise_amount_str": f"${int(raise_amount/1000000)}M",
75
+ "raise_amount": raise_amount,
76
+ "true_potential_multiplier": round(true_potential_mult, 2),
77
+ "cap_table": cap_table,
78
+ "founder_priorities": {
79
+ "prioritize_valuation": p_val,
80
+ "prioritize_board_control": p_board,
81
+ "prioritize_speed": p_speed
82
+ },
83
+ "founder_hints": {
84
+ "valuation_hint": hint
85
+ },
86
+ "win_conditions": {
87
+ "max_board_seats": 1 if p_board else 3,
88
+ "min_valuation": fair_eval * (0.9 if p_val else 0.6),
89
+ "fair_valuation": fair_eval,
90
+ "overpaid_valuation": fair_eval * 1.3,
91
+ "max_turns": 4 if p_speed else 8
92
+ }
93
+ }
94
+ scenarios.append(scenario)
95
+
96
+ with open("fund_scenarios.json", "w") as f:
97
+ json.dump(scenarios, f, indent=2)
98
+ print(f"Generated {count} fund scenarios.")
99
+
100
+ if __name__ == "__main__":
101
+ generate_competitors()
102
+ generate_fund_scenarios(100)
server/requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ openenv[core]>=0.2.0
2
+ fastapi>=0.115.0
3
+ uvicorn>=0.24.0
4
+
5
+
6
+
server/vc_gemini_v0_environment.py ADDED
@@ -0,0 +1,320 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import random
4
+ import tempfile
5
+ import csv
6
+ import shutil
7
+ from uuid import uuid4
8
+ from typing import Dict, Any, List
9
+
10
+ from openenv.core.env_server.interfaces import Environment
11
+ from openenv.core.env_server.types import State
12
+
13
+ from models import VcGeminiV0Action, VcGeminiV0Observation
14
+
15
+ class VcGeminiV0Environment(Environment):
16
+ SUPPORTS_CONCURRENT_SESSIONS: bool = True
17
+
18
+ def __init__(self):
19
+ self._state = State(episode_id=str(uuid4()), step_count=0)
20
+ self.workspace_dir = tempfile.mkdtemp(prefix="vc_env_v0_")
21
+
22
+ self.fund_budget = 100000000.0
23
+ self.portfolio = []
24
+ self.quarter = 1
25
+ self.MAX_QUARTERS = 5 # Reduced from 11 to 5 (4 playable quarters)
26
+ self.MAX_TURNS_PER_QUARTER = 5 # Reduced from 10 to 5 turns
27
+ self.turns_remaining = self.MAX_TURNS_PER_QUARTER
28
+
29
+ # Load Datasets
30
+ base_dir = os.path.dirname(os.path.dirname(__file__))
31
+ scenario_path = os.path.join(base_dir, "fund_scenarios.json")
32
+ with open(scenario_path, "r") as f:
33
+ self.scenarios = json.load(f)
34
+
35
+ comp_path = os.path.join(base_dir, "competitors.json")
36
+ with open(comp_path, "r") as f:
37
+ self.competitors = json.load(f)
38
+
39
+ self.available_scenarios = []
40
+ self.active_competitors = []
41
+ self.inbox_pitches = []
42
+
43
+ def reset(self) -> VcGeminiV0Observation:
44
+ self._state = State(episode_id=str(uuid4()), step_count=0)
45
+ self.fund_budget = 100000000.0
46
+ self.portfolio = []
47
+ self.quarter = 1
48
+
49
+ # 1 random rival for the episode (Curriculum V0 simplification)
50
+ self.active_competitors = random.sample(self.competitors, 1)
51
+ # 12 random startups for the deal flow (3 per quarter for 4 quarters)
52
+ self.available_scenarios = random.sample(self.scenarios, 12)
53
+
54
+ return self._setup_quarter()
55
+
56
+ def _mark_to_market(self):
57
+ """Updates the paper valuation of the portfolio and returns the interim reward (RVPI)."""
58
+ interim_reward = 0.0
59
+
60
+ for inv in self.portfolio:
61
+ if not inv["active"]:
62
+ continue
63
+
64
+ # Simulate a funding round event every quarter
65
+ if random.random() < 0.3: # 30% chance for a valuation event
66
+ # Up round vs Down round bias based on their true potential
67
+ if inv["true_potential_multiplier"] > 1.0:
68
+ rvpi_bump = random.uniform(0.1, 0.5)
69
+ inv["paper_multiplier"] += rvpi_bump
70
+ interim_reward += (rvpi_bump * 0.1) # Soft reward for good paper marks
71
+ elif inv["true_potential_multiplier"] == 0.0:
72
+ rvpi_drop = random.uniform(-0.1, -0.9)
73
+ inv["paper_multiplier"] = max(0.1, inv["paper_multiplier"] + rvpi_drop)
74
+ interim_reward += (rvpi_drop * 0.1)
75
+
76
+ return interim_reward
77
+
78
+ def _setup_quarter(self) -> VcGeminiV0Observation:
79
+ if self.quarter >= self.MAX_QUARTERS:
80
+ return self._calculate_final_tvpi()
81
+
82
+ # Clean up old Data Rooms
83
+ if os.path.exists(self.workspace_dir):
84
+ shutil.rmtree(self.workspace_dir, ignore_errors=True)
85
+
86
+ self.workspace_dir = tempfile.mkdtemp(prefix=f"vc_q{self.quarter}_")
87
+ self.turns_remaining = self.MAX_TURNS_PER_QUARTER
88
+
89
+ # Draw 3 Pitches for this quarter
90
+ start_idx = (self.quarter - 1) * 3
91
+ self.inbox_pitches = self.available_scenarios[start_idx:start_idx+3]
92
+
93
+ pitch_names = []
94
+ # Setup Data Rooms for the 3 Pitches
95
+ for scenario in self.inbox_pitches:
96
+ startup_dir = os.path.join(self.workspace_dir, scenario["startup_name"].replace(" ", "_"))
97
+ os.makedirs(startup_dir)
98
+
99
+ cap_table_path = os.path.join(startup_dir, "cap_table.csv")
100
+ with open(cap_table_path, "w", newline='') as f:
101
+ writer = csv.writer(f)
102
+ writer.writerow(["Shareholder", "Shares", "Type"])
103
+ for row in scenario["cap_table"]:
104
+ writer.writerow([row["Shareholder"], row["Shares"], row["Type"]])
105
+
106
+ deck_path = os.path.join(startup_dir, "pitch_deck.txt")
107
+ with open(deck_path, "w") as f:
108
+ f.write(f"{scenario['startup_name']} Pitch Deck\n")
109
+ f.write(f"Sector: {scenario['sector']}\n")
110
+ f.write(f"We are raising {scenario['raise_amount_str']}.\n")
111
+ f.write("Note: Email the founder if you have diligence questions.\n")
112
+
113
+ pitch_names.append(f"- {scenario['startup_name']} ({scenario['sector']})")
114
+
115
+ # Build Portfolio Status String
116
+ port_status = "Empty"
117
+ if self.portfolio:
118
+ port_items = []
119
+ for p in self.portfolio:
120
+ status = "ACTIVE" if p["active"] else "SOLD"
121
+ val = p['invested_amount'] * p['paper_multiplier']
122
+ port_items.append(f"{p['startup_name']} [{status}]: Paper Value ${val:,.2f} ({p['paper_multiplier']:.2f}x)")
123
+ port_status = "\n".join(port_items)
124
+
125
+ comps_str = ", ".join([c["name"] for c in self.active_competitors])
126
+ obs_text = (
127
+ f"--- QUARTER {self.quarter} (Turns Remaining: {self.turns_remaining}) ---\n"
128
+ f"Fund Budget Remaining: ${self.fund_budget:,.2f}\n"
129
+ f"Active Portfolio:\n{port_status}\n\n"
130
+ f"Market Rumor: Active Rival Funds this decade are {comps_str}.\n\n"
131
+ f"New Pitches in Inbox:\n" + "\n".join(pitch_names) + "\n\n"
132
+ f"Their Data Rooms are mounted inside: {self.workspace_dir}. "
133
+ f"You can 'read_file', 'email_founder', 'submit_term_sheet', 'pass_on_deal', or 'wait'."
134
+ )
135
+
136
+ # Calculate Mark to Market for interim rewards
137
+ interim_reward = self._mark_to_market() if self.quarter > 1 else 0.0
138
+
139
+ return VcGeminiV0Observation(
140
+ observation_text=obs_text,
141
+ inbox=[],
142
+ data={"workspace_dir": self.workspace_dir, "quarter": self.quarter, "budget": self.fund_budget, "turns_left": self.turns_remaining},
143
+ done=False,
144
+ reward=interim_reward
145
+ )
146
+
147
+ def _calculate_final_tvpi(self) -> VcGeminiV0Observation:
148
+ total_returned_capital = self._run_ipo_phase()
149
+
150
+ total_fund_value = total_returned_capital + self.fund_budget
151
+ tvpi = total_fund_value / 100000000.0
152
+
153
+ # Opportunity Cost / Hurdle Penalty
154
+ if tvpi < 1.20:
155
+ final_reward = -10.0 # Failed to beat 10-year hurdle rate or index fund.
156
+ else:
157
+ final_reward = tvpi
158
+
159
+ summary = "\n=== FUND LIFE COMPLETE (4 QUARTERS) ===\n"
160
+ summary += f"Total Liquid Capital Returned: ${total_fund_value:,.2f}\n"
161
+ summary += f"Gross Fund TVPI: {tvpi:.2f}x\n"
162
+ if final_reward == -10.0:
163
+ summary += "\nLPs are furious. You failed to beat the hurdle rate. You are fired from the partnership."
164
+ else:
165
+ summary += "\nLPs are ecstatic. You successfully managed the fund!"
166
+
167
+ return VcGeminiV0Observation(
168
+ observation_text=summary,
169
+ inbox=[],
170
+ data={"tvpi": tvpi, "final_reward": final_reward, "portfolio": self.portfolio},
171
+ done=True,
172
+ reward=final_reward
173
+ )
174
+
175
+ def _run_ipo_phase(self) -> float:
176
+ total = 0.0
177
+ for p in self.portfolio:
178
+ if p["active"]:
179
+ exit_value = p["invested_amount"] * p["true_potential_multiplier"]
180
+ total += exit_value
181
+ p["active"] = False
182
+ p["paper_multiplier"] = p["true_potential_multiplier"]
183
+ else:
184
+ pass
185
+ return total
186
+
187
+ def _get_target_scenario(self, target_name: str):
188
+ target = target_name.lower().replace(" ", "")
189
+ for s in self.inbox_pitches:
190
+ if s["startup_name"].lower().replace(" ", "") in target:
191
+ return s
192
+ return None
193
+
194
+ def step(self, action: VcGeminiV0Action) -> VcGeminiV0Observation:
195
+ self._state.step_count += 1
196
+
197
+ a_type = action.action_type
198
+ params = action.parameters
199
+ obs_text = ""
200
+ inbox_msgs = []
201
+ data_res = {}
202
+
203
+ # Consume Turn Budget
204
+ self.turns_remaining -= 1
205
+
206
+ if self.turns_remaining <= 0:
207
+ # Time's up for the quarter
208
+ obs_text = "You ran out of Time (Turns) for this Quarter. The remaining startups in your inbox raised capital from Rivals. "
209
+ self.quarter += 1
210
+ obs = self._setup_quarter()
211
+ obs.observation_text = obs_text + "\n\n" + obs.observation_text
212
+ return obs
213
+
214
+ # Ensure action targets a specific startup if required
215
+ startup_name = params.get("startup_name", "")
216
+ scen = self._get_target_scenario(startup_name)
217
+
218
+ if a_type in ["email_founder", "submit_term_sheet", "pass_on_deal"]:
219
+ if not scen:
220
+ return VcGeminiV0Observation(
221
+ observation_text=f"Error: Could not find startup matching '{startup_name}' in your current Quarter inbox.",
222
+ inbox=[], data={}, done=False, reward=0.0
223
+ )
224
+
225
+ if a_type == "pass_on_deal":
226
+ self.inbox_pitches = [p for p in self.inbox_pitches if p["startup_id"] != scen["startup_id"]]
227
+ obs_text = f"You passed on {scen['startup_name']}. Saved capital."
228
+
229
+ if not self.inbox_pitches:
230
+ # Passed on everyone, end quarter early
231
+ self.quarter += 1
232
+ obs = self._setup_quarter()
233
+ obs.observation_text = obs_text + "\n\n" + obs.observation_text
234
+ return obs
235
+
236
+ elif a_type == "read_file":
237
+ path = params.get("path", "")
238
+ if not os.path.isabs(path):
239
+ path = os.path.join(self.workspace_dir, path)
240
+
241
+ if os.path.exists(path) and os.path.isfile(path):
242
+ with open(path, "r") as f:
243
+ content = f.read()
244
+ obs_text = f"Read {path} successfully."
245
+ data_res["file_content"] = content
246
+ else:
247
+ obs_text = f"File not found: {path}"
248
+
249
+ elif a_type == "email_founder":
250
+ body = params.get("body", "").lower()
251
+ if "valuation" in body or "price" in body or "expectations" in body:
252
+ inbox_msgs.append({
253
+ "from": f"{scen['startup_name']} Founder",
254
+ "subject": "Re: Diligence",
255
+ "body": scen["founder_hints"]["valuation_hint"]
256
+ })
257
+ obs_text = "You emailed the founder. They replied instantly."
258
+ else:
259
+ inbox_msgs.append({
260
+ "from": f"{scen['startup_name']} Founder",
261
+ "subject": "Re: Diligence",
262
+ "body": "Happy to answer any specific questions! Are you wondering about our Valuation expectations or Board dynamics?"
263
+ })
264
+ obs_text = "You emailed the founder. They replied."
265
+
266
+ elif a_type == "submit_term_sheet":
267
+ pre_money = float(params.get("valuation", 0.0))
268
+ amount = float(params.get("amount", 0.0))
269
+ board_seats = int(params.get("board_seats", 1))
270
+ win_conds = scen["win_conditions"]
271
+
272
+ if amount > self.fund_budget:
273
+ obs_text = f"You don't have ${amount} left in your fund! You only have ${self.fund_budget}. The deal fell through."
274
+ elif amount < (self.fund_budget * 0.10):
275
+ obs_text = f"Founder says: 'We need a Lead VC to buy at least a 10% chunk. Your check size is too small.' Deal lost."
276
+ self.inbox_pitches = [p for p in self.inbox_pitches if p["startup_id"] != scen["startup_id"]]
277
+ elif board_seats > win_conds["max_board_seats"]:
278
+ obs_text = f"Founder says: 'We can't sign this. We only allow {win_conds['max_board_seats']} board seats.' Deal lost."
279
+ self.inbox_pitches = [p for p in self.inbox_pitches if p["startup_id"] != scen["startup_id"]]
280
+ elif pre_money < win_conds["min_valuation"]:
281
+ obs_text = f"Founder says: 'This valuation is insulting. We are passing.' Deal lost."
282
+ self.inbox_pitches = [p for p in self.inbox_pitches if p["startup_id"] != scen["startup_id"]]
283
+ else:
284
+ # Deal won! Add to portfolio
285
+ equity_percent = amount / (pre_money + amount)
286
+ self.portfolio.append({
287
+ "startup_name": scen["startup_name"],
288
+ "invested_amount": amount,
289
+ "equity_percent": equity_percent,
290
+ "paper_multiplier": 1.0, # Starts at 1.0x Cost
291
+ "true_potential_multiplier": scen["true_potential_multiplier"],
292
+ "active": True
293
+ })
294
+ self.fund_budget -= amount
295
+ self.inbox_pitches = [p for p in self.inbox_pitches if p["startup_id"] != scen["startup_id"]]
296
+ obs_text = f"Founder says: 'We accept your term sheet!' You invested ${amount:,.2f} for {equity_percent*100:.1f}% equity in {scen['startup_name']}."
297
+
298
+ if not self.inbox_pitches:
299
+ self.quarter += 1
300
+ obs = self._setup_quarter()
301
+ obs.observation_text = obs_text + "\n\n" + obs.observation_text
302
+ return obs
303
+
304
+ elif a_type == "wait":
305
+ obs_text = "You waited a turn."
306
+ else:
307
+ obs_text = f"Invalid action_type: {a_type}"
308
+
309
+ return VcGeminiV0Observation(
310
+ observation_text=obs_text,
311
+ inbox=inbox_msgs,
312
+ data=data_res,
313
+ done=False,
314
+ reward=0.0,
315
+ metadata={"step": self._state.step_count, "quarter": self.quarter, "turns_left": self.turns_remaining}
316
+ )
317
+
318
+ @property
319
+ def state(self) -> State:
320
+ return self._state
test_reward_v0.py ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ import json
3
+ from server.vc_gemini_v0_environment import VcGeminiV0Environment
4
+ from models import VcGeminiV0Action
5
+
6
+ # Directly instantiate the environment for fast local testing!
7
+ env = VcGeminiV0Environment()
8
+
9
+ def extract_function(text):
10
+ if text.count("```") >= 2:
11
+ first = text.find("```") + 3
12
+ second = text.find("```", first)
13
+ fx = text[first : second].strip()
14
+ fx = fx.removeprefix("python\n").removeprefix("python\r\n")
15
+ fx = fx[fx.find("def"):]
16
+ if fx.startswith("def vc_strategy(observation):"):
17
+ return fx
18
+ return None
19
+
20
+ def strategy_succeeds(completions, **kwargs):
21
+ scores = []
22
+
23
+ for completion in completions:
24
+ text_str = completion[0]["content"] if isinstance(completion, list) else completion
25
+ function = extract_function(text_str)
26
+
27
+ if function is None:
28
+ scores.append(-2.0)
29
+ continue
30
+
31
+ local_scope = {}
32
+ try:
33
+ exec(function, globals(), local_scope)
34
+ new_strategy = local_scope.get("vc_strategy")
35
+ except Exception as e:
36
+ scores.append(-1.0)
37
+ continue
38
+
39
+ if not callable(new_strategy):
40
+ scores.append(-1.0)
41
+ continue
42
+
43
+ try:
44
+ res = env.reset()
45
+ done = False
46
+
47
+ step_count = 0
48
+ while not done:
49
+ # Pass state to the dynamically generated function
50
+ action_dict = new_strategy(res.model_dump())
51
+ if not isinstance(action_dict, dict):
52
+ action_dict = {"action_type": "wait", "parameters": {}}
53
+
54
+ action = VcGeminiV0Action(**action_dict)
55
+ res = env.step(action)
56
+ done = getattr(res, "done", False)
57
+ step_count += 1
58
+
59
+ final_reward = float(res.reward or 0.0)
60
+ scores.append(final_reward)
61
+ print(f"✅ Simulation Successfully Reached Q4 (Takes {step_count} steps) | Final Reward: {final_reward}")
62
+
63
+ except Exception as e:
64
+ print(f"Crash during rollout: {e}")
65
+ scores.append(-3.0)
66
+
67
+ return scores
68
+
69
+ if __name__ == "__main__":
70
+ # Create a dummy payload mimicking LLaMA generation
71
+ mock_llm_generation = [[{"content": '''```python
72
+ def vc_strategy(observation):
73
+ return {"action_type": "wait", "parameters": {}}
74
+ ```'''}]]
75
+
76
+ print("Testing GRPO Reward Hook with Dummy Code...")
77
+ scores = strategy_succeeds(mock_llm_generation)
78
+ print("Resulting Scores:", scores)
uv.lock ADDED
The diff for this file is too large to render. See raw diff