Spaces:
Sleeping
Sleeping
Fix cancel-scope error: run the async agent on a dedicated worker thread
Browse files
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 |
-
|
| 521 |
-
async for tup in _agentic_answer(question):
|
| 522 |
-
yield tup
|
| 523 |
|
| 524 |
-
|
| 525 |
-
|
| 526 |
-
loop = asyncio.new_event_loop()
|
| 527 |
-
asyncio.set_event_loop(loop)
|
| 528 |
-
try:
|
| 529 |
-
gen = _drive().__aiter__()
|
| 530 |
-
while True:
|
| 531 |
try:
|
| 532 |
-
|
| 533 |
-
|
| 534 |
-
return
|
| 535 |
except _AgentError as exc:
|
| 536 |
-
|
| 537 |
-
return
|
| 538 |
except Exception as exc: # network blip, MCP transport
|
| 539 |
-
|
| 540 |
-
|
| 541 |
-
|
| 542 |
-
|
| 543 |
-
|
| 544 |
-
|
| 545 |
-
|
| 546 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 -----------------------------------------------------------------------
|