thisisgini commited on
Commit
3093d7c
·
verified ·
1 Parent(s): e94ebcd

Upload 4 files

Browse files
Files changed (4) hide show
  1. __init__.py +3 -8
  2. app.py +108 -0
  3. ecommerce_bot_environment.py +169 -0
  4. requirements.txt +6 -0
__init__.py CHANGED
@@ -4,13 +4,8 @@
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
- """Ecommerce Bot Environment."""
8
 
9
- from .client import EcommerceBotEnv
10
- from .models import EcommerceBotAction, EcommerceBotObservation
11
 
12
- __all__ = [
13
- "EcommerceBotAction",
14
- "EcommerceBotObservation",
15
- "EcommerceBotEnv",
16
- ]
 
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
+ """Ecommerce Bot environment server components."""
8
 
9
+ from .ecommerce_bot_environment import EcommerceBotEnvironment
 
10
 
11
+ __all__ = ["EcommerceBotEnvironment"]
 
 
 
 
app.py ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ import os
3
+ from pathlib import Path
4
+ from fastapi import FastAPI
5
+ from fastapi.middleware.cors import CORSMiddleware
6
+ from typing import Dict, Any
7
+
8
+ # 1. Path Management: Ensure models.py is findable
9
+ # We look one level up from the /server folder
10
+ BASE_DIR = Path(__file__).parent.parent
11
+ sys.path.append(str(BASE_DIR))
12
+
13
+ try:
14
+ from models import Action, Observation
15
+ from .ecommerce_bot_environment import EcommerceBotEnvironment
16
+ except ImportError as e:
17
+ print(f"CRITICAL ERROR: Missing modules. Check your folder structure. {e}")
18
+ sys.exit(1)
19
+
20
+ # 2. Initialize FastAPI
21
+ app = FastAPI(
22
+ title="Meta x Scalar: OpenEnv Ecommerce Bot",
23
+ description="Agentic System for Real-World E-commerce Automation",
24
+ version="1.0.0"
25
+ )
26
+
27
+ # 3. Add CORS (Allows the Hugging Face UI to talk to your API)
28
+ app.add_middleware(
29
+ CORSMiddleware,
30
+ allow_origins=["*"],
31
+ allow_methods=["*"],
32
+ allow_headers=["*"],
33
+ )
34
+
35
+ # 4. Initialize the Environment
36
+ # This triggers the SQLite connection in your EcommerceBotEnvironment class
37
+ env = EcommerceBotEnvironment()
38
+
39
+ # --- ROUTES ---
40
+
41
+ @app.get("/")
42
+ async def root():
43
+ """Entry point for Hugging Face Spaces UI."""
44
+ return {
45
+ "message": "E-commerce Bot API is running",
46
+ "status": "online",
47
+ "spec": "OpenEnv v1.0",
48
+ "university": "Mumbai University Campus"
49
+ }
50
+
51
+ @app.get("/health")
52
+ async def health():
53
+ """
54
+ DOCKER HEALTHCHECK:
55
+ Matches the 'CMD curl' in your Dockerfile to ensure container status is 'Healthy'.
56
+ """
57
+ return {"status": "healthy", "database": "connected"}
58
+
59
+ @app.get("/state")
60
+ async def get_state():
61
+ """
62
+ MANDATORY OPENENV ENDPOINT:
63
+ Used by 'openenv validate' to check readiness before testing starts.
64
+ """
65
+ return {
66
+ "status": "ready",
67
+ "tasks": ["create_order", "update_address", "issue_refund"],
68
+ "persistance": "sqlite3"
69
+ }
70
+
71
+ @app.post("/reset")
72
+ async def reset():
73
+ """
74
+ Standard OpenEnv Reset.
75
+ Clears local memory (but keeps the SQLite DB persistent).
76
+ """
77
+ observation = env.reset()
78
+ return observation
79
+
80
+ @app.post("/step")
81
+ async def step(action: Action):
82
+ """
83
+ Standard OpenEnv Step.
84
+ Returns the mandatory 4-part tuple (obs, reward, done, info).
85
+ """
86
+ # Unpack the environment logic
87
+ observation, reward, done, info = env.step(action)
88
+
89
+ # Cast values to ensure JSON compatibility for the grader
90
+ return {
91
+ "observation": observation,
92
+ "reward": float(reward),
93
+ "done": bool(done),
94
+ "info": info
95
+ }
96
+
97
+ # 5. Clean Shutdown
98
+ @app.on_event("shutdown")
99
+ def shutdown_event():
100
+ """Closes connections when the container stops."""
101
+ print("Shutting down E-commerce Bot Server...")
102
+
103
+ # 6. Local Debugging Support
104
+ if __name__ == "__main__":
105
+ import uvicorn
106
+ # Use environment port if available (for HF Spaces), otherwise default to 8000
107
+ port = int(os.environ.get("PORT", 8000))
108
+ uvicorn.run(app, host="0.0.0.0", port=port)
ecommerce_bot_environment.py ADDED
@@ -0,0 +1,169 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ import sqlite3
3
+ import sys
4
+ from pathlib import Path
5
+ from typing import Tuple, Dict, Any
6
+
7
+ # 1. Professional Logger Setup
8
+ logging.basicConfig(
9
+ filename='bot_activity.log',
10
+ level=logging.INFO,
11
+ format='%(asctime)s - %(levelname)s - %(message)s'
12
+ )
13
+
14
+ # Add project root to path to ensure models can be imported
15
+ sys.path.append(str(Path(__file__).parent.parent))
16
+ from models import Action, Observation
17
+
18
+ DB_PATH = "orders.db"
19
+
20
+ def init_db():
21
+ """Initializes the database with advanced state tracking columns."""
22
+ conn = sqlite3.connect(DB_PATH)
23
+ cursor = conn.cursor()
24
+ cursor.execute('''
25
+ CREATE TABLE IF NOT EXISTS orders (
26
+ id TEXT PRIMARY KEY,
27
+ status TEXT,
28
+ address TEXT,
29
+ price REAL,
30
+ requires_human INTEGER DEFAULT 0
31
+ )
32
+ ''')
33
+
34
+ # Seed data strictly aligned with openenv.yaml tasks
35
+ seed_data = [
36
+ ('101', 'Processing', '123 Mumbai St', 500.0, 0),
37
+ ('102', 'Shipped', '456 Delhi Rd', 1200.0, 0),
38
+ ('103', 'Delayed', '789 Bandra West', 500.0, 0)
39
+ ]
40
+ cursor.executemany("INSERT OR IGNORE INTO orders VALUES (?, ?, ?, ?, ?)", seed_data)
41
+ conn.commit()
42
+ conn.close()
43
+
44
+ init_db()
45
+
46
+ class EcommerceBotEnvironment:
47
+ def _get_connection(self):
48
+ return sqlite3.connect(DB_PATH)
49
+
50
+ def reset(self) -> Observation:
51
+ """Standard OpenEnv Reset Protocol."""
52
+ logging.info("System Reset: Database state verified.")
53
+ return Observation(status_msg="Database Connected & Ready", success=True)
54
+
55
+ def step(self, action: Action) -> Tuple[Observation, float, bool, Dict[str, Any]]:
56
+ """
57
+ OpenEnv Compliant Step Function.
58
+ Returns: (Observation, reward, done, info)
59
+ """
60
+ conn = self._get_connection()
61
+ cursor = conn.cursor()
62
+
63
+ # Initialize OpenEnv tracking variables
64
+ reward = 0.0
65
+ done = False
66
+ info = {"task_id": action.order_id}
67
+
68
+ logging.info(f"EXECUTION: {action.action_type} on Order {action.order_id}")
69
+
70
+ # --- TASK 0: DYNAMIC ONBOARDING (Registration) ---
71
+ if action.action_type == "create_order":
72
+ try:
73
+ addr, prc = action.data.split(",")
74
+ cursor.execute(
75
+ "INSERT INTO orders (id, status, address, price, requires_human) VALUES (?, ?, ?, ?, ?)",
76
+ (action.order_id, "Processing", addr, float(prc), 0)
77
+ )
78
+ conn.commit()
79
+ conn.close()
80
+ return Observation(status_msg="Order Created", success=True), 1.0, True, info
81
+ except Exception as e:
82
+ conn.close()
83
+ return Observation(status_msg=str(e), success=False), 0.0, False, info
84
+
85
+ # Validate Order Existence
86
+ cursor.execute("SELECT status, address, price, requires_human FROM orders WHERE id = ?", (action.order_id,))
87
+ row = cursor.fetchone()
88
+
89
+ if not row:
90
+ conn.close()
91
+ return Observation(status_msg="Order Not Found", success=False), 0.0, False, info
92
+
93
+ status, address, price, requires_human = row
94
+
95
+ # --- SAFETY GUARDRAIL: HITL LOCK ---
96
+ if requires_human == 1 and action.action_type != "query_order":
97
+ conn.close()
98
+ # Penalize for ignoring a security lock
99
+ return Observation(status_msg="Order locked for Human Review.", success=False), -0.5, True, info
100
+
101
+ # --- TASK 1 (MEDIUM): STATUS QUERY ---
102
+ if action.action_type == "query_order":
103
+ conn.close()
104
+ details = {"status": status, "address": address, "price": price}
105
+ # Partial reward for information retrieval
106
+ if action.order_id == "102" and status == "Shipped":
107
+ reward = 1.0
108
+ done = True
109
+ else:
110
+ reward = 0.2
111
+ return Observation(status_msg="Found", order_details=details, success=True), reward, done, info
112
+
113
+ # --- TASK 2 (EASY): ADDRESS UPDATE ---
114
+ if action.action_type == "update_address":
115
+ # Guardrail: Non-manipulatable state
116
+ if status == "Shipped":
117
+ conn.close()
118
+ return Observation(status_msg="Cannot update shipped order", success=False), 0.0, True, info
119
+
120
+ # Guardrail: Logistics Constraints
121
+ restricted = ["International", "Restricted", "No-Go Zone"]
122
+ if any(zone.lower() in action.data.lower() for zone in restricted):
123
+ conn.close()
124
+ # Reward for identifying a boundary correctly
125
+ return Observation(status_msg="Restricted Area detected", success=False), 0.5, True, info
126
+
127
+ cursor.execute("UPDATE orders SET address = ? WHERE id = ?", (action.data, action.order_id))
128
+ conn.commit()
129
+ conn.close()
130
+
131
+ # Grader: Match goal in openenv.yaml
132
+ if action.order_id == "101" and action.data == "789 Bandra West":
133
+ reward = 1.0
134
+ done = True
135
+ else:
136
+ reward = 0.5
137
+ return Observation(status_msg="Address Updated", success=True), reward, done, info
138
+
139
+ # --- TASK 3 (HARD): REFUND PROCESSING ---
140
+ if action.action_type == "issue_refund":
141
+ try:
142
+ refund_amt = float(action.data)
143
+ limit = price * 0.5
144
+
145
+ # Grader: Escalation Logic
146
+ if refund_amt > limit:
147
+ cursor.execute("UPDATE orders SET requires_human = 1 WHERE id = ?", (action.order_id,))
148
+ conn.commit()
149
+ conn.close()
150
+ # Reward for identifying high-risk correctly
151
+ return Observation(status_msg="Escalated to Human", success=True), 0.8, True, info
152
+
153
+ cursor.execute("UPDATE orders SET status = 'Refunded' WHERE id = ?", (action.order_id,))
154
+ conn.commit()
155
+ conn.close()
156
+
157
+ # Grader: Task Goal
158
+ if action.order_id == "103" and refund_amt == 250.0:
159
+ reward = 1.0
160
+ done = True
161
+ else:
162
+ reward = 0.4
163
+ return Observation(status_msg="Refund Processed", success=True), reward, done, info
164
+ except ValueError:
165
+ conn.close()
166
+ return Observation(status_msg="Invalid Amount", success=False), 0.0, False, info
167
+
168
+ conn.close()
169
+ return Observation(status_msg="Unknown Action", success=False), 0.0, False, info
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ openenv[core]>=0.2.0
2
+ fastapi>=0.115.0
3
+ uvicorn>=0.24.0
4
+
5
+
6
+