Skip to content

Add function invocation middleware with call identity - #1093

Draft
Irakli Gabisonia (gabisonia) wants to merge 2 commits into
microsoft:mainfrom
gabisonia:feat/tool-invocation-identity
Draft

Irakli Gabisonia (gabisonia) wants to merge 2 commits into
microsoft:mainfrom
gabisonia:feat/tool-invocation-identity

Conversation

@gabisonia

@gabisonia Irakli Gabisonia (gabisonia) commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

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.FunctionInvocationMiddleware implements agent.Middleware and wraps function tools. Automatic tool execution supplies the call ID through context.Context; FuncTool.Call(ctx, args) stays unchanged.

audit := agent.FunctionInvocationMiddleware(func(
    ctx context.Context,
    invocation *agent.FunctionInvocationContext,
    next agent.FunctionInvocationFunc,
) (any, error) {
    log.Printf("Starting %s: %s", invocation.Function.Name(), invocation.CallID)
    result, err := next(ctx, invocation)
    log.Printf("Finished %s: %v", invocation.CallID, err)
    return result, err
})

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.

@github-actions github-actions Bot added area:agent Changes files in the agent area area:internal Changes files in the internal area area:tool Changes files in the tool area size:xlarge More than 300 changed lines or 10 files kind:code Changes production behavior or code kind:tests Changes tests, fixtures, or test infrastructure labels Sep 16, 2026
@github-actions

This comment has been minimized.

@github-actions github-actions Bot added the public-api-change Pull Request changes public APIs label Sep 16, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Scope: public API, user-visible behavior

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.

Generated by Go API Consistency Review Agent · copilot · auto · 86.1 AIC · ⌖ 7.02 AIC · ⊞ 9.6K ·

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 area:internal Changes files in the internal area area:tool Changes files in the tool area kind:code Changes production behavior or code kind:tests Changes tests, fixtures, or test infrastructure public-api-change Pull Request changes public APIs size:xlarge More than 300 changed lines or 10 files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant