You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Adds function-invocation middleware so applications can inspect the tool, call ID, arguments, result, and error for each call. It follows the approach suggested in #638: agent.FunctionInvocationMiddleware implements agent.Middleware and wraps function tools. Automatic tool execution supplies the call ID through context.Context; FuncTool.Call(ctx, args) stays unchanged.
Register the callback in ProviderConfig.Middlewares before toolautocall. Callbacks can replace arguments, inspect or replace returned results/errors, or skip the underlying tool by not calling next. Skipping one invocation does not stop the whole agent loop. Tool schemas and approval requirements are preserved, and callbacks run after approval. tool.InvocationFromContext also exposes the ID inside tool handlers.
Opening as a draft to discuss the API shape and argument contract. #949 mentions normalized arguments; this implementation exposes raw JSON to match Go's existing tool API. Should normalization belong here or remain in the tool/application? The full issue contract still needs agreement.
The middleware wraps tools present in agent options at its position in the pipeline. An internal run option applies the same wrappers to separately configured AdditionalTools, without adding them to provider requests. Request tools retain precedence. Missing call IDs remain empty, and IDs are for correlation, not authorization or cross-run idempotency.
Validation: full race-enabled suite (go test -race -shuffle=on ./...), targeted callback/identity/approval/cancellation race tests, and golangci-lint (zero issues). Tests are in existing files and cover concurrent calls, approval/session restoration, context propagation, callback ordering, argument/result changes, errors, and repeated runs. An offline sample was also run locally.
Changed Go contract: agent.FunctionInvocationMiddleware, agent.FunctionInvocationContext, agent.FunctionInvocationFunc (new exported types in agent/middleware.go); tool.Invocation and tool.InvocationFromContext (new exported API in tool/tool.go); internal wiring in internal/toolcontext, internal/toolmiddleware, and agent/harness/toolautocall/autocall.go.
Upstream evidence reviewed:
.NET: dotnet/src/Microsoft.Agents.AI/FunctionInvocationDelegatingAgent.cs (FunctionInvocationContext, MiddlewareEnabledFunction.InvokeCoreAsync) — middleware wraps every AIFunction reaching the model's tool list uniformly, regardless of how it was registered.
Python: python/packages/core/agent_framework/_middleware.py (FunctionInvocationContext, FunctionMiddleware) and python/packages/core/agent_framework/_tools.py (call_id threading through middleware_context.metadata["call_id"], lines ~2048-2107) — same uniform-wrapping and call-ID-correlation semantics.
Result: aligned (previously reported gap resolved).
This is a follow-up on this PR's earlier review round. The prior finding — that toolautocall.Config.AdditionalTools tools bypassed FunctionInvocationMiddleware wrapping while request-supplied tools did not — has been fixed in commit 22c8b23 ("Apply function invocation middleware to additional tools"). autocall.createToolsMap now applies any toolmiddleware.Wrapper options to f.additionalTools before building the tool map, matching the uniform wrapping behavior in both .NET (MiddlewareEnabledFunction wraps all tools reaching ChatOptions.Tools) and Python (FunctionMiddleware pipeline applies to every function call regardless of tool source). New tests (additional tool wrappers, concurrent additional tool wrappers, additional tool failure, request tool takes precedence) cover ordering, concurrency, error propagation, and request-tool precedence over AdditionalTools.
The remaining open item — whether FunctionInvocationContext.Arguments should carry normalized arguments (as Python's contract implies) rather than raw JSON — is explicitly flagged by the PR author as an unresolved design question for this draft, tied to #949. This is not a regression and does not need to block further iteration on the draft, but should be reconciled with upstream's normalized-mapping contract before the PR leaves draft status.
Verified: go build ./... and go test ./agent/... ./tool/... ./internal/toolcontext/... ./internal/toolmiddleware/... pass on the PR branch.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
area:agentChanges files in the agent areaarea:internalChanges files in the internal areaarea:toolChanges files in the tool areakind:codeChanges production behavior or codekind:testsChanges tests, fixtures, or test infrastructurepublic-api-changePull Request changes public APIssize:xlargeMore than 300 changed lines or 10 files
1 participant
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Related to #949
Adds function-invocation middleware so applications can inspect the tool, call ID, arguments, result, and error for each call. It follows the approach suggested in #638:
agent.FunctionInvocationMiddlewareimplementsagent.Middlewareand wraps function tools. Automatic tool execution supplies the call ID throughcontext.Context;FuncTool.Call(ctx, args)stays unchanged.Register the callback in
ProviderConfig.Middlewaresbeforetoolautocall. Callbacks can replace arguments, inspect or replace returned results/errors, or skip the underlying tool by not callingnext. Skipping one invocation does not stop the whole agent loop. Tool schemas and approval requirements are preserved, and callbacks run after approval.tool.InvocationFromContextalso exposes the ID inside tool handlers.Opening as a draft to discuss the API shape and argument contract. #949 mentions normalized arguments; this implementation exposes raw JSON to match Go's existing tool API. Should normalization belong here or remain in the tool/application? The full issue contract still needs agreement.
The middleware wraps tools present in agent options at its position in the pipeline. An internal run option applies the same wrappers to separately configured
AdditionalTools, without adding them to provider requests. Request tools retain precedence. Missing call IDs remain empty, and IDs are for correlation, not authorization or cross-run idempotency.Validation: full race-enabled suite (
go test -race -shuffle=on ./...), targeted callback/identity/approval/cancellation race tests, and golangci-lint (zero issues). Tests are in existing files and cover concurrent calls, approval/session restoration, context propagation, callback ordering, argument/result changes, errors, and repeated runs. An offline sample was also run locally.