Rishav commited on
Commit
f7d383d
·
1 Parent(s): 9600701

Add packaging manifest and refine rewards

Browse files
docs/agent_eval_prompt.md ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Black-Box Agent Evaluation Prompt
2
+
3
+ Use this prompt when evaluating an external agent against Fleetmind without giving it source-code access.
4
+
5
+ ## Goal
6
+
7
+ Maximize cumulative reward in the live environment by interacting only through the HTTP API.
8
+
9
+ ## Instructions
10
+
11
+ You are interacting with a live delivery-dispatch environment as a black-box external agent.
12
+
13
+ You may use any reasoning tools available to you, including calculations, helper code, or temporary scripts, but you must not inspect the environment source code or hidden files. Treat the HTTP API as the only interface to the environment.
14
+
15
+ Use only these endpoints:
16
+ - `GET /health`
17
+ - `POST /reset`
18
+ - `GET /state`
19
+ - `POST /step`
20
+
21
+ ## Required Workflow
22
+
23
+ 1. Call `GET /health` to confirm the service is live.
24
+ 2. Start a fresh episode with `POST /reset`.
25
+ 3. Play the episode entirely through repeated `POST /step` calls until `done = true`.
26
+ 4. Use `GET /state` only if needed for recovery or inspection.
27
+ 5. Choose assignments and rejections based only on API observations and returned feedback.
28
+
29
+ ## Constraints
30
+
31
+ - Do not inspect local repository files or source code.
32
+ - Do not assume hidden future orders or hidden reward terms beyond what can be inferred from the API.
33
+ - Do not modify the environment implementation.
34
+ - You may write local scratch logic for your own planning, but the environment must be treated as a black box.
35
+
36
+ ## Final Report
37
+
38
+ At the end of the episode, report:
39
+ - final cumulative reward
40
+ - the policy or strategy you converged on
41
+ - key assignment and rejection decisions
42
+ - what the API feedback taught you
43
+ - what felt confusing, too easy, too derived, or gameable
pyproject.toml ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [build-system]
2
+ requires = ["setuptools>=69", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "fleetmind"
7
+ version = "0.1.0"
8
+ description = "OpenEnv delivery dispatch environment for sequential agent evaluation."
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ dependencies = [
12
+ "pydantic==2.12.5",
13
+ "openai==2.30.0",
14
+ "fastapi==0.135.2",
15
+ "uvicorn==0.42.0",
16
+ "pyyaml==6.0.1",
17
+ "requests==2.32.3",
18
+ ]
19
+
20
+ [tool.setuptools]
21
+ package-dir = {"" = "src"}
22
+
23
+ [tool.setuptools.packages.find]
24
+ where = ["src"]
src/delivery_dispatch/environment.py CHANGED
@@ -25,12 +25,12 @@ class DeliveryDispatchEnv:
25
  """Deterministic event-driven delivery dispatch simulator."""
26
 
27
  invalid_assignment_penalty = -1.0
28
- idle_penalty = -0.5
29
  service_time = 1
30
- missed_order_penalty_multiplier = 0.6
31
  feasible_assignment_bonus = 0.5
32
  infeasible_assignment_penalty = -1.5
33
- rejection_penalty_multiplier = 0.2
34
  service_grace_window = 4
35
  early_bonus_per_tick = 0.45
36
  early_bonus_cap = 4
@@ -217,7 +217,7 @@ class DeliveryDispatchEnv:
217
  self.recent_events.append(f"ignored invalid rejection for {order_id}")
218
  continue
219
 
220
- rejection_penalty = -max(1.0, self.rejection_penalty_multiplier * order.reward_value)
221
  step_reward += rejection_penalty
222
  reward_breakdown["rejection_penalty"] += rejection_penalty
223
  order.status = "rejected"
@@ -272,10 +272,10 @@ class DeliveryDispatchEnv:
272
  f"assigned {order.order_id} to {agent.agent_id} until t={agent.busy_until}"
273
  )
274
 
275
- if self._avoidable_idle_exists():
 
276
  idle_agents = len([agent for agent in self.agents if agent.status == "idle"])
277
- pending_orders = len([order for order in self._visible_orders() if order.status == "unassigned"])
278
- idle_penalty = self.idle_penalty * min(idle_agents, pending_orders)
279
  step_reward += idle_penalty
280
  reward_breakdown["idle_penalty"] += idle_penalty
281
  self.recent_events.append("avoidable idle capacity remained")
@@ -494,7 +494,7 @@ class DeliveryDispatchEnv:
494
  if self._service_cutoff(order) < self.current_time:
495
  order.status = "expired"
496
  self.stats["expired_orders"] += 1
497
- penalty += -(self.missed_order_penalty_multiplier * order.reward_value)
498
  events.append(f"order {order.order_id} expired")
499
  if order.reward_value >= self.high_value_threshold:
500
  high_value_missed += 1
@@ -618,11 +618,17 @@ class DeliveryDispatchEnv:
618
  self.current_time = terminal_time
619
  return reward, events, error_summary, terminal_info
620
 
621
- def _avoidable_idle_exists(self) -> bool:
622
- visible_unassigned = [
623
- order for order in self._visible_orders() if order.status == "unassigned"
 
 
 
 
 
 
624
  ]
