Siddh12334 commited on
Commit
7a8a0f0
·
1 Parent(s): 4e71c52

feat: rewrite env to be fully openenv-core compliant

Browse files
environment/actions.py CHANGED
@@ -1,6 +1,7 @@
1
  from enum import Enum
2
  from typing import Optional
3
  from pydantic import BaseModel, field_validator
 
4
 
5
 
6
  class ActionType(str, Enum):
@@ -10,7 +11,7 @@ class ActionType(str, Enum):
10
  submit_answer = "submit_answer"
11
 
12
 
13
- class ContextCorruptionAction(BaseModel):
14
  action_type: ActionType
15
  doc_id: Optional[int] = None
16
  answer: Optional[str] = None
@@ -31,12 +32,22 @@ class Document(BaseModel):
31
  is_flagged: bool = False
32
 
33
 
34
- class EpisodeObservation(BaseModel):
35
- question: str
36
- documents: list[Document]
37
- flagged_ids: list[int]
38
- budget_remaining: int
39
- turn: int
40
- episode_done: bool = False
41
- reward: Optional[float] = None
42
  message: Optional[str] = None
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  from enum import Enum
2
  from typing import Optional
3
  from pydantic import BaseModel, field_validator
4
+ from openenv.core import Action, Observation, State
5
 
6
 
7
  class ActionType(str, Enum):
 
11
  submit_answer = "submit_answer"
12
 
13
 
14
+ class ContextCorruptionAction(Action):
15
  action_type: ActionType
16
  doc_id: Optional[int] = None
17
  answer: Optional[str] = None
 
32
  is_flagged: bool = False
33
 
34
 
35
+ class EpisodeObservation(Observation):
36
+ question: str = ""
37
+ documents: list[Document] = []
38
+ flagged_ids: list[int] = []
39
+ budget_remaining: int = 0
40
+ turn: int = 0
 
 
41
  message: Optional[str] = None
42
+ # `done` and `reward` inherited from Observation
43
+
44
+
45
+ class ContextCorruptionState(State):
46
+ question: str = ""
47
+ ground_truth: str = ""
48
+ corrupt_ids: list[int] = []
49
+ flagged_ids: list[int] = []
50
+ budget_used: int = 0
51
+ done: bool = False
52
+ reward: Optional[float] = None
53
+ breakdown: Optional[dict] = None
environment/env.py CHANGED
@@ -2,20 +2,28 @@ import json
2
  import random
3
  from pathlib import Path
4
 
5
- from environment.actions import ActionType, ContextCorruptionAction, Document, EpisodeObservation
6
- from environment.reward import compute_reward
 
 
 
 
 
7
 
8
  _FALLBACK_FACTS = [
9
  {"question": "What is the capital of France?", "answer": "Paris"}
10
  ]
11
 
12
 
13
- class ContextCorruptionEnv:
14
  MAX_BUDGET = 12
15
  NUM_DOCS = 8
16
  DIFFICULTY_LEVELS = [1, 2, 3, 4]
 
17
 
18
  def __init__(self, difficulty=None):
 
 
19
  self.difficulty = difficulty
20
  facts_path = Path(__file__).parent.parent / "data" / "facts.json"
21
  if facts_path.exists():
@@ -37,8 +45,11 @@ class ContextCorruptionEnv:
37
  self._reward = None
38
  self._breakdown = None
39
 
40
- def reset(self) -> EpisodeObservation:
 
41
  self._reset_state()
 
 
42
  fact = random.choice(self._facts)
43
  n_corrupt = self.difficulty if self.difficulty is not None else random.choice(self.DIFFICULTY_LEVELS)
44
  self._corrupt_ids = random.sample(range(self.NUM_DOCS), n_corrupt)
@@ -55,18 +66,17 @@ class ContextCorruptionEnv:
55
  ]
56
 
57
  self._documents = raw_docs
58
- return self._build_observation()
59
 
60
- def step(self, action: ContextCorruptionAction) -> EpisodeObservation:
61
  if self._done:
62
- return self._build_observation(message="Episode already done.")
63
 
64
  self._turn += 1
65
  self._budget_used += 1
66
- reward = None
67
 
68
  if action.action_type == ActionType.read_doc:
69
- pass # budget cost is the point; content already in observation
70
 
71
  elif action.action_type == ActionType.flag_suspicious:
72
  if action.doc_id is not None and action.doc_id not in self._flagged_ids:
