Spaces:
Sleeping
Sleeping
Upload folder using huggingface_hub
Browse files- README.md +33 -6
- assets/Architecture_diagram.jpeg +0 -0
- inference.py +47 -2
- models.py +1 -1
- openenv.yaml +11 -5
- rag_optimizer_environment.py +4 -4
- server/app.py +1 -1
- server/kb_seed.json +161 -0
- server/rag_optimizer_environment.py +122 -62
- server/requirements.txt +2 -0
README.md
CHANGED
|
@@ -43,7 +43,7 @@ RagOptimizerEnv simulates the critical and computationally intensive role of an
|
|
| 43 |
|
| 44 |
<br>
|
| 45 |
<p align="center">
|
| 46 |
-
<img src="assets/Architecture_diagram.
|
| 47 |
</p>
|
| 48 |
<br>
|
| 49 |
|
|
@@ -109,7 +109,7 @@ Agents manipulate the embedding search space via a strictly defined remote schem
|
|
| 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.
|
| 113 |
|
| 114 |
---
|
| 115 |
|
|
@@ -125,6 +125,33 @@ Evaluation is robust, empirical, and mathematically bounded:
|
|
| 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
|
|
@@ -135,9 +162,9 @@ The environment tests agents across three progressively demanding task distribut
|
|
| 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:
|
| 139 |
-
**The Vector Issue:**
|
| 140 |
-
**System Goal:**
|
| 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.
|
|
@@ -156,7 +183,7 @@ Telemetry formats strictly adhere to high-velocity logging required for large-sc
|
|
| 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=
|
| 160 |
```
|
| 161 |
|
| 162 |
### Execution
|
|
|
|
| 43 |
|
| 44 |
<br>
|
| 45 |
<p align="center">
|
| 46 |
+
<img src="assets/Architecture_diagram.jpeg" width="800" alt="System Architecture Diagram" />
|
| 47 |
</p>
|
| 48 |
<br>
|
| 49 |
|
|
|
|
| 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.01` and `0.99`.
|
| 113 |
|
| 114 |
---
|
| 115 |
|
|
|
|
| 125 |
|
| 126 |
**Reward Yield:** `(Successful Vectors / Total Vector Payload)` providing high-density intermediate signals mapping continuously toward the `±1.0` upper bound.
|
| 127 |
|
| 128 |
+
### Practical RAG FAQ (Important Clarifications)
|
| 129 |
+
|
| 130 |
+
These are fantastic questions, and they cut right to the core of how real-world RAG systems operate.
|
| 131 |
+
|
| 132 |
+
#### 1. If we just send the KB summary, is it just metadata optimization?
|
| 133 |
+
Not exactly. While the agent sees the summary by default, it also has the `read_document` action.
|
| 134 |
+
|
| 135 |
+
- When the agent uses `read_document("doc_monolithic")`, the environment returns the entire raw text of that document in the `message` field of the next observation.
|
| 136 |
+
- The agent can read the text, identify that it is noisy or overloaded across multiple topics, and then use `update_document` to split or rewrite content into cleaner chunks.
|
| 137 |
+
- This means the benchmark supports true content optimization, not just metadata operations.
|
| 138 |
+
|
| 139 |
+
#### 2. Doesn't sending the entire KB to the embedding model blow up its context window?
|
| 140 |
+
No. The grader does not send the entire KB as one giant prompt.
|
| 141 |
+
|
| 142 |
+
- Embedding models operate per document, not as one monolithic concatenated input.
|
| 143 |
+
- The environment encodes each document individually into vectors, then stores those vectors in an index-like structure for retrieval scoring.
|
| 144 |
+
- Query-time evaluation compares a query vector against document vectors. It does not require processing the full KB in one context window.
|
| 145 |
+
|
| 146 |
+
#### 3. Is the result just fetching the right document, not the exact answer?
|
| 147 |
+
Exactly. This environment evaluates retrieval quality, the "R" in RAG.
|
| 148 |
+
|
| 149 |
+
- If retrieval is wrong, generation quality collapses and hallucination risk rises.
|
| 150 |
+
- The grader checks whether documents containing the target concept are ranked near the top for each control query.
|
| 151 |
+
- Optimizing document quality, structure, and noise levels pushes the clean source document toward rank 1, enabling downstream generators to answer correctly.
|
| 152 |
+
|
| 153 |
+
In practice, this environment trains an agent to behave like a retrieval-focused KB operator: maintain high signal quality so the retriever does not fail under noisy enterprise conditions.
|
| 154 |
+
|
| 155 |
---
|
| 156 |
|
| 157 |
## 4. Curriculum Topologies
|
|
|
|
| 162 |
**The Vector Issue:** The base contains heavily overlapping parameters (competing versions of legacy and modern timeline protocols).
|
| 163 |
**System Goal:** Autonomously survey the semantic differences, deduce the temporal conflict, and execute `delete_document` sweeps to purge vector hallucination triggers.
|
| 164 |
|
| 165 |
+
### Level II: Signal Separation
|
| 166 |
+
**The Vector Issue:** The KB contains overlapping incident narratives mixed with distractor engineering notes, causing retrieval ambiguity.
|
| 167 |
+
**System Goal:** Deduplicate noisy/partial incident content so retrieval consistently surfaces the clean resolution document at rank 1.
|
| 168 |
|
| 169 |
### Level III: Syntactic Splintering (The Monolith)
|
| 170 |
**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.
|
|
|
|
| 183 |
[STEP] step=1 action=read('doc_monolithic_onboarding') reward=0.33 done=false error=null
|
| 184 |
[STEP] step=2 action=update('doc_vpn_policy') reward=0.50 done=false error=null
|
| 185 |
...
|
| 186 |
+
[END] success=true steps=12 score=0.99 rewards=0.33,0.50,...
|
| 187 |
```
|
| 188 |
|
| 189 |
### Execution
|
assets/Architecture_diagram.jpeg
ADDED
|
inference.py
CHANGED
|
@@ -41,7 +41,7 @@ SYSTEM_PROMPT = """You are an automated Data Engineer managing an AI Knowledge B
|
|
| 41 |
Your goal is to optimize the messy chunks of text in the database so that a TF-IDF Search Algorithm can find answers easily.
|
| 42 |
You must resolve contradictions, categorize documents, and delete unnecessary documents.
|
| 43 |
|
| 44 |
-
After each action you will receive a "current_reward" score (0.
|
| 45 |
|
| 46 |
You have the following actions:
|
| 47 |
- {"action_type": "read_document", "doc_id": "..."}
|
|
@@ -65,6 +65,34 @@ def format_action_str(action: RagOptimizerAction) -> str:
|
|
| 65 |
return "submit()"
|
| 66 |
return f"{action.action_type}()"
|
| 67 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
|
| 69 |
def _safe_reset(env: RagOptimizerEnvClient, task_id: str):
|
| 70 |
"""Reset env for a specific task with compatibility fallbacks."""
|
|
@@ -111,7 +139,7 @@ def run_task_episode(
|
|
| 111 |
print(f"[END] success=false steps=0 score=0.01 rewards=")
|
| 112 |
return
|
| 113 |
|
| 114 |
-
history = [{"role": "system", "content":
|
| 115 |
|
| 116 |
init_obs = {
|
| 117 |
"server_feedback": observation.message,
|
|
@@ -194,6 +222,23 @@ def run_task_episode(
|
|
| 194 |
done_str = "true" if success else "false"
|
| 195 |
print(f"[END] success={done_str} steps={step} score={score:.2f} rewards={rewards_str}")
|
| 196 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 197 |
def main():
|
| 198 |
# Setup OpenAI Client
|
| 199 |
client = OpenAI(base_url=API_BASE_URL, api_key=HF_TOKEN)
|
|
|
|
| 41 |
Your goal is to optimize the messy chunks of text in the database so that a TF-IDF Search Algorithm can find answers easily.
|
| 42 |
You must resolve contradictions, categorize documents, and delete unnecessary documents.
|
| 43 |
|
| 44 |
+
After each action you will receive a "current_reward" score (0.01 to 0.99) indicating how well the KB currently performs. Use this to guide your strategy.
|
| 45 |
|
| 46 |
You have the following actions:
|
| 47 |
- {"action_type": "read_document", "doc_id": "..."}
|
|
|
|
| 65 |
return "submit()"
|
| 66 |
return f"{action.action_type}()"
|
| 67 |
|
| 68 |
+
# --- Reflexion (Long Term Memory) ---
|
| 69 |
+
LESSONS_FILE = os.path.join(os.path.dirname(__file__), "memory", "lessons_learned.json")
|
| 70 |
+
|
| 71 |
+
def load_lessons():
|
| 72 |
+
if os.path.exists(LESSONS_FILE):
|
| 73 |
+
try:
|
| 74 |
+
with open(LESSONS_FILE, "r") as f:
|
| 75 |
+
return json.load(f)
|
| 76 |
+
except:
|
| 77 |
+
pass
|
| 78 |
+
return []
|
| 79 |
+
|
| 80 |
+
def save_lesson(lesson_text, task_id):
|
| 81 |
+
os.makedirs(os.path.dirname(LESSONS_FILE), exist_ok=True)
|
| 82 |
+
lessons = load_lessons()
|
| 83 |
+
lessons.append({"task": task_id, "lesson": lesson_text})
|
| 84 |
+
with open(LESSONS_FILE, "w") as f:
|
| 85 |
+
json.dump(lessons, f, indent=2)
|
| 86 |
+
|
| 87 |
+
def get_system_prompt():
|
| 88 |
+
prompt = SYSTEM_PROMPT
|
| 89 |
+
lessons = load_lessons()
|
| 90 |
+
if lessons:
|
| 91 |
+
prompt += "\n\nPAST LESSONS LEARNED (DO NOT REPEAT MISTAKES):\n"
|
| 92 |
+
for l in lessons[-5:]: # Show only top 5 recent
|
| 93 |
+
prompt += f"- {l['lesson']}\n"
|
| 94 |
+
return prompt
|
| 95 |
+
|
| 96 |
|
| 97 |
def _safe_reset(env: RagOptimizerEnvClient, task_id: str):
|
| 98 |
"""Reset env for a specific task with compatibility fallbacks."""
|
|
|
|
| 139 |
print(f"[END] success=false steps=0 score=0.01 rewards=")
|
| 140 |
return
|
| 141 |
|
| 142 |
+
history = [{"role": "system", "content": get_system_prompt()}]
|
| 143 |
|
| 144 |
init_obs = {
|
| 145 |
"server_feedback": observation.message,
|
|
|
|
| 222 |
done_str = "true" if success else "false"
|
| 223 |
print(f"[END] success={done_str} steps={step} score={score:.2f} rewards={rewards_str}")
|
| 224 |
|
| 225 |
+
# Memory Reflexion Trigger
|
| 226 |
+
if not success and score < 0.6:
|
| 227 |
+
# Agent failed, try to reflect
|
| 228 |
+
hist_str = json.dumps([m["content"] for m in history[-6:]]) # get last few actions/obs
|
| 229 |
+
ref_prompt = f"The agent failed task '{task_id}' with final reward {score}. Last context: {hist_str}. Write a 1-sentence tactical lesson stating explicitly what data engineering action the agent should have done instead."
|
| 230 |
+
try:
|
| 231 |
+
resp = llm_client.chat.completions.create(
|
| 232 |
+
model=MODEL_NAME,
|
| 233 |
+
messages=[{"role": "user", "content": ref_prompt}],
|
| 234 |
+
max_tokens=60
|
| 235 |
+
)
|
| 236 |
+
lesson = resp.choices[0].message.content.strip()
|
| 237 |
+
save_lesson(lesson, task_id)
|
| 238 |
+
print(f"[MEMORY] Learned lesson: {lesson}")
|
| 239 |
+
except:
|
| 240 |
+
pass
|
| 241 |
+
|
| 242 |
def main():
|
| 243 |
# Setup OpenAI Client
|
| 244 |
client = OpenAI(base_url=API_BASE_URL, api_key=HF_TOKEN)
|
models.py
CHANGED
|
@@ -58,5 +58,5 @@ class RagOptimizerObservation(BaseModel):
|
|
| 58 |
|
| 59 |
# Required OpenEnv standard fields
|
| 60 |
done: bool = Field(False, description="Whether the episode has finished.")
|
| 61 |
-
reward: float = Field(0.
|
| 62 |
metadata: Dict = Field(default_factory=dict, description="Additional optional information.")
|
|
|
|
| 58 |
|
| 59 |
# Required OpenEnv standard fields
|
| 60 |
done: bool = Field(False, description="Whether the episode has finished.")
|
| 61 |
+
reward: float = Field(0.01, description="The reward obtained from the last step.")
|
| 62 |
metadata: Dict = Field(default_factory=dict, description="Additional optional information.")
|
openenv.yaml
CHANGED
|
@@ -13,17 +13,23 @@ tasks:
|
|
| 13 |
- id: easy
|
| 14 |
name: "Conflict Resolution"
|
| 15 |
description: "Resolve overlapping pricing parameters by deleting the legacy pricing format."
|
| 16 |
-
reward_range: [0.
|
| 17 |
grader: "rag_optimizer.server.grader:grade_easy"
|
| 18 |
- id: medium
|
| 19 |
-
name: "
|
| 20 |
-
description:
|
| 21 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
grader: "rag_optimizer.server.grader:grade_medium"
|
| 23 |
- id: hard
|
| 24 |
name: "Syntactic Splintering"
|
| 25 |
description: "Break down the monolithic onboarding blob into multiple granular chunks."
|
| 26 |
-
reward_range: [0.
|
| 27 |
grader: "rag_optimizer.server.grader:grade_hard"
|
| 28 |
agent:
|
| 29 |
inference_entrypoint: "inference.py"
|
|
|
|
| 13 |
- id: easy
|
| 14 |
name: "Conflict Resolution"
|
| 15 |
description: "Resolve overlapping pricing parameters by deleting the legacy pricing format."
|
| 16 |
+
reward_range: [0.01, 0.99]
|
| 17 |
grader: "rag_optimizer.server.grader:grade_easy"
|
| 18 |
- id: medium
|
| 19 |
+
name: "Signal Separation"
|
| 20 |
+
description: >
|
| 21 |
+
Multi-doc deduplication. KB contains 3 overlapping incident reports
|
| 22 |
+
for the same outage - one raw, one partial, one with the correct
|
| 23 |
+
resolution - plus 20 engineering distractors. Agent must delete the
|
| 24 |
+
noisy duplicates so retrieval surfaces the clean resolution doc at
|
| 25 |
+
rank 1. Reward improves continuously as noise is removed; no binary
|
| 26 |
+
metadata gate.
|
| 27 |
+
reward_range: [0.01, 0.99]
|
| 28 |
grader: "rag_optimizer.server.grader:grade_medium"
|
| 29 |
- id: hard
|
| 30 |
name: "Syntactic Splintering"
|
| 31 |
description: "Break down the monolithic onboarding blob into multiple granular chunks."
|
| 32 |
+
reward_range: [0.01, 0.99]
|
| 33 |
grader: "rag_optimizer.server.grader:grade_hard"
|
| 34 |
agent:
|
| 35 |
inference_entrypoint: "inference.py"
|
rag_optimizer_environment.py
CHANGED
|
@@ -119,7 +119,7 @@ class RagOptimizerEnvironment(Environment):
|
|
| 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.
|
| 123 |
|
| 124 |
doc_texts = [doc["text"] for doc in self.kb.values()]
|
| 125 |
|
|
@@ -127,7 +127,7 @@ class RagOptimizerEnvironment(Environment):
|
|
| 127 |
try:
|
| 128 |
doc_vectors = vectorizer.fit_transform(doc_texts)
|
| 129 |
except ValueError:
|
| 130 |
-
return 0.
|
| 131 |
|
| 132 |
score = 0.0
|
| 133 |
|
|
@@ -147,14 +147,14 @@ class RagOptimizerEnvironment(Environment):
|
|
| 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.
|
| 158 |
|
| 159 |
try:
|
| 160 |
if action.action_type == "read_document":
|
|
|
|
| 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.01
|
| 123 |
|
| 124 |
doc_texts = [doc["text"] for doc in self.kb.values()]
|
| 125 |
|
|
|
|
| 127 |
try:
|
| 128 |
doc_vectors = vectorizer.fit_transform(doc_texts)
|
| 129 |
except ValueError:
|
| 130 |
+
return 0.01
|
| 131 |
|
| 132 |
score = 0.0
|
| 133 |
|
|
|
|
| 147 |
if found:
|
| 148 |
score += 1.0
|
| 149 |
|
| 150 |
+
return max(0.01, min(0.99, 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.01
|
| 158 |
|
| 159 |
try:
|
| 160 |
if action.action_type == "read_document":
|
server/app.py
CHANGED
|
@@ -53,7 +53,7 @@ app = create_app(
|
|
| 53 |
)
|
| 54 |
|
| 55 |
|
| 56 |
-
def main(host: str = "0.0.0.0", port: int =
|
| 57 |
"""
|
| 58 |
Entry point for direct execution via uv run or python -m.
|
| 59 |
|
|
|
|
| 53 |
)
|
| 54 |
|
| 55 |
|
| 56 |
+
def main(host: str = "0.0.0.0", port: int = 8000):
|
| 57 |
"""
|
| 58 |
Entry point for direct execution via uv run or python -m.
|
| 59 |
|
server/kb_seed.json
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"easy": {
|
| 3 |
+
"fastapi_1": {
|
| 4 |
+
"text": "FastAPI is a modern Python web framework designed for building APIs with high performance. It leverages Python type hints and integrates tightly with validation libraries. One of its key strengths is that it defaults to Pydantic v2, enabling efficient data parsing and validation with minimal boilerplate. Developers can define request and response models clearly, ensuring correctness. FastAPI also supports asynchronous programming using async and await, making it ideal for I/O-bound services. Its automatic OpenAPI schema generation provides interactive API documentation out of the box, improving developer productivity.",
|
| 5 |
+
"metadata": {
|
| 6 |
+
"topic": "fastapi",
|
| 7 |
+
"type": "useful"
|
| 8 |
+
}
|
| 9 |
+
},
|
| 10 |
+
"fastapi_2": {
|
| 11 |
+
"text": "FastAPI includes a powerful dependency injection system that allows developers to manage shared logic such as authentication, database sessions, and configuration in a clean and reusable way. Dependencies can be declared at different levels, including routes and entire applications. This feature reduces duplication and improves maintainability. The framework also supports async database calls, ensuring efficient resource usage. By combining dependency injection with type hints, FastAPI ensures that applications remain scalable and structured, making it a preferred choice for production-grade microservices.",
|
| 12 |
+
"metadata": {
|
| 13 |
+
"topic": "fastapi"
|
| 14 |
+
}
|
| 15 |
+
},
|
| 16 |
+
"fastapi_3": {
|
| 17 |
+
"text": "FastAPI supports WebSocket communication, enabling real-time features such as chat applications, live dashboards, and streaming data pipelines. Unlike traditional HTTP request-response cycles, WebSockets maintain persistent connections. FastAPI provides simple decorators to define WebSocket endpoints and manage connections efficiently. Combined with async support, this allows handling thousands of concurrent connections. Developers can integrate WebSockets alongside REST endpoints in the same application, making FastAPI highly versatile for both synchronous and real-time applications.",
|
| 18 |
+
"metadata": {
|
| 19 |
+
"topic": "fastapi"
|
| 20 |
+
}
|
| 21 |
+
},
|
| 22 |
+
"fastapi_4": {
|
| 23 |
+
"text": "Running FastAPI applications in production typically involves using ASGI servers such as Uvicorn. A common command used is uvicorn main:app --workers 4, which starts multiple worker processes for handling concurrent requests. This setup improves performance and scalability. FastAPI is designed around ASGI, allowing it to fully utilize asynchronous capabilities. With proper configuration, it can handle high throughput while maintaining low latency, making it suitable for APIs serving large-scale applications.",
|
| 24 |
+
"metadata": {
|
| 25 |
+
"topic": "fastapi"
|
| 26 |
+
}
|
| 27 |
+
},
|
| 28 |
+
"fastapi_5": {
|
| 29 |
+
"text": "FastAPI automatically generates interactive documentation using Swagger UI and ReDoc. This feature is powered by OpenAPI standards and provides developers with a convenient way to test endpoints directly from the browser. The documentation reflects the actual codebase, ensuring accuracy. By leveraging type annotations, FastAPI produces clear schemas for requests and responses. This reduces manual documentation effort and improves collaboration between frontend and backend teams.",
|
| 30 |
+
"metadata": {
|
| 31 |
+
"topic": "fastapi"
|
| 32 |
+
}
|
| 33 |
+
},
|
| 34 |
+
"fastapi_6": {
|
| 35 |
+
"text": "FastAPI emphasizes performance comparable to Node.js and Go by utilizing asynchronous programming and efficient serialization. It uses Starlette under the hood, which provides high-speed routing and middleware support. Developers can create middleware for logging, authentication, and caching. FastAPI also integrates seamlessly with ORMs and external services. Its performance benefits make it suitable for modern cloud-native applications.",
|
| 36 |
+
"metadata": {
|
| 37 |
+
"topic": "fastapi"
|
| 38 |
+
}
|
| 39 |
+
},
|
| 40 |
+
"fastapi_7": {
|
| 41 |
+
"text": "FastAPI simplifies request validation using Python type hints. When defining endpoints, developers specify expected input types, and FastAPI automatically validates incoming requests. Errors are returned in a structured format, helping developers debug quickly. This reduces the need for manual validation logic. Combined with automatic serialization, FastAPI ensures consistency and reliability across APIs.",
|
| 42 |
+
"metadata": {
|
| 43 |
+
"topic": "fastapi"
|
| 44 |
+
}
|
| 45 |
+
},
|
| 46 |
+
"fastapi_8": {
|
| 47 |
+
"text": "FastAPI integrates well with modern Python ecosystems, including async frameworks, ORMs, and testing tools. It supports dependency overrides, which are useful during testing. Developers can simulate database connections or authentication systems easily. This makes writing unit and integration tests straightforward. The framework encourages clean architecture and modular design.",
|
| 48 |
+
"metadata": {
|
| 49 |
+
"topic": "fastapi"
|
| 50 |
+
}
|
| 51 |
+
},
|
| 52 |
+
"fastapi_9": {
|
| 53 |
+
"text": "FastAPI allows developers to define background tasks that run after returning a response. This is useful for sending emails, logging events, or processing data asynchronously. The background task system is lightweight and easy to use. It helps improve response times while handling additional processing efficiently.",
|
| 54 |
+
"metadata": {
|
| 55 |
+
"topic": "fastapi"
|
| 56 |
+
}
|
| 57 |
+
},
|
| 58 |
+
"fastapi_10": {
|
| 59 |
+
"text": "FastAPI supports security features such as OAuth2, API keys, and JWT authentication. It provides built-in utilities for implementing authentication flows. These features integrate seamlessly with dependency injection, allowing secure endpoints to be defined easily. This makes FastAPI suitable for building secure APIs.",
|
| 60 |
+
"metadata": {
|
| 61 |
+
"topic": "fastapi"
|
| 62 |
+
}
|
| 63 |
+
},
|
| 64 |
+
"distractor_1": {
|
| 65 |
+
"text": "Django is a high-level Python web framework that follows the Model-View-Template pattern. It provides an ORM for database interactions and includes built-in features such as authentication, admin panels, and form handling. Django is known for its batteries-included philosophy, allowing developers to build full-stack applications quickly. It is widely used for monolithic applications and supports scalability through modular apps.",
|
| 66 |
+
"metadata": {
|
| 67 |
+
"topic": "distractor"
|
| 68 |
+
}
|
| 69 |
+
},
|
| 70 |
+
"distractor_2": {
|
| 71 |
+
"text": "Flask is a lightweight Python web framework designed for simplicity and flexibility. Unlike Django, it does not include many built-in components, giving developers more control. Flask uses extensions to add functionality such as database integration and authentication. It is ideal for small projects and microservices.",
|
| 72 |
+
"metadata": {
|
| 73 |
+
"topic": "distractor"
|
| 74 |
+
}
|
| 75 |
+
},
|
| 76 |
+
"distractor_3": {
|
| 77 |
+
"text": "Express.js is a popular web framework for Node.js that simplifies building server-side applications. It provides routing, middleware support, and integration with databases. Express is widely used for building REST APIs and full-stack applications using JavaScript. Its minimal design allows developers to structure applications as needed.",
|
| 78 |
+
"metadata": {
|
| 79 |
+
"topic": "distractor"
|
| 80 |
+
}
|
| 81 |
+
},
|
| 82 |
+
"distractor_4": {
|
| 83 |
+
"text": "React is a JavaScript library for building user interfaces. It uses a component-based architecture and virtual DOM for efficient rendering. React is commonly used for frontend development and can be integrated with backend APIs. It supports state management and hooks for managing component lifecycle.",
|
| 84 |
+
"metadata": {
|
| 85 |
+
"topic": "distractor"
|
| 86 |
+
}
|
| 87 |
+
},
|
| 88 |
+
"distractor_5": {
|
| 89 |
+
"text": "Ruby on Rails is a web application framework that emphasizes convention over configuration. It provides a full-stack solution with built-in tools for database management, routing, and views. Rails is known for rapid development and clean code structure.",
|
| 90 |
+
"metadata": {
|
| 91 |
+
"topic": "distractor"
|
| 92 |
+
}
|
| 93 |
+
},
|
| 94 |
+
"distractor_6": {
|
| 95 |
+
"text": "Angular is a frontend framework developed by Google. It uses TypeScript and provides tools for building large-scale applications. Angular includes dependency injection, routing, and state management.",
|
| 96 |
+
"metadata": {
|
| 97 |
+
"topic": "distractor"
|
| 98 |
+
}
|
| 99 |
+
},
|
| 100 |
+
"distractor_7": {
|
| 101 |
+
"text": "Laravel is a PHP framework that simplifies web development with features like routing, authentication, and database migrations. It is widely used for building scalable web applications.",
|
| 102 |
+
"metadata": {
|
| 103 |
+
"topic": "distractor"
|
| 104 |
+
}
|
| 105 |
+
},
|
| 106 |
+
"distractor_8": {
|
| 107 |
+
"text": "Spring Boot is a Java-based framework for building enterprise applications. It simplifies configuration and provides tools for building REST APIs and microservices.",
|
| 108 |
+
"metadata": {
|
| 109 |
+
"topic": "distractor"
|
| 110 |
+
}
|
| 111 |
+
},
|
| 112 |
+
"distractor_9": {
|
| 113 |
+
"text": "Vue.js is a progressive JavaScript framework for building user interfaces. It focuses on simplicity and flexibility, making it easy to integrate into projects.",
|
| 114 |
+
"metadata": {
|
| 115 |
+
"topic": "distractor"
|
| 116 |
+
}
|
| 117 |
+
},
|
| 118 |
+
"distractor_10": {
|
| 119 |
+
"text": "ASP.NET Core is a cross-platform framework for building web applications using C#. It supports MVC architecture and integrates with Microsoft technologies.",
|
| 120 |
+
"metadata": {
|
| 121 |
+
"topic": "distractor"
|
| 122 |
+
}
|
| 123 |
+
}
|
| 124 |
+
},
|
| 125 |
+
"medium": {
|
| 126 |
+
"falcon9": {
|
| 127 |
+
"text": "Falcon 9 is a reusable rocket developed by SpaceX for transporting payloads to orbit. It uses SpaceX Merlin engines, which are designed for efficiency and reusability. The first stage can land vertically after launch, significantly reducing costs. Falcon 9 has been used for satellite deployments, cargo missions, and crewed flights to the International Space Station. Its reliability and cost-effectiveness have made it a cornerstone of modern space exploration.",
|
| 128 |
+
"metadata": {}
|
| 129 |
+
},
|
| 130 |
+
"dragon": {
|
| 131 |
+
"text": "The Dragon spacecraft developed by SpaceX is used for cargo and crew transport. The original Dragon capsule was replaced by Dragon 2, which includes advanced life-support systems and autonomous docking capabilities. Dragon has been used extensively for resupply missions to the International Space Station and has enabled private spaceflight.",
|
| 132 |
+
"metadata": {}
|
| 133 |
+
},
|
| 134 |
+
"starship": {
|
| 135 |
+
"text": "Starship is a fully reusable spacecraft designed by SpaceX for deep space missions. It aims to transport humans to Mars and beyond. The system includes a booster and spacecraft, both reusable. Starship is powered by Raptor engines and is designed for rapid turnaround.",
|
| 136 |
+
"metadata": {}
|
| 137 |
+
},
|
| 138 |
+
"distractor_1": {
|
| 139 |
+
"text": "Deep sea exploration involves studying ocean depths using submersibles and remote-operated vehicles. Scientists investigate marine ecosystems and geological formations.",
|
| 140 |
+
"metadata": {}
|
| 141 |
+
}
|
| 142 |
+
},
|
| 143 |
+
"hard": {
|
| 144 |
+
"crisis_monolith": {
|
| 145 |
+
"text": "The 2008 financial crisis was one of the most severe economic downturns since the Great Depression. It was triggered by the collapse of the housing market and widespread use of Mortgage-backed securities. Financial institutions had heavily invested in subprime mortgages, which were loans given to low-income homebuyers with poor credit histories. As housing prices declined, these borrowers began defaulting, leading to massive losses. The bankruptcy of Lehman Brothers marked a critical moment, causing panic across global markets. Complex financial instruments such as CDOs amplified risk, spreading it throughout the system. Governments intervened with bailouts, including the TARP program, to stabilize banks. Central banks implemented quantitative easing to inject liquidity into the economy. The crisis exposed weaknesses in financial regulation and led to reforms such as the Dodd-Frank Act. It also highlighted the dangers of excessive leverage and lack of transparency in financial markets.",
|
| 146 |
+
"metadata": {}
|
| 147 |
+
},
|
| 148 |
+
"tarp": {
|
| 149 |
+
"text": "The Troubled Asset Relief Program was introduced to stabilize financial institutions during the crisis. It allowed the government to purchase toxic assets and inject capital into banks.",
|
| 150 |
+
"metadata": {}
|
| 151 |
+
},
|
| 152 |
+
"qe": {
|
| 153 |
+
"text": "Quantitative easing was used by central banks to increase liquidity by purchasing government securities and lowering interest rates.",
|
| 154 |
+
"metadata": {}
|
| 155 |
+
},
|
| 156 |
+
"noise_1": {
|
| 157 |
+
"text": "During the 1920s, financial markets experienced rapid growth followed by a crash. Speculative investments and excessive leverage contributed to economic instability.",
|
| 158 |
+
"metadata": {}
|
| 159 |
+
}
|
| 160 |
+
}
|
| 161 |
+
}
|
server/rag_optimizer_environment.py
CHANGED
|
@@ -16,11 +16,15 @@ from typing import Dict, Any, List
|
|
| 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:
|
|
@@ -37,62 +41,81 @@ class RagOptimizerEnvironment(Environment):
|
|
| 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": "
|
| 59 |
-
{"query": "
|
| 60 |
]
|
|
|
|
| 61 |
elif task_id == "medium":
|
| 62 |
self.kb = {
|
| 63 |
-
"
|
| 64 |
-
"text": "User
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
"metadata": {}
|
| 66 |
},
|
| 67 |
-
"
|
| 68 |
-
"text": "
|
| 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": "
|
| 75 |
-
|
|
|
|
|
|
|
| 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": "
|
| 91 |
-
{"query": "What
|
| 92 |
-
{"query": "
|
| 93 |
]
|
| 94 |
-
|
| 95 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
|
| 97 |
def _get_kb_summary(self) -> Dict[str, Dict]:
|
| 98 |
"""Returns a summary of the KB for the observation."""
|
|
@@ -114,44 +137,78 @@ class RagOptimizerEnvironment(Environment):
|
|
| 114 |
)
|
| 115 |
|
| 116 |
def _evaluate_kb(self) -> float:
|
| 117 |
-
"""The Grader: Evaluates the
|
| 118 |
-
if not self.kb:
|
| 119 |
-
return 0.
|
| 120 |
|
| 121 |
-
|
|
|
|
| 122 |
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
|
|
|
| 130 |
|
| 131 |
for case in self.test_suite:
|
| 132 |
-
|
| 133 |
-
|
|
|
|
|
|
|
| 134 |
|
| 135 |
-
#
|
| 136 |
-
|
|
|
|
|
|
|
| 137 |
|
| 138 |
-
|
| 139 |
-
for
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
score += 1.0
|
| 146 |
|
| 147 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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.
|
| 155 |
|
| 156 |
try:
|
| 157 |
if action.action_type == "read_document":
|
|
@@ -163,6 +220,7 @@ class RagOptimizerEnvironment(Environment):
|
|
| 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."
|
|
@@ -174,6 +232,7 @@ class RagOptimizerEnvironment(Environment):
|
|
| 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":
|
|
@@ -184,6 +243,7 @@ class RagOptimizerEnvironment(Environment):
|
|
| 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":
|
|
|
|
| 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 (fallback/legacy)
|
| 20 |
from sklearn.feature_extraction.text import TfidfVectorizer
|
| 21 |
from sklearn.metrics.pairwise import cosine_similarity
|
| 22 |
import numpy as np
|
| 23 |
|
| 24 |
+
# Hybrid Search imports
|
| 25 |
+
from sentence_transformers import SentenceTransformer
|
| 26 |
+
from rank_bm25 import BM25Okapi
|
| 27 |
+
|
| 28 |
try:
|
| 29 |
from models import RagOptimizerAction, RagOptimizerObservation
|
| 30 |
except ImportError:
|
|
|
|
| 41 |
|
| 42 |
def __init__(self):
|
| 43 |
self._state = State(episode_id=str(uuid4()), step_count=0)
|
| 44 |
+
self.embedding_model = SentenceTransformer('all-MiniLM-L6-v2')
|
| 45 |
self.kb = {}
|
| 46 |
self.test_suite = []
|
| 47 |
+
self.dense_vectors = {}
|
| 48 |
+
|
| 49 |
+
# Load the Real World Datasets
|
| 50 |
+
import json
|
| 51 |
+
import os
|
| 52 |
+
seed_path = os.path.join(os.path.dirname(__file__), "kb_seed.json")
|
| 53 |
+
with open(seed_path, "r") as f:
|
| 54 |
+
self.seed_data = json.load(f)
|
| 55 |
+
|
| 56 |
self._setup_task("easy")
|
| 57 |
|
| 58 |
def _setup_task(self, task_id: str):
|
| 59 |
+
if task_id not in ["easy", "medium", "hard"]:
|
| 60 |
+
task_id = "easy"
|
| 61 |
+
|
| 62 |
+
# Clone the fresh dataset from seed so the agent can destroy it
|
| 63 |
+
self.kb = deepcopy(self.seed_data[task_id])
|
| 64 |
+
|
| 65 |
if task_id == "easy":
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
self.test_suite = [
|
| 67 |
+
{"query": "How do I run the server in production with concurrency?", "target_concept": "uvicorn main:app --workers 4"},
|
| 68 |
+
{"query": "Which version of Pydantic does FastAPI use by default?", "target_concept": "defaults to Pydantic v2"}
|
| 69 |
]
|
| 70 |
+
|
| 71 |
elif task_id == "medium":
|
| 72 |
self.kb = {
|
| 73 |
+
"doc_incident_raw": {
|
| 74 |
+
"text": "User reported frontend button missing. Database latency also flagged. No fix yet.",
|
| 75 |
+
"metadata": {}
|
| 76 |
+
},
|
| 77 |
+
"doc_incident_partial": {
|
| 78 |
+
"text": "Button issue and DB latency investigated. CSS fix attempted. Email 401 error also reported.",
|
| 79 |
"metadata": {}
|
| 80 |
},
|
| 81 |
+
"doc_incident_resolved": {
|
| 82 |
+
"text": "Frontend button restored by updating CSS stylesheet. Email 401 resolved after API key rotation on Tuesday.",
|
| 83 |
"metadata": {}
|
| 84 |
},
|
|
|
|
| 85 |
}
|
| 86 |
+
for i in range(20):
|
| 87 |
+
self.kb[f"doc_distractor_eng_{i}"] = {
|
| 88 |
+
"text": f"Architecture decision {i}: chose Postgres for horizontal scaling.",
|
| 89 |
+
"metadata": {}
|
| 90 |
+
}
|
| 91 |
self.test_suite = [
|
| 92 |
+
{"query": "How was the missing UI element fixed?",
|
| 93 |
+
"target_concept": "updating CSS stylesheet"},
|
| 94 |
+
{"query": "What caused the authentication failure?",
|
| 95 |
+
"target_concept": "API key rotation on Tuesday"},
|
| 96 |
]
|
| 97 |
+
|
| 98 |
elif task_id == "hard":
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 99 |
self.test_suite = [
|
| 100 |
+
{"query": "Which entity bankruptcy was the climax of the disaster?", "target_concept": "bankruptcy of Lehman Brothers"},
|
| 101 |
+
{"query": "What types of collateralized assets lost their worth?", "target_concept": "Mortgage-backed securities"},
|
| 102 |
+
{"query": "Who did predatory lenders primarily go after?", "target_concept": "low-income homebuyers"}
|
| 103 |
]
|
| 104 |
+
|
| 105 |
+
self._rebuild_cache()
|
| 106 |
+
|
| 107 |
+
def _rebuild_cache(self):
|
| 108 |
+
"""Called whenever KB documents are added, removed, or updated."""
|
| 109 |
+
if not self.kb:
|
| 110 |
+
self.dense_vectors = {}
|
| 111 |
+
return
|
| 112 |
+
|
| 113 |
+
doc_ids = list(self.kb.keys())
|
| 114 |
+
# Append metadata to text for embedding
|
| 115 |
+
doc_texts = [(self.kb[d]["text"] + " " + " ".join(self.kb[d]["metadata"].values())).strip() for d in doc_ids]
|
| 116 |
+
|
| 117 |
+
vectors = self.embedding_model.encode(doc_texts, convert_to_tensor=False)
|
| 118 |
+
self.dense_vectors = {doc_id: vectors[i] for i, doc_id in enumerate(doc_ids)}
|
| 119 |
|
| 120 |
def _get_kb_summary(self) -> Dict[str, Dict]:
|
| 121 |
"""Returns a summary of the KB for the observation."""
|
|
|
|
| 137 |
)
|
| 138 |
|
| 139 |
def _evaluate_kb(self) -> float:
|
| 140 |
+
"""The Grader: Evaluates the current KB using Hybrid RRF (BM25 + Semantic MRR)."""
|
| 141 |
+
if not self.kb or not self.test_suite:
|
| 142 |
+
return 0.01
|
| 143 |
|
| 144 |
+
doc_ids = list(self.kb.keys())
|
| 145 |
+
doc_texts = [(self.kb[d]["text"] + " " + " ".join(self.kb[d]["metadata"].values())).strip() for d in doc_ids]
|
| 146 |
|
| 147 |
+
# 1. BM25 Corpus Preparation
|
| 148 |
+
tokenized_corpus = [doc.lower().split() for doc in doc_texts]
|
| 149 |
+
bm25 = BM25Okapi(tokenized_corpus)
|
| 150 |
+
|
| 151 |
+
# 2. Dense Matrix
|
| 152 |
+
doc_vectors = np.array([self.dense_vectors[d] for d in doc_ids])
|
| 153 |
+
|
| 154 |
+
mrr_sum = 0.0
|
| 155 |
|
| 156 |
for case in self.test_suite:
|
| 157 |
+
# BM25 Search
|
| 158 |
+
tokenized_query = case["query"].lower().split()
|
| 159 |
+
bm25_scores = bm25.get_scores(tokenized_query)
|
| 160 |
+
bm25_ranks = bm25_scores.argsort()[::-1]
|
| 161 |
|
| 162 |
+
# Dense Search
|
| 163 |
+
query_vec = self.embedding_model.encode(case["query"])
|
| 164 |
+
dense_scores = cosine_similarity([query_vec], doc_vectors)[0]
|
| 165 |
+
dense_ranks = dense_scores.argsort()[::-1]
|
| 166 |
|
| 167 |
+
# Reciprocal Rank Fusion (RRF)
|
| 168 |
+
rrf_scores = {d: 0.0 for d in doc_ids}
|
| 169 |
+
k = 60
|
| 170 |
+
for rank, idx in enumerate(bm25_ranks):
|
| 171 |
+
rrf_scores[doc_ids[idx]] += 1.0 / (k + rank + 1)
|
| 172 |
+
for rank, idx in enumerate(dense_ranks):
|
| 173 |
+
rrf_scores[doc_ids[idx]] += 1.0 / (k + rank + 1)
|
|
|
|
| 174 |
|
| 175 |
+
# Grade Top-K fused list using MRR
|
| 176 |
+
ranked_doc_ids = sorted(rrf_scores.keys(), key=lambda x: rrf_scores[x], reverse=True)
|
| 177 |
+
|
| 178 |
+
req_meta = case.get("required_metadata_key")
|
| 179 |
+
req_meta_val = case.get("required_metadata_value")
|
| 180 |
+
|
| 181 |
+
case_mrr = 0.0
|
| 182 |
+
for i, doc_id in enumerate(ranked_doc_ids):
|
| 183 |
+
# Is this the true doc?
|
| 184 |
+
doc_text = self.kb[doc_id]["text"].lower()
|
| 185 |
+
if case["target_concept"].lower() in doc_text:
|
| 186 |
+
valid = True
|
| 187 |
+
|
| 188 |
+
# Medium Task Semantic Check
|
| 189 |
+
if req_meta and req_meta_val:
|
| 190 |
+
if self.kb[doc_id]["metadata"].get(req_meta) != req_meta_val:
|
| 191 |
+
valid = False
|
| 192 |
+
|
| 193 |
+
if valid:
|
| 194 |
+
case_mrr = 1.0 / (i + 1) # MRR formula starts at rank 1
|
| 195 |
+
break
|
| 196 |
+
|
| 197 |
+
mrr_sum += case_mrr
|
| 198 |
+
|
| 199 |
+
base_reward = float(mrr_sum / len(self.test_suite))
|
| 200 |
+
|
| 201 |
+
# Step Cost Penalty calculation (-0.01 per step)
|
| 202 |
+
cost_penalty = self._state.step_count * 0.01
|
| 203 |
+
|
| 204 |
+
return max(0.01, min(0.99, base_reward - cost_penalty))
|
| 205 |
|
| 206 |
def step(self, action: RagOptimizerAction) -> RagOptimizerObservation: # type: ignore[override]
|
| 207 |
self._state.step_count += 1
|
| 208 |
|
| 209 |
msg = ""
|
| 210 |
done = False
|
| 211 |
+
reward = 0.01
|
| 212 |
|
| 213 |
try:
|
| 214 |
if action.action_type == "read_document":
|
|
|
|
| 220 |
elif action.action_type == "delete_document":
|
| 221 |
if action.doc_id in self.kb:
|
| 222 |
del self.kb[action.doc_id]
|
| 223 |
+
self._rebuild_cache()
|
| 224 |
msg = f"Deleted {action.doc_id}."
|
| 225 |
else:
|
| 226 |
msg = f"Error: doc_id {action.doc_id} not found."
|
|
|
|
| 232 |
if action.doc_id not in self.kb:
|
| 233 |
self.kb[action.doc_id] = {"text": "", "metadata": {}}
|
| 234 |
self.kb[action.doc_id]["text"] = action.text
|
| 235 |
+
self._rebuild_cache()
|
| 236 |
msg = f"Updated text for {action.doc_id}."
|
| 237 |
|
| 238 |
elif action.action_type == "add_metadata":
|
|
|
|
| 243 |
msg = f"Error: doc_id {action.doc_id} not found."
|
| 244 |
else:
|
| 245 |
self.kb[action.doc_id]["metadata"][action.metadata_key] = action.metadata_value
|
| 246 |
+
self._rebuild_cache()
|
| 247 |
msg = f"Added metadata to {action.doc_id}."
|
| 248 |
|
| 249 |
elif action.action_type == "submit":
|
server/requirements.txt
CHANGED
|
@@ -2,3 +2,5 @@ openenv[core]>=0.2.0
|
|
| 2 |
fastapi>=0.115.0
|
| 3 |
uvicorn>=0.24.0
|
| 4 |
scikit-learn>=1.3.0
|
|
|
|
|
|
|
|
|
| 2 |
fastapi>=0.115.0
|
| 3 |
uvicorn>=0.24.0
|
| 4 |
scikit-learn>=1.3.0
|
| 5 |
+
sentence-transformers>=3.0.0
|
| 6 |
+
rank_bm25>=0.2.2
|