Skip to content

Add MaximumFunctionCallsPerRequest to toolautocall (#1202) - #1331

Open
Lokesh Nelapati (Lokesh118) wants to merge 1 commit into
microsoft:mainfrom
Lokesh118:issue-1202
Open

Lokesh Nelapati (Lokesh118) wants to merge 1 commit into
microsoft:mainfrom
Lokesh118:issue-1202

Conversation

@Lokesh118

@Lokesh118 Lokesh Nelapati (Lokesh118) commented Oct 27, 2026 •

Copy link
Copy Markdown

Description

Adds MaximumFunctionCallsPerRequest to toolautocall.Config to allow callers to cap the total cumulative number of tool invocations performed in a single run (addressing #1202 and achieving parity with Python's max_function_calls).

Summary of Changes

  1. Configuration:

    • Added MaximumFunctionCallsPerRequest *int to toolautocall.Config.
    • Validates that negative values return a configuration error (MaximumFunctionCallsPerRequest must be 0 or greater).
    • nil preserves the default unlimited behavior.
    • 0 disables automatic function calls while allowing the model to return a final response without schema tools.
  2. Accounting, Batch Handling & Review Follow-ups:

    • Tracks cumulative actual execution attempts (totalFunctionCalls) across iterations.
    • Includes executions that resulted in errors or panics.
    • Excludes functionInvocationStatusNotFound (unknown tools) from execution attempts so models can retry real tools when TerminateOnUnknownCalls=false.
    • Ignores provider-handled function calls (which already contain FunctionResultContent in streamed updates and are not locally executed).
    • Checks budget before dispatching approved calls on re-entry, emitting valid non-execution error results if budget is already exhausted so conversation history remains valid without unwanted tool execution.
    • Prevents auto-approval bypassing when the budget limit is reached.
    • Clears named required tool choice in prepareOptionsForLastIteration when its associated schema tool is stripped even if non-schema tools remain.
    • Shares consumed tool call counts across toolapproval middleware re-entries within the same agent.Run via run-scoped session tracking, and resets cleanly for independent runs.
  3. Tests:

    • TestFunctionInvoking_NegativeMaximumFunctionCallsReturnsError
    • TestFunctionInvoking_ZeroMaximumFunctionCallsDisablesAutomaticCallsAndReturnsFinalResponse
    • TestFunctionInvoking_MaximumFunctionCallsCapsSingleCallsPerIteration
    • TestFunctionInvoking_MaximumFunctionCallsAllowsCurrentBatchToFinish
    • TestFunctionInvoking_MaximumFunctionCallsCountsFailedExecutions
    • TestFunctionInvoking_MaximumFunctionCallsCountsPanickingExecutions
    • TestFunctionInvoking_MaximumFunctionCallsIgnoresNotFoundCalls
    • TestFunctionInvoking_MaximumFunctionCallsIgnoresProviderHandledCalls
    • TestFunctionInvoking_MaximumFunctionCallsIncludesApprovedCalls
    • TestFunctionInvoking_ResumedApprovalWithZeroBudgetSkipsExecution
    • TestFunctionInvoking_ZeroBudgetClearsRequiredToolChoiceWhenSchemaToolRemoved
    • TestToolApproval_SharesMaximumFunctionCallsAcrossAutoApprovedReEntries
    • Ran full test suite (go test ./...) and race detector (go test -race ./agent/harness/...).

Fixes #1202

@github-actions github-actions Bot added area:agent Changes files in the agent area parity-approved Go API consistency review found no parity issues public-api-change Pull Request changes public APIs labels Oct 27, 2026
@github-actions

This comment has been minimized.

@github-actions github-actions Bot removed the parity-approved Go API consistency review found no parity issues label Oct 29, 2026
@github-actions

This comment has been minimized.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Tool-call budget enforcement can incorrectly block retries, allow approved calls beyond the limit, and leave an invalid forced tool choice when schema tools are removed.

4 open issues
What changed in this PR

Adds a configurable tool-call budget to toolautocall, complementing its existing iteration limit and addressing the cost-control use case in #1202.

Changes:

  • Adds MaximumFunctionCallsPerRequest, with negative-value validation, an unlimited default, and zero-budget handling.
  • Tracks calls across tool rounds, including failures and approved calls, and removes schema tools once the budget is reached while allowing active batches to finish.
  • Adds tests for limits, batch overshoot, failed executions, provider-handled calls, and approval resumption.
File Description
agent/​harness/​toolautocall/​autocall.go Adds configuration, call accounting, and budget-based loop termination.
agent/​harness/​toolautocall/​autocall_test.go Tests validation, budget boundaries, batch completion, and execution accounting.
agent/​harness/​toolautocall/​autocall_approval_test.go Tests counting approved calls toward the budget.

Review effort: Max

Comment thread agent/harness/toolautocall/autocall.go Resolved
Comment thread agent/harness/toolautocall/autocall.go Resolved
Comment thread agent/harness/toolautocall/autocall.go Resolved
Comment thread agent/harness/toolautocall/autocall.go Resolved
@Lokesh118

Copy link
Copy Markdown
Author

@microsoft-github-policy-service agree

@github-actions

This comment has been minimized.

@Lokesh118
Lokesh Nelapati (Lokesh118) marked this pull request as ready for review November 1, 2026 23:38
@Lokesh118
Lokesh Nelapati (Lokesh118) requested a review from a team as a code owner November 1, 2026 23:38
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

Comment thread agent/agent.go Outdated

// RunToken returns an opaque unique identity for the current agent run,
// or nil if the context was not created by an agent run.
func RunToken(ctx context.Context) any {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't like passing params around in the context, unless the value they provide is high. I don't think this meets the bar. Any alternative design?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the feedback! Updated the design.

I have completely reverted the changes to agent/agent.go to match upstream/main, eliminating RunToken and context injection altogether.

Instead, toolautocall now manages the budget lifecycle strictly through agent.Session, aligning with the existing conventions in toolapproval, filememory, and todo:

  1. Approval Resumption & Re-entries: When incoming messages contain approval content (hasAnyApprovalContent(messages) == true)—whether from human approval across turns or an auto-approval loop in toolapproval—toolautocall continues from the session's recorded consumed count.
  2. Independent Runs: A new user prompt (hasAnyApprovalContent(messages) == false) resets any leftover state in the session and starts with a fresh budget of 0.
  3. Cleanup: At the end of a run, if no pending approval requests were produced, a defer cleans up the state from the session.

Please let me know your thoughts on the updated design, thanks!

@github-actions

github-actions Bot commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

API consistency review

Result Scope
unable to determine public API, user-visible behavior

Changed Go contract

New exported field toolautocall.Config.MaximumFunctionCallsPerRequest *int. nil means unlimited, 0 disables automatic calls, and negative values return a config error. It adds cumulative per-run counting, which includes failed and panicking calls and excludes unknown-tool and provider-handled calls. The budget is shared across toolapproval re-entries.

Upstream evidence reviewed

No equivalent found. Upstream evidence was not retrievable: code search was rate-limited (HTTP 429), and the file-read payload for Python _tools.py was too large to inspect with the available tools (shell and file access are disabled).

Assessment

The PR claims parity with Python max_function_calls. I could not verify the upstream defaults, the semantics of 0, how failed and unknown calls are counted, or the .NET FunctionInvokingChatClient equivalent. No mismatch is confirmed, so no inline findings are raised. public-api-change is already present and left unchanged. parity-approved is not added. Maintainers should check the Go semantics against Python max_function_calls and the .NET equivalent.

Generated by Go API Consistency Review Agent · copilot · auto · 18.7 AIC · ⌖ 0.617 AIC · ⊞ 5.1K · ◷

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:agent Changes files in the agent area public-api-change Pull Request changes public APIs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add a per-run limit for tool calls

3 participants