@@ -77,48 +87,44 @@ class ContextCorruptionEnv:
77
  self._flagged_ids.remove(action.doc_id)
78
 
79
  elif action.action_type == ActionType.submit_answer:
80
- reward, self._breakdown = compute_reward(
81
- submitted_answer=action.answer or "",
82
- ground_truth_answer=self._ground_truth,
83
- flagged_ids=self._flagged_ids,
84
- corrupt_ids=self._corrupt_ids,
85
- confidence=action.confidence or 0.0,
86
- budget_used=self._budget_used,
87
- max_budget=self.MAX_BUDGET,
88
- )
89
- self._reward = reward
90
  self._done = True
91
 
92
  # Force-submit on budget exhaustion
93
  if self._budget_used >= self.MAX_BUDGET and not self._done:
94
- reward, self._breakdown = compute_reward(
95
- submitted_answer="",
96
- ground_truth_answer=self._ground_truth,
97
- flagged_ids=self._flagged_ids,
98
- corrupt_ids=self._corrupt_ids,
99
- confidence=0.0,
100
- budget_used=self._budget_used,
101
- max_budget=self.MAX_BUDGET,
102
- )
103
- self._reward = reward
104
  self._done = True
105
 
106
- return self._build_observation(reward=reward)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
 
108
- def state(self) -> dict:
109
  return {
110
- "question": self._question,
111
  "ground_truth": self._ground_truth,
112
- "corrupt_ids": self._corrupt_ids,
113
- "flagged_ids": self._flagged_ids,
114
  "budget_used": self._budget_used,
115
- "turn": self._turn,
116
- "done": self._done,
117
- "reward": self._reward,
118
- "breakdown": self._breakdown,
119
  }
120
 
121
- def _build_observation(self, reward=None, message=None) -> EpisodeObservation:
122
  docs = [
123
  Document(
124
  id=d["id"],
@@ -134,7 +140,7 @@ class ContextCorruptionEnv:
134
  flagged_ids=list(self._flagged_ids),
135
  budget_remaining=self.MAX_BUDGET - self._budget_used,
136
  turn=self._turn,
137
- episode_done=self._done,
138
- reward=reward if reward is not None else self._reward,
139
  message=message,
140
  )
 
2
  import random
3
  from pathlib import Path
4
 
5
+ from openenv.core import Environment
6
+
7
+ from environment.actions import (
8
+ ActionType, ContextCorruptionAction, Document,
9
+ EpisodeObservation, ContextCorruptionState,
10
+ )
11
+ from environment.reward import ContextCorruptionRubric
12
 
13
  _FALLBACK_FACTS = [
14
  {"question": "What is the capital of France?", "answer": "Paris"}
15
  ]
16
 
17
 
18
+ class ContextCorruptionEnv(Environment[ContextCorruptionAction, EpisodeObservation, ContextCorruptionState]):
19
  MAX_BUDGET = 12
20
  NUM_DOCS = 8
21
  DIFFICULTY_LEVELS = [1, 2, 3, 4]
22
+ SUPPORTS_CONCURRENT_SESSIONS = True
23
 
24
  def __init__(self, difficulty=None):
25
+ rubric = ContextCorruptionRubric(state_fn=self._state_dict)
26
+ super().__init__(rubric=rubric)
27
  self.difficulty = difficulty
28
  facts_path = Path(__file__).parent.parent / "data" / "facts.json"
29
  if facts_path.exists():
 
45
  self._reward = None
46
  self._breakdown = None
47
 
48
+ def reset(self, seed=None, episode_id=None, **kwargs) -> EpisodeObservation:
49
+ self._reset_rubric()
50
  self._reset_state()
51
+ if seed is not None:
52
+ random.seed(seed)
53
  fact = random.choice(self._facts)
54
  n_corrupt = self.difficulty if self.difficulty is not None else random.choice(self.DIFFICULTY_LEVELS)
55
  self._corrupt_ids = random.sample(range(self.NUM_DOCS), n_corrupt)
 
66
  ]
67
 
68
  self._documents = raw_docs
69
+ return self._apply_transform(self._build_observation())
70
 
71
+ def step(self, action: ContextCorruptionAction, timeout_s=None, **kwargs) -> EpisodeObservation:
72
  if self._done:
73
+ return self._apply_transform(self._build_observation(message="Episode already done."))
74
 
75
  self._turn += 1
