DJAYADEV commited on
Commit
bd468ee
·
verified ·
1 Parent(s): e3379be

Upload folder using huggingface_hub

Browse files
Files changed (13) hide show
  1. __init__.py +16 -16
  2. client.py +56 -56
  3. inference.py +180 -180
  4. models.py +62 -62
  5. openenv.yaml +16 -7
  6. pyproject.toml +45 -45
  7. rag_optimizer_environment.py +212 -212
  8. run.sh +179 -179
  9. server/__init__.py +11 -11
  10. server/app.py +79 -79
  11. server/rag_optimizer_environment.py +209 -214
  12. server/requirements.txt +4 -4
  13. uv.lock +0 -0
__init__.py CHANGED
@@ -1,16 +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
- """Rag Optimizer Environment."""
8
-
9
- from .client import RagOptimizerEnv
10
- from .models import RagOptimizerAction, RagOptimizerObservation
11
-
12
- __all__ = [
13
- "RagOptimizerAction",
14
- "RagOptimizerObservation",
15
- "RagOptimizerEnv",
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
+ """Rag Optimizer Environment."""
8
+
9
+ from .client import RagOptimizerEnv
10
+ from .models import RagOptimizerAction, RagOptimizerObservation
11
+
12
+ __all__ = [
13
+ "RagOptimizerAction",
14
+ "RagOptimizerObservation",
15
+ "RagOptimizerEnv",
16
+ ]
client.py CHANGED
@@ -1,56 +1,56 @@
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
- """Rag Optimizer Environment Client."""
8
-
9
- from typing import Dict
10
-
11
- from openenv.core import EnvClient
12
- from openenv.core.client_types import StepResult
13
- from openenv.core.env_server.types import State
14
-
15
- from models import RagOptimizerAction, RagOptimizerObservation
16
-
17
-
18
- class RagOptimizerEnvClient(EnvClient[RagOptimizerAction, RagOptimizerObservation, State]):
19
- """
20
- Client for the Rag Optimizer Environment.
21
- Translates local Pydantic objects to JSON for the OpenEnv WebSocket.
22
- """
23
-
24
- def _step_payload(self, action: RagOptimizerAction) -> Dict:
25
- """Convert RagOptimizerAction to JSON payload."""
26
- return {
27
- "action_type": action.action_type,
28
- "doc_id": action.doc_id,
29
- "text": action.text,
30
- "metadata_key": action.metadata_key,
31
- "metadata_value": action.metadata_value,
32
- }
33
-
34
- def _parse_result(self, payload: Dict) -> StepResult[RagOptimizerObservation]:
35
- """Parse server response back into RagOptimizerObservation."""
36
- obs_data = payload.get("observation", {})
37
- observation = RagOptimizerObservation(
38
- message=obs_data.get("message", ""),
39
- current_docs=obs_data.get("current_docs", {}),
40
- done=payload.get("done", False),
41
- reward=payload.get("reward", 0.0),
42
- metadata=obs_data.get("metadata", {}),
43
- )
44
-
45
- return StepResult(
46
- observation=observation,
47
- reward=payload.get("reward", 0.0),
48
- done=payload.get("done", False),
49
- )
50
-
51
- def _parse_state(self, payload: Dict) -> State:
52
- """Parse the hidden tracking state."""
53
- return State(
54
- episode_id=payload.get("episode_id"),
55
- step_count=payload.get("step_count", 0),
56
- )
 
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
+ """Rag Optimizer Environment Client."""
8
+
9
+ from typing import Dict
10
+
11
+ from openenv.core import EnvClient
12
+ from openenv.core.client_types import StepResult
13
+ from openenv.core.env_server.types import State
14
+
15
+ from models import RagOptimizerAction, RagOptimizerObservation
16
+
17
+
18
+ class RagOptimizerEnvClient(EnvClient[RagOptimizerAction, RagOptimizerObservation, State]):
19
+ """
20
+ Client for the Rag Optimizer Environment.
21
+ Translates local Pydantic objects to JSON for the OpenEnv WebSocket.
22
+ """
23
+
24
+ def _step_payload(self, action: RagOptimizerAction) -> Dict:
25
+ """Convert RagOptimizerAction to JSON payload."""
26
+ return {
27
+ "action_type": action.action_type,
28
+ "doc_id": action.doc_id,
29
+ "text": action.text,
30
+ "metadata_key": action.metadata_key,
31
+ "metadata_value": action.metadata_value,
32
+ }
33
+
34
+ def _parse_result(self, payload: Dict) -> StepResult[RagOptimizerObservation]:
35
+ """Parse server response back into RagOptimizerObservation."""
36
+ obs_data = payload.get("observation", {})
37
+ observation = RagOptimizerObservation(
38
+ message=obs_data.get("message", ""),
39
+ current_docs=obs_data.get("current_docs", {}),
40
+ done=payload.get("done", False),
41
+ reward=payload.get("reward", 0.0),
42
+ metadata=obs_data.get("metadata", {}),
43
+ )
44
+
45
+ return StepResult(
46
+ observation=observation,
47
+ reward=payload.get("reward", 0.0),
48
+ done=payload.get("done", False),
49
+ )
50
+
51
+ def _parse_state(self, payload: Dict) -> State:
52
+ """Parse the hidden tracking state."""
53
+ return State(
54
+ episode_id=payload.get("episode_id"),
55
+ step_count=payload.get("step_count", 0),
56
+ )
inference.py CHANGED
@@ -1,180 +1,180 @@
1
- import os
2
- import sys
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
38
-
39
- SYSTEM_PROMPT = """You are an automated Data Engineer managing an AI Knowledge Base.
40
- Your goal is to optimize the messy chunks of text in the database so that a TF-IDF Search Algorithm can find answers easily.
41
- You must resolve contradictions, categorize documents, and delete unnecessary documents.
42
-
43
- After each action you will receive a "current_reward" score (0.0 to 1.0) indicating how well the KB currently performs. Use this to guide your strategy.
44
-
45
- You have the following actions:
46
- - {"action_type": "read_document", "doc_id": "..."}
47
- - {"action_type": "update_document", "doc_id": "...", "text": "..."}
48
- - {"action_type": "delete_document", "doc_id": "..."}
49
- - {"action_type": "add_metadata", "doc_id": "...", "metadata_key": "...", "metadata_value": "..."}
50
- - {"action_type": "submit"}
51
-
52
- You must return ONLY a raw JSON object detailing the action you want to take!"""
53
-
54
- def format_action_str(action: RagOptimizerAction) -> str:
55
- if action.action_type == "read_document":
56
- return f"read('{action.doc_id}')"
57
- elif action.action_type == "update_document":
58
- return f"update('{action.doc_id}')"
59
- elif action.action_type == "delete_document":
60
- return f"delete('{action.doc_id}')"
61
- elif action.action_type == "add_metadata":
62
- return f"add_metadata('{action.doc_id}','{action.metadata_key}')"
63
- elif action.action_type == "submit":
64
- return "submit()"
65
- return f"{action.action_type}()"
66
-
67
- def main():
68
- # Setup OpenAI Client
69
- client = OpenAI(base_url=API_BASE_URL, api_key=HF_TOKEN)
70
-
71
- # Track metrics for the final output
72
- step_rewards = []
73
- success = False
74
- error_msg = "null"
75
- score = 0.0
76
-
77
- print(f"[START] task=rag_optimizer_env env=OpenEnv model={MODEL_NAME}")
78
-
79
- # We suppress any other custom prints to respect the STDOUT format strictly
80
- import contextlib
81
- import io
82
-
83
- with RagOptimizerEnvClient(base_url="http://localhost:8000").sync() as env:
84
- # Suppress prints from client or env reset
85
- with contextlib.redirect_stdout(io.StringIO()):
86
- try:
87
- result = env.reset()
88
- observation = result.observation
89
- except Exception as e:
90
- error_msg = str(e).replace('\n', ' ')
91
- print(f"[END] success=false steps=0 score=0.00 rewards=")
92
- return
93
-
94
- history = [{"role": "system", "content": SYSTEM_PROMPT}]
95
-
96
- init_obs = {
97
- "server_feedback": observation.message,
98
- "current_reward": observation.reward,
99
- "current_knowledge_base": observation.current_docs
100
- }
101
- history.append({"role": "user", "content": json.dumps(init_obs, indent=2)})
102
-
103
- step = 0
104
- for i in range(1, MAX_STEPS + 1):
105
- step = i
106
- messages = list(history)
107
-
108
- action_str = "unknown"
109
- error_msg = "null"
110
-
111
- try:
112
- completion = client.chat.completions.create(
113
- model=MODEL_NAME,
114
- messages=messages,
115
- response_format={"type": "json_object"},
116
- max_tokens=1000
117
- )
118
- response_text = completion.choices[0].message.content or ""
119
- action_data = json.loads(response_text)
120
-
121
- # Normalize fields if model returns lists instead of strings
122
- for field in ("doc_id", "text", "metadata_key", "metadata_value"):
123
- val = action_data.get(field)
124
- if isinstance(val, list):
125
- if val and isinstance(val[0], str):
126
- action_data[field] = " ".join(val)
127
- elif val and isinstance(val[0], dict):
128
- action_data[field] = json.dumps(val[0])
129
- else:
130
- action_data[field] = str(val[0]) if val else ""
131
-
132
- action = RagOptimizerAction(**action_data)
133
- action_str = format_action_str(action)
134
-
135
- except Exception as exc:
136
- error_msg = str(exc).replace('\n', ' ')
137
- action = RagOptimizerAction(action_type="submit")
138
- action_str = format_action_str(action)
139
-
140
- # Suppress normal prints during step
141
- with contextlib.redirect_stdout(io.StringIO()):
142
- try:
143
- result = env.step(action)
144
- observation = result.observation
145
- reward = result.reward
146
- except Exception as e:
147
- error_msg = str(e).replace('\n', ' ')
148
- reward = 0.0
149
- result = type('obj', (object,), {'done': True})()
150
- observation = type('obj', (object,), {'message': 'error', 'current_docs': {}})()
151
-
152
- step_rewards.append(reward)
153
- done = "true" if result.done else "false"
154
-
155
- print(f"[STEP] step={step} action={action_str} reward={reward:.2f} done={done} error={error_msg}")
156
-
157
- if result.done:
158
- success = True if reward > 0.5 else False # Or however you define success
159
- score = float(reward)
160
- break
161
-
162
- history.append({"role": "assistant", "content": json.dumps(action.model_dump(), default=str)})
163
- next_obs = {
164
- "server_feedback": observation.message,
165
- "current_reward": observation.reward,
166
- "current_knowledge_base": observation.current_docs
167
- }
168
- history.append({"role": "user", "content": json.dumps(next_obs, indent=2)})
169
-
170
- else:
171
- # Reached max steps
172
- success = False
173
- score = float(result.reward)
174
-
175
- rewards_str = ",".join([f"{r:.2f}" for r in step_rewards])
176
- done_str = "true" if success else "false"
177
- print(f"[END] success={done_str} steps={step} score={score:.2f} rewards={rewards_str}")
178
-
179
- if __name__ == "__main__":
180
- main()
 
1
+ import os
2
+ import sys
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
38
+
39
+ SYSTEM_PROMPT = """You are an automated Data Engineer managing an AI Knowledge Base.
40
+ Your goal is to optimize the messy chunks of text in the database so that a TF-IDF Search Algorithm can find answers easily.
41
+ You must resolve contradictions, categorize documents, and delete unnecessary documents.
42
+
43
+ After each action you will receive a "current_reward" score (0.0 to 1.0) indicating how well the KB currently performs. Use this to guide your strategy.
44
+
45
+ You have the following actions:
46
+ - {"action_type": "read_document", "doc_id": "..."}
47
+ - {"action_type": "update_document", "doc_id": "...", "text": "..."}
48
+ - {"action_type": "delete_document", "doc_id": "..."}
49
+ - {"action_type": "add_metadata", "doc_id": "...", "metadata_key": "...", "metadata_value": "..."}
50
+ - {"action_type": "submit"}
51
+
52
+ You must return ONLY a raw JSON object detailing the action you want to take!"""
53
+
54
+ def format_action_str(action: RagOptimizerAction) -> str:
55
+ if action.action_type == "read_document":
56
+ return f"read('{action.doc_id}')"
57
+ elif action.action_type == "update_document":
58
+ return f"update('{action.doc_id}')"
59
+ elif action.action_type == "delete_document":
60
+ return f"delete('{action.doc_id}')"
61
+ elif action.action_type == "add_metadata":
62
+ return f"add_metadata('{action.doc_id}','{action.metadata_key}')"
63
+ elif action.action_type == "submit":
64
+ return "submit()"
65
+ return f"{action.action_type}()"
66
+
67
+ def main():
68
+ # Setup OpenAI Client
69
+ client = OpenAI(base_url=API_BASE_URL, api_key=HF_TOKEN)
70
+
71
+ # Track metrics for the final output
72
+ step_rewards = []
73
+ success = False
74
+ error_msg = "null"
75
+ score = 0.0
76
+
77
+ print(f"[START] task=rag_optimizer_env env=OpenEnv model={MODEL_NAME}")
78
+
79
+ # We suppress any other custom prints to respect the STDOUT format strictly
80
+ import contextlib
81
+ import io
82
+
83
+ with RagOptimizerEnvClient(base_url="http://localhost:8000").sync() as env:
84
+ # Suppress prints from client or env reset
85
+ with contextlib.redirect_stdout(io.StringIO()):
86
+ try:
87
+ result = env.reset()
88
+ observation = result.observation
89
+ except Exception as e:
90
+ error_msg = str(e).replace('\n', ' ')
91
+ print(f"[END] success=false steps=0 score=0.00 rewards=")
92
+ return
93
+
94
+ history = [{"role": "system", "content": SYSTEM_PROMPT}]
95
+
96
+ init_obs = {
97
+ "server_feedback": observation.message,
98
+ "current_reward": observation.reward,
99
+ "current_knowledge_base": observation.current_docs
100
+ }
101
+ history.append({"role": "user", "content": json.dumps(init_obs, indent=2)})
102
+
103
+ step = 0
104
+ for i in range(1, MAX_STEPS + 1):
105
+ step = i
106
+ messages = list(history)
107
+
108
+ action_str = "unknown"
109
+ error_msg = "null"
110
+
111
+ try:
112
+ completion = client.chat.completions.create(
113
+ model=MODEL_NAME,
114
+ messages=messages,
115
+ response_format={"type": "json_object"},
116
+ max_tokens=1000
117
+ )
118
+ response_text = completion.choices[0].message.content or ""
119
+ action_data = json.loads(response_text)
120
+
121
+ # Normalize fields if model returns lists instead of strings
122
+ for field in ("doc_id", "text", "metadata_key", "metadata_value"):
123
+ val = action_data.get(field)
124
+ if isinstance(val, list):
125
+ if val and isinstance(val[0], str):
126
+ action_data[field] = " ".join(val)
127
+ elif val and isinstance(val[0], dict):
128
+ action_data[field] = json.dumps(val[0])
129
+ else:
130
+ action_data[field] = str(val[0]) if val else ""
131
+
132
+ action = RagOptimizerAction(**action_data)
133
+ action_str = format_action_str(action)
134
+
135
+ except Exception as exc:
136
+ error_msg = str(exc).replace('\n', ' ')
137
+ action = RagOptimizerAction(action_type="submit")
138
+ action_str = format_action_str(action)
139
+
140
+ # Suppress normal prints during step
141
+ with contextlib.redirect_stdout(io.StringIO()):
142
+ try:
143
+ result = env.step(action)
144
+ observation = result.observation
145
+ reward = result.reward
146
+ except Exception as e:
147
+ error_msg = str(e).replace('\n', ' ')
148
+ reward = 0.0
149
+ result = type('obj', (object,), {'done': True})()
150
+ observation = type('obj', (object,), {'message': 'error', 'current_docs': {}})()
151
+
152
+ step_rewards.append(reward)
153
+ done = "true" if result.done else "false"
154
+
155
+ print(f"[STEP] step={step} action={action_str} reward={reward:.2f} done={done} error={error_msg}")
156
+
157
+ if result.done:
158
+ success = True if reward > 0.5 else False # Or however you define success
159
+ score = float(reward)
160
+ break
161
+
162
+ history.append({"role": "assistant", "content": json.dumps(action.model_dump(), default=str)})
163
+ next_obs = {
164
+ "server_feedback": observation.message,
165
+ "current_reward": observation.reward,
166
+ "current_knowledge_base": observation.current_docs
167
+ }
168
+ history.append({"role": "user", "content": json.dumps(next_obs, indent=2)})
169
+
170
+ else:
171
+ # Reached max steps
172
+ success = False
173
+ score = float(result.reward)
174
+
175
+ rewards_str = ",".join([f"{r:.2f}" for r in step_rewards])
176
+ done_str = "true" if success else "false"
177
+ print(f"[END] success={done_str} steps={step} score={score:.2f} rewards={rewards_str}")
178
+
179
+ if __name__ == "__main__":
180
+ main()
models.py CHANGED
@@ -1,62 +1,62 @@
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
- Pydantic schemas for the Rag Optimizer environment.
9
- These define the API contract between the client (agent) and the server.
10
- """
11
-
12
- from typing import Dict, Literal, Optional
13
- from pydantic import BaseModel, Field
14
-
15
-
16
- class RagOptimizerAction(BaseModel):
17
- """
18
- Actions the agent can take to interact with the Knowledge Base.
19
- """
20
-
21
- action_type: Literal["read_document", "update_document", "delete_document", "add_metadata", "submit"] = Field(
22
- ...,
23
- description="The RAG optimization tool to execute."
24
- )
25
-
26
- doc_id: Optional[str] = Field(
27
- None,
28
- description="The ID of the document to target."
29
- )
30
- text: Optional[str] = Field(
31
- None,
32
- description="The text content (used for update_document)."
33
- )
34
- metadata_key: Optional[str] = Field(
35
- None,
36
- description="The key of the metadata tag (used for add_metadata)."
37
- )
38
- metadata_value: Optional[str] = Field(
39
- None,
40
- description="The value of the metadata tag (used for add_metadata)."
41
- )
42
-
43
-
44
- class RagOptimizerObservation(BaseModel):
45
- """
46
- The environment's response to an action, including the state of the KB.
47
- """
48
-
49
- message: str = Field(
50
- ...,
51
- description="Feedback from the last action (e.g., success/error messages)."
52
- )
53
-
54
- current_docs: Dict[str, Dict] = Field(
55
- ...,
56
- description="A live summary of the documents currently inside the KB (doc_id -> metadata/length)."
57
- )
58
-
59
- # Required OpenEnv standard fields
60
- done: bool = Field(False, description="Whether the episode has finished.")
61
- reward: float = Field(0.0, description="The reward obtained from the last step.")
62
- metadata: Dict = Field(default_factory=dict, description="Additional optional information.")
 
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
+ Pydantic schemas for the Rag Optimizer environment.
9
+ These define the API contract between the client (agent) and the server.
10
+ """
11
+
12
+ from typing import Dict, Literal, Optional
13
+ from pydantic import BaseModel, Field
14
+
15
+
16
+ class RagOptimizerAction(BaseModel):
17
+ """
18
+ Actions the agent can take to interact with the Knowledge Base.
19
+ """
20
+
21
+ action_type: Literal["read_document", "update_document", "delete_document", "add_metadata", "submit"] = Field(
22
+ ...,
23
+ description="The RAG optimization tool to execute."
24
+ )
25
+
26
+ doc_id: Optional[str] = Field(
27
+ None,
28
+ description="The ID of the document to target."
29
+ )
30
+ text: Optional[str] = Field(
31
+ None,
32
+ description="The text content (used for update_document)."
33
+ )
34
+ metadata_key: Optional[str] = Field(
35
+ None,
36
+ description="The key of the metadata tag (used for add_metadata)."
37
+ )
38
+ metadata_value: Optional[str] = Field(
39
+ None,
40
+ description="The value of the metadata tag (used for add_metadata)."
41
+ )
42
+
43
+
44
+ class RagOptimizerObservation(BaseModel):
45
+ """
46
+ The environment's response to an action, including the state of the KB.
47
+ """
48
+
49
+ message: str = Field(
50
+ ...,
51
+ description="Feedback from the last action (e.g., success/error messages)."
52
+ )
53
+
54
+ current_docs: Dict[str, Dict] = Field(
55
+ ...,
56
+ description="A live summary of the documents currently inside the KB (doc_id -> metadata/length)."
57
+ )
58
+
59
+ # Required OpenEnv standard fields
60
+ done: bool = Field(False, description="Whether the episode has finished.")
61
+ reward: float = Field(0.0, description="The reward obtained from the last step.")
62
+ metadata: Dict = Field(default_factory=dict, description="Additional optional information.")
openenv.yaml CHANGED
@@ -1,7 +1,16 @@
1
- spec_version: 1
2
- name: rag_optimizer
3
- type: space
4
- runtime: fastapi
5
- app: server.app:app
6
- port: 8000
7
-
 
 
 
 
 
 
 
 
 
 
1
+ spec_version: 1
2
+ name: rag_optimizer
3
+ type: space
4
+ runtime: fastapi
5
+ app: server.app:app
6
+ port: 8000
7
+ tasks:
8
+ - id: easy
9
+ steps: 10
10
+ description: Conflict Resolution - Resolve overlapping pricing parameters by deleting the legacy pricing format.
11
+ - id: medium
12
+ steps: 10
13
+ description: Ontological Tagging - Route exact programatic metadata tags to support tickets.
14
+ - id: hard
15
+ steps: 15
16
+ description: Syntactic Splintering - Break down the monolithic onboarding blob into multiple granular chunks.
pyproject.toml CHANGED
@@ -1,46 +1,46 @@
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-rag_optimizer"
13
- version = "0.1.0"
14
- description = "Rag Optimizer 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.2",
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
- "scikit-learn>=1.3.0",
30
- ]
31
-
32
- [project.optional-dependencies]
33
- dev = [
34
- "pytest>=8.0.0",
35
- "pytest-cov>=4.0.0",
36
- ]
37
-
38
- [project.scripts]
39
- # Server entry point - enables running via: uv run --project . server
40
- # or: python -m rag_optimizer.server.app
41
- server = "rag_optimizer.server.app:main"
42
-
43
- [tool.setuptools]
44
- include-package-data = true
45
- packages = ["rag_optimizer", "rag_optimizer.server"]
46
  package-dir = { "rag_optimizer" = ".", "rag_optimizer.server" = "server" }
 
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-rag_optimizer"
13
+ version = "0.1.0"
14
+ description = "Rag Optimizer 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.2",
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
+ "scikit-learn>=1.3.0",
30
+ ]
31
+
32
+ [project.optional-dependencies]
33
+ dev = [
34
+ "pytest>=8.0.0",
35
+ "pytest-cov>=4.0.0",
36
+ ]
37
+
38
+ [project.scripts]
39
+ # Server entry point - enables running via: uv run --project . server
40
+ # or: python -m rag_optimizer.server.app
41
+ server = "rag_optimizer.server.app:main"
42
+
43
+ [tool.setuptools]
44
+ include-package-data = true
45
+ packages = ["rag_optimizer", "rag_optimizer.server"]
46
  package-dir = { "rag_optimizer" = ".", "rag_optimizer.server" = "server" }
