OpenAI Function Calling Integration

Integrate cryptographic human approval into OpenAI function calling loops and the OpenAI Agents SDK.

Key Concepts

The signedapproval-openai package provides a ready-to-use OpenAI tool definition (signed_approval_tool) and a handler function (handle_approval) that blocks until a real human authenticates on their phone and approves the action.

The tool works with both the classic function calling API (client.chat.completions.create) and the OpenAI Agents SDK (openai-agents). Every approval is backed by an Ed25519 signature — unforgeable, offline-verifiable proof.

Setup Guide
1

Install the package

bash
pip install signedapproval-openai
# with OpenAI SDK:
pip install "signedapproval-openai[openai]"
2

Get your API key

Create an API key in the Dashboard → API Keys and store it as an environment variable.

bash
export SIGNEDAPPROVAL_API_KEY="sa_live_..."
3

Function calling loop

Add signed_approval_tool to your tools list and call handle_approval in your tool-call loop:

python
import os
import json
from openai import OpenAI
from signedapproval_openai import signed_approval_tool, create_approval_handler

client = OpenAI()
handler = create_approval_handler(api_key=os.environ["SIGNEDAPPROVAL_API_KEY"])

messages = [{"role": "user", "content": "Deploy the API to production."}]

while True:
    response = client.chat.completions.create(
        model="gpt-4o",
        messages=messages,
        tools=[signed_approval_tool],
        tool_choice="auto",
    )
    choice = response.choices[0]

    if choice.finish_reason == "tool_calls":
        messages.append(choice.message)
        for tool_call in choice.message.tool_calls:
            if tool_call.function.name == "request_human_approval":
                args = json.loads(tool_call.function.arguments)
                # Blocks until human approves on phone
                result = handler(args)
                messages.append({
                    "role": "tool",
                    "tool_call_id": tool_call.id,
                    "content": result,
                })
    else:
        print(choice.message.content)
        break
4

OpenAI Agents SDK

Use a @function_tool decorator to wrap the handler for the Agents SDK:

python
import os
from agents import Agent, Runner, function_tool
from signedapproval_openai import handle_approval

@function_tool
def request_human_approval(action: str) -> str:
    """
    Request cryptographic human approval before performing a high-stakes action.
    Call this before any irreversible, financial, or destructive operation.
    """
    return handle_approval(
        {"action": action},
        api_key=os.environ["SIGNEDAPPROVAL_API_KEY"],
    )

agent = Agent(
    name="Deployment Agent",
    instructions=(
        "You manage production deployments. Always call request_human_approval "
        "before deploying, deleting data, or sending external communications."
    ),
    tools=[request_human_approval],
)

result = Runner.run_sync(agent, "Deploy payment-service v2.1.0 to production.")
print(result.final_output)

Tool definition reference

The exported signed_approval_tool (also aliased as OPENAI_TOOL_DEFINITION) is a plain Python dict in OpenAI's JSON Schema tool format:

python
{
    "type": "function",
    "function": {
        "name": "request_human_approval",
        "description": "Request cryptographic human approval ...",
        "parameters": {
            "type": "object",
            "properties": {
                "action": {
                    "type": "string",
                    "description": "Human-readable description of the action requiring approval",
                },
                "context": {
                    "type": "object",
                    "description": "Optional metadata shown to the approver",
                    "additionalProperties": True,
                },
            },
            "required": ["action"],
        },
    },
}

Handler return value

handle_approval() and create_approval_handler() both return a JSON string suitable for passing directly back to the model as the tool result:

json
{
  "request_id": "req_01jq...",
  "decision": "approved",
  "approved": true,
  "signature": "base64...",
  "public_key_id": "key_abc123",
  "verified": true
}

When the decision is rejected or expired, approved and verified will be false and signature will be null. The model can read this and abort the planned action.

Note
Add the optional contextparameter to pass structured metadata to the approver — for example, the target environment, amount, or affected records. This appears in the approval request on the approver's phone.
Tip
Use create_approval_handler() over calling handle_approval()directly — it pre-binds your API key and TTL so you don't repeat them on every call.