Beemer0 commited on
Commit
ba4d952
·
verified ·
1 Parent(s): fdd7b3e

Fix cancel-scope error: run the async agent on a dedicated worker thread

Browse files
Files changed (1) hide show
  1. app.py +45 -24
app.py CHANGED
@@ -14,7 +14,9 @@ secrets. Run locally with: python app.py
14
  import asyncio
15
  import json
16
  import os
 
17
  import sys
 
18
  import urllib.error
19
  import urllib.request
20
  from datetime import timedelta
@@ -510,40 +512,59 @@ async def _agentic_answer(question: str):
510
  ANSWER_PLACEHOLDER = "*Your answer will appear here.*"
511
 
512
 
 
 
 
513
  def answer(question: str):
514
- """Generator wrapping the async agent for Gradio's progressive UI."""
 
 
 
 
 
 
 
 
515
  question = (question or "").strip()
516
  if not question:
517
  yield "Please enter a legal question above.", ANSWER_PLACEHOLDER, ""
518
  return
519
 
520
- async def _drive():
521
- async for tup in _agentic_answer(question):
522
- yield tup
523
 
524
- # Bridge the async generator to a sync generator. We run a single asyncio
525
- # task per submission; Gradio's queue serializes them per user session.
526
- loop = asyncio.new_event_loop()
527
- asyncio.set_event_loop(loop)
528
- try:
529
- gen = _drive().__aiter__()
530
- while True:
531
  try:
532
- status, ans, sources = loop.run_until_complete(gen.__anext__())
533
- except StopAsyncIteration:
534
- return
535
  except _AgentError as exc:
536
- yield (f"**{exc}**", ANSWER_PLACEHOLDER, "")
537
- return
538
  except Exception as exc: # network blip, MCP transport
539
- yield (f"**Could not complete the request.**\n\n"
540
- f"`{type(exc).__name__}: {exc}`\n\n"
541
- "The MCP service may be waking from sleep -- try again "
542
- "in a moment.", ANSWER_PLACEHOLDER, "")
543
- return
544
- yield status, ans, sources
545
- finally:
546
- loop.close()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
547
 
548
 
549
  # --- UI -----------------------------------------------------------------------
 
14
  import asyncio
15
  import json
16
  import os
17
+ import queue
18
  import sys
19
+ import threading
20
  import urllib.error
21
  import urllib.request
22
  from datetime import timedelta
 
512
  ANSWER_PLACEHOLDER = "*Your answer will appear here.*"
513
 
514
 
515
+ _SENTINEL = object()
516
+
517
+
518
  def answer(question: str):
519
+ """Generator wrapping the async agent for Gradio's progressive UI.
520
+
521
+ The async work runs on a dedicated worker thread with its own event loop
522
+ and stays inside a single asyncio task for the whole question. Items are
523
+ handed back to this sync generator through a thread-safe queue. The
524
+ previous loop.run_until_complete-per-anext pattern created a fresh task
525
+ on every yield, which tripped anyio's cancel-scope check inside the MCP
526
+ streamable-HTTP client ('Attempted to exit cancel scope in a different
527
+ task than it was entered in')."""
528
  question = (question or "").strip()
529
  if not question:
530
  yield "Please enter a legal question above.", ANSWER_PLACEHOLDER, ""
531
  return
532
 
533
+ events: queue.Queue = queue.Queue()
 
 
534
 
535
+ def worker():
536
+ async def run():
 
 
 
 
 
537
  try:
538
+ async for tup in _agentic_answer(question):
539
+ events.put(("yield", tup))
 
540
  except _AgentError as exc:
541
+ events.put(("agent_error", exc))
 
542
  except Exception as exc: # network blip, MCP transport
543
+ events.put(("error", exc))
544
+ finally:
545
+ events.put((_SENTINEL,))
546
+ try:
547
+ asyncio.run(run())
548
+ except Exception as exc: # loop setup failures
549
+ events.put(("error", exc))
550
+ events.put((_SENTINEL,))
551
+
552
+ threading.Thread(target=worker, daemon=True).start()
553
+
554
+ while True:
555
+ kind, *payload = events.get()
556
+ if kind is _SENTINEL:
557
+ return
558
+ if kind == "yield":
559
+ yield payload[0]
560
+ elif kind == "agent_error":
561
+ yield (f"**{payload[0]}**", ANSWER_PLACEHOLDER, "")
562
+ elif kind == "error":
563
+ exc = payload[0]
564
+ yield (f"**Could not complete the request.**\n\n"
565
+ f"`{type(exc).__name__}: {exc}`\n\n"
566
+ "The MCP service may be waking from sleep -- try again "
567
+ "in a moment.", ANSWER_PLACEHOLDER, "")
568
 
569
 
570
  # --- UI -----------------------------------------------------------------------