76
  self._budget_used += 1
 
77
 
78
  if action.action_type == ActionType.read_doc:
79
+ pass
80
 
81
  elif action.action_type == ActionType.flag_suspicious:
82
  if action.doc_id is not None and action.doc_id not in self._flagged_ids:
 
87
  self._flagged_ids.remove(action.doc_id)
88
 
89
  elif action.action_type == ActionType.submit_answer:
 
 
 
 
 
 
 
 
 
 
90
  self._done = True
91
 
92
  # Force-submit on budget exhaustion
93
  if self._budget_used >= self.MAX_BUDGET and not self._done:
 
 
 
 
 
 
 
 
 
 
94
  self._done = True
95
 
96
+ obs = self._build_observation()
97
+
98
+ if obs.done:
99
+ obs.reward = self._apply_rubric(action, obs)
100
+ self._reward = obs.reward
101
+ self._breakdown = self.rubric.last_breakdown if self.rubric else None
102
+
103
+ return self._apply_transform(obs)
104
+
105
+ @property
106
+ def state(self) -> ContextCorruptionState:
107
+ return ContextCorruptionState(
108
+ question=self._question,
109
+ ground_truth=self._ground_truth,
110
+ corrupt_ids=list(self._corrupt_ids),
111
+ flagged_ids=list(self._flagged_ids),
112
+ budget_used=self._budget_used,
113
+ done=self._done,
114
+ reward=self._reward,
115
+ breakdown=self._breakdown,
116
+ )
117
 
118
+ def _state_dict(self) -> dict:
119
  return {
 
120
  "ground_truth": self._ground_truth,
121
+ "flagged_ids": list(self._flagged_ids),
122
+ "corrupt_ids": list(self._corrupt_ids),
123
  "budget_used": self._budget_used,
124
+ "max_budget": self.MAX_BUDGET,
 
 
 
125
  }
126
 
127
+ def _build_observation(self, message=None) -> EpisodeObservation:
128
  docs = [
129
  Document(
130
  id=d["id"],
 
140
  flagged_ids=list(self._flagged_ids),
141
  budget_remaining=self.MAX_BUDGET - self._budget_used,
142
  turn=self._turn,
143
+ done=self._done,
144
+ reward=self._reward,
145
  message=message,
146
  )
environment/reward.py CHANGED
@@ -1,4 +1,5 @@
1
  import re
 
2
 
3
 
4
  def _normalize(text: str) -> str:
@@ -17,24 +18,19 @@ def compute_reward(
17
  budget_used: int,
18
  max_budget: int,
19
  ) -> tuple[float, dict]:
20
- # Answer correctness
21
  correct = _normalize(submitted_answer) == _normalize(ground_truth_answer)
22
  answer_score = 0.4 if correct else 0.0
23
 
24
- # Flag recall
25
  true_positives = [i for i in flagged_ids if i in corrupt_ids]
26
  recall = len(true_positives) / len(corrupt_ids) if corrupt_ids else 0.0
27
  recall_score = 0.3 * recall
28
 
29
- # Precision (false positive penalty)
30
  false_positives = [i for i in flagged_ids if i not in corrupt_ids]
31
  precision_score = max(0.0, 0.2 - 0.1 * len(false_positives))
32
 
33
- # Confidence calibration
34
  confidence = confidence or 0.0
35
  calibration_score = (0.1 * confidence) if correct else (-0.2 * confidence)
36
 
37
- # Efficiency bonus
38
  efficiency_score = 0.05 * (1 - budget_used / max_budget)
39
 
40
  total = answer_score + recall_score + precision_score + calibration_score + efficiency_score
@@ -48,3 +44,32 @@ def compute_reward(
48
  "total": round(total, 4),
49
  }
50
  return round(total, 4), breakdown
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import re
2
+ from openenv.core.rubrics import Rubric
3
 
4
 
5
  def _normalize(text: str) -> str:
 
18
  budget_used: int,
19
  max_budget: int,
20
  ) -> tuple[float, dict]:
 
21
  correct = _normalize(submitted_answer) == _normalize(ground_truth_answer)
22
  answer_score = 0.4 if correct else 0.0
23
 
 
24
  true_positives = [i for i in flagged_ids if i in corrupt_ids]
25
  recall = len(true_positives) / len(corrupt_ids) if corrupt_ids else 0.0
26
  recall_score = 0.3 * recall