rag_optimizer_environment.py CHANGED
@@ -1,212 +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
 
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 CHANGED
@@ -1,180 +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"
 
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"
server/__init__.py CHANGED
@@ -1,11 +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
- """Rag Optimizer environment server components."""
8
-
9
- from .rag_optimizer_environment import RagOptimizerEnvironment
10
-
11
- __all__ = ["RagOptimizerEnvironment"]
 
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
+ """Rag Optimizer environment server components."""
8
+
9
+ from .rag_optimizer_environment import RagOptimizerEnvironment
10
+
11
+ __all__ = ["RagOptimizerEnvironment"]
server/app.py CHANGED
@@ -1,79 +1,79 @@
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 Rag Optimizer Environment.
9
-
10
- This module creates an HTTP server that exposes the RagOptimizerEnvironment
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
- try:
39
- from models import RagOptimizerAction, RagOptimizerObservation
40
- from .rag_optimizer_environment import RagOptimizerEnvironment
41
- except ModuleNotFoundError:
42
- from models import RagOptimizerAction, RagOptimizerObservation
43
- from server.rag_optimizer_environment import RagOptimizerEnvironment
44
-
45
-
46
- # Create the app with web interface and README integration
47
- app = create_app(
48
- RagOptimizerEnvironment,
49
- RagOptimizerAction,
50
- RagOptimizerObservation,
51
- env_name="rag_optimizer",
52
- max_concurrent_envs=1, # increase this number to allow more concurrent WebSocket sessions
53
- )
54
-
55
-
56
- def main(host: str = "0.0.0.0", port: int = 8002):
57
- """
58
- Entry point for direct execution via uv run or python -m.
59
-
60
- This function enables running the server without Docker:
61
- uv run --project . server
62
- uv run --project . server --port 8001
63
- python -m rag_optimizer.server.app
64
-
65
- Args:
66
- host: Host address to bind to (default: "0.0.0.0")
67
- port: Port number to listen on (default: 8000)
68
-
69
- For production deployments, consider using uvicorn directly with
70
- multiple workers:
71
- uvicorn rag_optimizer.server.app:app --workers 4
72
- """
73
- import uvicorn
74
-
75
- uvicorn.run(app, host=host, port=port)
76
-
77
-
78
- if __name__ == '__main__':
79
- main()
 
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 Rag Optimizer Environment.
9
+
10
+ This module creates an HTTP server that exposes the RagOptimizerEnvironment
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
+ try:
39
+ from models import RagOptimizerAction, RagOptimizerObservation
40
+ from .rag_optimizer_environment import RagOptimizerEnvironment
41
+ except ModuleNotFoundError:
42
+ from models import RagOptimizerAction, RagOptimizerObservation
43
+ from server.rag_optimizer_environment import RagOptimizerEnvironment
44
+
45
+
46
+ # Create the app with web interface and README integration
47
+ app = create_app(
48
+ RagOptimizerEnvironment,
49
+ RagOptimizerAction,
50
+ RagOptimizerObservation,
51
+ env_name="rag_optimizer",
52
+ max_concurrent_envs=1, # increase this number to allow more concurrent WebSocket sessions
53
+ )
54
+
55
+
56
+ def main(host: str = "0.0.0.0", port: int = 8002):
57
+ """
58
+ Entry point for direct execution via uv run or python -m.
59
+
60
+ This function enables running the server without Docker:
61
+ uv run --project . server
62
+ uv run --project . server --port 8001
63
+ python -m rag_optimizer.server.app
64
+
65
+ Args:
66
+ host: Host address to bind to (default: "0.0.0.0")
67
+ port: Port number to listen on (default: 8000)
68
+
69
+ For production deployments, consider using uvicorn directly with
70
+ multiple workers:
71
+ uvicorn rag_optimizer.server.app:app --workers 4
72
+ """
73
+ import uvicorn
74
+
75
+ uvicorn.run(app, host=host, port=port)
76
+
77
+
78
+ if __name__ == '__main__':
79
+ main()
server/rag_optimizer_environment.py CHANGED
@@ -1,214 +1,209 @@
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 copy import deepcopy
13
- from uuid import uuid4
14
- from typing import Dict, Any, List
15
-
16
- from openenv.core.env_server.interfaces import Environment
17
- from openenv.core.env_server.types import State
18
-
19
- # Import scikit-learn for our Grader
20
- from sklearn.feature_extraction.text import TfidfVectorizer
21
- from sklearn.metrics.pairwise import cosine_similarity
22
- import numpy as np
23
-
24
- try:
25
- from models import RagOptimizerAction, RagOptimizerObservation
26
- except ImportError:
27
- from models import RagOptimizerAction, RagOptimizerObservation
28
-
29
-
30
- class RagOptimizerEnvironment(Environment):
31
- """
32
- RAG Optimizer Engine.
33
- Maintains a simulated Knowledge Base and grades it using TF-IDF.
34
- """
35
-
36
- SUPPORTS_CONCURRENT_SESSIONS: bool = True
37
-
38
- @staticmethod
39
- def _build_initial_kb() -> Dict[str, Dict[str, Any]]:
40
- """Construct a fresh KB snapshot for each new episode/reset."""
41
- return {
42
- "doc_pricing_legacy": {
43
- "text": "Pricing for 2021: Enterprise tier is $1000/mo. Standard is $500/mo. All plans include 10 users.",
44
- "metadata": {"type": "pricing"}
45
- },
46
- "doc_pricing_current_v2": {
47
- "text": "Current Pricing 2024: Enterprise is $1500/mo. Standard is $750/mo. Refunds are not permitted on the enterprise tier.",
48
- "metadata": {}
49
- },
50
- "doc_shipping_policy": {
51
- "text": "All internal shipments to remote branch offices take 5-7 business days. Overnight shipping is only available for C-suite.",
52
- "metadata": {"department": "logistics"}
53
- },
54
- "doc_messy_support_ticket_1": {
55
- "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.",
56
- "metadata": {}
57
- },
58
- "doc_messy_support_ticket_2": {
59
- "text": "Email integration is failing with error 401 Unauthorized. The API key was rotated on Tuesday.",
60
- "metadata": {}
61
- },
62
- "doc_monolithic_onboarding": {
63
- "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.",
64
- "metadata": {}
65
- },
66
- # Add distractor files
67
- **{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)},
68
- **{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)},
69
- **{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)},
70
- }
71
-
72
- def __init__(self):
73
- self._state = State(episode_id=str(uuid4()), step_count=0)
74
- self.kb = deepcopy(self._build_initial_kb())
75
-
76
- # Hidden test suite for the grader
77
- self.test_suite = [
78
- {
79
- "query": "What is the current 2024 price for standard?",
80
- "target_concept": "750/mo"
81
- },
82
- {
83
- "query": "What is the refund policy for enterprise?",
84
- "target_concept": "Refunds are not permitted"
85
- },
86
- {
87
- "query": "UI issues frontend CSS missing button",
88
- "target_concept": "frontend team fixed the button"
89
- },
90
- {
91
- "query": "How long does shipping take to branch offices?",
92
- "target_concept": "5-7 business days"
93
- },
94
- {
95
- "query": "What months do parking passes need to be renewed?",
96
- "target_concept": "March"
97
- },
98
- {
99
- "query": "What holidays are we off in 2024?",
100
- "target_concept": "July 4"
101
- }
102
- ]
103
-
104
- def _get_kb_summary(self) -> Dict[str, Dict]:
105
- """Returns a summary of the KB for the observation."""
106
- summary = {}
107
- for k, v in self.kb.items():
108
- summary[k] = {"metadata": v.get("metadata", {}), "length": len(v.get("text", ""))}
109
- return summary
110
-
111
- def reset(self) -> RagOptimizerObservation:
112
- self._state = State(episode_id=str(uuid4()), step_count=0)
113
- self.kb = deepcopy(self._build_initial_kb())
114
- return RagOptimizerObservation(
115
- message="RagOptimizerEnv Initialized. You have messy chunks in the KB. Resolve conflicts, add metadata tags to short tickets, and splinter monolithic files to win.",
116
- current_docs=self._get_kb_summary(),
117
- done=False,
118
- reward=self._evaluate_kb()
119
- )
120
-
121
- def _evaluate_kb(self) -> float:
122
- """The Grader: Evaluates the agent's current KB using TF-IDF."""
123
- if not self.kb:
124
- return 0.0
125
-
126
- doc_texts = [doc["text"] for doc in self.kb.values()]
127
-
128
- vectorizer = TfidfVectorizer(stop_words='english')
129
- try:
130
- doc_vectors = vectorizer.fit_transform(doc_texts)
131
- except ValueError:
132
- return 0.0
133
-
134
- score = 0.0
135
-
136
- for case in self.test_suite:
137
- query_vec = vectorizer.transform([case["query"]])
138
- similarities = cosine_similarity(query_vec, doc_vectors)[0]
139
-
140
- # Get top 3
141
- top_k_indices = similarities.argsort()[-3:][::-1]
142
-
143
- found = False
144
- for idx in top_k_indices:
145
- if similarities[idx] > 0.01:
146
- if case["target_concept"].lower() in doc_texts[idx].lower():
147
- found = True
148
- break
149
- if found:
150
- score += 1.0
151
-
152
- return float(score / len(self.test_suite))
153
-
154
- def step(self, action: RagOptimizerAction) -> RagOptimizerObservation: # type: ignore[override]
155
- self._state.step_count += 1
156
-
157
- msg = ""
158
- done = False
159
- reward = 0.0
160
-
161
- try:
162
- if action.action_type == "read_document":
163
- if action.doc_id in self.kb:
164
- msg = f"Content of {action.doc_id}: {self.kb[action.doc_id]['text']}"
165
- else:
166
- msg = f"Error: doc_id {action.doc_id} not found."
167
-
168
- elif action.action_type == "delete_document":
169
- if action.doc_id in self.kb:
170
- del self.kb[action.doc_id]
171
- msg = f"Deleted {action.doc_id}."
172
- else:
173
- msg = f"Error: doc_id {action.doc_id} not found."
174
-
175
- elif action.action_type == "update_document":
176
- if not action.doc_id or not action.text:
177
- msg = "Error: doc_id and text required for update_document."
178
- else:
179
- if action.doc_id not in self.kb:
180
- self.kb[action.doc_id] = {"text": "", "metadata": {}}
181
- self.kb[action.doc_id]["text"] = action.text
182
- msg = f"Updated text for {action.doc_id}."
183
-
184
- elif action.action_type == "add_metadata":
185
- if not action.doc_id or not action.metadata_key or not action.metadata_value:
186
- msg = "Error: doc_id, metadata_key, and metadata_value required."
187
- else:
188
- if action.doc_id not in self.kb:
189
- msg = f"Error: doc_id {action.doc_id} not found."
190
- else:
191
- self.kb[action.doc_id]["metadata"][action.metadata_key] = action.metadata_value
192
- msg = f"Added metadata to {action.doc_id}."
193
-
194
- elif action.action_type == "submit":
195
- done = True
196
- reward = self._evaluate_kb()
197
- msg = f"Evaluation complete. Final reward: {reward:.2f}"
198
-
199
- except Exception as e:
200
- msg = f"Action failed: {str(e)}"
201
-
202
- if not done:
203
- reward = self._evaluate_kb()
204
-
205
- return RagOptimizerObservation(
206
- message=msg,
207
- current_docs=self._get_kb_summary(),
208
- done=done,
209
- reward=reward,
210
- )
211
-
212
- @property
213
- def state(self) -> State:
214
- return self._state
 
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 copy import deepcopy
13
+ from uuid import uuid4
14
+ from typing import Dict, Any, List
15
+
16
+ from openenv.core.env_server.interfaces import Environment
17
+ from openenv.core.env_server.types import State
18
+
19
+ # Import scikit-learn for our Grader
20
+ from sklearn.feature_extraction.text import TfidfVectorizer
21
+ from sklearn.metrics.pairwise import cosine_similarity
22
+ import numpy as np
23
+
24
+ try:
25
+ from models import RagOptimizerAction, RagOptimizerObservation
26
+ except ImportError:
27
+ from models import RagOptimizerAction, RagOptimizerObservation
28
+
29
+
30
+ class RagOptimizerEnvironment(Environment):
31
+ """
32
+ RAG Optimizer Engine.
33
+ Maintains a simulated Knowledge Base and grades it using TF-IDF.
34
+ """
35
+
36
+ SUPPORTS_CONCURRENT_SESSIONS: bool = True
37
+
38
+ def __init__(self):
39
+ self._state = State(episode_id=str(uuid4()), step_count=0)
40
+ self.kb = {}
41
+ self.test_suite = []
42
+ self._setup_task("easy")
43
+
44
+ def _setup_task(self, task_id: str):
45
+ if task_id == "easy":
46
+ self.kb = {
47
+ "doc_pricing_legacy": {
48
+ "text": "Pricing for 2021: Enterprise tier is $1000/mo. Standard is $500/mo. All plans include 10 users.",
49
+ "metadata": {"type": "pricing"}
50
+ },
51
+ "doc_pricing_current_v2": {
52
+ "text": "Current Pricing 2024: Enterprise is $1500/mo. Standard is $750/mo. Refunds are not permitted on the enterprise tier.",
53
+ "metadata": {}
54
+ },
55
+ **{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)}
56
+ }
57
+ self.test_suite = [
58
+ {"query": "What is the current 2024 price for standard?", "target_concept": "750/mo"},
59
+ {"query": "What is the refund policy for enterprise?", "target_concept": "Refunds are not permitted"}
60
+ ]
61
+ elif task_id == "medium":
62
+ self.kb = {
63
+ "doc_messy_support_ticket_1": {
64
+ "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.",
65
+ "metadata": {}
66
+ },
67
+ "doc_messy_support_ticket_2": {
68
+ "text": "Email integration is failing with error 401 Unauthorized. The API key was rotated on Tuesday.",
69
+ "metadata": {}
70
+ },
71
+ **{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)}
72
+ }
73
+ self.test_suite = [
74
+ {"query": "UI issues frontend CSS missing button", "target_concept": "frontend team fixed the button"},
75
+ {"query": "Email integration 401", "target_concept": "API key was rotated"}
76
+ ]
77
+ elif task_id == "hard":
78
+ self.kb = {
79
+ "doc_shipping_policy": {
80
+ "text": "All internal shipments to remote branch offices take 5-7 business days. Overnight shipping is only available for C-suite.",
81
+ "metadata": {"department": "logistics"}
82
+ },
83
+ "doc_monolithic_onboarding": {
84
+ "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.",
85
+ "metadata": {}
86
+ },
87
+ **{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)}
88
+ }
89
+ self.test_suite = [
90
+ {"query": "How long does shipping take to branch offices?", "target_concept": "5-7 business days"},
91
+ {"query": "What months do parking passes need to be renewed?", "target_concept": "March"},
92
+ {"query": "What holidays are we off in 2024?", "target_concept": "July 4"}
93
+ ]
94
+ else:
95
+ self._setup_task("easy")
96
+
97
+ def _get_kb_summary(self) -> Dict[str, Dict]:
98
+ """Returns a summary of the KB for the observation."""
99
+ summary = {}
100
+ for k, v in self.kb.items():
101
+ summary[k] = {"metadata": v.get("metadata", {}), "length": len(v.get("text", ""))}
102
+ return summary
103
+
104
+ def reset(self, **kwargs) -> RagOptimizerObservation:
105
+ self._state = State(episode_id=str(uuid4()), step_count=0)
106
+ task_id = kwargs.get("task_id") or kwargs.get("task") or "easy"
107
+ self._setup_task(task_id)
108
+
109
+ return RagOptimizerObservation(
110
+ message=f"RagOptimizerEnv Initialized for task: {task_id}. Resolve conflicts, append metadata, or splinter chunks to win.",
111
+ current_docs=self._get_kb_summary(),
112
+ done=False,
113
+ reward=self._evaluate_kb()
114
+ )
115
+
116
+ def _evaluate_kb(self) -> float:
117
+ """The Grader: Evaluates the agent's current KB using TF-IDF."""
118
+ if not self.kb:
119
+ return 0.0
120
+
121
+ doc_texts = [doc["text"] for doc in self.kb.values()]
122
+
123
+ vectorizer = TfidfVectorizer(stop_words='english')
124
+ try:
125
+ doc_vectors = vectorizer.fit_transform(doc_texts)
126
+ except ValueError:
127
+ return 0.0
128
+
129
+ score = 0.0
130
+
131
+ for case in self.test_suite:
132
+ query_vec = vectorizer.transform([case["query"]])
133
+ similarities = cosine_similarity(query_vec, doc_vectors)[0]
134
+
135
+ # Get top 3
136
+ top_k_indices = similarities.argsort()[-3:][::-1]
137
+
138
+ found = False
139
+ for idx in top_k_indices:
140
+ if similarities[idx] > 0.01:
141
+ if case["target_concept"].lower() in doc_texts[idx].lower():
142
+ found = True
143
+ break
144
+ if found:
145
+ score += 1.0
146
+
147
+ return float(score / len(self.test_suite))
148
+
149
+ def step(self, action: RagOptimizerAction) -> RagOptimizerObservation: # type: ignore[override]
150
+ self._state.step_count += 1
151
+
152
+ msg = ""
153
+ done = False
154
+ reward = 0.0
155
+
156
+ try:
157
+ if action.action_type == "read_document":
158
+ if action.doc_id in self.kb:
159
+ msg = f"Content of {action.doc_id}: {self.kb[action.doc_id]['text']}"
160
+ else:
161
+ msg = f"Error: doc_id {action.doc_id} not found."
162
+
163
+ elif action.action_type == "delete_document":
164
+ if action.doc_id in self.kb:
165
+ del self.kb[action.doc_id]
166
+ msg = f"Deleted {action.doc_id}."
167
+ else:
168
+ msg = f"Error: doc_id {action.doc_id} not found."
169
+
170
+ elif action.action_type == "update_document":
171
+ if not action.doc_id or not action.text:
172
+ msg = "Error: doc_id and text required for update_document."
173
+ else:
174
+ if action.doc_id not in self.kb:
175
+ self.kb[action.doc_id] = {"text": "", "metadata": {}}
176
+ self.kb[action.doc_id]["text"] = action.text
177
+ msg = f"Updated text for {action.doc_id}."
178
+
179
+ elif action.action_type == "add_metadata":
180
+ if not action.doc_id or not action.metadata_key or not action.metadata_value:
181
+ msg = "Error: doc_id, metadata_key, and metadata_value required."
182
+ else:
183
+ if action.doc_id not in self.kb:
184
+ msg = f"Error: doc_id {action.doc_id} not found."
185
+ else:
186
+ self.kb[action.doc_id]["metadata"][action.metadata_key] = action.metadata_value
187
+ msg = f"Added metadata to {action.doc_id}."
188
+
189
+ elif action.action_type == "submit":
190
+ done = True
191
+ reward = self._evaluate_kb()
192
+ msg = f"Evaluation complete. Final reward: {reward:.2f}"
193
+
194
+ except Exception as e:
195
+ msg = f"Action failed: {str(e)}"
196
+
197
+ if not done:
198
+ reward = self._evaluate_kb()
199
+
200
+ return RagOptimizerObservation(
201
+ message=msg,
202
+ current_docs=self._get_kb_summary(),
203
+ done=done,
204
+ reward=reward,
205
+ )
206
+
207
+ @property
208
+ def state(self) -> State:
209
+ return self._state
 
 
 
 
 
server/requirements.txt CHANGED
@@ -1,4 +1,4 @@
1
- openenv[core]>=0.2.0
2
- fastapi>=0.115.0
3
- uvicorn>=0.24.0
4
- scikit-learn>=1.3.0
 
1
+ openenv[core]>=0.2.0
2
+ fastapi>=0.115.0
3
+ uvicorn>=0.24.0
4
+ scikit-learn>=1.3.0
uv.lock CHANGED
The diff for this file is too large to render. See raw diff