{"record":{"id":"ac956ea94b544472","repo":"datawhalechina/hello-agents","slug":"tool-name-ac956e","errorCode":null,"errorMessage":"工具 '{tool_name}' 执行超时","messagePattern":"工具 '(.+?)' 执行超时","errorType":"exception","errorClass":"TimeoutException","httpStatus":500,"severity":"error","filePath":"Co-creation-projects/Apricity-InnocoreAI/agents/base.py","lineNumber":82,"sourceCode":"            tool_func = self.tools[tool_name][\"function\"]\n            if asyncio.iscoroutinefunction(tool_func):\n                result = await asyncio.wait_for(\n                    tool_func(tool_input), \n                    timeout=self.timeout\n                )\n            else:\n                result = await asyncio.wait_for(\n                    asyncio.to_thread(tool_func, tool_input),\n                    timeout=self.timeout\n                )\n            \n            self._add_to_history(f\"Tool {tool_name} called with input: {tool_input}\")\n            self._add_to_history(f\"Tool {tool_name} result: {result}\")\n            \n            return result\n            \n        except asyncio.TimeoutError:\n            raise TimeoutException(f\"工具 '{tool_name}' 执行超时\")\n        except Exception as e:\n            raise AgentException(f\"工具 '{tool_name}' 执行失败: {str(e)}\")\n    \n    async def think(self, prompt: str, context: Dict = None) -> str:\n        \"\"\"调用LLM进行思考\"\"\"\n        try:\n            # 构建完整的提示词\n            full_prompt = prompt\n            \n            # 添加上下文信息\n            if context:\n                context_str = json.dumps(context, ensure_ascii=False, indent=2)\n                full_prompt = f\"上下文信息:\\n{context_str}\\n\\n任务:\\n{prompt}\"\n            \n            # 添加历史记录\n            if self.history:\n                history_str = \"\\n\".join(self.history[-10:])  # 只保留最近10条\n                full_prompt += f\"\\n\\n历史记录:\\n{history_str}\"","sourceCodeStart":64,"sourceCodeEnd":100,"githubUrl":"https://github.com/datawhalechina/hello-agents/blob/606a07d341a47be773fab7f4b71177f53f96b2c3/Co-creation-projects/Apricity-InnocoreAI/agents/base.py#L64-L100","documentation":"TimeoutException from BaseAgent.call_tool: asyncio.wait_for wrapped the tool coroutine (or asyncio.to_thread for sync tools) with self.timeout seconds, the deadline elapsed, and wait_for cancelled the task. The message names the tool that hung. Important caveat: for to_thread tools the underlying thread cannot be force-killed, so it may keep running after the exception.","triggerScenarios":"Calling a network-bound tool (ArXiv/IEEE search, PDF download) against a slow or stalled endpoint with the default agent timeout; a sync tool doing blocking I/O inside to_thread while the event loop waits; tool deadlocking on a lock or unbounded feedparser parse of a huge response.","commonSituations":"Default timeout too low for arXiv's occasionally slow API; aiohttp session without its own sock_read timeout so the coroutine never progresses; tools doing requests.get (blocking) instead of aiohttp; thread-pool saturation making even fast tools exceed the budget.","solutions":["Raise self.timeout for network tools or make it per-tool configurable (e.g. timeout=120 for download-heavy tools).","Set aiohttp.ClientTimeout(total=..., sock_read=30) inside the tool so it fails fast with its own clear error before the agent deadline.","Replace blocking requests with async clients so wait_for's cancellation actually stops the work.","Retry transient network hangs with a bounded retry loop inside the tool (backoff on the API call, not the whole agent step).","For CPU-bound sync tools, move heavy parsing into a subprocess if cancellation must actually stop it."],"exampleFix":"# before\nasync with session.get(self.arxiv_base_url, params=params) as response:\n    ...  # no inner timeout; agent-level wait_for fires\n\n# after — tool owns its timeout so it fails (and retries) before the agent deadline\ntimeout = aiohttp.ClientTimeout(total=30, sock_read=10)\nasync with aiohttp.ClientSession(timeout=timeout) as session:\n    for attempt in range(3):\n        try:\n            async with session.get(self.arxiv_base_url, params=params) as response:\n                ...\n            break\n        except aiohttp.ClientError:\n            if attempt == 2:\n                raise","handlingStrategy":"retry","validationCode":"# Verify tool timeouts are sane before running the agent\nassert agent.timeout >= 30, f\"timeout={agent.timeout}s too low for network tools\"\nfor name, info in agent.tools.items():\n    if asyncio.iscoroutinefunction(info['function']):\n        continue  # async tools can be cancelled cleanly\n    log.warning(f\"sync tool '{name}' blocks the loop thread if it hangs\")","typeGuard":null,"tryCatchPattern":"from agents.exceptions import TimeoutException\ntry:\n    result = await agent.call_tool('search_arxiv', q)\nexcept TimeoutException as e:\n    result = await with_backoff(lambda: agent.call_tool('search_arxiv', q), retries=2)","preventionTips":["Give each tool its own timeout sized to its latency profile instead of one agent-wide value.","Set client-level timeouts inside network tools so they fail before the agent deadline.","Prefer async I/O in tools so wait_for cancellation actually stops the work."],"tags":["python","asyncio","timeout","agent","network"],"backgroundTag":null,"analyzedSha":"606a07d341a47be773fab7f4b71177f53f96b2c3","analyzedAt":"2026-08-14T22:57:27.446Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}