async_runner.py 2.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172
  1. """find_agent 异步运行与资源清理。"""
  2. from __future__ import annotations
  3. import asyncio
  4. import logging
  5. import threading
  6. from supply_agent.agent.core import Agent
  7. from supply_agent.types import AgentResult
  8. logger = logging.getLogger(__name__)
  9. _thread_loop = threading.local()
  10. async def _drain_event_loop() -> None:
  11. """等待 httpx/OpenAI 等异步客户端的收尾任务完成。"""
  12. loop = asyncio.get_running_loop()
  13. for _ in range(5):
  14. pending = [
  15. task
  16. for task in asyncio.all_tasks(loop)
  17. if task is not asyncio.current_task() and not task.done()
  18. ]
  19. if not pending:
  20. break
  21. results = await asyncio.gather(*pending, return_exceptions=True)
  22. for result in results:
  23. if isinstance(result, Exception) and not isinstance(
  24. result, asyncio.CancelledError
  25. ):
  26. logger.debug("pending async task error during drain: %s", result)
  27. await asyncio.sleep(0)
  28. async def _close_agent_async_resources(agent: Agent) -> None:
  29. """在事件循环关闭前显式释放异步 HTTP 连接。"""
  30. async_client = getattr(agent.llm, "_async_client", None)
  31. if async_client is not None:
  32. try:
  33. await async_client.close()
  34. except Exception:
  35. logger.debug("close async llm client failed", exc_info=True)
  36. await _drain_event_loop()
  37. async def arun_find_agent(agent: Agent, user_input: str) -> AgentResult:
  38. """在单个事件循环内运行 find_agent 并确保资源释放。"""
  39. try:
  40. return await agent.arun(user_input)
  41. finally:
  42. await _close_agent_async_resources(agent)
  43. def _run_coroutine(coro) -> AgentResult:
  44. """同步入口:主线程用 asyncio.run,worker 线程复用线程级 loop。"""
  45. try:
  46. asyncio.get_running_loop()
  47. except RuntimeError:
  48. pass
  49. else:
  50. raise RuntimeError("run_find_agent 不能在已运行的事件循环内调用")
  51. if threading.current_thread() is threading.main_thread():
  52. return asyncio.run(coro)
  53. loop = getattr(_thread_loop, "loop", None)
  54. if loop is None or loop.is_closed():
  55. loop = asyncio.new_event_loop()
  56. _thread_loop.loop = loop
  57. asyncio.set_event_loop(loop)
  58. return loop.run_until_complete(coro)