27
 
 
28
  false_positives = [i for i in flagged_ids if i not in corrupt_ids]
29
  precision_score = max(0.0, 0.2 - 0.1 * len(false_positives))
30
 
 
31
  confidence = confidence or 0.0
32
  calibration_score = (0.1 * confidence) if correct else (-0.2 * confidence)
33
 
 
34
  efficiency_score = 0.05 * (1 - budget_used / max_budget)
35
 
36
  total = answer_score + recall_score + precision_score + calibration_score + efficiency_score
 
44
  "total": round(total, 4),
45
  }
46
  return round(total, 4), breakdown
47
+
48
+
49
+ class ContextCorruptionRubric(Rubric):
50
+ """Scores a completed episode using compute_reward().
51
+
52
+ Requires a state_fn closure to access ground-truth env state that is
53
+ intentionally hidden from the agent's observation.
54
+ """
55
+
56
+ def __init__(self, state_fn):
57
+ super().__init__()
58
+ self._state_fn = state_fn
59
+ self.last_breakdown: dict = {}
60
+
61
+ def forward(self, action, observation) -> float:
62
+ if not observation.done:
63
+ return 0.0
64
+ s = self._state_fn()
65
+ reward, breakdown = compute_reward(
66
+ submitted_answer=getattr(action, "answer", None) or "",
67
+ ground_truth_answer=s["ground_truth"],
68
+ flagged_ids=s["flagged_ids"],
69
+ corrupt_ids=s["corrupt_ids"],
70
+ confidence=getattr(action, "confidence", None) or 0.0,
71
+ budget_used=s["budget_used"],
72
+ max_budget=s["max_budget"],
73
+ )
74
+ self.last_breakdown = breakdown
75
+ return reward
environment/server.py CHANGED
@@ -1,56 +1,16 @@
1
- import uuid
2
- from fastapi import FastAPI, HTTPException
3
  import uvicorn
4
 
5
  from environment.actions import ContextCorruptionAction, EpisodeObservation
6
  from environment.env import ContextCorruptionEnv
7
 
8
- app = FastAPI(title="ContextCorruption-Env")
9
-
10
- # session_id -> env instance
11
- _sessions: dict[str, ContextCorruptionEnv] = {}
12
- _MAX_SESSIONS = 64
13
-
14
-
15
- @app.post("/reset", response_model=EpisodeObservation)
16
- def reset(session_id: str | None = None):
17
- if session_id is None:
18
- if len(_sessions) >= _MAX_SESSIONS:
19
- raise HTTPException(status_code=503, detail="Max concurrent sessions reached")
20
- session_id = str(uuid.uuid4())
21
- if session_id not in _sessions:
22
- _sessions[session_id] = ContextCorruptionEnv()
23
- obs = _sessions[session_id].reset()
24
- return obs
25
-
26
-
27
- @app.post("/step/{session_id}", response_model=EpisodeObservation)
28
- def step(session_id: str, action: ContextCorruptionAction):
29
- if session_id not in _sessions:
30
- raise HTTPException(status_code=404, detail="Session not found")
31
- obs = _sessions[session_id].step(action)
32
- if obs.episode_done:
33
- del _sessions[session_id]
34
- return obs
35
-
36
-
37
- @app.get("/state/{session_id}")
38
- def state(session_id: str):
39
- if session_id not in _sessions:
40
- raise HTTPException(status_code=404, detail="Session not found")
41
- return _sessions[session_id].state()
42
-
43
-
44
- @app.delete("/session/{session_id}")
45
- def close_session(session_id: str):
46
- _sessions.pop(session_id, None)
47
- return {"status": "closed"}
48
-
49
-
50
- @app.get("/health")
51
- def health():
52
- return {"status": "ok", "active_sessions": len(_sessions)}
53
-
54
 
55
  if __name__ == "__main__":
56
  uvicorn.run("environment.server:app", host="0.0.0.0", port=8000, reload=False)
 
1
+ from openenv.core import create_app
 
2
  import uvicorn
3
 
4
  from environment.actions import ContextCorruptionAction, EpisodeObservation
5
  from environment.env import ContextCorruptionEnv
6
 