625
- return any(agent.status == "idle" for agent in self.agents) and bool(visible_unassigned)
626
 
627
  def _is_done(self) -> bool:
628
  scenario = self._require_scenario()
@@ -693,3 +699,49 @@ class DeliveryDispatchEnv:
693
  else:
694
  break
695
  return chosen
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  """Deterministic event-driven delivery dispatch simulator."""
26
 
27
  invalid_assignment_penalty = -1.0
28
+ idle_penalty = -0.35
29
  service_time = 1
30
+ missed_order_penalty_multiplier = 0.75
31
  feasible_assignment_bonus = 0.5
32
  infeasible_assignment_penalty = -1.5
33
+ rejection_penalty_multiplier = 0.4
34
  service_grace_window = 4
35
  early_bonus_per_tick = 0.45
36
  early_bonus_cap = 4
 
217
  self.recent_events.append(f"ignored invalid rejection for {order_id}")
218
  continue
219
 
220
+ rejection_penalty = -self._rejection_penalty(order)
221
  step_reward += rejection_penalty
222
  reward_breakdown["rejection_penalty"] += rejection_penalty
223
  order.status = "rejected"
 
272
  f"assigned {order.order_id} to {agent.agent_id} until t={agent.busy_until}"
273
  )
274
 
275
+ avoidable_idle_slots = self._avoidable_idle_slots()
276
+ if avoidable_idle_slots > 0:
277
  idle_agents = len([agent for agent in self.agents if agent.status == "idle"])
278
+ idle_penalty = self.idle_penalty * min(idle_agents, avoidable_idle_slots)
 
279
  step_reward += idle_penalty
280
  reward_breakdown["idle_penalty"] += idle_penalty
281
  self.recent_events.append("avoidable idle capacity remained")
 
494
  if self._service_cutoff(order) < self.current_time:
495
  order.status = "expired"
496
  self.stats["expired_orders"] += 1
497
+ penalty -= self._missed_order_penalty(order, self.current_time)
498
  events.append(f"order {order.order_id} expired")
499
  if order.reward_value >= self.high_value_threshold:
500
  high_value_missed += 1
 
618
  self.current_time = terminal_time
619
  return reward, events, error_summary, terminal_info
620
 
621
+ def _avoidable_idle_slots(self) -> int:
622
+ idle_agents = [agent for agent in self.agents if agent.status == "idle"]
623
+ if not idle_agents:
624
+ return 0
625
+
626
+ worthwhile_orders = [
627
+ order
628
+ for order in self._visible_orders()
629
+ if order.status == "unassigned" and self._is_worth_serving_now(order, idle_agents)
630
  ]
631
+ return len(worthwhile_orders)
632
 
633
  def _is_done(self) -> bool:
634
  scenario = self._require_scenario()
 
699
  else:
700
  break
701
  return chosen
702
+
703
+ def _best_idle_finish_time(self, order: OrderState, idle_agents: list[AgentState] | None = None) -> int | None:
704
+ idle_agents = idle_agents or [agent for agent in self.agents if agent.status == "idle"]
705
+ if not idle_agents:
706
+ return None
707
+ best_cost = min(self._job_time(agent, order) for agent in idle_agents)
708
+ return self.current_time + best_cost
709
+
710
+ def _priority_multiplier(self, order: OrderState, reference_time: int) -> float:
711
+ urgency = order.deadline - reference_time
712
+ multiplier = 1.0
713
+ if order.reward_value >= self.high_value_threshold:
714
+ multiplier += 0.3
715
+ elif order.reward_value >= 12:
716
+ multiplier += 0.15
717
+
718
+ if urgency <= 3:
719
+ multiplier += 0.3
720
+ elif urgency <= 6:
721
+ multiplier += 0.15
722
+ return multiplier
723
+
724
+ def _missed_order_penalty(self, order: OrderState, reference_time: int) -> float:
725
+ return self.missed_order_penalty_multiplier * order.reward_value * self._priority_multiplier(
726
+ order, reference_time
727
+ )
728
+
729
+ def _rejection_penalty(self, order: OrderState) -> float:
730
+ expiry_penalty = self._missed_order_penalty(order, self.current_time)
731
+ best_finish = self._best_idle_finish_time(order)
732
+ if best_finish is None:
733
+ ratio = 0.55
734
+ elif best_finish > self._service_cutoff(order):
735
+ ratio = 0.5
736
+ elif best_finish > order.deadline:
737
+ ratio = 0.65
738
+ else:
739
+ ratio = 0.8
740
+ return max(1.0, expiry_penalty * ratio, self.rejection_penalty_multiplier * order.reward_value)
741
+
742
+ def _is_worth_serving_now(self, order: OrderState, idle_agents: list[AgentState]) -> bool:
743
+ best_finish = self._best_idle_finish_time(order, idle_agents)
744
+ if best_finish is None or best_finish > self._service_cutoff(order):
745
+ return False
746
+ delivery_value = self._completion_reward(order, best_finish)
747
+ return delivery_value >= max(3.0, self._rejection_penalty(order) * 1.2)