Skip to main content

confidence-escalation

Framework-agnostic confidence-gated escalation middleware for LLM agents.

PyPI version Python 3.9+ License: MIT Coverage CI

Multi-signal confidence scoring (logprob + verbalized + ASR + tool risk) with threshold-based escalation policies and pluggable handlers. Works with LangChain, LangGraph, CrewAI, AutoGen, Google ADK, and any Python agent framework.

Addresses OWASP Agentic AI Top 10 ASI-09: Human-Agent Trust Exploitation — prevents agents from taking high-stakes actions when confidence is insufficient.


The Problem

LLM agents fail silently. When an agent is uncertain, it still returns a response — often confidently-worded — with no mechanism to:

  • Detect that confidence is low before executing a high-risk tool call
  • Route uncertain responses to a human reviewer
  • Escalate to a stronger model when needed
  • Produce a compliance audit trail of every escalation event

confidence-escalation solves all four.


Features

  • Multi-signal scoring — combine logprobs, verbalized confidence, and tool-call risk into a single composite score
  • Threshold policies — single-threshold, dual-threshold (normal + critical), composite multi-policy chains
  • Pluggable handlers — human-in-loop, model upgrade, tool restriction, compliance logging
  • Framework adapters — LangChain callbacks, CrewAI step_callback, AutoGen reply function wrapper, Google ADK event interceptor
  • EU AI Act Article 12 audit logging — structured JSON compliance log on every escalation
  • Zero required dependencies — core library runs with no dependencies; framework integrations are optional extras

Quick Start

Installation

pip install confidence-escalation
# With LangChain:
pip install "confidence-escalation[langchain]"
# With all frameworks:
pip install "confidence-escalation[all]"

Basic Scoring

from confidence_escalation import MultiSignalConfidenceScorer

scorer = MultiSignalConfidenceScorer(
    weights={"logprob": 0.5, "verbalized": 0.3, "tool_risk": -0.2}
)

score = scorer.score(
    logprobs=[-0.1, -0.3, -0.2],
    verbalized_response="I am 70% confident about this answer.",
    tool_call_risk=0.15,
)

print(f"Confidence: {score.value:.3f}")   # e.g. 0.712
print(f"Reliable: {score.is_reliable()}")  # True (above 0.6 default)

Threshold Policy + Human-in-Loop

from confidence_escalation import (
    ThresholdPolicy,
    EscalationAction,
    HumanInLoopHandler,
    ComplianceLoggingHandler,
    ConfidenceEscalationMiddleware,
)

def notify_human(ctx, result):
    print(f"Routing to human review: session={ctx['session_id']}, confidence={result.confidence_score:.3f}")

policy = ThresholdPolicy(
    threshold=0.65,
    action=EscalationAction.HUMAN_IN_LOOP,
    critical_threshold=0.3,
    critical_action=EscalationAction.ABORT,
)

middleware = ConfidenceEscalationMiddleware(
    policy=policy,
    handlers=[
        HumanInLoopHandler(callback=notify_human),
        ComplianceLoggingHandler(),
    ],
)

result = middleware.call(
    agent_step=lambda: my_llm.invoke(messages),
    context={"session_id": "abc123", "model": "claude-sonnet-4-6"},
    logprobs=[-0.4, -0.5],
)

if result["escalation"]["triggered"]:
    print("Escalated — stopping agent execution.")

Model Upgrade Handler

from confidence_escalation import ModelUpgradeHandler, ThresholdPolicy, EscalationAction

handler = ModelUpgradeHandler(
    upgrade_map={
        "claude-haiku-4-5": "claude-sonnet-4-6",
        "claude-sonnet-4-6": "claude-opus-4-7",
    }
)

policy = ThresholdPolicy(threshold=0.7, action=EscalationAction.MODEL_UPGRADE)
result = policy.evaluate(score, context={"model": "claude-haiku-4-5"})

if result.triggered:
    upgrade_info = handler.handle(result, context={"model": "claude-haiku-4-5"})
    print(f"Retry with: {upgrade_info['upgraded_model']}")

Tool Restriction

from confidence_escalation import ToolRestrictionHandler, ThresholdPolicy, EscalationAction

handler = ToolRestrictionHandler(
    high_risk_tools=["delete_record", "send_email", "execute_sql"],
    allow_read_only=True,
)

policy = ThresholdPolicy(threshold=0.65, action=EscalationAction.TOOL_RESTRICTION)
result = policy.evaluate(score, context={"available_tools": ["get_customer", "delete_record"]})

if result.triggered:
    restriction = handler.handle(result, context={"available_tools": agent_tools})
    safe_tools = restriction["allowed_tools"]
    # Re-invoke agent with only safe_tools

LangChain Integration

from confidence_escalation.adapters.langchain import LangChainEscalationAdapter
from confidence_escalation.handlers import HumanInLoopHandler

adapter = LangChainEscalationAdapter(
    threshold=0.65,
    handlers=[HumanInLoopHandler(raise_on_trigger=True)],
)

# Attach as LangChain callback
chain = LLMChain(llm=llm, callbacks=[adapter.as_callback()])

# Or call directly from a LangGraph node
def research_node(state):
    response = llm.invoke(state["messages"])
    try:
        adapter.on_llm_end(response.content, logprobs=response.response_metadata.get("logprobs"))
    except HumanInLoopHandler.HumanReviewRequired:
        return {"status": "escalated"}
    return {"response": response.content}

CrewAI Integration

from crewai import Agent
from confidence_escalation.adapters.crewai import CrewAIEscalationAdapter

adapter = CrewAIEscalationAdapter(threshold=0.65)

