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

  1. Increase the agent timeout or make the tool faster (add internal timeouts to its HTTP/DB clients).
  2. Put a deadline inside the tool (e.g. httpx timeout, cursor timeout) so it returns cleanly instead of being abandoned mid-thread.
  3. 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

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.