-
Notifications
You must be signed in to change notification settings - Fork 785
Description
I have encountered two related design issues regarding how function execution limits are enforced. These behaviors make it difficult to safely deploy agents in long-running (e.g., web server) environments using standard patterns.
1. The max_invocations "Time Bomb" (State Scope Issue)
The max_invocations counter is stored on the Tool/AIFunction instance itself (self.invocation_count).
I assume that when using the standard @ai_function decorator at a module level the tool instance is global and persistent. This is definitely the case if registering tool through dependency injection as singleton (DI is preferred for proper testing). In a long-running application, this counter never resets between requests.
# Defined at module level (Singleton)
@ai_function(max_invocations=100)
def search_tool(query: str): ...
# Request A (Day 1): Uses 50 calls. (Tool count: 50)
# Request B (Day 2): Uses 50 calls. (Tool count: 100)
# Request C (Day 3): CRASH. ToolException limit reached.Implication: Setting max_invocations creates a "time bomb" that guarantees functionality failure after N calls globally, rather than rate-limiting per request/conversation or somehow externally (based on actual cost calculations).
2. max_iterations vs. Actual Tool Usage
The FunctionInvocationConfiguration.max_iterations setting (default 40) limits LLM roundtrips/turns, not actual function invocations.
The _tools.py code shows that _try_execute_function_calls is called from a max iterations bound loop in _handle_function_calls_response. Because _try_execute_function_calls triggers several tools in parallel this means that FunctionInvocationConfiguration.max_iterations does not limit function invocation as stated and the following can happen:
config.max_iterations = 3
# Iteration 1: LLM requests 10 parallel searches -> 10 executions
# Iteration 2: LLM requests 10 parallel searches -> 10 executions
# Iteration 3: LLM requests 10 parallel searches -> 10 executions
# Result: 3 Iterations, but 30+ Function Calls.Implication: max_iterations does not protect against cost overruns or runaway execution loops within a single turn.