OppaAI commited on
Commit
dcc51ac
·
verified ·
1 Parent(s): a837225

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +19 -244
app.py CHANGED
@@ -1,252 +1,27 @@
1
- # app.py
2
- import os
3
- import base64
4
- import json
5
- import gradio as gr
6
- from huggingface_hub import upload_file, InferenceClient
7
- from datetime import datetime
8
- import traceback
9
- import threading
10
- from typing import Optional, Dict, Any, Tuple
11
-
12
- from fastmcp import FastMCP
13
-
14
-
15
- HF_DATASET_REPO = "OppaAI/Robot_MCP"
16
- HF_VLM_MODEL = "Qwen/Qwen2.5-VL-7B-Instruct"
17
-
18
- mcp = FastMCP("Robot_MCP")
19
-
20
- # -----------------------------------------------------
21
- # Register Robot Tools (MCP)
22
- # -----------------------------------------------------
23
- @mcp.tool()
24
- def speak(text: str, emotion: str = "neutral"):
25
- """Robot speech output"""
26
- return {
27
- "status": "success",
28
- "action_executed": "speak",
29
- "payload": {"text": text, "emotion": emotion},
30
- }
31
-
32
-
33
- @mcp.tool()
34
- def navigate(direction: str, distance_meters: float):
35
- """Move robot safely"""
36
- if distance_meters > 5.0:
37
- return {"status": "error", "message": "Safety limit exceeded"}
38
- return {
39
- "status": "success",
40
- "action_executed": "navigate",
41
- "payload": {"direction": direction, "distance": distance_meters},
42
- }
43
-
44
-
45
- @mcp.tool()
46
- def scan_hazard(hazard_type: str, severity: str):
47
- """Hazard scan + log"""
48
- timestamp = datetime.now().isoformat()
49
- return {
50
- "status": "warning_logged",
51
- "log": f"[{timestamp}] HAZARD: {hazard_type} (Severity: {severity})",
52
- }
53
-
54
-
55
- @mcp.tool()
56
- def analyze_human(clothing_color: str, estimated_action: str):
57
- """Human detection description"""
58
- return {
59
- "status": "human_tracked",
60
- "details": f"Human wearing {clothing_color} is {estimated_action}",
61
- }
62
-
63
-
64
- # -----------------------------------------------------
65
- # Save and Upload Image
66
- # -----------------------------------------------------
67
- def save_and_upload_image(image_b64: str, hf_token: str):
68
- try:
69
- image_bytes = base64.b64decode(image_b64)
70
- size_bytes = len(image_bytes)
71
- print("[debug] decoded image bytes:", size_bytes)
72
-
73
- timestamp = datetime.now().strftime("%Y%m%d_%H%M%S_%f")
74
- local_path = f"/tmp/robot_img_{timestamp}.jpg"
75
-
76
- with open(local_path, "wb") as f:
77
- f.write(image_bytes)
78
-
79
- print("[debug] wrote local tmp file:", local_path)
80
-
81
- filename = f"robot_{timestamp}.jpg"
82
-
83
- upload_file(
84
- path_or_fileobj=local_path,
85
- path_in_repo=filename,
86
- repo_id=HF_DATASET_REPO,
87
- token=hf_token,
88
- repo_type="dataset",
89
- )
90
-
91
- print("[debug] upload successful:", filename)
92
-
93
- url = f"https://huggingface.co/datasets/{HF_DATASET_REPO}/resolve/main/{filename}"
94
- return local_path, url, filename, size_bytes
95
-
96
- except Exception:
97
- traceback.print_exc()
98
- return None, None, None, 0
99
-
100
-
101
- # -----------------------------------------------------
102
- # JSON Parsing Helper
103
- # -----------------------------------------------------
104
- def safe_parse_json_from_text(text: str):
105
- if not text:
106
- return None
107
- try:
108
- return json.loads(text)
109
- except:
110
- pass
111
-
112
- cleaned = text.strip().strip("`")
113
- try:
114
- start = cleaned.find("{")
115
- end = cleaned.rfind("}")
116
- if start >= 0 and end > start:
117
- return json.loads(cleaned[start : end + 1])
118
- except:
119
- pass
120
-
121
- return None
122
-
123
-
124
- # -----------------------------------------------------
125
- # Only allow tools from MCP registry
126
- # -----------------------------------------------------
127
- def validate_and_call_tool(tool_name: str, tool_args: dict):
128
- if tool_name not in mcp.tools:
129
- return {"error": f"Unknown or unauthorized tool '{tool_name}'"}
130
- try:
131
- return mcp.tools[tool_name](**tool_args)
132
- except Exception as e:
133
- traceback.print_exc()
134
- return {"error": f"Tool error: {str(e)}"}
135
-
136
-
137
- # -----------------------------------------------------
138
- # Main Pipeline
139
- # -----------------------------------------------------
140
- def process_and_describe(payload):
141
-
142
- if isinstance(payload, str):
143
- try:
144
- payload = json.loads(payload)
145
- except:
146
- return {"error": "Invalid JSON payload"}
147
-
148
- print("\n========== NEW REQUEST ==========")
149
- print("[debug] Incoming payload:", payload)
150
-
151
- hf_token = payload.get("hf_token")
152
- if not hf_token:
153
- return {"error": "hf_token missing"}
154
-
155
- robot_id = payload.get("robot_id", "unknown")
156
- image_b64 = payload.get("image_b64")
157
- if not image_b64:
158
- return {"error": "image_b64 missing"}
159
-
160
- # Save + Upload
161
- local_tmp_path, hf_url, filename, size_bytes = save_and_upload_image(
162
- image_b64, hf_token
163
- )
164
-
165
- if not hf_url:
166
- return {"error": "Image upload failed"}
167
-
168
- print("[debug] HF image URL:", hf_url)
169
-
170
- # VLM SYSTEM PROMPT
171
- system_prompt = """
172
- Respond in STRICT JSON ONLY. Format:
173
- {
174
- "description": "short visual description",
175
- "tool_name": "one of: speak, navigate, scan_hazard, analyze_human",
176
- "arguments": { ... }
177
- }
178
- """
179
-
180
- messages = [
181
- {"role": "system", "content": system_prompt},
182
- {
183
- "role": "user",
184
- "content": [
185
- {"type": "text", "text": "Analyze the image and choose ONE tool."},
186
- {
187
- "type": "image_url",
188
- "image_url": {"url": f"data:image/jpeg;base64,{image_b64}"},
189
- },
190
- ],
191
- },
192
- ]
193
-
194
- # VLM CALL
195
- print("[debug] Calling VLM model...")
196
- client = InferenceClient(token=hf_token)
197
-
198
- response = client.chat.completions.create(
199
- model=HF_VLM_MODEL,
200
- messages=messages,
201
- max_tokens=300,
202
- temperature=0.1,
203
- )
204
-
205
- vlm_output = response.choices[0].message.content.strip()
206
-
207
- print("\n------ VLM RAW OUTPUT ------")
208
- print(vlm_output)
209
- print("------ END VLM RAW ------\n")
210
-
211
- parsed = safe_parse_json_from_text(vlm_output)
212
-
213
- if parsed is None:
214
- return {
215
- "status": "model_no_json",
216
- "robot_id": robot_id,
217
- "image_url": hf_url,
218
- "vlm_raw": vlm_output,
219
- "message": "VLM returned invalid JSON",
220
- }
221
-
222
- tool_name = parsed.get("tool_name")
223
- tool_args = parsed.get("arguments") or {}
224
-
225
- tool_result = validate_and_call_tool(tool_name, tool_args)
226
-
227
- return {
228
- "status": "success",
229
- "robot_id": robot_id,
230
- "image_url": hf_url,
231
- "file_size_bytes": size_bytes,
232
- "vlm_description": parsed.get("description"),
233
- "chosen_tool": tool_name,
234
- "tool_arguments": tool_args,
235
- "tool_execution_result": tool_result,
236
- "vlm_raw": vlm_output,
237
- }
238
-
239
-
240
- # -----------------------------------------------------
241
- # Gradio Interface + MCP Serve
242
- # -----------------------------------------------------
243
  iface = gr.Interface(
244
  fn=process_and_describe,
245
  inputs=gr.JSON(label="Input JSON"),
246
  outputs=gr.JSON(label="Output JSON"),
247
  api_name="predict",
248
- allow_flagging="never",
249
  )
250
 
 
 
 
251
  if __name__ == "__main__":
252
- mcp.run_gradio(iface)
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ------------------------------
2
+ # Gradio Interface
3
+ # ------------------------------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  iface = gr.Interface(
5
  fn=process_and_describe,
6
  inputs=gr.JSON(label="Input JSON"),
7
  outputs=gr.JSON(label="Output JSON"),
8
  api_name="predict",
9
+ flagging_mode="never"
10
  )
11
 
12
+ # ------------------------------
13
+ # Main Entry
14
+ # ------------------------------
15
  if __name__ == "__main__":
16
+ # Start MCP server in background thread
17
+ import threading
18
+
19
+ def run_mcp():
20
+ print("[MCP] Starting MCP server...")
21
+ mcp.run() # <--- THIS is correct
22
+
23
+ threading.Thread(target=run_mcp, daemon=True).start()
24
+
25
+ # Start Gradio normally (HF Space auto serves)
26
+ print("[Gradio] Launching interface...")
27
+ iface.launch(server_name="0.0.0.0", server_port=7860)