Update app.py
Browse files
app.py
CHANGED
@@ -265,62 +265,96 @@ class BasicAgent:
|
|
265 |
final_state = self.workflow.invoke(state)
|
266 |
return final_state["final_answer"]
|
267 |
|
268 |
-
def
|
269 |
-
|
270 |
-
|
271 |
-
|
272 |
-
|
273 |
-
|
274 |
-
|
275 |
-
|
276 |
-
|
277 |
-
|
278 |
-
|
279 |
-
|
280 |
-
|
281 |
-
|
282 |
-
|
283 |
-
|
284 |
-
|
285 |
-
|
286 |
-
|
287 |
-
|
288 |
-
|
289 |
-
|
290 |
-
|
291 |
-
|
292 |
-
|
293 |
-
|
294 |
-
|
295 |
-
|
296 |
-
|
297 |
-
|
298 |
-
|
299 |
-
|
300 |
-
|
301 |
-
|
302 |
-
|
303 |
-
|
304 |
-
|
305 |
-
|
306 |
-
|
307 |
-
|
308 |
-
|
309 |
-
|
310 |
-
|
311 |
-
|
312 |
-
|
313 |
-
|
314 |
-
|
315 |
-
|
316 |
-
|
317 |
-
|
318 |
-
|
319 |
-
|
320 |
-
|
321 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
322 |
|
323 |
-
|
|
|
324 |
print("\nProcessing as text-only question...")
|
325 |
prompt = f"""
|
326 |
Answer this question using the materials provided.
|
@@ -342,11 +376,77 @@ QUESTION:
|
|
342 |
state["current_step"] = "done"
|
343 |
return state
|
344 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
345 |
def _build_workflow(self) -> Graph:
|
|
|
346 |
sg = StateGraph(state_schema=AgentState)
|
347 |
-
|
348 |
-
|
349 |
-
sg.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
350 |
return sg.compile()
|
351 |
|
352 |
# ----------------------------------------------------------------------------------
|
|
|
265 |
final_state = self.workflow.invoke(state)
|
266 |
return final_state["final_answer"]
|
267 |
|
268 |
+
def _process_image(self, state: AgentState) -> AgentState:
|
269 |
+
"""Process image files using LLaVA."""
|
270 |
+
try:
|
271 |
+
print(f"Downloading {state['file_url']} …")
|
272 |
+
response = SESSION.get(state["file_url"], timeout=30)
|
273 |
+
response.raise_for_status()
|
274 |
+
data = response.content
|
275 |
+
print(f"Successfully downloaded file, size: {len(data)} bytes")
|
276 |
+
|
277 |
+
print("Processing as image...")
|
278 |
+
answer = image_qa_bytes(data, state["question"])
|
279 |
+
|
280 |
+
print(f"Generated answer: {answer}")
|
281 |
+
state["final_answer"] = answer
|
282 |
+
state["current_step"] = "done"
|
283 |
+
return state
|
284 |
+
except Exception as e:
|
285 |
+
print(f"\nError processing image {state['file_url']}: {str(e)}")
|
286 |
+
state["final_answer"] = f"Error processing image: {str(e)}"
|
287 |
+
state["current_step"] = "done"
|
288 |
+
return state
|
289 |
+
|
290 |
+
def _process_video(self, state: AgentState) -> AgentState:
|
291 |
+
"""Process video files using VideoMAE."""
|
292 |
+
try:
|
293 |
+
print(f"Downloading {state['file_url']} …")
|
294 |
+
response = SESSION.get(state["file_url"], timeout=30)
|
295 |
+
response.raise_for_status()
|
296 |
+
data = response.content
|
297 |
+
print(f"Successfully downloaded file, size: {len(data)} bytes")
|
298 |
+
|
299 |
+
print("Processing as video...")
|
300 |
+
answer = video_label_bytes(data)
|
301 |
+
|
302 |
+
print(f"Generated answer: {answer}")
|
303 |
+
state["final_answer"] = answer
|
304 |
+
state["current_step"] = "done"
|
305 |
+
return state
|
306 |
+
except Exception as e:
|
307 |
+
print(f"\nError processing video {state['file_url']}: {str(e)}")
|
308 |
+
state["final_answer"] = f"Error processing video: {str(e)}"
|
309 |
+
state["current_step"] = "done"
|
310 |
+
return state
|
311 |
+
|
312 |
+
def _process_spreadsheet(self, state: AgentState) -> AgentState:
|
313 |
+
"""Process spreadsheet files."""
|
314 |
+
try:
|
315 |
+
print(f"Downloading {state['file_url']} …")
|
316 |
+
response = SESSION.get(state["file_url"], timeout=30)
|
317 |
+
response.raise_for_status()
|
318 |
+
data = response.content
|
319 |
+
print(f"Successfully downloaded file, size: {len(data)} bytes")
|
320 |
+
|
321 |
+
print("Processing as spreadsheet...")
|
322 |
+
answer = sheet_answer_bytes(data)
|
323 |
+
|
324 |
+
print(f"Generated answer: {answer}")
|
325 |
+
state["final_answer"] = answer
|
326 |
+
state["current_step"] = "done"
|
327 |
+
return state
|
328 |
+
except Exception as e:
|
329 |
+
print(f"\nError processing spreadsheet {state['file_url']}: {str(e)}")
|
330 |
+
state["final_answer"] = f"Error processing spreadsheet: {str(e)}"
|
331 |
+
state["current_step"] = "done"
|
332 |
+
return state
|
333 |
+
|
334 |
+
def _process_python(self, state: AgentState) -> AgentState:
|
335 |
+
"""Process Python files."""
|
336 |
+
try:
|
337 |
+
print(f"Downloading {state['file_url']} …")
|
338 |
+
response = SESSION.get(state["file_url"], timeout=30)
|
339 |
+
response.raise_for_status()
|
340 |
+
data = response.content
|
341 |
+
print(f"Successfully downloaded file, size: {len(data)} bytes")
|
342 |
+
|
343 |
+
print("Processing as Python file...")
|
344 |
+
answer = run_python(data.decode())
|
345 |
+
|
346 |
+
print(f"Generated answer: {answer}")
|
347 |
+
state["final_answer"] = answer
|
348 |
+
state["current_step"] = "done"
|
349 |
+
return state
|
350 |
+
except Exception as e:
|
351 |
+
print(f"\nError processing Python file {state['file_url']}: {str(e)}")
|
352 |
+
state["final_answer"] = f"Error processing Python file: {str(e)}"
|
353 |
+
state["current_step"] = "done"
|
354 |
+
return state
|
355 |
|
356 |
+
def _process_text(self, state: AgentState) -> AgentState:
|
357 |
+
"""Process text-only questions using LLM."""
|
358 |
print("\nProcessing as text-only question...")
|
359 |
prompt = f"""
|
360 |
Answer this question using the materials provided.
|
|
|
376 |
state["current_step"] = "done"
|
377 |
return state
|
378 |
|
379 |
+
def _route_to_tool(self, state: AgentState) -> str:
|
380 |
+
"""Route the state to the appropriate tool based on file type."""
|
381 |
+
if not state["file_url"]:
|
382 |
+
return "process_text"
|
383 |
+
|
384 |
+
try:
|
385 |
+
response = SESSION.get(state["file_url"], timeout=30)
|
386 |
+
response.raise_for_status()
|
387 |
+
data = response.content
|
388 |
+
|
389 |
+
# Get content type from response headers first, fallback to URL-based detection
|
390 |
+
kind = response.headers.get("Content-Type", "")
|
391 |
+
if kind in ("application/octet-stream", ""):
|
392 |
+
# rough sniff: look at the first few bytes
|
393 |
+
sig = data[:4]
|
394 |
+
if sig.startswith(b"\x89PNG"):
|
395 |
+
kind = "image/png"
|
396 |
+
elif sig.startswith(b"\xFF\xD8"):
|
397 |
+
kind = "image/jpeg"
|
398 |
+
elif sig[:2] == b"PK": # XLSX = ZIP
|
399 |
+
kind = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
400 |
+
elif not kind: # fallback if header missing
|
401 |
+
kind = mimetypes.guess_type(state["file_url"])[0] or ""
|
402 |
+
|
403 |
+
if "image" in kind:
|
404 |
+
return "process_image"
|
405 |
+
elif "video" in kind:
|
406 |
+
return "process_video"
|
407 |
+
elif "spreadsheet" in kind or "excel" in kind:
|
408 |
+
return "process_spreadsheet"
|
409 |
+
elif state["file_url"].endswith(".py"):
|
410 |
+
return "process_python"
|
411 |
+
else:
|
412 |
+
print(f"Unsupported file type: {kind}")
|
413 |
+
return "process_text"
|
414 |
+
|
415 |
+
except Exception as e:
|
416 |
+
print(f"Error determining file type: {str(e)}")
|
417 |
+
return "process_text"
|
418 |
+
|
419 |
def _build_workflow(self) -> Graph:
|
420 |
+
"""Build the workflow graph with conditional edges."""
|
421 |
sg = StateGraph(state_schema=AgentState)
|
422 |
+
|
423 |
+
# Add nodes for each tool
|
424 |
+
sg.add_node("route", self._route_to_tool)
|
425 |
+
sg.add_node("process_image", self._process_image)
|
426 |
+
sg.add_node("process_video", self._process_video)
|
427 |
+
sg.add_node("process_spreadsheet", self._process_spreadsheet)
|
428 |
+
sg.add_node("process_python", self._process_python)
|
429 |
+
sg.add_node("process_text", self._process_text)
|
430 |
+
|
431 |
+
# Set entry point
|
432 |
+
sg.set_entry_point("route")
|
433 |
+
|
434 |
+
# Add conditional edges
|
435 |
+
sg.add_conditional_edges(
|
436 |
+
"route",
|
437 |
+
{
|
438 |
+
"process_image": lambda x: x == "process_image",
|
439 |
+
"process_video": lambda x: x == "process_video",
|
440 |
+
"process_spreadsheet": lambda x: x == "process_spreadsheet",
|
441 |
+
"process_python": lambda x: x == "process_python",
|
442 |
+
"process_text": lambda x: x == "process_text"
|
443 |
+
}
|
444 |
+
)
|
445 |
+
|
446 |
+
# Set finish points for all tool nodes
|
447 |
+
for node in ["process_image", "process_video", "process_spreadsheet", "process_python", "process_text"]:
|
448 |
+
sg.set_finish_point(node)
|
449 |
+
|
450 |
return sg.compile()
|
451 |
|
452 |
# ----------------------------------------------------------------------------------
|