Skip to main content

Human-in-the-loop approval gate middleware for AI agent tool calls

Project description

๐Ÿ” AzureAICommunity - Agent - Approval Middleware

Add a human-in-the-loop approval gate to AI agent tool calls with a single middleware registration.

License Python GitHub Repo GitHub Follow YouTube Channel LinkedIn

Getting Started ยท Callback Contract ยท How It Works ยท Contributing


Overview

azureaicommunity-agent-approval adds an approval gate directly into the agent-framework function-invocation pipeline. You pass only the tools that need approval โ€” all other tools registered on the agent execute freely without interruption. Before any gated tool executes, your callback is called with the FunctionInvocationContext; you decide whether to approve or deny using any UI: console, desktop dialog, HTTP call to a remote approver, etc. The middleware itself contains no UI code.

This mirrors the pattern established by the C# ApprovalMiddleware in the AzureAICommunity Agent Framework.


โœจ Features

Feature
๐ŸŽฏ Selective gating โ€” only the tools you specify are intercepted; others run freely
๐Ÿ”” Simple callback โ€” receives the full FunctionInvocationContext with tool name and arguments
๐Ÿ’ฌ LLM-aware denial โ€” when denied, a descriptive message is set as the tool result so the model can reason about the refusal
๐Ÿ–ฅ๏ธ UI agnostic โ€” use any approval UI: console, GUI, HTTP, webhooks
๐Ÿ”€ Sync & async callbacks โ€” both synchronous and asynchronous callbacks are supported
๐Ÿ”— Composable โ€” stacks with other agent-framework middleware in the same pipeline

๐Ÿ“ฆ Installation

pip install azureaicommunity-agent-approval

Or install directly from source:

cd AgentFramework/Python/Middleware/ApprovalMiddleware
pip install -e .

๐Ÿš€ Quick Start

import asyncio
from agent_framework import tool, FunctionInvocationContext
from agent_framework.openai import OpenAIChatCompletionClient
from approval_middleware import ApprovalMiddleware


@tool
def get_device_status(device_name: str) -> str:
    """Get the current status of a smart-home device. Does not change device state."""
    return f"{device_name} is currently OFF."


@tool
def turn_on_device(device_name: str) -> str:
    """Turn on a smart-home device."""
    return f"{device_name} is now ON."


@tool
def turn_off_device(device_name: str) -> str:
    """Turn off a smart-home device."""
    return f"{device_name} is now OFF."


async def console_approve(context: FunctionInvocationContext) -> bool | None:
    print(f"\n[Approval Required] Tool: {context.function.name}")
    answer = input("  Allow? [y/N] ").strip().lower()
    return answer == "y"


async def main():
    client = OpenAIChatCompletionClient(model="gpt-4o", api_key="...", base_url="...")

    # Only the destructive tools are gated.
    # get_device_status is intentionally omitted โ€” it runs freely without prompting.
    middleware = ApprovalMiddleware(
        approval_tools=["turn_on_device", "turn_off_device"],
        approval_callback=console_approve,
    )

    agent = client.as_agent(
        name="HomeAssistant",
        instructions="You are a helpful smart-home assistant.",
        tools=[get_device_status, turn_on_device, turn_off_device],
        middleware=[middleware],
    )

    response = await agent.run(
        "Check the living room lights, then turn them on and turn off the bedroom fan."
    )
    print(response.text)


asyncio.run(main())

๐Ÿ”” Callback Contract

The approval_callback receives the full FunctionInvocationContext.

Return value Effect
True Approved โ€” the tool executes and its real result is returned to the LLM
False or None Denied โ€” a denial message is set as the tool result so the LLM can reason about the refusal

The callback can be sync or async โ€” both are supported automatically:

# Async callback
async def my_callback(context: FunctionInvocationContext) -> bool | None:
    # context.function.name  โ€” tool name
    # context.arguments      โ€” validated arguments (dict or Pydantic model)
    return True   # approve
    return False  # deny
    return None   # deny

# Sync callback
def my_callback(context: FunctionInvocationContext) -> bool | None:
    return True

โš™๏ธ Configuration

ApprovalMiddleware

Parameter Type Default Description
approval_tools list[str] required Tool names that require approval before execution
approval_callback Callable[[FunctionInvocationContext], bool | None] required Sync or async callback invoked before each gated tool call
denial_message str "User denied the call to '{tool_name}'." Template string for the denial result. Use {tool_name} as a placeholder

โš™๏ธ How It Works

LLM decides to call a tool
    โ””โ”€โ–บ FunctionInvocationContext intercepted by ApprovalMiddleware
            โ”‚
            โ”œโ”€ tool name in approval_tools?
            โ”‚       โ”œโ”€ YES โ†’ approval_callback(context) called
            โ”‚       โ”‚           โ”œโ”€ returns True         โ†’ call_next()  โ† tool executes normally
            โ”‚       โ”‚           โ””โ”€ returns False/None   โ†’ context.result = denial message
            โ”‚       โ””โ”€ NO  โ†’ call_next()    โ† tool executes freely, no approval prompt
            โ”‚
            โ””โ”€ agent continues with the result

๐Ÿค Contributing

Contributions are welcome! Please open an issue to discuss what you'd like to change before submitting a pull request.

๐Ÿ“ Repository: https://github.com/rvinothrajendran/AgentFramework

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/my-feature)
  3. Commit your changes (git commit -m 'Add my feature')
  4. Push to the branch (git push origin feature/my-feature)
  5. Open a Pull Request

๐Ÿ‘ค Author

Built and maintained by Vinoth Rajendran.


๐Ÿ“„ License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

azureaicommunity_agent_approval-0.1.0.tar.gz (7.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

File details

Details for the file azureaicommunity_agent_approval-0.1.0.tar.gz.

File metadata

File hashes

Hashes for azureaicommunity_agent_approval-0.1.0.tar.gz
Algorithm Hash digest
SHA256 f95f45018b9a2995b818dff1a7a3a5318f942185a53ccb83ac68cca7e4f2c3a7
MD5 8a299082ce5d898b3c69b7f45c9a8419
BLAKE2b-256 c715ace095125a522377061787d61cd42a2b1f92e44f863a2f8967a47b467ec1

See more details on using hashes here.

File details

Details for the file azureaicommunity_agent_approval-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for azureaicommunity_agent_approval-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 f1c2dbe266da82fcb7c07738643d0198db58d179a2de0715b00cb10a16bed9c3
MD5 e7ed563dc064d448a50c3237bfeb8224
BLAKE2b-256 83f8dcf1c11e165b8dfe4a2f36bfa3d39d750f27f26718ee2ffbfe87a78b5d48

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page