7
+ app = create_app(
8
+ env=lambda: ContextCorruptionEnv(),
9
+ action_cls=ContextCorruptionAction,
10
+ observation_cls=EpisodeObservation,
11
+ env_name="ContextCorruption-Env",
12
+ max_concurrent_envs=64,
13
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
 
15
  if __name__ == "__main__":
16
  uvicorn.run("environment.server:app", host="0.0.0.0", port=8000, reload=False)
eval/baseline_eval.py CHANGED
@@ -33,7 +33,7 @@ def run_baseline():
33
  )
34
 
35
  obs = env.step(action)
36
- done = obs.episode_done
37
 
38
  rewards.append(obs.reward)
39
  if (ep + 1) % 10 == 0:
 
33
  )
34
 
35
  obs = env.step(action)
36
+ done = obs.done
37
 
38
  rewards.append(obs.reward)
39
  if (ep + 1) % 10 == 0:
requirements.txt CHANGED
@@ -1,4 +1,5 @@
1
  accelerate==1.13.0
 
2
  aiohappyeyeballs==2.6.1
3
  aiohttp==3.13.5
4
  aiosignal==1.4.0
@@ -6,59 +7,118 @@ annotated-doc==0.0.4
6
  annotated-types==0.7.0
7
  anyio==4.13.0
8
  attrs==26.1.0
 
 
 
 
 
 
9
  certifi==2026.4.22
 
10
  charset-normalizer==3.4.7
11
  click==8.3.3
 
 
12
  datasets==4.8.4
13
  dill==0.4.1
 
 
 
 
 
 
14
  Faker==40.15.0
15
  fastapi==0.136.1
 
16
  filelock==3.29.0
17
  frozenlist==1.8.0
18
  fsspec==2026.2.0
19
  gitdb==4.0.12
20
  GitPython==3.1.47
 
 
 
 
21
  h11==0.16.0
 
22
  hf-xet==1.4.3
23
  httpcore==1.0.9
24
  httpx==0.28.1
 
25
  huggingface_hub==1.12.0
26
  idna==3.13
 
 
 
 
27
  Jinja2==3.1.6
 
 
 
 
 
 
 
28
  markdown-it-py==4.0.0
29
  MarkupSafe==3.0.3
 
30
  mdurl==0.1.2
 
31
  mpmath==1.3.0
32
  multidict==6.7.1
33
  multiprocess==0.70.19
34
  networkx==3.6.1
35
  numpy==2.4.4
36
- openenv==0.1.13
 
 
 
 
37
  packaging==26.2
38
  pandas==3.0.2
 
 
39
  platformdirs==4.9.6
40
  propcache==0.4.1
41
  protobuf==7.34.1
42
  psutil==7.2.2
 
43
  pyarrow==24.0.0
 
44
  pydantic==2.13.3
 
45
  pydantic_core==2.46.3
 
46
  Pygments==2.20.0
 
 
47
  python-dateutil==2.9.0.post0
48
  python-dotenv==1.2.2
 
 
49
  PyYAML==6.0.3
 
50
  regex==2026.4.4
51
  requests==2.33.1
52
  rich==15.0.0
 
 
 
53
  safetensors==0.7.0
 
54
  sentry-sdk==2.58.0
55
  setuptools==81.0.0
56
  shellingham==1.5.4
57
  six==1.17.0
58
  smmap==5.0.3
 
 
59
  starlette==1.0.0
60
  sympy==1.14.0
61
  tokenizers==0.22.2
 
 
 
62
  torch==2.11.0
63
  tqdm==4.67.3
64
  transformers==5.6.2
@@ -66,9 +126,12 @@ trl==1.2.0
66
  typer==0.24.2
67
  typing-inspection==0.4.2
68
  typing_extensions==4.15.0
 
69
  urllib3==2.6.3
70
  uvicorn==0.46.0
71
  wandb==0.26.1
 
72
  websockets==16.0
73
  xxhash==3.6.0
74
  yarl==1.23.0
 
 
1
  accelerate==1.13.0
2
+ aiofile==3.9.0
3
  aiohappyeyeballs==2.6.1
4
  aiohttp==3.13.5
5
  aiosignal==1.4.0
 
7
  annotated-types==0.7.0
8
  anyio==4.13.0
9
  attrs==26.1.0
10
+ audioop-lts==0.2.2
11
+ Authlib==1.7.0
12
+ beartype==0.22.9
13
+ brotli==1.2.0
14
+ cachetools==7.0.6
15
+ caio==0.9.25
16
  certifi==2026.4.22
17
+ cffi==2.0.0
18
  charset-normalizer==3.4.7