agent = Agent(
    role="Research Specialist",
    goal="Analyze market trends",
    backstory="...",
    step_callback=adapter.step_callback,
)

Google ADK Integration

from google.adk.agents import BaseAgent
from confidence_escalation.adapters.google_adk import ADKEscalationAdapter

class GovernedAgent(BaseAgent):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._escalation = ADKEscalationAdapter(threshold=0.65)

    async def _run_async_impl(self, ctx):
        async for event in self._llm_agent._run_async_impl(ctx):
            if event.is_final_response():
                result = self._escalation.evaluate_event(event, ctx)
                if result["triggered"]:
                    yield self._escalation.build_escalation_event(result)
                    return
            yield event

Composite Policy Chains

from confidence_escalation import ThresholdPolicy, EscalationAction
from confidence_escalation.policy import CompositePolicy

policy = CompositePolicy(policies=[
    ThresholdPolicy(threshold=0.25, action=EscalationAction.ABORT),
    ThresholdPolicy(threshold=0.55, action=EscalationAction.HUMAN_IN_LOOP),
    ThresholdPolicy(threshold=0.75, action=EscalationAction.COMPLIANCE_LOG),
])

result = policy.evaluate(score, context={"session_id": "abc"})
# First matching threshold wins

OWASP Agentic AI Coverage

OWASP ASI ID Risk Coverage
ASI-09 Human-Agent Trust Exploitation Confidence gating before high-stakes actions
ASI-02 Tool Misuse Tool restriction handler removes high-risk tools at low confidence
ASI-03 Identity/Privilege Abuse ComplianceLoggingHandler creates immutable audit trail


License

MIT License. See LICENSE.

Reliability updates (0.2.0)

Explicit call_guarded pre-action gating; finite/range input validation and missing-signal metadata; reproducible synthetic evaluation. Introduced in 0.2.0.

ConfidenceEscalationMiddleware.call scores after execution. Use middleware.call_guarded(action, confidence, *args, context=context, **kwargs) before side effects. Escalation or explicitly missing evidence raises PermissionError without invoking the action. Re-evaluate after human review; callbacks do not grant approval. This gate does not replace identity/resource authorization.

Scores/thresholds must be finite in [0,1], log probabilities finite and non-positive. Missing signals are marked; weights are heuristics, not calibrated probabilities. Run python benchmarks/evaluate.py to reproduce results. Six synthetic hand-labeled cases illustrate coverage, error/abstention, Brier score, and aggregate confidence bias. They are not representative or fitted calibration; collect independently labeled domain outcomes before choosing deployment thresholds.

September 2026 boundary review (0.3.0)

Add async pre-action gating, await async callable objects, reject deferred execution through the synchronous gate, and exercise native SDK tool input guardrails with the real Runner.

Use await AsyncConfidenceEscalationMiddleware().call_guarded(action, confidence) for async side effects. The async class lives in confidence_escalation.async_middleware. Low or missing confidence blocks before invocation; sync callables run in a worker thread, async callables and returned awaitables are awaited. Cancellation cannot undo effects already started. The synchronous gate rejects async actions and awaitable results; use the async API instead. Confidence checks do not replace identity/resource authorization.

For OpenAI Agents SDK integration, install pip install ".[dev,openai-agents]" from this checkout on Python 3.10+. SDK 0.22.3 is tested through its actual Runner with a scripted local model and tracing disabled:

from agents import function_tool
from confidence_escalation.adapters.openai_agents import OpenAIAgentsEscalationAdapter
adapter = OpenAIAgentsEscalationAdapter()

@function_tool(tool_input_guardrails=[adapter.as_tool_guardrail()])
async def protected_action() -> str:
    return "performed"

Attach adapter.as_hooks() to Runner for lifecycle observations; hooks alone are not the execution boundary. Tool input guardrails raise the SDK tripwire before a triggered function tool executes. The adapter's risk-only score is conservative and is not calibrated confidence; with default thresholds it escalates both low-risk and high-risk tools. Supply a deliberately reviewed policy rather than assuming low-risk tools are automatically permitted. This does not cover hosted tools or calls outside the configured SDK tool path.

Official tool guardrail guidance distinguishes input guardrails before execution from output checks afterward. Lifecycle signatures include agent, tool, response, and output arguments exercised by the regression test. Run python -m pytest tests/test_openai_sdk_execution.py; the dedicated CI installs the SDK so this check cannot silently skip there.

Metadata

Release files for confidence-escalation 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for confidence-escalation 0.3.0
File Size Uploaded
confidence_escalation-0.3.0.tar.gz 49.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for confidence-escalation 0.3.0
File Interpreter ABI Platform
confidence_escalation-0.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 83.0 kB

Release files / confidence_escalation-0.3.0.tar.gz

Download URL confidence_escalation-0.3.0.tar.gz
Size 49.1 kB
Tags Source
SHA-256 checksum
How to use checksums
89a6984492eb9f6ef285d9b0b0d33625cd300f98a795d58ecd2b2a966b9c9a3d
BLAKE2b-256 checksum
How to use checksums
bdb28426fa1e505a22347e352198cbc8e17d289350c7c4239a68bb9158b62c15
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.16

Release files / confidence_escalation-0.3.0-py3-none-any.whl

Download URL confidence_escalation-0.3.0-py3-none-any.whl
Size 33.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
b07f53867b78dc872ad8a059dc18855d3c6f11d7d364a1f4cb9c00505ed19a05
BLAKE2b-256 checksum
How to use checksums
32f1fc7c74f92baca2d9066eb90902901a03eecb6f764dd5d1b096d34b463f64
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.16

Release history Release notifications | RSS feed

This release

0.3.0 This release

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page