DJAYADEV commited on
Commit
82cd281
·
verified ·
1 Parent(s): cac2413

Upload folder using huggingface_hub

Browse files
Files changed (6) hide show
  1. .gitattributes +1 -0
  2. README.md +183 -255
  3. assets/Architecture_diagram.png +3 -0
  4. inference.py +23 -2
  5. rag_optimizer_environment.py +212 -0
  6. run.sh +180 -0
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ assets/Architecture_diagram.png filter=lfs diff=lfs merge=lfs -text
README.md CHANGED
@@ -1,255 +1,183 @@
1
- ---
2
- title: Rag Optimizer Environment Server
3
- emoji: 📸
4
- colorFrom: blue
5
- colorTo: gray
6
- sdk: docker
7
- pinned: false
8
- app_port: 8000
9
- base_path: /web
10
- tags:
11
- - openenv
12
- ---
13
-
14
- # Rag Optimizer 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 Rag Optimizer environment is through the `RagOptimizerEnv` class:
21
-
22
- ```python
23
- from rag_optimizer import RagOptimizerAction, RagOptimizerEnv
24
-
25
- try:
26
- # Create environment from Docker image
27
- rag_optimizerenv = RagOptimizerEnv.from_docker_image("rag_optimizer-env:latest")
28
-
29
- # Reset
30
- result = rag_optimizerenv.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 = rag_optimizerenv.step(RagOptimizerAction(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
- rag_optimizerenv.close()
46
- ```
47
-
48
- That's it! The `RagOptimizerEnv.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 rag_optimizer-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
- **RagOptimizerAction**: Contains a single field
123
- - `message` (str) - The message to echo back
124
-
125
- ### Observation
126
- **RagOptimizerObservation**: 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 Rag Optimizer environment server running, you can connect directly:
144
-
145
- ```python
146
- from rag_optimizer import RagOptimizerEnv
147
-
148
- # Connect to existing server
149
- rag_optimizerenv = RagOptimizerEnv(base_url="<ENV_HTTP_URL_HERE>")
150
-
151
- # Use as normal
152
- result = rag_optimizerenv.reset()
153
- result = rag_optimizerenv.step(RagOptimizerAction(message="Hello!"))
154
- ```
155
-
156
- Note: When connecting to an existing server, `rag_optimizerenv.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 rag_optimizer import RagOptimizerAction, RagOptimizerEnv
164
-
165
- # Connect with context manager (auto-connects and closes)
166
- with RagOptimizerEnv(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(RagOptimizerAction(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
- RagOptimizerEnvironment, # Pass class, not instance
189
- RagOptimizerAction,
190
- RagOptimizerObservation,
191
- max_concurrent_envs=4, # Allow 4 concurrent sessions
192
- )
193
- ```
194
-
195
- Then multiple clients can connect simultaneously:
196
-
197
- ```python
198
- from rag_optimizer import RagOptimizerAction, RagOptimizerEnv
199
- from concurrent.futures import ThreadPoolExecutor
200
-
201
- def run_episode(client_id: int):
202
- with RagOptimizerEnv(base_url="http://localhost:8000") as env:
203
- result = env.reset()
204
- for i in range(10):
205
- result = env.step(RagOptimizerAction(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/rag_optimizer_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
- rag_optimizer/
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 # RagOptimizerEnv client
249
- ├── models.py # Action and Observation models
250
- └── server/
251
- ├── __init__.py # Server module exports
252
- ├── rag_optimizer_environment.py # Core environment logic
253
- ├── app.py # FastAPI application (HTTP + WebSocket endpoints)
254
- └── Dockerfile # Container image definition
255
- ```
 
