Spaces:
Sleeping
Sleeping
Upload folder using huggingface_hub
Browse files- .gitattributes +1 -0
- README.md +183 -255
- assets/Architecture_diagram.png +3 -0
- inference.py +23 -2
- rag_optimizer_environment.py +212 -0
- 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
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo: gray
|
| 6 |
-
sdk: docker
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
tags:
|
| 11 |
-
-
|
| 12 |
-
--
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
``
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
#
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
The
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
-
|
| 88 |
-
-
|
| 89 |
-
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
`
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
**
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
**
|
| 127 |
-
|
| 128 |
-
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
#
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
```
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 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
|
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"
|