tools / qwen_bridge.py
Adapts Ghost Agent's async tools to Qwen Agent's synchronous BaseTool interface, threading the runtime context through ContextVars.
Why
Qwen Agent's ReAct loop calls tools synchronously. Ghost Agent's tools are async and need access to live context (sandbox, memory, scratchpad). The bridge:
- Detects whether an event loop is already running and, if so, runs the coroutine in a worker thread with a fresh loop (
_run_coro_blocking, line 14-55). - Threads the active
GhostContextviacontextvars.ContextVar(_CTX), so sibling async tasks each see their own context (line 58-88).
Bridged tools
| Qwen tool class | Backend |
|---|---|
GhostFileSystem.call() | tool_file_system |
GhostExecute.call() | tool_execute |
GhostKnowledgeBase.call() | tool_knowledge_base |
Named parameters are extracted from the Qwen call; unknown extras are passed via **extra.
Back-compat
GLOBAL_CONTEXT is exposed as a property that returns _CTX.get(), so older callsites continue to work without per-task isolation.
Transitive soundfile dependency
qwen-agent's utils/utils.py does import soundfile at module load, so importing anything under qwen_agent.* (including qwen_agent.tools.base used by this module) fails with ModuleNotFoundError: No module named 'soundfile' unless the transitive dep is installed. The default agent path (ghost_agent.main) does NOT touch qwen_bridge; only the core/agent_qwen.py variant does.
soundfile>=0.12.0is pinned inrequirements.txtso fresh installs never hit the bug.- The module wraps the
from qwen_agent.tools.base import ...line in atry/exceptthat re-raises asImportErrorwith an actionable message (pip install instruction, Linuxlibsndfile1note, clarification that the default path is unaffected). - On Linux the system package
libsndfile1is also required:sudo apt-get install libsndfile1. macOS / Windows wheels ship libsndfile bundled.
Covered by tests/test_qwen_bridge_import_error.py.