datawhalechina/hello-agents · error · TimeoutException
工具 '{tool_name}' 执行超时
Error message
工具 '{tool_name}' 执行超时 What it means
call_tool runs the tool function (awaited directly if a coroutine, else via asyncio.to_thread) under asyncio.wait_for with self.timeout; asyncio.TimeoutError is re-raised as TimeoutException('工具 ... 执行超时'). The tool function itself is not cancelled cooperatively — the await gives up, but a blocked to_thread call may still occupy its worker thread.
Source
Thrown at Co-creation-projects/Shawnxyxy-HealthRecordAgent/backend/agents/base.py:189
tool_func = self.tools[tool_name]["function"]
if asyncio.iscoroutinefunction(tool_func):
result = await asyncio.wait_for(
tool_func(tool_input),
timeout=self.timeout
)
else:
result = await asyncio.wait_for(
asyncio.to_thread(tool_func, tool_input),
timeout=self.timeout
)
self._add_to_history(f"Tool {tool_name} called with input: {tool_input}")
self._add_to_history(f"Tool {tool_name} result: {result}")
return result
except asyncio.TimeoutError:
raise TimeoutException(f"工具 '{tool_name}' 执行超时")
except Exception as e:
raise AgentException(f"工具 '{tool_name}' 执行失败: {str(e)}")
# ========== 状态 & 历史 ==========
def _add_to_history(self, message: str):
"""添加到历史记录"""
timestamp = datetime.now().isoformat()
self.history.append(f"[{timestamp}] {message}")
# 限制历史记录长度
if len(self.history) > 100:
self.history = self.history[-50:]
def get_history(self, limit: int = 10) -> List[str]:
"""获取历史记录"""
return self.history[-limit:]
def clear_history(self):
"""清空历史记录"""View on GitHub (pinned to 606a07d341)
Solutions
- Increase the agent timeout or make the tool faster (add internal timeouts to its HTTP/DB clients).
- Put a deadline inside the tool (e.g. httpx timeout, cursor timeout) so it returns cleanly instead of being abandoned mid-thread.
- Catch TimeoutException in the agent loop and either retry once or report a graceful 'tool unavailable' result to the LLM.
Example fix
# before
def fetch_report(cid):
return requests.get(f'{API}/reports/{cid}', timeout=None).json() # can hang
agent.add_tool('fetch_report', fetch_report)
# after
def fetch_report(cid):
return requests.get(f'{API}/reports/{cid}', timeout=10).json() # bounded inside the tool too Defensive patterns
Strategy: retry
Try / catch
try:
r = await agent.call_tool(name, tool_input)
except TimeoutException:
r = f'tool {name} timed out; try different arguments' # graceful signal to the LLM Prevention
- Give every tool its own internal deadline (HTTP/DB client timeouts) shorter than the agent timeout.
- Keep tool work chunked/idempotent so a timeout-then-retry does not duplicate side effects.
- Track per-tool latency and raise the agent timeout only for genuinely slow tools.
When it happens
Trigger: A tool doing slow synchronous I/O (HTTP request to a slow API, big DB query) inside asyncio.to_thread exceeding the timeout; a coroutine tool awaiting a hung network call; the shared timeout being too small for legitimately slow tools like file scans or external lookups.
Common situations: Third-party medical/API endpoints with high latency; per-tool timeouts being forced to share one agent-level timeout; thread pool starvation when several to_thread tools hang.
Related errors
AI-assisted analysis of datawhalechina/hello-agents@606a07d341 (2026-08-14).
Data as JSON: /api/errors/37f77f19a633f109.
Report an issue: GitHub.