19
  click==8.3.3
20
+ cryptography==47.0.0
21
+ cyclopts==4.11.0
22
  datasets==4.8.4
23
  dill==0.4.1
24
+ distro==1.9.0
25
+ dnspython==2.8.0
26
+ docstring_parser==0.18.0
27
+ docutils==0.22.4
28
+ email-validator==2.3.0
29
+ exceptiongroup==1.3.1
30
  Faker==40.15.0
31
  fastapi==0.136.1
32
+ fastmcp==3.2.4
33
  filelock==3.29.0
34
  frozenlist==1.8.0
35
  fsspec==2026.2.0
36
  gitdb==4.0.12
37
  GitPython==3.1.47
38
+ gradio==6.13.0
39
+ gradio_client==2.5.0
40
+ griffelib==2.0.2
41
+ groovy==0.1.2
42
  h11==0.16.0
43
+ hf-gradio==0.4.1
44
  hf-xet==1.4.3
45
  httpcore==1.0.9
46
  httpx==0.28.1
47
+ httpx-sse==0.4.3
48
  huggingface_hub==1.12.0
49
  idna==3.13
50
+ importlib_metadata==8.7.1
51
+ jaraco.classes==3.4.0
52
+ jaraco.context==6.1.2
53
+ jaraco.functools==4.4.0
54
  Jinja2==3.1.6
55
+ jiter==0.14.0
56
+ joserfc==1.6.4
57
+ jsonref==1.1.0
58
+ jsonschema==4.26.0
59
+ jsonschema-path==0.4.5
60
+ jsonschema-specifications==2025.9.1
61
+ keyring==25.7.0
62
  markdown-it-py==4.0.0
63
  MarkupSafe==3.0.3
64
+ mcp==1.27.0
65
  mdurl==0.1.2
66
+ more-itertools==11.0.2
67
  mpmath==1.3.0
68
  multidict==6.7.1
69
  multiprocess==0.70.19
70
  networkx==3.6.1
71
  numpy==2.4.4
72
+ openai==2.32.0
73
+ openapi-pydantic==0.5.1
74
+ openenv-core==0.2.3
75
+ opentelemetry-api==1.41.1
76
+ orjson==3.11.8
77
  packaging==26.2
78
  pandas==3.0.2
79
+ pathable==0.5.0
80
+ pillow==12.2.0
81
  platformdirs==4.9.6
82
  propcache==0.4.1
83
  protobuf==7.34.1
84
  psutil==7.2.2
85
+ py-key-value-aio==0.4.4
86
  pyarrow==24.0.0
87
+ pycparser==3.0
88
  pydantic==2.13.3
89
+ pydantic-settings==2.14.0
90
  pydantic_core==2.46.3
91
+ pydub==0.25.1
92
  Pygments==2.20.0
93
+ PyJWT==2.12.1
94
+ pyperclip==1.11.0
95
  python-dateutil==2.9.0.post0
96
  python-dotenv==1.2.2
97
+ python-multipart==0.0.26
98
+ pytz==2026.1.post1
99
  PyYAML==6.0.3
100
+ referencing==0.37.0
101
  regex==2026.4.4
102
  requests==2.33.1
103
  rich==15.0.0
104
+ rich-rst==1.3.2
105
+ rpds-py==0.30.0
106
+ safehttpx==0.1.7
107
  safetensors==0.7.0
108
+ semantic-version==2.10.0
109
  sentry-sdk==2.58.0
110
  setuptools==81.0.0
111
  shellingham==1.5.4
112
  six==1.17.0
113
  smmap==5.0.3
114
+ sniffio==1.3.1
115
+ sse-starlette==3.3.4
116
  starlette==1.0.0
117
  sympy==1.14.0
118
  tokenizers==0.22.2
119
+ tomli==2.4.1
120
+ tomli_w==1.2.0
121
+ tomlkit==0.14.0
122
  torch==2.11.0
123
  tqdm==4.67.3
124
  transformers==5.6.2
 
126
  typer==0.24.2
127
  typing-inspection==0.4.2
128
  typing_extensions==4.15.0
129
+ uncalled-for==0.3.1
130
  urllib3==2.6.3
131
  uvicorn==0.46.0
132
  wandb==0.26.1
133
+ watchfiles==1.1.1
134
  websockets==16.0
135
  xxhash==3.6.0
136
  yarl==1.23.0
137
+ zipp==3.23.1