1
+ ---
2
+ title: Rag Optimizer Env
3
+ emoji: 🧹
4
+ colorFrom: green
5
+ colorTo: gray
6
+ sdk: docker
7
+ app_port: 8000
8
+ pinned: false
9
+ license: mit
10
+ tags:
11
+ - reinforcement-learning
12
+ - data-engineering
13
+ - rag
14
+ - openenv
15
+ - llm-agent
16
+ base_path: /web
17
+ ---
18
+ <br>
19
+
20
+ <p align="center">
21
+ <img src="https://img.shields.io/badge/Hugging%20Face-FFD21E?style=for-the-badge&logo=huggingface&logoColor=black"/>
22
+ <img src="https://img.shields.io/badge/HF%20Spaces-FFBF00?style=for-the-badge&logo=huggingface&logoColor=black"/>
23
+ <img src="https://img.shields.io/badge/FastAPI-009688?style=for-the-badge&logo=fastapi&logoColor=white"/>
24
+ <img src="https://img.shields.io/badge/Docker-2496ED?style=for-the-badge&logo=docker&logoColor=white"/>
25
+ <img src="https://img.shields.io/badge/OpenEnv-4B5563?style=for-the-badge&logo=envato&logoColor=white"/>
26
+ <img src="https://img.shields.io/badge/scikit--learn-F7931E?style=for-the-badge&logo=scikit-learn&logoColor=white"/>
27
+ </p>
28
+ <br>
29
+
30
+ <h1 align="center">RagOptimizerEnv</h1>
31
+ <p align="center">
32
+ A high-fidelity Reinforcement Learning environment modeling the complex dynamics of enterprise Knowledge Base curation. LLM-driven agents must iteratively optimize, chunk, and groom raw document structures to natively improve vector-search retrieval accuracy over a continuous continuous state space.
33
+ <br />
34
+ </p>
35
+
36
+ <br>
37
+
38
+ ## System Architecture & Theme
39
+
40
+ **DATA ENGINEERING & AI INFRASTRUCTURE**
41
+
42
+ RagOptimizerEnv simulates the critical and computationally intensive role of an AI Data Engineer. The environment forces an autonomous agent to resolve conflicting semantic documentation, heuristically categorize disjointed metadata, and structurally splinter monolithic text corpora to prevent embedding decay within a Retrieval-Augmented Generation (RAG) pipeline.
43
+
44
+ <br>
45
+ <p align="center">
46
+ <img src="assets/Architecture_diagram.png" width="800" alt="System Architecture Diagram" />
47
+ </p>
48
+ <br>
49
+
50
+ ## The Engineering Problem
51
+
52
+ A pervasive engineering bottleneck in deployed RAG systems is **embedding decay caused by underlying data swamps**. A corpus containing overlapping legacy documentation, unstructured support tickets, and bloated monolithic manuals causes downstream deterministic embedding models to suffer from severe multi-collinearity and contextual wash-out.
53
+
54
+ To resolve this, human data engineers must navigate the database, read dense architectural texts, deduce semantic boundaries, and execute targeted CRUD operations to optimize the topological search space. Simulating and automating this workflow poses a multi-hop, highly contextual reinforcement learning challenge that strictly tests an agent's reasoning bounds, context retention, and operational planning over long task trajectories.
55
+
56
+ ## The Proposed Solution
57
+
58
+ We present an OpenEnv RL environment featuring an embedded, deterministic `scikit-learn` continuous Grader. The environment vectorizes the current database state after every single agent action, evaluating the structural integrity of the Knowledge Base in real-time. This provides a continuous reward density mapping—grading the agent purely on whether its structural operations inherently improved the theoretical Recall@3 limit of a downstream similarity search algorithm.
59
+
60
+ ---
61
+
62
+ ## Technical Novelty
63
+
64
+ - **Continuous Deterministic Grader:** Rather than relying on sparse terminal rewards, the environment maintains a live embedded `TfidfVectorizer`. It continuously restructures its internal sparse embedding matrix on every action step, calculating the cosine-similarity of hidden semantic payloads to supply dynamic gradient signals.
65
+ - **Topological State Feedback:** The environment streams a live topological representation of the knowledge base schema back to the agent on every interaction. Providing the `doc_id` indices mapped to character `length` and structural `metadata` ensures that agents operate efficiently without hallucinating system state across long inference contexts.
66
+ - **Dynamic Noise Mitigation:** The target corpus is embedded within an array of generated distractor documents designed to mathematically dilute the TF-IDF search space, testing the robust operational precision of deployed LLM agents.
67
+ - **Multi-Step Deductive Curriculum:** Sub-tasks require agents to temporarily retain the historical context of prior operations to successfully coordinate complex restructurings, pushing agents beyond single-shot function calling.
68
+
69
+ ---
70
+
71
+ ## Table of Contents
72
+
73
+ 1. [Environment Abstractions](#1-environment-abstractions)
74
+ 2. [Action & Observation Theory](#2-action--observation-theory)
75
+ 3. [The Grader & Reward Analytics](#3-the-grader--reward-analytics)
76
+ 4. [Curriculum Topologies](#4-curriculum-topologies)
77
+ 5. [Inference Baseline Sandbox](#5-inference-baseline-sandbox)
78
+ 6. [Containerized Deployment](#6-containerized-deployment)
79
+
80
+ ---
81
+
82
+ ## 1. Environment Abstractions
83
+
84
+ RagOptimizerEnv is an **OpenEnv** benchmark environment. Built atop stable microservice interfaces (`/reset`, `/step`, `/state`), it natively conforms to robust distributed training paradigms for Policy Gradient reinforcement algorithms.
85
+
86
+ **Complexity Vectors:**
87
+ - The underlying corpus intentionally mirrors unstructured enterprise deployments.
88
+ - Actions compound natively. Wiping an incorrect document permanently alters the topological alignment of the search algorithm.
89
+ - Frontier agents are challenged to execute a non-trivial "discover, contextualize, execute, verify" loop under a rigid step budget.
90
+
91
+ ---
92
+
93
+ ## 2. Action & Observation Theory
94
+
95
+ ### Action Abstraction (`RagOptimizerAction`)
96
+
97
+ Agents manipulate the embedding search space via a strictly defined remote schema:
98
+
99
+ | Action Primitive | Arguments | Effect Profile |
100
+ |---|---|---|
101
+ | `read_document` | `doc_id` | Fetches the specific byte-buffer into the context string without impacting reward density. |
102
+ | `update_document` | `doc_id`, `text` | Hard-overwrites existant node structures, or instantiates a fresh target node to achieve semantic splintering. |
103
+ | `delete_document` | `doc_id` | Destructively isolates and purges contradictory embeddings from the similarity array. |
104
+ | `add_metadata` | `doc_id`, `metadata_key`, `metadata_value` | Injects structured ontological markers to deterministically force embedding activations. |
105
+ | `submit` | None | Terminates the operational trajectory. |
106
+
107
+ ### Feedback Space (`RagOptimizerObservation`)
108
+
109
+ Pydantic-typed JSON states returned immediately upon trajectory execution:
110
+ 1. `message`: Terminal I/O logs, error stack-traces, or `read_document` raw buffers.
111
+ 2. `current_docs`: A hierarchical mapping of the existing document index payload ensuring contextual grounding.
112
+ 3. `reward`: The live theoretical model convergence rate formulated between `0.0` and `1.0`.
113
+
114
+ ---
115
+
116
+ ## 3. The Grader & Reward Analytics
117
+
118
+ Evaluation is robust, empirical, and mathematically bounded:
119
+
120
+ 1. **State Mutation:** The agent executes an array transaction within the topological space.
121
+ 2. **Re-Embedding:** The environment abstracts the updated schema into a standard English-corpus `TfidfVectorizer`.
122
+ 3. **Validation Probes:** The internal testing suite embeds an array of control queries targeting defined semantic concepts.
123
+ 4. **Retrieval Benchmark:** Sparse `cosine_similarity` calculates the topological displacement.
124
+ 5. **Score Allocation:** A hit is awarded conditionally if the modified Knowledge Base successfully forces the `target_concept` vector upward into the Top 3 search indices.
125
+
126
+ **Reward Yield:** `(Successful Vectors / Total Vector Payload)` providing high-density intermediate signals mapping continuously toward the `±1.0` upper bound.
127
+
128
+ ---
129
+
130
+ ## 4. Curriculum Topologies
131
+
132
+ The environment tests agents across three progressively demanding task distributions mirroring production RAG degradation scenarios.
133
+
134
+ ### Level I: Contamination Purging
135
+ **The Vector Issue:** The base contains heavily overlapping parameters (competing versions of legacy and modern timeline protocols).
136
+ **System Goal:** Autonomously survey the semantic differences, deduce the temporal conflict, and execute `delete_document` sweeps to purge vector hallucination triggers.
137
+
138
+ ### Level II: Ontological Tagging
139
+ **The Vector Issue:** Textual segments representing technical telemetry are too dense and sparse on specific categorical keywords, resulting in low coordinate density for deterministic algorithms.
140
+ **System Goal:** Execute inferential reading, deduce categorical bounds organically, and route exact programmatic `metadata` tags onto corresponding payloads to anchor the search vectors.
141
+
142
+ ### Level III: Syntactic Splintering (The Monolith)
143
+ **The Vector Issue:** Extreme embedding decay caused by disparate conceptual structures compacted under a single referential document. This represents the well-known "PDF chunk wash-out" phenomenon.
144
+ **System Goal:** Methodically `read` the extensive parent block, temporarily cache the semantic context limits, and utilize rapid consecutive `update_document` calls to mechanically splinter and redistribute the knowledge logic across multiple fine-grained nodes.
145
+
146
+ ---
147
+
148
+ ## 5. Inference Baseline Sandbox
149
+
150
+ The root tree provides `inference.py`, a reference baseline executing an OpenAI-compliant autonomous data-engineering agent.
151
+
152
+ Telemetry formats strictly adhere to high-velocity logging required for large-scale evaluation pipelines:
153
+
154
+ ```text
155
+ [START] task=rag_optimizer_env env=OpenEnv model=meta-llama/Meta-Llama-3.1-8B-Instruct
156
+ [STEP] step=1 action=read('doc_monolithic_onboarding') reward=0.33 done=false error=null
157
+ [STEP] step=2 action=update('doc_vpn_policy') reward=0.50 done=false error=null
158
+ ...
159
+ [END] success=true steps=12 score=1.00 rewards=0.33,0.50,...
160
+ ```
161
+
162
+ ### Execution
163
+ Ensure the environment contains an initialized `.env` matching standard model provider interfaces.
164
+ ```bash
165
+ source env/bin/activate
166
+ python inference.py
167
+ ```
168
+
169
+ ---
170
+
171
+ ## 6. Containerized Deployment
172
+
173
+ This architecture packages seamlessly into distributed environments utilizing strict Docker integrations for Hugging Face Spaces compatibility.
174
+
175
+ ```bash
176
+ # Compile SDK Instance
177
+ docker build -f server/Dockerfile -t ragoptimizer:latest .
178
+
179
+ # Execute API Server Loop
180
+ docker run -p 8000:8000 ragoptimizer:latest
181
+ ```
182
+
183
+ The primary instance binds over standard ASGI loops, broadcasting stable interfaces over standard HTTP routing (`/reset`, `/step`).
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
assets/Architecture_diagram.png ADDED

Git LFS Details

  • SHA256: 0a84f913bf41d7f597e98479a74b07c0fbcdf0f37f6f6629b10b4f0fecdf330f
  • Pointer size: 131 Bytes
  • Size of remote file: 178 kB
inference.py CHANGED
@@ -3,14 +3,35 @@ import sys
3
  import json
4
  from openai import OpenAI
5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  # Add the envs module to path so we can import client and models
7
  sys.path.append(os.path.join(os.path.dirname(__file__), "envs", "rag_optimizer_env"))
8
  from client import RagOptimizerEnvClient
9
  from models import RagOptimizerAction
10
 
11
  # Load environment variables
12
- API_BASE_URL = os.getenv("API_BASE_URL")
13
- MODEL_NAME = os.getenv("MODEL_NAME")
14
  HF_TOKEN = os.getenv("HF_TOKEN")
15
 
16
  MAX_STEPS = 30
 
3
  import json
4
  from openai import OpenAI
5
 
6
+
7
+ def load_local_env() -> None:
8
+ """Lightweight .env loader for local runs without requiring python-dotenv."""
9
+ env_path = os.path.join(os.path.dirname(__file__), ".env")
10
+ if not os.path.exists(env_path):
11
+ return
12
+
13
+ with open(env_path, "r", encoding="utf-8") as f:
14
+ for raw_line in f:
15
+ line = raw_line.strip()
16
+ if not line or line.startswith("#") or "=" not in line:
17
+ continue
18
+ key, value = line.split("=", 1)
19
+ key = key.strip()
20
+ value = value.strip().strip('"').strip("'")
21
+ if key and key not in os.environ:
22
+ os.environ[key] = value
23
+
24
+
25
+ load_local_env()
26
+
27
  # Add the envs module to path so we can import client and models
28
  sys.path.append(os.path.join(os.path.dirname(__file__), "envs", "rag_optimizer_env"))
29
  from client import RagOptimizerEnvClient
30
  from models import RagOptimizerAction
31
 
32
  # Load environment variables
33
+ API_BASE_URL = os.getenv("API_BASE_URL", "https://router.huggingface.co/v1")
34
+ MODEL_NAME = os.getenv("MODEL_NAME", "Qwen/Qwen2.5-72B-Instruct")
35
  HF_TOKEN = os.getenv("HF_TOKEN")
36
 
37
  MAX_STEPS = 30
rag_optimizer_environment.py ADDED
@@ -0,0 +1,212 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ Rag Optimizer Environment Implementation.
9
+ The agent acts as a Data Engineer to un-block a broken RAG pipeline.
10
+ """
11
+
12
+ from uuid import uuid4
13
+ from typing import Dict, Any, List
14
+
15
+ from openenv.core.env_server.interfaces import Environment
16
+ from openenv.core.env_server.types import State
17
+
18
+ # Import scikit-learn for our Grader
19
+ from sklearn.feature_extraction.text import TfidfVectorizer
20
+ from sklearn.metrics.pairwise import cosine_similarity
21
+ import numpy as np
22
+
23
+ try:
24
+ from models import RagOptimizerAction, RagOptimizerObservation
25
+ except ImportError:
26
+ from models import RagOptimizerAction, RagOptimizerObservation
27
+
28
+
29
+ class RagOptimizerEnvironment(Environment):
30
+ """
31
+ RAG Optimizer Engine.
32
+ Maintains a simulated Knowledge Base and grades it using TF-IDF.
33
+ """
34
+
35
+ SUPPORTS_CONCURRENT_SESSIONS: bool = True
36
+
37
+ def __init__(self):
38
+ self._state = State(episode_id=str(uuid4()), step_count=0)
39
+ self.kb = self._get_initial_kb()
40
+
41
+ # Hidden test suite for the grader
42
+ self.test_suite = [
43
+ {
44
+ "query": "What is the current 2024 price for standard?",
45
+ "target_concept": "750/mo"
46
+ },
47
+ {
48
+ "query": "What is the refund policy for enterprise?",
49
+ "target_concept": "Refunds are not permitted"
50
+ },
51
+ {
52
+ "query": "UI issues frontend CSS missing button",
53
+ "target_concept": "frontend team fixed the button"
54
+ },
55
+ {
56
+ "query": "How long does shipping take to branch offices?",
57
+ "target_concept": "5-7 business days"
58
+ },
59
+ {
60
+ "query": "What months do parking passes need to be renewed?",
61
+ "target_concept": "March"
62
+ },
63
+ {
64
+ "query": "What holidays are we off in 2024?",
65
+ "target_concept": "July 4"
66
+ }
67
+ ]
68
+
69
+ def _get_initial_kb(self) -> Dict[str, Dict]:
70
+ """Returns a fresh copy of the initial messy knowledge base."""
71
+ return {
72
+ "doc_pricing_legacy": {
73
+ "text": "Pricing for 2021: Enterprise tier is $1000/mo. Standard is $500/mo. All plans include 10 users.",
74
+ "metadata": {"type": "pricing"}
75
+ },
76
+ "doc_pricing_current_v2": {
77
+ "text": "Current Pricing 2024: Enterprise is $1500/mo. Standard is $750/mo. Refunds are not permitted on the enterprise tier.",
78
+ "metadata": {}
79
+ },
80
+ "doc_shipping_policy": {
81
+ "text": "All internal shipments to remote branch offices take 5-7 business days. Overnight shipping is only available for C-suite.",
82
+ "metadata": {"department": "logistics"}
83
+ },
84
+ "doc_messy_support_ticket_1": {
85
+ "text": "User complained the button disappeared on the frontend. Another user said the database latency was high. The frontend team fixed the button by updating CSS.",
86
+ "metadata": {}
87
+ },
88
+ "doc_messy_support_ticket_2": {
89
+ "text": "Email integration is failing with error 401 Unauthorized. The API key was rotated on Tuesday.",
90
+ "metadata": {}
91
+ },
92
+ "doc_monolithic_onboarding": {
93
+ "text": "Welcome to the company! Here are some rules. 1) VPN access requires DUO. 2) The cafetaria opens at 8 AM. 3) For HR issues, email hr@company.com. 4) The 2024 holiday schedule includes Dec 25, Jan 1, and July 4. 5) Parking passes must be renewed annually in March.",
94
+ "metadata": {}
95
+ },
96
+ # Add distractor files
97
+ **{f"doc_distractor_hr_{i}": {"text": f"This is an old HR policy document regarding {['pto', 'sick leave', 'travel', 'expenses'][i%4]} from 201{i%10}.", "metadata":{}} for i in range(10)},
98
+ **{f"doc_distractor_eng_{i}": {"text": f"Engineering architecture decision record {i}. We decided to use {['React', 'Postgres', 'Redis', 'Kafka'][i%4]} because of scaling concerns.", "metadata":{}} for i in range(10)},
99
+ **{f"doc_distractor_random_{i}": {"text": f"Weekly team update notes. Nothing important here, just discussed the weather and the upcoming launch {i}.", "metadata":{}} for i in range(10)},
100
+ }
101
+
102
+ def _get_kb_summary(self) -> Dict[str, Dict]:
103
+ """Returns a summary of the KB for the observation."""
104
+ summary = {}
105
+ for k, v in self.kb.items():
106
+ summary[k] = {"metadata": v.get("metadata", {}), "length": len(v.get("text", ""))}
107
+ return summary
108
+
109
+ def reset(self) -> RagOptimizerObservation:
110
+ self._state = State(episode_id=str(uuid4()), step_count=0)
111
+ self.kb = self._get_initial_kb()
112
+ return RagOptimizerObservation(
113
+ message="RagOptimizerEnv Initialized. You have messy chunks in the KB. Resolve conflicts, add metadata tags to short tickets, and splinter monolithic files to win.",
114
+ current_docs=self._get_kb_summary(),
115
+ done=False,
116
+ reward=self._evaluate_kb()
117
+ )
118
+
119
+ def _evaluate_kb(self) -> float:
120
+ """The Grader: Evaluates the agent's current KB using TF-IDF."""
121
+ if not self.kb:
122
+ return 0.0
123
+
124
+ doc_texts = [doc["text"] for doc in self.kb.values()]
125
+
126
+ vectorizer = TfidfVectorizer(stop_words='english')
127
+ try:
128
+ doc_vectors = vectorizer.fit_transform(doc_texts)
129
+ except ValueError:
130
+ return 0.0
131
+
132
+ score = 0.0
133
+
134
+ for case in self.test_suite:
135
+ query_vec = vectorizer.transform([case["query"]])
136
+ similarities = cosine_similarity(query_vec, doc_vectors)[0]
137
+
138
+ # Get top 3
139
+ top_k_indices = similarities.argsort()[-3:][::-1]
140
+
141
+ found = False
142
+ for idx in top_k_indices:
143
+ if similarities[idx] > 0.01:
144
+ if case["target_concept"].lower() in doc_texts[idx].lower():
145
+ found = True
146
+ break
147
+ if found:
148
+ score += 1.0
149
+
150
+ return float(score / len(self.test_suite))
151
+
152
+ def step(self, action: RagOptimizerAction) -> RagOptimizerObservation: # type: ignore[override]
153
+ self._state.step_count += 1
154
+
155
+ msg = ""
156
+ done = False
157
+ reward = 0.0
158
+
159
+ try:
160
+ if action.action_type == "read_document":
161
+ if action.doc_id in self.kb:
162
+ msg = f"Content of {action.doc_id}: {self.kb[action.doc_id]['text']}"
163
+ else:
164
+ msg = f"Error: doc_id {action.doc_id} not found."
165
+
166
+ elif action.action_type == "delete_document":
167
+ if action.doc_id in self.kb:
168
+ del self.kb[action.doc_id]
169
+ msg = f"Deleted {action.doc_id}."
170
+ else:
171
+ msg = f"Error: doc_id {action.doc_id} not found."
172
+
173
+ elif action.action_type == "update_document":
174
+ if not action.doc_id or not action.text:
175
+ msg = "Error: doc_id and text required for update_document."
176
+ else:
177
+ if action.doc_id not in self.kb:
178
+ self.kb[action.doc_id] = {"text": "", "metadata": {}}
179
+ self.kb[action.doc_id]["text"] = action.text
180
+ msg = f"Updated text for {action.doc_id}."
181
+
182
+ elif action.action_type == "add_metadata":
183
+ if not action.doc_id or not action.metadata_key or not action.metadata_value:
184
+ msg = "Error: doc_id, metadata_key, and metadata_value required."
185
+ else:
186
+ if action.doc_id not in self.kb:
187
+ msg = f"Error: doc_id {action.doc_id} not found."
188
+ else:
189
+ self.kb[action.doc_id]["metadata"][action.metadata_key] = action.metadata_value
190
+ msg = f"Added metadata to {action.doc_id}."
191
+
192
+ elif action.action_type == "submit":
193
+ done = True
194
+ reward = self._evaluate_kb()
195
+ msg = f"Evaluation complete. Final reward: {reward:.2f}"
196
+
197
+ except Exception as e:
198
+ msg = f"Action failed: {str(e)}"
199
+
200
+ if not done:
201
+ reward = self._evaluate_kb()
202
+
203
+ return RagOptimizerObservation(
204
+ message=msg,
205
+ current_docs=self._get_kb_summary(),
206
+ done=done,
207
+ reward=reward,
208
+ )
209
+
210
+ @property
211
+ def state(self) -> State:
212
+ return self._state
run.sh ADDED
@@ -0,0 +1,180 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #
2
+ # Checks that your HF Space is live, Docker image builds, and openenv validate passes.
3
+ #
4
+ # Prerequisites:
5
+ # - Docker: https://docs.docker.com/get-docker/
6
+ # - openenv-core: pip install openenv-core
7
+ # - curl (usually pre-installed)
8
+ #
9
+ # Run:
10
+ # curl -fsSL https://raw.githubusercontent.com/<owner>/<repo>/main/scripts/validate-submission.sh | bash -s -- <ping_url> [repo_dir]
11
+ #
12
+ # Or download and run locally:
13
+ # chmod +x validate-submission.sh
14
+ # ./validate-submission.sh <ping_url> [repo_dir]
15
+ #
16
+ # Arguments:
17
+ # ping_url Your HuggingFace Space URL (e.g. https://your-space.hf.space)
18
+ # repo_dir Path to your repo (default: current directory)
19
+ #
20
+ # Examples:
21
+ # ./validate-submission.sh https://my-team.hf.space
22
+ # ./validate-submission.sh https://my-team.hf.space ./my-repo
23
+ #
24
+
25
+ set -uo pipefail
26
+
27
+ DOCKER_BUILD_TIMEOUT=600
28
+ if [ -t 1 ]; then
29
+ RED='\033[0;31m'
30
+ GREEN='\033[0;32m'
31
+ YELLOW='\033[1;33m'
32
+ BOLD='\033[1m'
33
+ NC='\033[0m'
34
+ else
35
+ RED='' GREEN='' YELLOW='' BOLD='' NC=''
36
+ fi
37
+
38
+ run_with_timeout() {
39
+ local secs="$1"; shift
40
+ if command -v timeout &>/dev/null; then
41
+ timeout "$secs" "$@"
42
+ elif command -v gtimeout &>/dev/null; then
43
+ gtimeout "$secs" "$@"
44
+ else
45
+ "$@" &
46
+ local pid=$!
47
+ ( sleep "$secs" && kill "$pid" 2>/dev/null ) &
48
+ local watcher=$!
49
+ wait "$pid" 2>/dev/null
50
+ local rc=$?
51
+ kill "$watcher" 2>/dev/null
52
+ wait "$watcher" 2>/dev/null
53
+ return $rc
54
+ fi
55
+ }
56
+
57
+ portable_mktemp() {
58
+ local prefix="${1:-validate}"
59
+ mktemp "${TMPDIR:-/tmp}/${prefix}-XXXXXX" 2>/dev/null || mktemp
60
+ }
61
+
62
+ CLEANUP_FILES=()
63
+ cleanup() { rm -f "${CLEANUP_FILES[@]+"${CLEANUP_FILES[@]}"}"; }
64
+ trap cleanup EXIT
65
+
66
+ PING_URL="${1:-}"
67
+ REPO_DIR="${2:-.}"
68
+
69
+ if [ -z "$PING_URL" ]; then
70
+ printf "Usage: %s <ping_url> [repo_dir]\n" "$0"
71
+ printf "\n"
72
+ printf " ping_url Your HuggingFace Space URL (e.g. https://your-space.hf.space)\n"
73
+ printf " repo_dir Path to your repo (default: current directory)\n"
74
+ exit 1
75
+ fi
76
+
77
+ if ! REPO_DIR="$(cd "$REPO_DIR" 2>/dev/null && pwd)"; then
78
+ printf "Error: directory '%s' not found\n" "${2:-.}"
79
+ exit 1
80
+ fi
81
+ PING_URL="${PING_URL%/}"
82
+ export PING_URL
83
+ PASS=0
84
+
85
+ log() { printf "[%s] %b\n" "$(date -u +%H:%M:%S)" "$*"; }
86
+ pass() { log "${GREEN}PASSED${NC} -- $1"; PASS=$((PASS + 1)); }
87
+ fail() { log "${RED}FAILED${NC} -- $1"; }
88
+ hint() { printf " ${YELLOW}Hint:${NC} %b\n" "$1"; }
89
+ stop_at() {
90
+ printf "\n"
91
+ printf "${RED}${BOLD}Validation stopped at %s.${NC} Fix the above before continuing.\n" "$1"
92
+ exit 1
93
+ }
94
+
95
+ printf "\n"
96
+ printf "${BOLD}========================================${NC}\n"
97
+ printf "${BOLD} OpenEnv Submission Validator${NC}\n"
98
+ printf "${BOLD}========================================${NC}\n"
99
+ log "Repo: $REPO_DIR"
100
+ log "Ping URL: $PING_URL"
101
+ printf "\n"
102
+
103
+ log "${BOLD}Step 1/3: Pinging HF Space${NC} ($PING_URL/reset) ..."
104
+
105
+ CURL_OUTPUT=$(portable_mktemp "validate-curl")
106
+ CLEANUP_FILES+=("$CURL_OUTPUT")
107
+ HTTP_CODE=$(curl -s -o "$CURL_OUTPUT" -w "%{http_code}" -X POST \
108
+ -H "Content-Type: application/json" -d '{}' \
109
+ "$PING_URL/reset" --max-time 30 2>"$CURL_OUTPUT" || printf "000")
110
+
111
+ if [ "$HTTP_CODE" = "200" ]; then
112
+ pass "HF Space is live and responds to /reset"
113
+ elif [ "$HTTP_CODE" = "000" ]; then
114
+ fail "HF Space not reachable (connection failed or timed out)"
115
+ hint "Check your network connection and that the Space is running."
116
+ hint "Try: curl -s -o /dev/null -w '%%{http_code}' -X POST $PING_URL/reset"
117
+ stop_at "Step 1"
118
+ else
119
+ fail "HF Space /reset returned HTTP $HTTP_CODE (expected 200)"
120
+ hint "Make sure your Space is running and the URL is correct."
121
+ hint "Try opening $PING_URL in your browser first."
122
+ stop_at "Step 1"
123
+ fi
124
+
125
+ log "${BOLD}Step 2/3: Running docker build${NC} ..."
126
+
127
+ if ! command -v docker &>/dev/null; then
128
+ fail "docker command not found"
129
+ hint "Install Docker: https://docs.docker.com/get-docker/"
130
+ stop_at "Step 2"
131
+ fi
132
+
133
+ if [ -f "$REPO_DIR/Dockerfile" ]; then
134
+ DOCKER_CONTEXT="$REPO_DIR"
135
+ elif [ -f "$REPO_DIR/server/Dockerfile" ]; then
136
+ DOCKER_CONTEXT="$REPO_DIR/server"
137
+ else
138
+ fail "No Dockerfile found in repo root or server/ directory"
139
+ stop_at "Step 2"
140
+ fi
141
+
142
+ log " Found Dockerfile in $DOCKER_CONTEXT"
143
+
144
+ BUILD_OK=false
145
+ BUILD_OUTPUT=$(run_with_timeout "$DOCKER_BUILD_TIMEOUT" docker build "$DOCKER_CONTEXT" 2>&1) && BUILD_OK=true
146
+
147
+ if [ "$BUILD_OK" = true ]; then
148
+ pass "Docker build succeeded"
149
+ else
150
+ fail "Docker build failed (timeout=${DOCKER_BUILD_TIMEOUT}s)"
151
+ printf "%s\n" "$BUILD_OUTPUT" | tail -20
152
+ stop_at "Step 2"
153
+ fi
154
+
155
+ log "${BOLD}Step 3/3: Running openenv validate${NC} ..."
156
+
157
+ if ! command -v openenv &>/dev/null; then
158
+ fail "openenv command not found"
159
+ hint "Install it: pip install openenv-core"
160
+ stop_at "Step 3"
161
+ fi
162
+
163
+ VALIDATE_OK=false
164
+ VALIDATE_OUTPUT=$(cd "$REPO_DIR" && openenv validate 2>&1) && VALIDATE_OK=true
165
+
166
+ if [ "$VALIDATE_OK" = true ]; then
167
+ pass "openenv validate passed"
168
+ [ -n "$VALIDATE_OUTPUT" ] && log " $VALIDATE_OUTPUT"
169
+ else
170
+ fail "openenv validate failed"
171
+ printf "%s\n" "$VALIDATE_OUTPUT"
172
+ stop_at "Step 3"
173
+ fi
174
+
175
+ printf "\n"
176
+ printf "${BOLD}========================================${NC}\n"
177
+ printf "${GREEN}${BOLD} All 3/3 checks passed!${NC}\n"
178
+ printf "${GREEN}${BOLD} Your submission is ready to submit.${NC}\n"
179
+ printf "${BOLD}========================================${NC}\n"
180
+ printf "\n"