Repository navigation
Description
Description
For an async FunctionTool, exceptions raised inside the coroutine body do not increment invocation_exception_count. As a result, max_invocation_exceptions is never enforced for async tools, although the public documentation describes it as the maximum number of exceptions allowed during invocations.
This can allow a failing async tool to be executed repeatedly after its configured failure budget is exhausted. The separate max_consecutive_errors_per_request loop setting is not equivalent: it is request-scoped and does not enforce the per-tool lifetime limit.
Reproduction
Run against upstream/main at c37de519b:
import asyncio
from agent_framework import FunctionTool
async def always_fails() -> str:
raise RuntimeError("boom")
async def main() -> None:
tool = FunctionTool(
name="always_fails",
func=always_fails,
max_invocation_exceptions=1,
)
for attempt in range(1, 4):
try:
await tool.invoke(skip_parsing=True)
except Exception as exc:
print(
attempt,
type(exc).__name__,
tool.invocation_count,
tool.invocation_exception_count,
)
asyncio.run(main())Observed:
1 RuntimeError 1 0
2 RuntimeError 2 0
3 RuntimeError 3 0
For comparison, an equivalent synchronous function produces:
1 RuntimeError 1 1
2 ToolException 1 1
Expected behavior
After the first async invocation failure, the second invocation should raise ToolException when max_invocation_exceptions=1, and invocation_exception_count should report the failure.
Suspected cause
FunctionTool.__call__ catches exceptions only while creating the function result. For an async function, func(...) returns a coroutine successfully; the exception is raised later by await res in _invoke_function, outside the counter's try/except block.
Relevant code:
python/packages/core/agent_framework/_tools.py:595-632python/packages/core/agent_framework/_tools.py:421-422
The repository already supports async tools through FunctionTool.invoke, and the option is not documented as sync-only.
Impact
Any application using async tools with max_invocation_exceptions can exceed the configured failure budget, potentially repeating failing or side-effectful network/database/MCP operations.
Metadata
Metadata
Labels
Type
Projects
- StatusShow more project fieldsDone
Activity
github-actions commentedon Sep 11, 2026
🤖 Automated triage reproduction notes (agent-authored — trust but verify)
Agent analysis
The bug reproduces in
python/packages/core/agent_framework/_tools.py::FunctionTool._invoke_functionat lines 624-632 when an async wrapped function raises from its coroutine body.FunctionTool.__call__returns the coroutine before the exception occurs, soinvocation_exception_countremains zero and later calls bypassmax_invocation_exceptions. Minimal repro: wrap an always-raising async function withmax_invocation_exceptions=1, invoke it twice withskip_parsing=True, and observe that both calls raiseRuntimeErrorinstead of the second raisingToolException.python/packages/core/tests/core/test_tools.py::test_async_tool_enforces_max_invocation_exceptions1.18.0